ember.js self defining apps
TRANSCRIPT
Oli Griffiths - @oligriffiths
Custom Ember Data
Adapters
Self Defining
Apps
Oli Griffiths - @oligriffiths
Custom Ember Data Adapters
Oli Griffiths - @oligriffiths
Custom Ember Data Adapters
• Interface between Ember Data identity mapper store and some external data store
• Provides methods to find, create, update and delete records in external data store
• External data store can be anything you like, REST API, SOAP API, RSS Feed, LocalStorage…
Oli Griffiths - @oligriffiths
Custom Ember Data Adapters
//Set the adapter as the application adapter//Rest API adapterApp.ApplicationAdapter = DS.RestAdapter.extend() //Fixture (in memory only)App.ApplicationAdapter = DS.FixtureAdapter.extend()//Rails active record adapterApp.ApplicationAdapter = DS.ActiveModelAdapter.extend() //A custom adapterApp.ApplicationAdapter = CustomAdapter.extend()
Oli Griffiths - @oligriffiths
Custom Ember Data Adaptersvar CustomAdapter = DS.Adapter.extend({ /** @method find @param {DS.Store} store @param {subclass of DS.Model} type @param {String} id @return {Promise} promise */ find: function(store, type, url) {}, /** @private @method findAll @param {DS.Store} store @param {subclass of DS.Model} type @param {String} sinceToken @return {Promise} promise */ findAll: function(store, type) {},
Oli Griffiths - @oligriffiths
Custom Ember Data Adapters/** @private @method findQuery @param {DS.Store} store @param {subclass of DS.Model} type @param {Object} query @param {DS.AdapterPopulatedRecordArray} recordArray @return {Promise} promise */findQuery: function(store, type, query, array) {},/** @method createRecord @param {DS.Store} store @param {subclass of DS.Model} type @param {DS.Model} record @return {Promise} promise */createRecord: function(store, type, record) {},
Oli Griffiths - @oligriffiths
Custom Ember Data Adapters
/** @method updateRecord @param {DS.Store} store @param {subclass of DS.Model} type @param {DS.Model} record @return {Promise} promise */updateRecord: function(store, type, record) {},/** @method deleteRecord @param {DS.Store} store @param {subclass of DS.Model} type @param {DS.Model} record @return {Promise} promise */deleteRecord: function(store, type, record) {}
Oli Griffiths - @oligriffiths
REST Adapter Example
find: function(store, type, id) { return this.ajax(this.buildURL(type.typeKey, id), 'GET');}, findAll: function(store, type, sinceToken) { var query; if (sinceToken) { query = { since: sinceToken }; } return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query });}, findQuery: function(store, type, query) { return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query });},
Oli Griffiths - @oligriffiths
REST Adapter ExamplecreateRecord: function(store, type, record) { var data = {}; var serializer = store.serializerFor(type.typeKey); serializer.serializeIntoHash(data, type, record, { includeId: true }); return this.ajax(this.buildURL(type.typeKey), "POST", { data: data });}, updateRecord: function(store, type, record) { var data = {}; var serializer = store.serializerFor(type.typeKey); serializer.serializeIntoHash(data, type, record); var id = get(record, 'id'); return this.ajax(this.buildURL(type.typeKey, id), "PUT", { data: data });}, deleteRecord: function(store, type, record) { var id = get(record, 'id'); return this.ajax(this.buildURL(type.typeKey, id), "DELETE"); },
Oli Griffiths - @oligriffiths
Custom Ember Data Adapters
• Do not store data within the adapter
• Merely transport data from the local data store to some “external” data store
• Serializers are used to convert the data format to/from the local object representation to/from the “external” data store format
Oli Griffiths - @oligriffiths
Self Defining Apps
Oli Griffiths - @oligriffiths
What is a Self Defining App?
• When the full functionality of an app is unknown at “build time” (when the developer writes the application)
• Additional app functionality is created at runtime based on external dependencies
• Thus the app “builds” itself whilst running
Oli Griffiths - @oligriffiths
Ember: route first mentality
PizzaRoute
PizzaController
PizzaModel
PizzaView
Oli Griffiths - @oligriffiths
What if your routes are unknown at build time?
?Route
?Controller
?Model
?View
Oli Griffiths - @oligriffiths
What if your routes are unknown at build time?
• You have a problem, everything depends on routes
• Without defined routes, transitioning to a view is impossible
• Some websites do not have pattern-able routes, so how can you define routes at “build time”
Oli Griffiths - @oligriffiths
So how can we handle this?
• Wild card routes allow you to register a route in the eventuality that no corresponding route is found
• Wild card routes are intended to catch when a user tries to transition to an undefined route and show an error page
• We can use this functionality to our advantage to build new routes at runtime
Oli Griffiths - @oligriffiths
So how can we handle this?• Here is the flow:
1. In the model hook, create an ember valid route name based off the URL being transitioned to
2. Make an ajax request to the URL you’re attempting to transition to (swap .html to .json, or however your API works)
3. Extract an identifying property from the response, perhaps “type” or “model”
4. Register the URL (path) against the created route name from (1)
Oli Griffiths - @oligriffiths
So how we can handle this?
5. Register a new route instance in the application container, manually setting the controller, view and template name
6. Transition to the newly defined route
Oli Griffiths - @oligriffiths
What’s this look like?App.CatchAllRoute = Ember.Route.extend({ model: function(params, transition) { //Get the URL and convert to route name var url = transition.intent.url; var route_name = Ember.String.classify(url.replace(/[\.|\-|\/]+/g,'_')); //Check if route already exists, if so, transition to it var route = route_name ? this.container.resolve('route:'+route_name) : null; if(route) return this.transitionTo(route_name); //Get the data loader and load the data for the destination url var resolver = this.container.lookup(‘resolver:entity'); //Make an ajax request to the endpoint to get the endpoint data return resolver.request(url).then(function(data){ //Get the type from the response model property var type = data.model; //If no route is set (index) then set the route name to the type if(!route_name) route_name = type;
Oli Griffiths - @oligriffiths
What’s this look like?
//Add a new route for the url in questionApp.Router.map(function(){ this.resource(route_name, {path: url});});//Register new route, manually setting the controller,template and view namesthis.container.register('route:'+route_name, Ember.Route.extend({ controllerName: type, viewName: type, templateName: type, !
model: function(){ var plural = type.substr(-1) == 's'; var name = plural ? type.substr(0, type.length-1) : type; if(!plural) return this.store.find(name, url); return this.store.filter(name, {url: url}, function(entity){
//filter model data if applicable }.bind(this)); }
}));
Oli Griffiths - @oligriffiths
What’s this look like?!//Transition to new routereturn this.transitionTo(route_name); }.bind(this), function(data){ //Force a manual page change document.location.href = url; });
! } }) !App.ApplicationView = Ember.View.extend({ didInsertElement: function() {
//Setup click handler to trigger transitionthis.$().on('click', 'a', function(e){ var a = $(e.target).closest('a'); //Ensure only links on this domain are captured if(a.prop('origin') != document.location.origin) return; //Stop default browser handler e.preventDefault(); ! //Now you can transition to any route within a view using a links url this.controller.transitionToRoute(a.prop(‘pathname’)); })
} })
Oli Griffiths - @oligriffiths
Demo time