In my experience, UI models have been incredibly helpful. We maintain a ContextModel
for every "page" in our app. What makes it really nice is that we guarantee "pages" to be initialized with a complete ContextModel
so there is only ever one place the context is originally created.
Our AppView
always handles creating the initial context for a page when a specific route is triggered. The route handler would be where the defaults for a context are set, and things are parsed from the route's values.
Once the page itself has been initialized, it only has to worry about changes to the ContextModel
and have handlers that do any updates necessary and then update the path.
Here's an example pulled directly from our AppView
:
onSomeRoute : function (project, category, pivot, tab, item) {
// Setup page context with defaults.
var options = {
category : 'traits',
tab : 'population',
item : null
};
// Figure out what the allowed categories and tabs are from this
// environment by checking against all schema items.
[snipped]
// Resolve the `category` and the `pivot`. Try and match the pivot's
// slug, else choose the first pivot in the category.
if (_(categories).contains(category)) options.category = category;
[snipped]
if (pivots) options.pivot = pivots.find({ slug : pivot }) || pivots.first();
// Resolve the `tab` and `item`. Only allow valid tabs, and then try
// to match the item's slug as well, or choose the first one.
var tabs = ['population', 'sources', 'engagement', 'retention'];
if (_(tabs).contains(tab)) options.tab = tab;
[snipped]
// Now that we've applied some defaults, make sure the path is
// updated to reflect what the interface is showing.
[snipped]
this.loadPage('some-page', options);
}
And then in the PageView
in question we have a couple change handlers:
// When category changes, try to match the current pivot against pivots
// in the category, else choose the first pivot from the category.
onCategoryChange : function (context, category) {
var schema = this.context.get('environment').get(category);
if (!schema.contains(this.context.get('pivot')))
this.context.set('pivot', schema.first());
this.router.update({
category : category
});
},
// When the pivot changes, create a new set of segments for the context.
onPivotChange : function (context, pivot) {
Segmenter.segment(context, function (segments) {
context.get('segments').reset(segments).each(function (segment) {
segment.evaluate();
});
});
this.router.update({
pivot : pivot.get('slug')
});
},
Notice that all of the change handlers keep the URL for the page updated based on the changes in the context. (We wrote a couple custom methods on the router to make this really easy for us.) But they also do any other logic we need to have happen for the page.
Hopefully some of that helps give you ideas. In general, any state that is stored in the URL is in our PageContext
and then a couple other things that are derived from that state are also stored there for convenience.