A Brief History of Query Params ● 2013: Alex Speller implements standalone QP lib, later merged into Canary ● 2014: Alex Matchneer rewrites query params (query-params-new) ● 2014: Alex Matchneer rewrites query params again (query-params-new-new-final- 2)
Where should QPs live? The Router? ● The original QP implementation was very route-centric: ● Specify all QPs in the Router.map DSL ● Optionally pass QP values to server queries ● Optionally use QP values to set controller properties
Problems with Route-centric QPs ● Lots of hoops to jump through to get QPs into controllers. ● Keeping QPs in sync w controller props was a manual, cumbersome, error-prone process
Query Params Today (query-params-new) ● Extremely simple API ● QPs are bound to controller properties: update controller QP prop and URL will update, and vice versa ● No need for custom observing / coalescing of controller QP property changes
QPs: Default values simplify (de)serialization App.ArticlesController = Ember.ArrayController.extend({ queryParams: [‘page’], page: 1 // default value }); ● When `page` === 1, URL is “/articles” ● When `page` === 2, URL is “/articles?page=2” ● If user clicks back button, intelligently deserialize into same type as default value; no need for parseInt, boolValue === “true”, etc
QPs: and more... ● Can opt into full on transition (re-fire model hooks) via config hash on Ember.Route ● Can also control whether URL query param syncs use pushState/replaceState, etc
Isn’t it an API smell that you’ve taken this thing that’s predominantly been the responsibility of the router to manage and moved it to controllers? - Mr. Router and other h8rz, 17 days ago
What is the job of a controller anyway? ● Manage application state ● Wrap the model with additional information to present to the template (also application state)
URL-less Ember Apps have ● links between app state hierarchies (with link-to and transitionTo) ● Deeply nested templates ● Loading / error substates ● Action bubbling ● …and a beautiful API to manage and reason about the above very challenging use cases
Serializing App State ● It should be easy to swap methods of serialization ● e.g. moving ?isActive=true to localStorage instead shouldn’t involve gutting your Router.map code
Hopefully we can all agree that ● the following is the wrong API: this.transitionTo(‘current.route’, { queryParams: { page: 3 } }); ● really you should just be able to write: controller.set(‘page’, 3);
Devil is in the details: property stickiness ● Enter a route ● Set controller query param properties ● Leave route ● Come back into the route… ● Are those QP properties remembered from before?
Controller Property Stickiness ● No One Right Answer (only some wrong ones) ● Welcome to the Dismal Science of API Design ● Goal: find the missing primitive, expose it as something that can be configured when necessary, but offer a high-level convention so that configuration is rare
Controller Property Stickiness Today ● Route-driven controllers, once instantiated, live forever ● Others have shorter lifecycles, e.g. item controllers
Controller Property Stickiness Today ● The sticky behavior we have today is coupled to controller lifecycle ● Very convenient for the ArticlesController at /articles ● Pretty undesirable / error-prone for the ArticlesShowController at /articles/some- title
The Primitive? ● The difference between the controllers at /articles vs /articles/some-title is that one has a uniquely identifiable model ● Property stickiness for ArticlesController: desirable ● Property stickiness for ArticlesShowController: only desirable when its model hasn’t changed ● e.g. isSelected shouldn’t be preserved when switching b/w some-title and other-title articles
Model-dependent State ● State, accessible to controllers, tied to a specific model ● Store / restore controller properties scoped to the controller’s model ● e.g. remember that article with ID ‘some-title’ isSelected, but ‘other-title’ is not
How is it implemented? ● Inject a cache object onto all controllers ● Each sticky property is responsible for computing a bucket key ● Replace sticky properties with bindings into the cache object, keyed by bucket key
How is it implemented? (cont.) ● Bucket keys are computed properties; when they change, a new bucket is allocated (or a previous one is looked up) ● Bucket keys based on the controller’s model ID give us… Model Dependent State
Bucket as thou wilt ● Full control over how a bucket is allocated ● Could just be a POJO (memory cache, forgotten on page reload) ● Could be your own LocalStorageProxy that writes into local storage as values change (remembered upon page reload)
Roadmap ● Iterate on query-params-new-new-final-7 to use model-dep state ● Later, provide higher-level API for property stickiness ● In the meantime, collect feedback from the folk mucking around in the bucket key trenches