0.2.5 • Published 9 years ago

autocrat v0.2.5

Weekly downloads
-
License
MIT
Repository
github
Last release
9 years ago

API UNSTABLE

:warning: The Autocrat and Reactionary APIs are still very much a work in progress, hence the semver 0.x.x version numbers! If you're curious what's in the works, check out the roadmap.

Autocrat

Autocrat is a utility belt for Javascript apps to derive a single observable global state object (see Why a single global state object) in response to diverse application events. Changes to that state object trigger events, batched inside the time span of an animation frame, to which an application can respond by, for example, re-rendering the UI, persisting data, saving to session storage, lazy-loading new assets, issuing requests to tracking services, and more.

Conceptually, this is not particularly novel. To my knowledge, several stellar implementations of something philosophically similar pre-date Autocrat:

(Disclaimer: I haven't deeply investigated these, though I hope to eventually.)

For those who gleefully gobble up any ReactJS candy (I include myself in this group), it's also an interpretation of the Flux architectural pattern. Autocrat is therefore an ideal companion to React, or any application hoping to realize a more reactive nature. Autocrat is to state what React to is to views; it's whole point is to reactively compose a state object through a uni-directional data flow out of smaller concerns.

So, in case you are just scanning this README for the goods...

OMG, OMG, REACT!

Autocrat distiguishes itself from the projects listed above and other state coordination layers for React and its derivatives by:

  • Embracing vanilla JS and its idioms. Yes, Clojurescript is a magic unicorn of immutability and functional perfection, but some of us just don't have the freedom to use it in the applications we develop and maintain. Plus, we want mere mortals to understand our code.

  • Being highly modular and hewing to the mini-module philosophy taking hold in the npm ecosystem. So, use only what makes sense for your application, rest assured that SemVer will be strictly followed in Autocrat, and keep your browser bundle size as small as possible.

  • Judiciously using a fairly intuitive and narrow flavor of FRP (Functional Reactive Programming), via BaconJS, versus getting all academic compsci wonky whole-hog. (I don't think I could do the latter, even if I wanted to, so I'm kind of biased by my ignorance.) Elm and RxJs, I really want to know and love ya, but Bacon is gonna be my BFF for now, until I get some time to play the field :) Anyway FRP/Bacon makes events (aka Advisors) in Autocrat composable and reactive in a similar spirit to views in React. Sweet.

  • Recognizing that user interaction with the view layer isn't the only driver of state change. Likewise, changes in state aren't only ever germane to view rendering. There's data to persist, invisible network calls to be made behind the scenes, like for tracking, etc. It baffles me why so many of the state coordination layers built atop React and similar tools seem to discount that there is a plurality of state influences and outcomes in the environment of a JS browser application. Perhaps it's because React itself is so view-centric. Autocrat allows any event, even a simple method call, to become a reactive stream/signal that leads to the production of one of perhaps several perspectives on global state, called Domains. These perspectives are specially-tailored to their purpose: the view (ala a View Model), persistence to a data store, etc.

  • Being compatible with nearly any JS MV* or whatever framework your legacy apps might already be using. You don't need to be using React or care about functional or reactive principles to find benefit in using it. Autocrat restricts the typical state-oriented mutation mayhem of JS apps to a small easily-identifiable region of code. This is good no matter what framework you use.

Did I mention that Autocrat also embraces and enforces immutable data (buzz) and the functional (buzz buzz) aims to reduce and contain side effects (buzz). Oh, and "easy to reason about" (buzz buzz buzz). If you ever find yourself wondering "what the hell just happened" in your application -- if you say you haven't, you're either lying or you've only ever tinkered with TodoMvc apps -- this will really, really, help.

Lastly, maybe of interest is Reactionary, an MV* view layer integrating Autocrat with various reactive UI and virtual dom frameworks to form something akin to Om and its copycat spawn, but for vanilla JS apps.

What's up with the name?

It's probably not hard to deduce that the name was chosen because of its metaphorical appropriateness. An autocrat wields absolute control of their state, and such is the nature of the Autocrat singleton. Superficially, a real-world nation state and application state appear to be totally unrelated. It turns out, however, that the concept of a nation state is uncannily rich with metaphors and terminology applicable to governing how application state changes.

Read on below for evidence of this.

Why a single global state object?

In a word, simplicity. Though a single global state object might be internally complex, it's a single source of truth about application state. Without such a thing, state is distributed, and possibly obscured, around application code in global variables, scope closures, object properties, cookies, and local and session storage. Even the device-specific JS host environment holds a good amount of what could be considered application state, the window.location object for example. Ahem, and of course there's the lovely DOM

Hunting through this dispersion to get an overview of application state can be absolutely maddening, if not impossible, without the ability to consult a single object derived from it all. Anyone who has worked on a sizable JS app has likely experienced the unpleasantness of needing to track down and mentally cobble together state, which usually involves jumping back and forth between source files and browser dev tools, as well as hopping form source file to source file to follow code flow. This task rapidly stresses the capabilities of human working memory and frankly just sucks. Add asychronicity into the mix and you'll seriously consider harming kittens.

The gospel of pubsub/observer as a central organizational pattern in application architecture is partly to blame for this. Pubsub goes far to decouple application modules, which is a very good thing, but in large organically-developed applications the result is usually modules listening to modules that listen to modules, and so on. This is a swarm of bugs waiting to be born, particularly those of the cyclic dependency type. An event mediator can drastically improve this situation, but that only lessens the hassle of tracking down state. It's still damn difficult to get an overview of state if this is all you have.

Development and debugging becomes much easier with the ability to comprehensively know state at any given point in time. The likeihood of bugs emerging related to a developer's lack of state understanding reduces as well. It's probably not an overstatement to claim that nearly all logical bugs result from developer confusion about state.

Besides bringing some sanity to developers' lives, a single global state object also offers some very interesting bonuses. One which has been touted much by frameworks utilizing singular global state is how global state objects serve as a historical snapshot of an app. This makes implementation of undo and history features fairly trivial, and when paired with persistence of a succession of state object allows traveling back and forward in time through application state! Also, imagine the QA and debugging value of being able to simply grab a snapshop of some buggy application state to send over email or chat to a developer for reconstitution and debugging.

Features

A global representation of application state can easily grow to be quite complex and is potentially subject to influences from every corner of application logic. Autocrat helps make reasoning about this easier in several ways:

  • Provides a means to logically modularize the modeling of state according to concern (breaks down a big problem into many smaller problems), e.g. What changes when page navigation occurs? or What changes when a user logs in?. It does this via Governors.

  • Automatically aggregates concern-oriented state structures into a cumulative structure, accessible as a property of the Autocrat singleton and also as the data payload for batched state change events.

  • Supports an intuitive and readable object literal syntax to declare state structure. Structure declarations are done within Governors.

  • Has the capability to produce multiple representations of global state specially-suited to different uses, such as rendering the UI or persisting new and updated data. These representations are called Domains.

  • Enforces a unidirectional flow of data, in the manner advocated by Facebook's Flux Architecture, resulting in easy-to-trace paths between cause and effect.

  • Normalizes all change-causing events, DOM, xhr, custom, or even those from third-party code, to share a single format and usage protocol. It does this via Advisors.

  • Models event sources, Advisors, as FRP (Functional Reactive Programming) streams, allowing semantically weak events to be easily composed and transformed into more meaningful, intuitive, and useful ones. Bacon.js is used under the hood for Autocrat's FRP goodness.

  • Provides a specialized state machine for producing events and executing event handlers at moments of transition between sets of concurrent state concerns. It does this via Provinces and Federations.

  • Confines all expressions of state-mutation- and side-effect-causing logic to a single area (sort of) in application code. These expessions are called Laws.

  • Intuitively handles synchronizing data from asynchronous flows within the callback functions of Laws.

  • Triggers events at key moments during state mutation to allow for timely handling, including an event for every property change as well as an event marking an atomic/batched change to the global state structure (the batch change occurs inside an animation frame from Window.requestAnimationFrame)

  • Defines a plugin API for hooking into batched state mutation events and producing explicit large-scale side-effects in response, rendering the UI for example. It does this via Domains.

  • Provides cursors into specific nodes of the global state hierarchy for the purposes of observing and reacting to mutation of underlying values. This feature is inspired by cursors as implemented in Om, but Autocrat cursors are limited to serving as observables. State cannot be updated in Autocrat except by way to of the unidirectional data flow of Advisor -> Governor -> Domain -> Autocrat.

  • Delivers immutable data representions of state objects (see The case for Immutability) in mutation event payloads, preventing state mutation outside of predictable points in code flow. Immutable.js is used under the hood for Autocrat's immutability goodness.

The Autocrat loves bacon and hates spaghetti

One of the virtues of functional reactive programming is its tendency to nudge a developer coming from OOP ever more towards thinking in terms of linear incremental change over time. It forces consideration of the problem of state which is so easy to ignore when everything in your code seems adequately modeled with classes, attributes, and methods that update stuff. FRP streams (sometimes called signals) chunk and contain logic related to change into functional pipelines, which are deterministic (if using pure functions), incremental, and linear by nature and read as such. Contrast this with method Foo.bar calling method Baz.booz, which consumes a promise object constructed in Wt.f, inside a for loop that updates a variable set in an outer closure, and... well you might begin to see why FRP (and pure functions) are useful in the arsenal of state management. This kind of frenetic criss-cross of data flow is what some disparage as "spaghetti code".

Spaghetti is ok. Well, not really, but OOP is ok, at least when it's used to model structure and application nouns. But bacon is much much better, Bacon.js that is, where application verbs/events are concerned.

The FRP underpinnings of Autocrat are supplied by the excellent Bacon.js library. It's capabilities are central to the definition of Advisor event streams, so it would be well worth the time investment to read its documentation. Also worth a read is The Introduction to Reactive Programming you've been missing by André Staltz.

To boil down how FRP and Bacon is primarily utilized in Autocrat, two points:

Citizens of the state

The state, as governed by Autocrat, has three main types of actors which are defined by extending base classes provided in the Autocrat library: Autocrat, Governor, and Advisor. The relationship between these is just as their names might imply.

  • Advisors report to Governors and the Autocrat singleton with inert events, passing along knowledge that something has changed. Advisors can also consult other Advisors and their events. Side effects should never be caused by an Advisor. Advisors simply package up and pass along useful information derived from raw sources of information about change, such as DOM events. The Autocrat and Governors can choose to do what they wish with Advisor events.

  • Governors report only to the Autocrat singleton, and their primary responsibility is translating Advisor event data into properties of the global state object. More specifically, they prepare concern-oriented fragments of the state object, called Domains, to hand off to the Autocrat singleton. When appropriate, Governors have also been given authority to produce direct side effects -- normally only the Autocrat singleton would do that by way of sending domain state objects to Domain handlers. One example of such a side effect is halting and redirecting ASAP if the user tries to navigate between two routes whose transition isn't allowed. This would be the case for an unauthenticated user trying to go from a login route to the route of a page only intended for viewing after authentication.

  • The Autocrat listens to Advisors in order to coordinate a state machine that triggers Advisor events when transitioning between sets of concurrently active state concerns, called Federations. Concerns might be things like "homepage", "auth", "error", and a federation of these might be "homepage+auth+error". Federations represent a set of Provinces, which in turn individually represent a single state concern. What to do in response to a set of active concerns transcends the relatively narrow focus of Governors, hence the Autocrat singleton being in direct control of the Federation state machine. When the Autocrat singleton receives all updated state domain objects from Governors, usually batched within the timespan of an animation frame, it has sole authority to determine when to trigger global state change events and how to structure the final global state object.

So, the upshot is this:

Advisors (data)-> Governors (domains)-> Autocrat (state)-> Domain handlers (render UI, persist data, etc)

And in case you're wondering "What comes before advisors? Something must happen there.", you might want to read about Advisors next.

Concerns of the state

To gain context helpful for understanding this section, first read Citizens of the state.

Several constructs exist in Autocrat to keep together code responsible for mutating state properties and producing other side effects related to the same concern. This is extremely useful for understanding at a glace state influences that tend to work in unison. What constitutes a "concern" is ultimately driven by what makes sense to a developer for a given application, but examples of ones likely to be in most apps include page properties (e.g. html title, main heading), authorization status, error handling, and syncing data with a data store.

Of the constructs listed below only two have in the Autocrat library corresponding base classes intended for extension: Governor and Domains.

  • Provinces and Federations are a convenient tool for producing events at the moments a confluence of application concerns emerges or passes away. For example, the co-existence of two pieces of the UI might clash or need coordination in some way, making it necessary to adjust when this happens . Federations are not directly produced by developers, but rather occur organically as resultant states of a special state machine run by the Autocrat singleton. The state machine transitions based on the current federation, a predefined graph of Province relationships supplied in the Autocrat singleton class definition, and the occurrence of observed Advisor events. Advisor events are triggered for every state machine transition, allowing other Advisors and Laws to react to them.

    Um, z-index battles, anyone?

  • Governors act as container structures uniting all (or most) of the code directly involved with translating Advisor events into properties of Domain objects, which the Autocrat singleton aggregates into a global state object. Theoretically, an applicaiton could use only one Governor, but ideally a Governor is devoted to doing its job with respect to a single concern and an app would therefore have many Governors. This keeps code succint, module files sizes small, and makes it easier to locate state mutation code for a concern by examining the project filesystem (assuming one file per Governor). Governors are the main consumers of Advisors, including those related to the Federation state machine.

  • Domains represent state concerns related to how state is finally used. You might find that one global state representation for all potential uses results in awkward concessions as to the structure and contents of the state object. Domains provide the means to tailor different global state objects to work most effectively for a particular use. The "view" domain, for example, should end up containing all variable strings necessary for UI rendering, and will likely contain boolean properties indicating whether a UI region is active. If this same object was used to supply data for POSTing to an API endpoint after a state change, it would seem overloaded with unrelated content, and probably require more-than-ideal traversal and data plucking. Though Domain object structures are defined within Governors, the Autocrat singleton ends up making use of them via registered domain handlers. It's these domain handlers that extend an Autocrat libary base class.

  • Laws are little more than syntactic sugar for functionally merging (ANDing) and combining (ORing) Advisor event streams and registering event handlers. What little more there is mostly acts inservice of synchronizing asynchronous event stream handlers. All expressions of state mutation should be confined to Laws and the handlers they reference, defined within Governors.

So, the upshot is this:

  1. Any change in the set of Provinces triggers a Federation Advisor event.
  2. Governors define Laws that react to Advisor events, including those for Federation changes.
  3. Laws prescribe handlers for Advisor events.
  4. Law handlers, usually just methods of Governor instances, change reactive properties which in turn update Domain objects
  5. The Autocrat observes batch/atomic changes to Domains and triggers change events for each Domain
  6. Domain handlers react to their change event. For example, the UI rerenders for every change event on the "view" Domain.

Organizing the filesystem of Autocrat modules

app/                      # Within the JS source root of your app...
  state/                  # Include a folder to hold all state-related files
    advisors/             # Custom Advisor definitions go here
      thing.js            # You'll want Advisors for all you models, at least
      things.js           # It's a best practice to separate Advisors for models and collections
    constants/            # Make explicit any invariant properties included in state
      death-and-taxes.js
    governors/            # This folder is where the state action mostly happens
      page.js
      route.js
    laws/                 # If there are laws you wish to reuse across Governors, place them here
    policies/             # Policies are simply reusable libraries of Law handler functions
    app.js                # This is where you app's extended Autocrat class is defined

The Autocrat

Defining

// Require the Autocrat class for custom extension
var Autocrat = require('autocrat');

module.exports = Autocrat.extend({

  advisors: [
    // Autocrat provides a suite of commonly-useful Advisors...
    require('autocrat/advisors/link'),
    require('autocrat/advisors/view'),
    require('autocrat/advisors/form'),

    // ... but you'll want to define custom Advisors for your app
    require('./advisors/things'),
    require('./advisors/thing'),
  ],

  governors: [
    // The "concerns" around which Governors are defined are totally
    // up to you. Theoretically you could simply have one for everything,
    // though that's an anti-pattern.
    require('./governors/page'),
    require('./governors/persisted'),
    require('./governors/route'),
    require('./governors/form')
  ],

  domains: [
    // At the very least your app will need handling for the view domain
    require('autocrat/domains/view'),
    // Domain handlers can really be for any side-effect use-case
    // This one below updates window.document[whatever] properties, like document.title
    require('autocrat/domains/document'),
    require('autocrat/domains/session'),
    require('autocrat/domains/persisted'),
    // Lazyload is a really interesting possiblity for a Domain handler.
    // Imagine simply executing an image, script, or stylesheet lazyload
    // in response to a reactive domain property updated on page scroll
    require('autocrat/domains/lazyload')
  ],

  // The API for Province declaration is accessible through
  // the arguments to the provinces method.
  provinces: function (advisors, province, get) {
    var navigatingTo = advisors.route.navigatesTo;

    // Provinces have two types of relationships to one another: Hierarchical and mutual-exclusiveness.
    // Every root level Province in the hierarchy is assumed to be mutually exclusive.
    var homePage = province('home')
      // For every Province, specify an Advisor event that triggers annexation (activation)
      .annexWhen(navigatingTo('home'))
      // The province().sub() method is how hierarchies are formed
      .sub(
        province('homeSub1'),
        // Mutual exclusion can be declared inline within the chained
        // .sub() calls
        province.mutex(
          province('homeSub2')
            // An example of another hierarchy level
            .sub(province('homeSub2.1')),
          province('homeSub3')
        ),
        province('homeSub4')
      );

    // It's possible to make provinces mutually exclusive after they've
    // been initially declared
    province.mutex(get(/homeSub1|homeSub3/, homePage.subProvinces));

    // The next three provinces show how a standard CRUD operation
    // might be interpreted as
    province('thingAdd').annexWhen(navigatingTo('thingAdd'));
    province('thingView').annexWhen(navigatingTo('thingView'));
    province('thingEdit').annexWhen(navigatingTo('thingEdit'));

    get('all', _.where(this.provinces.all, {level: 1}))
      .sub(province('error'));
  }

});

Instantiating

In a hypothetical app.js file for a Reactionary-based app:

var domReady = require('domready');
var View = require('autocrat/reactionary/view');
var CollectionView = require('autocrat/reactionary/collection-view');
var AppView = require('./views/app');
var Things = require('./models/things');
var Router = require('./router');
var templates = require('./templates');

var State = require('./state/app');

module.exports = {

  initialize: function () {
    var self = window.app = this;
    this.things = new Things();
    this.router = new Router();

    // Though technically the instance's constructor is Autocrat
    // it makes semantic sense to simply call it "app.state"
    this.state = new State({

      // Invading objects adds a property of .autocrat to them
      invade: [ View.prototype, CollectionView.prototype ],

      // The above invasions, and a few others automatically occur
      // when you tell Autocrat to use Reactionary for view Domain handling
      reactionary: true,

      // Adding wards to the state instance makes them accessible
      // from within and Autocrat-based class instances from
      // this.autocrat.wards['someWard']
      wards: {
        router: this.router,
        AppView: AppView,
        things: this.people.
        templates: templates
      }
    });

    domReady(function() {
      self.router.history.start({ pushState: true, root: '/' });
    });
  }

};

Advisors

// Advisors all utilize Bacon, as they produce Bacon streams
var Bacon = require('baconjs');
var Advisor = require('autocrat/advisor');

module.exports = Advisor.extend({

  // A name is necessary for referencing this Advisor elsewhere in code
  name: 'things',

  initialize: function(advisors, wards) {
    // Initialize is mostly useful for creating aliases/shortcuts to wards
    this.collection = wards.things;
  },

  // Any properties of an Advisor class definition NOT named "name", "initialize"
  // or "helpers" is assumed to be an "expertise" definition. Expertise always
  // comes in the form of a dichotomy of abilities/actions and knowledge.
  fetching: {

    // This Advisor has the "ability" to fetch by wrapping, and thus tapping into
    // a call to app.things.fetch()
    fetch: Advisor.WrapperAction(function(){
      return {
        wrapMethod: [this.collection, 'fetch'],

        // The data payload passed along to the event stream for this action can
        // be refined here by using all known information about the wrapped method.
        extractData: function(collection, methodRetVal, methodArgs, baseStream){
          return methodRetVal;
        }
      };
    }),

    // This is an example of making use of the "standard" stream of "knowledge",
    // which is to trigger a Bacon.EventStream event whenever the action for
    // this expertise is performed.
    fetches: Advisor.Stream,

    // Knowledge can be a custom Bacon.EventStream. If third-party code triggers
    // custom events, this is a useful way to relay those through an Advisor
    // event stream.
    hasFetched: Advisor.Stream(function(baseStream, helpers, customStream) {
      var stream = new Bacon.Bus(),
          advisorName = this.name;

      this.collection.on('sync', function(collection, e){
        var eventData = {
          collection: this,
          triggeringEvent: e
        };
        stream.push(new Advisor.Event(advisorName, 'fetched', eventData));
      });

      return stream;
    }),

    // Bacon is just so badass! It's this easy to define an Advisor event stream
    // that represents the waiting period of an async operation. Use something like
    // this to update the global state to show a loader in the UI.
    isFetching: Advisor.Stream(function(baseStream) {
      return baseStream.awaiting(this.streams.hasFetched);
    })

  }

};

Governors

var Governor = require('autocrat/governor');
var templates = require('../../templates');
var setFavicon = require('favicon-setter');

// Page properties are a perfect example of state constants
var pages = require('../constants/pages');

module.exports = Governor.extend({

  // A name is necessary for referencing this Governor elsewhere in code
  name: 'page',

  domains: {

    view: function(prop){
      return {
        name: prop('name'),
        // This heading gets interpolated in the view template for each page
        heading: prop('heading')
      };
    },

    document: function(prop) {
      return {
        // Page titles SHOULD change when a SPA logically navigates between pages
        title: prop('title'),
        // Scrolling back to the top is a typical need for SPA "pages"
        scrollTop: 0
        // Awesome! Update the head html using a simple template
        head: prop('head')
      }
    }

  },

  // The API for Law declaration is accessible through the arguments to the laws method.
  laws: function(when, advisors, policies) {

    // Laws almost read like imperative control flow. Don't let that
    // fool you, though. They are syntactic sugar to create a functional
    // pipeline.

    // You can see below how important naming conventions can be
    // for making Advisor event streams semantically rich. The Advisor
    // used below, for federation transitions, is particularly useful
    // for identifying when a UI state, such as a new page, comes into being.
    when(advisors.federation.isCurrently('*'))
      // Conveniently call updater handlers for all props, if that makes sense
      .updateProps()
      // Notice how handlers can be chained, and also that they can be referenced
      // by a string of the method name if the method is defined on this Governor.
      .then('setFavicon');

    when(advisors.federation.isCurrently('home'))
      .then(function() {
        // Anonymous functions can be passed as handlers, but for the love of
        // all things good, don't use alert().
        alert('Welcome!!!')
      });

    // Laws can beget Advisor events to delegate handling of side-effects to more
    // appropriate Laws
    when(advisors.federation.isCurrently('moneyMaker'))
      .ask(advisors.ads).to('display');

    // By default the when() function ANDs (merges) the Advisor event streams
    // passed as arguments to it. To OR (combine) streams do something like:
    // when.any(advisors.federation.isCurrently('home'), advisors.scroll.hitsPageBottom)
    //   .updateProp('popToast')
    //
    // BTW, when.all() is an alias for when()
    //
    // For more about Bacon merging vs. combining, see:
    // https://baconjs.github.io/api.html#stream-merge
    // https://baconjs.github.io/api.html#combining-multiple-streams-and-properties

  },

  // By and large the majority of Law handlers will be Domain property updaters.
  // Calling .updateProp('propName') or .updateProps(/* all props */) on a Law
  // automatically invokes Governor methods named update[PropName] and uses the
  // returned value to update the correspondingly named Domain property.
  updateName: function(currVal, e) {
    return e.data.pendingProvince.name;
  },

  updateTitle: function(currVal, e) {
    // The e argument is the data payload from the Advisor event/s referenced
    // in the Law that references this handler.
    return pages[e.data.pendingProvince.name].title;
  },

  updateHeading: function(currVal, e) {
    return pages[e.data.pendingProvince.name].heading;
  },

  updateHead: function() {
    return this.wards.templates.head();
  },

  // Some Law handers might directly cause side effects, if it makes
  // sense to do so.
  setFavicon: function(advisorEvent, deferred) {
    setFavicon('/images/ampersand.png');

    // In most cases, you'll want to either return true or call deferred() when
    // an async handler completes. Returning false halts execution of subsequent
    // handlers for Laws with the same Advisor argument signature.
    return true;
  }

});

Domains

var Domain = require('autocrat/domain');

module.exports = Domain.extend({

  // The name property is required in order for the Autocrat singleton
  // to bind the onChange handler to changes in the appropriate state
  // domain object.
  name: 'view',

  // Initialize is mostly useful for defining aliases/shortcuts
  initialize: function() {
    this.AppView = this.autocrat.wards.AppView;
  },

  // Do something useful in response to changes in state
  onChange: function(state) {
    var appView;

    if(!this.view) {
      appView = this.view = new this.AppView();
      appView.render(state);
      document.documentElement.replaceChild(appView.bodyEl, document.body);
    } else {
      appView = this.view;
      appView.render(state);
    }

    // Advisor events can even be fired to signify handling of state changes
    this.autocrat.advisors.view.actions.render(appView.bodyEl);
  }
};

Laws

Provinces and Federations

Province selector syntax guide

'*', '...' OR '...' — Any combination of any root and any subs in any number (every transition, unrestricted length). If this is the first selection, then ignore the others.

'*' — Any single root (restricted length). Will match when any root, and only the root, is annexed.

'*', '*' — Any combination of any single sub and any root (restricted length). Will match whenever there is a federation with length of 2, in other words whenevever any sub is added as the first sub under a root.

'*', '*', '*' — Any combination of any root and any subs where federation length is 3 (restricted length). Will match whenever there is federation with length of 3, in other words, whenever the second sub is added to a root.

'home' OR 'home' — Will match when the 'home' root, and only it, is annexed.

'*', 'error' — Any combination of any root and only the 'error' sub (restricted length). Will match when the 'error' sub is annexed over any sub.

'home', '*' — Any combination of the 'home' root and any one other sub (restricted length). Will match when any single sub is annexed under 'home'.

/^home/, 'homeSub2', 'error' — Any combination of any root matching /^home/ and exactly the two subs 'homeSub2' and 'error'. Will match when either 'homeSub2' or 'error' sub is added and the other is already active.

/^home/, 'homeSub2', 'error', '...' — Any combination of any root matching /^home/ and the two subs 'homeSub2' and 'error', and any number of other subs (unrestricted length). Will match when either 'homeSub2' or 'error' is added, or when every sub that is not 'homeSub2' or 'error' is annexed and 'homeSub2' and 'error' are already active.

/^collection/, '*', 'error' — Any combination of any root matching /^collection/ and the sub 'error' and one other sub. (restricted length). Will match when the 'error' sub is added and at least one other sub is already active, or when any sub that is not 'error' is annexed and 'error' is the only sub already active.

'*', '*', 'error' — Will match whenever the 'error' sub is added under any sub, and the federation length is exactly 3, or when any sub that is not 'error' is added and 'error' is the only active sub in the federation.

'*', '*', 'error', '...' — Will match whenever the 'error' sub is added under any sub, and the federation length is at least 3, or when any sub that is not 'error' is added and 'error' sub is one of 2, and only 2, subs in the federation.

Cursors

// Taps into a specific property of state
var pageNameCursor = app.state.cursor('view.page.name');

// Taps into a collection embedded in state, finding the first
// one matching a condition
var thingCursor = app.state.cursor('view.persisted.things', function(thing) {
  return thing.name.match(/^foo\./);
});

// Will only invoke the callback when the first thing with a name starting 
// with "foo" changes
thingCursor.onChange(function() {
  // re-render a view, etc.
});