Forked from oligriffiths/ember-self-defining-apps-adapter.js
Last active
August 29, 2015 14:06
-
-
Save vire/7368a43e86329bd155d1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Custom data adapter | |
* | |
* Adapter delegates request methods to the entity resolver, rather than a regular ajax request. | |
* Add/edit/delete methods are suppressed for this implementation but could easily be enabled | |
* | |
***/ | |
module.exports = DS.Adapter.extend({ | |
entities: {}, | |
// by default, entities are already in normalized form | |
serializer: null, | |
/** | |
Implement this method in order to provide data associated with a type | |
@method entitiesForType | |
@param {Subclass of DS.Model} type | |
@return {Array} | |
*/ | |
entitiesForType: function(type) { | |
if(!this.entities[type.typeKey]){ | |
this.entities[type.typeKey] = Ember.A(); | |
} | |
return this.entities[type.typeKey]; | |
}, | |
/** | |
Implement this method in order to query entities data | |
@method queryEntities | |
@param {Array} entity | |
@param {Object} query | |
@param {Subclass of DS.Model} type | |
@return {Promise|Array} | |
*/ | |
queryEntities: function(entities, query, type) { | |
Ember.assert('Not implemented: You must override the DS.EntityAdapter::queryEntities method to support querying the entity store.'); | |
}, | |
/** | |
@method updateEntities | |
@param {Subclass of DS.Model} type | |
@param {Array} entity | |
*/ | |
updateEntities: function(type, entity) { | |
if(!this.entities[type.typeKey]){ | |
this.entities[type.typeKey] = Ember.A(); | |
} | |
var entities = this.entities[type.typeKey]; | |
this.deleteLoadedEntity(type, entity); | |
entities.push(entity); | |
}, | |
/** | |
Implement this method in order to provide json for CRUD methods | |
@method mockJSON | |
@param {Subclass of DS.Model} type | |
@param {DS.Model} record | |
*/ | |
mockJSON: function(store, type, record) { | |
return store.serializerFor(type).serialize(record, { includeId: true }); | |
}, | |
/** | |
@method generateIdForRecord | |
@param {DS.Store} store | |
@param {DS.Model} record | |
@return {String} id | |
*/ | |
generateIdForRecord: function(store) { | |
return "entity-" + counter++; | |
}, | |
/** | |
@method find | |
@param {DS.Store} store | |
@param {subclass of DS.Model} type | |
@param {String} id | |
@return {Promise} promise | |
*/ | |
find: function(store, type, url) { | |
var entities = this.entitiesForType(type), | |
entity; | |
if (entities) { | |
entity = Ember.A(entities).findProperty('id', url); | |
} | |
if (entity) { | |
return this.getPromise(function() { | |
return entity; | |
}, this); | |
} | |
var resolver = this.container.lookup('resolver:entity'); | |
return resolver.requestContent(url).then(function(data){ | |
return data; | |
}); | |
}, | |
/** | |
@method findMany | |
@param {DS.Store} store | |
@param {subclass of DS.Model} type | |
@param {Array} ids | |
@return {Promise} promise | |
*/ | |
findMany: function(store, type, ids) { | |
var entities = this.entitiesForType(type); | |
Ember.assert("Unable to find entities for model type "+type.toString(), entities); | |
if (entities) { | |
entities = entities.filter(function(item) { | |
return indexOf(ids, item.id) !== -1; | |
}); | |
} | |
if (entities) { | |
return this.getPromise(function() { | |
return entities; | |
}, this); | |
} | |
}, | |
/** | |
@private | |
@method findAll | |
@param {DS.Store} store | |
@param {subclass of DS.Model} type | |
@param {String} sinceToken | |
@return {Promise} promise | |
*/ | |
findAll: function(store, type) { | |
var entities = this.entitiesForType(type); | |
Ember.assert("Unable to find entities for model type "+type.toString(), entities); | |
return this.getPromise(function() { | |
return entities; | |
}, this); | |
}, | |
/** | |
@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) { | |
var url = query.url; | |
Ember.assert("No url passed to findQuery(), pass {url: '/the/url'}", url); | |
var resolver = this.container.lookup('resolver:entity'); | |
return resolver.requestContent(url, query.callback).then(function(data){ | |
return data; | |
}, function(){ | |
Ember.assert("Unable to find entities for model type " + type.toString()); | |
}); | |
}, | |
/** | |
@method createRecord | |
@param {DS.Store} store | |
@param {subclass of DS.Model} type | |
@param {DS.Model} record | |
@return {Promise} promise | |
*/ | |
createRecord: function(store, type, record) { | |
var entity = this.mockJSON(store, type, record); | |
this.updateEntities(type, entity); | |
return this.getPromise(function() { | |
return entity; | |
}, this); | |
}, | |
/** | |
@method updateRecord | |
@param {DS.Store} store | |
@param {subclass of DS.Model} type | |
@param {DS.Model} record | |
@return {Promise} promise | |
*/ | |
updateRecord: function(store, type, record) { | |
var entity = this.mockJSON(store, type, record); | |
this.updateEntities(type, entity); | |
return this.getPromise(function() { | |
return entity; | |
}, this); | |
}, | |
/** | |
@method deleteRecord | |
@param {DS.Store} store | |
@param {subclass of DS.Model} type | |
@param {DS.Model} record | |
@return {Promise} promise | |
*/ | |
deleteRecord: function(store, type, record) { | |
var entity = this.mockJSON(store, type, record); | |
this.deleteLoadedEntity(type, entity); | |
return this.getPromise(function() { | |
// no payload in a deletion | |
return null; | |
}); | |
}, | |
/* | |
@method deleteLoadedEntity | |
@private | |
@param type | |
@param record | |
*/ | |
deleteLoadedEntity: function(type, record) { | |
var existingEntity = this.findExistingEntity(type, record); | |
if(existingEntity) { | |
var index = indexOf(this.entities[type.typeKey], existingEntity); | |
this.entities[type.typeKey].splice(index, 1); | |
return true; | |
} | |
}, | |
/* | |
@method findExistingEntity | |
@private | |
@param type | |
@param record | |
*/ | |
findExistingEntity: function(type, record) { | |
var entities = this.entitiesForType(type); | |
var id = Ember.get(record, 'id'); | |
return this.findEntityById(entities, id); | |
}, | |
/* | |
@method findEntityById | |
@private | |
@param entities | |
@param id | |
*/ | |
findEntityById: function(entities, id) { | |
return Ember.A(entities).find(function(r) { | |
if(''+Ember.get(r, 'id') === ''+id) { | |
return true; | |
} else { | |
return false; | |
} | |
}); | |
}, | |
/* | |
@method getPromise | |
@private | |
@param callback | |
@param context | |
*/ | |
getPromise: function(callback, context) { | |
var adapter = this; | |
return new Ember.RSVP.Promise(function(resolve) { | |
resolve(callback.call(context)); | |
}, "DS: EntityAdapter#getPromise"); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*** | |
* Entity resolver: | |
* | |
* This class is a wrapper for a 3rd party service called Mobify (mobify.com) | |
* Essentially this class handles interfacing with the mobify library, and formatting the response | |
* in an ember-friendly way. This includes ensuring the entities have an identifier. | |
* It also provides a level of caching to ensure requests and re-submitted | |
**/ | |
var App = require('application'); | |
var Mobify = require('./mobify') | |
var EntityResolver = Ember.Object.extend({ | |
defaults: { | |
data: {}, | |
url: null | |
}, | |
baseLocation: null, | |
data: {}, | |
init: function(){ | |
if(this.defaults){ | |
if(this.defaults.url && this.defaults.data) this.formatData(this.defaults.data, this.defaults.url); | |
if(this.defaults.data && this.defaults.data.location) this.baseLocation = this.defaults.data.location; | |
} | |
}, | |
request: function (url) { | |
var url = this.stripDomain(url); | |
Ember.Logger.info('Requesting url: "'+url); | |
return new Ember.RSVP.Promise(function (resolve, failure) { | |
if (this.data[url]){ | |
Ember.Logger.info('URL "'+url+'" previously requested, returning cached data:', this.data[url]); | |
return resolve(this.data[url]); | |
} | |
Mobify.request(url, | |
function (data) { | |
Ember.Logger.info('URL "'+url+'" responded with data:', data); | |
if(data.content) resolve(this.formatData(data, url)); | |
else failure(data); | |
}.bind(this), | |
failure | |
); | |
}.bind(this)); | |
}, | |
requestContent: function(url, callback) { | |
return this.request(url).then(callback).then(function (data) { | |
var rtn = null; | |
switch (data.content.templateName) { | |
case 'panels': | |
rtn = data.content.panels; | |
break; | |
case 'article': | |
rtn = data.content; | |
break; | |
} | |
return rtn; | |
}) | |
}, | |
getData: function(url){ | |
return this.data[url]; | |
}, | |
formatData: function (data, url) { | |
//Ensure this data wasn't previously imported | |
if(this.data[url]) return this.data[url]; | |
switch (data.content.templateName) { | |
case 'panels': | |
if (data.content.panels instanceof Array) { | |
for (var i = 0; i < data.content.panels.length; i++) { | |
var panel = data.content.panels[i]; | |
panel.id = this.stripDomain(panel.href, data.location); | |
panel.location = url; | |
data.content.panels[i] = panel; | |
} | |
} | |
break; | |
case 'article': | |
var article = data.content; | |
article.id = data.location.pathname; | |
article.href = data.absoluteUrl; | |
data.content = article;; | |
break; | |
} | |
//Cache the data | |
this.data[url] = data; | |
return data; | |
}, | |
stripDomain: function (url, location) { | |
location = location || this.baseLocation || {origin: ''}; | |
var quote = function (str) { | |
return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); | |
}; | |
var regex = new RegExp('^' + quote(location.origin)); | |
return url.replace(regex, ''); | |
} | |
}); | |
var resolver = EntityResolver.create({defaults: {data:Mobify.evaluatedData, url: document.location.pathname}}); | |
module.exports = resolver; | |
Ember.Application.initializer({ | |
name: 'entityresolver', | |
initialize: function(container, application) { | |
application.register('resolver:entity', resolver, {instantiate: false, singleton: true}); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Routing setup: | |
* Here we define a wildcard route that catches all requests, even to the homepage | |
* In the catchall route model hook, we delegate a call to the entity resolver, which in turn returns data | |
* on how to render the current route. This data is then used to build a route during runtime, add it to the | |
* defined routes, so that in future, transitioning to this route will use the pre-built route. | |
* Once the route is built, we immediately transition to the new route. | |
* A custom model hook for the new route is defined in order to return the correct data from the store, | |
* this is the only implementation (client) specific part of this operation. | |
* | |
**/ | |
var App = require('application'); | |
//Setup routes | |
App.Router.map(function() { | |
this.route('catchAll', { path: '*:' }); | |
}); | |
App.CatchAllRoute = App.ApplicationRoute.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 loader and load the data for the destination url | |
var resolver = this.container.lookup('resolver:entity'); | |
return resolver.request(url).then(function(data){ | |
//Get the type from the response | |
var type = data.type; | |
//If no route is set (index) then set the route name to the type | |
if(!route_name) route_name = type; | |
//Add a new route for the url in question | |
App.Router.map(function(){ | |
this.resource(route_name, {path: url}); | |
}); | |
//Register a new route, manually setting the controller,template and view names | |
this.container.register('route:'+route_name, App.ApplicationRoute.extend({ | |
controllerName: type, | |
viewName: type, | |
templateName: type, | |
url: url, | |
model: function(){ | |
var plural = type.substr(-1) == 's'; //Simple plurality check, | |
var name = plural ? type.substr(0, type.length-1) : type; | |
//If not plural, do a find by url (id). Model objects are indexed by id | |
if(!plural) return this.store.find(name, url); | |
//If plural, make a dyanamic filter | |
return this.store.filter(name, {url: url}, function(entity){ | |
var match = entity.get('location') == url; | |
return match; | |
}.bind(this)); | |
} | |
})); | |
//Transition to new route | |
return this.transitionTo(route_name); | |
}.bind(this), function(data){ | |
//Force a manual page change | |
document.location.href = url; | |
}); | |
} | |
}); | |
App.IndexRoute = App.CatchAllRoute.extend(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment