Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Adding ember-data as a dependency.

  • Loading branch information...
commit e44d16f4433904b9836a2819c5095d92ea97b9af 1 parent 7ee4acf
@joachimhs authored
Showing with 1,773 additions and 0 deletions.
  1. +1 −0  src/main/webapp/index.html
  2. +1,772 −0 src/main/webapp/scripts/ember-data-latest.js
View
1  src/main/webapp/index.html
@@ -12,6 +12,7 @@
<script src="scripts/jquery-1.7.1.min.js" type="text/javascript" charset="utf-8"></script>
<script src="scripts/jquery.transit.js" type="text/javascript" charset="utf-8"></script>
<script src="scripts/ember-0.9.4.js" type="text/javascript" charset="utf-8"></script>
+ <script src="scripts/ember-data-latest.js" type="text/javascript" charset="utf-8"></script>
<script src="scripts/ember-touch.js" type="text/javascript" charset="utf-8"></script>
<script src="app/app.js" type="text/javascript" charset="utf-8"></script>
<script src="app/main.js" type="text/javascript" charset="utf-8"></script>
View
1,772 src/main/webapp/scripts/ember-data-latest.js
@@ -0,0 +1,1772 @@
+
+(function(exports) {
+window.DS = Ember.Namespace.create();
+
+})({});
+
+
+(function(exports) {
+DS.Adapter = Ember.Object.extend({
+ commit: function(store, commitDetails) {
+ commitDetails.updated.eachType(function(type, array) {
+ this.updateRecords(store, type, array.slice());
+ }, this);
+
+ commitDetails.created.eachType(function(type, array) {
+ this.createRecords(store, type, array.slice());
+ }, this);
+
+ commitDetails.deleted.eachType(function(type, array) {
+ this.deleteRecords(store, type, array.slice());
+ }, this);
+ },
+
+ createRecords: function(store, type, models) {
+ models.forEach(function(model) {
+ this.createRecord(store, type, model);
+ }, this);
+ },
+
+ updateRecords: function(store, type, models) {
+ models.forEach(function(model) {
+ this.updateRecord(store, type, model);
+ }, this);
+ },
+
+ deleteRecords: function(store, type, models) {
+ models.forEach(function(model) {
+ this.deleteRecord(store, type, model);
+ }, this);
+ },
+
+ findMany: function(store, type, ids) {
+ ids.forEach(function(id) {
+ this.find(store, type, id);
+ }, this);
+ }
+});
+})({});
+
+
+(function(exports) {
+DS.fixtureAdapter = DS.Adapter.create({
+ find: function(store, type, id) {
+ var fixtures = type.FIXTURES;
+
+ ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
+ if (fixtures.hasLoaded) { return; }
+
+ setTimeout(function() {
+ store.loadMany(type, fixtures);
+ fixtures.hasLoaded = true;
+ }, 300);
+ },
+
+ findMany: function() {
+ this.find.apply(this, arguments);
+ },
+
+ findAll: function(store, type) {
+ var fixtures = type.FIXTURES;
+
+ ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
+
+ var ids = fixtures.map(function(item, index, self){ return item.id });
+ store.loadMany(type, ids, fixtures);
+ }
+
+});
+
+})({});
+
+
+(function(exports) {
+var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
+
+DS.RESTAdapter = DS.Adapter.extend({
+ createRecord: function(store, type, model) {
+ var root = this.rootForType(type);
+
+ var data = {};
+ data[root] = get(model, 'data');
+
+ this.ajax("/" + this.pluralize(root), "POST", {
+ data: data,
+ success: function(json) {
+ store.didCreateRecord(model, json[root]);
+ }
+ });
+ },
+
+ createRecords: function(store, type, models) {
+ if (get(this, 'bulkCommit') === false) {
+ return this._super(store, type, models);
+ }
+
+ var root = this.rootForType(type),
+ plural = this.pluralize(root);
+
+ var data = {};
+ data[plural] = models.map(function(model) {
+ return get(model, 'data');
+ });
+
+ this.ajax("/" + this.pluralize(root), "POST", {
+ data: data,
+ success: function(json) {
+ store.didCreateRecords(type, models, json[plural]);
+ }
+ });
+ },
+
+ updateRecord: function(store, type, model) {
+ var primaryKey = getPath(type, 'proto.primaryKey'),
+ id = get(model, primaryKey);
+ var root = this.rootForType(type);
+
+ var data = {};
+ data[root] = get(model, 'data');
+
+ var url = ["", this.pluralize(root), id].join("/");
+
+ this.ajax(url, "PUT", {
+ data: data,
+ success: function(json) {
+ store.didUpdateRecord(model, json[root]);
+ }
+ });
+ },
+
+ updateRecords: function(store, type, models) {
+ if (get(this, 'bulkCommit') === false) {
+ return this._super(store, type, models);
+ }
+
+ var root = this.rootForType(type),
+ plural = this.pluralize(root);
+
+ var data = {};
+ data[plural] = models.map(function(model) {
+ return get(model, 'data');
+ });
+
+ this.ajax("/" + this.pluralize(root), "POST", {
+ data: data,
+ success: function(json) {
+ store.didUpdateRecords(models, json[plural]);
+ }
+ });
+ },
+
+ deleteRecord: function(store, type, model) {
+ var primaryKey = getPath(type, 'proto.primaryKey'),
+ id = get(model, primaryKey);
+ var root = this.rootForType(type);
+
+ var url = ["", this.pluralize(root), id].join("/");
+
+ this.ajax(url, "DELETE", {
+ success: function(json) {
+ store.didDeleteRecord(model);
+ }
+ });
+ },
+
+ deleteRecords: function(store, type, models) {
+ if (get(this, 'bulkCommit') === false) {
+ return this._super(store, type, models);
+ }
+
+ var root = this.rootForType(type),
+ plural = this.pluralize(root),
+ primaryKey = getPath(type, 'proto.primaryKey');
+
+ var data = {};
+ data[plural] = models.map(function(model) {
+ return get(model, primaryKey);
+ });
+
+ this.ajax("/" + this.pluralize(root) + "/delete", "POST", {
+ data: data,
+ success: function(json) {
+ store.didDeleteRecords(models);
+ }
+ });
+ },
+
+ find: function(store, type, id) {
+ var root = this.rootForType(type);
+
+ var url = ["", this.pluralize(root), id].join("/");
+
+ this.ajax(url, "GET", {
+ success: function(json) {
+ store.load(type, json[root]);
+ }
+ });
+ },
+
+ findMany: function(store, type, ids) {
+ var root = this.rootForType(type), plural = this.pluralize(root);
+
+ this.ajax("/" + plural, "GET", {
+ data: { ids: ids },
+ success: function(json) {
+ store.loadMany(type, ids, json[plural]);
+ }
+ });
+ var url = "/" + plural;
+ },
+
+ findAll: function(store, type) {
+ var root = this.rootForType(type), plural = this.pluralize(root);
+
+ this.ajax("/" + plural, "GET", {
+ success: function(json) {
+ store.loadMany(type, json[plural]);
+ }
+ });
+ },
+
+ findQuery: function(store, type, query, modelArray) {
+ var root = this.rootForType(type), plural = this.pluralize(root);
+
+ this.ajax("/" + plural, "GET", {
+ data: query,
+ success: function(json) {
+ modelArray.load(json[plural]);
+ }
+ });
+ },
+
+ // HELPERS
+
+ plurals: {},
+
+ // define a plurals hash in your subclass to define
+ // special-case pluralization
+ pluralize: function(name) {
+ return this.plurals[name] || name + "s";
+ },
+
+ rootForType: function(type) {
+ if (type.url) { return type.url; }
+
+ // use the last part of the name as the URL
+ var parts = type.toString().split(".");
+ var name = parts[parts.length - 1];
+ return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1);
+ },
+
+ ajax: function(url, type, hash) {
+ hash.url = url;
+ hash.type = type;
+ hash.dataType = "json";
+
+ jQuery.ajax(hash);
+ }
+});
+
+
+})({});
+
+
+(function(exports) {
+var get = Ember.get, set = Ember.set;
+
+DS.ModelArray = Ember.ArrayProxy.extend({
+ type: null,
+ content: null,
+ store: null,
+
+ init: function() {
+ set(this, 'modelCache', Ember.A([]));
+ this._super();
+ },
+
+ arrayDidChange: function(array, index, removed, added) {
+ var modelCache = get(this, 'modelCache');
+ modelCache.replace(index, 0, Array(added));
+
+ this._super(array, index, removed, added);
+ },
+
+ arrayWillChange: function(array, index, removed, added) {
+ this._super(array, index, removed, added);
+
+ var modelCache = get(this, 'modelCache');
+ modelCache.replace(index, removed);
+ },
+
+ objectAtContent: function(index) {
+ var modelCache = get(this, 'modelCache');
+ var model = modelCache.objectAt(index);
+
+ if (!model) {
+ var store = get(this, 'store');
+ var content = get(this, 'content');
+
+ var contentObject = content.objectAt(index);
+
+ if (contentObject !== undefined) {
+ model = store.findByClientId(get(this, 'type'), contentObject);
+ modelCache.replace(index, 1, [model]);
+ }
+ }
+
+ return model;
+ }
+});
+
+DS.FilteredModelArray = DS.ModelArray.extend({
+ filterFunction: null,
+
+ updateFilter: Ember.observer(function() {
+ var store = get(this, 'store');
+ store.updateModelArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
+ }, 'filterFunction')
+});
+
+DS.AdapterPopulatedModelArray = DS.ModelArray.extend({
+ query: null,
+ isLoaded: false,
+
+ load: function(array) {
+ var store = get(this, 'store'), type = get(this, 'type');
+
+ var clientIds = store.loadMany(type, array).clientIds;
+
+ this.beginPropertyChanges();
+ set(this, 'content', Ember.A(clientIds));
+ set(this, 'isLoaded', true);
+ this.endPropertyChanges();
+ }
+});
+
+})({});
+
+
+(function(exports) {
+var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
+
+var OrderedSet = Ember.Object.extend({
+ init: function() {
+ this.clear();
+ },
+
+ clear: function() {
+ this.set('presenceSet', {});
+ this.set('list', Ember.NativeArray.apply([]));
+ },
+
+ add: function(obj) {
+ var guid = Ember.guidFor(obj),
+ presenceSet = get(this, 'presenceSet'),
+ list = get(this, 'list');
+
+ if (guid in presenceSet) { return; }
+
+ presenceSet[guid] = true;
+ list.pushObject(obj);
+ },
+
+ remove: function(obj) {
+ var guid = Ember.guidFor(obj),
+ presenceSet = get(this, 'presenceSet'),
+ list = get(this, 'list');
+
+ delete presenceSet[guid];
+ list.removeObject(obj);
+ },
+
+ isEmpty: function() {
+ return getPath(this, 'list.length') === 0;
+ },
+
+ forEach: function(fn, self) {
+ get(this, 'list').forEach(function(item) {
+ fn.call(self, item);
+ });
+ },
+
+ toArray: function() {
+ return get(this, 'list').slice();
+ }
+});
+
+/**
+ A Hash stores values indexed by keys. Unlike JavaScript's
+ default Objects, the keys of a Hash can be any JavaScript
+ object.
+
+ Internally, a Hash has two data structures:
+
+ `keys`: an OrderedSet of all of the existing keys
+ `values`: a JavaScript Object indexed by the
+ Ember.guidFor(key)
+
+ When a key/value pair is added for the first time, we
+ add the key to the `keys` OrderedSet, and create or
+ replace an entry in `values`. When an entry is deleted,
+ we delete its entry in `keys` and `values`.
+*/
+
+var Hash = Ember.Object.extend({
+ init: function() {
+ set(this, 'keys', OrderedSet.create());
+ set(this, 'values', {});
+ },
+
+ add: function(key, value) {
+ var keys = get(this, 'keys'), values = get(this, 'values');
+ var guid = Ember.guidFor(key);
+
+ keys.add(key);
+ values[guid] = value;
+
+ return value;
+ },
+
+ remove: function(key) {
+ var keys = get(this, 'keys'), values = get(this, 'values');
+ var guid = Ember.guidFor(key), value;
+
+ keys.remove(key);
+
+ value = values[guid];
+ delete values[guid];
+
+ return value;
+ },
+
+ fetch: function(key) {
+ var values = get(this, 'values');
+ var guid = Ember.guidFor(key);
+
+ return values[guid];
+ },
+
+ forEach: function(fn, binding) {
+ var keys = get(this, 'keys'), values = get(this, 'values');
+
+ keys.forEach(function(key) {
+ var guid = Ember.guidFor(key);
+ fn.call(binding, key, values[guid]);
+ });
+ }
+});
+
+DS.Transaction = Ember.Object.extend({
+ init: function() {
+ set(this, 'dirty', {
+ created: Hash.create(),
+ updated: Hash.create(),
+ deleted: Hash.create()
+ });
+ },
+
+ createRecord: function(type, hash) {
+ var store = get(this, 'store');
+
+ return store.createRecord(type, hash, this);
+ },
+
+ add: function(model) {
+ var modelTransaction = get(model, 'transaction');
+ ember_assert("Models cannot belong to more than one transaction at a time.", !modelTransaction);
+
+ set(model, 'transaction', this);
+ },
+
+ modelBecameDirty: function(kind, model) {
+ var dirty = get(get(this, 'dirty'), kind),
+ type = model.constructor;
+
+ var models = dirty.fetch(type);
+
+ models = models || dirty.add(type, OrderedSet.create());
+ models.add(model);
+ },
+
+ modelBecameClean: function(kind, model) {
+ var dirty = get(get(this, 'dirty'), kind),
+ type = model.constructor;
+
+ var models = dirty.fetch(type);
+ models.remove(model);
+
+ set(model, 'transaction', null);
+ },
+
+ commit: function() {
+ var dirtyMap = get(this, 'dirty');
+
+ var iterate = function(kind, fn, binding) {
+ var dirty = get(dirtyMap, kind);
+
+ dirty.forEach(function(type, models) {
+ if (models.isEmpty()) { return; }
+
+ models.forEach(function(model) { model.willCommit(); });
+ fn.call(binding, type, models.toArray());
+ });
+ };
+
+ var commitDetails = {
+ updated: {
+ eachType: function(fn, binding) { iterate('updated', fn, binding); }
+ },
+
+ created: {
+ eachType: function(fn, binding) { iterate('created', fn, binding); }
+ },
+
+ deleted: {
+ eachType: function(fn, binding) { iterate('deleted', fn, binding); }
+ }
+ };
+
+ var store = get(this, 'store');
+ var adapter = get(store, '_adapter');
+ if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
+ else { throw fmt("Adapter is either null or do not implement `commit` method", this); }
+ }
+});
+
+})({});
+
+
+(function(exports) {
+var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
+
+var OrderedSet = Ember.Object.extend({
+ init: function() {
+ this.clear();
+ },
+
+ clear: function() {
+ this.set('presenceSet', {});
+ this.set('list', Ember.NativeArray.apply([]));
+ },
+
+ add: function(obj) {
+ var guid = Ember.guidFor(obj),
+ presenceSet = get(this, 'presenceSet'),
+ list = get(this, 'list');
+
+ if (guid in presenceSet) { return; }
+
+ presenceSet[guid] = true;
+ list.pushObject(obj);
+ },
+
+ remove: function(obj) {
+ var guid = Ember.guidFor(obj),
+ presenceSet = get(this, 'presenceSet'),
+ list = get(this, 'list');
+
+ delete presenceSet[guid];
+ list.removeObject(obj);
+ },
+
+ isEmpty: function() {
+ return getPath(this, 'list.length') === 0;
+ },
+
+ forEach: function(fn, self) {
+ get(this, 'list').forEach(function(item) {
+ fn.call(self, item);
+ });
+ }
+});
+
+// Implementors Note:
+//
+// The variables in this file are consistently named according to the following
+// scheme:
+//
+// * +id+ means an identifier managed by an external source, provided inside the
+// data hash provided by that source.
+// * +clientId+ means a transient numerical identifier generated at runtime by
+// the data store. It is important primarily because newly created objects may
+// not yet have an externally generated id.
+// * +type+ means a subclass of DS.Model.
+
+/**
+ The store contains all of the hashes for data models loaded from the server.
+ It is also responsible for creating instances of DS.Model when you request one
+ of these data hashes, so that they can be bound to in your Handlebars templates.
+
+ Create a new store like this:
+
+ MyApp.store = DS.Store.create();
+
+ You can retrieve DS.Model instances from the store in several ways. To retrieve
+ a model for a specific id, use the `find()` method:
+
+ var model = MyApp.store.find(MyApp.Contact, 123);
+
+ By default, the store will talk to your backend using a standard REST mechanism.
+ You can customize how the store talks to your backend by specifying a custom adapter:
+
+ MyApp.store = DS.Store.create({
+ adapter: 'MyApp.CustomAdapter'
+ });
+
+ You can learn more about writing a custom adapter by reading the `DS.Adapter`
+ documentation.
+*/
+DS.Store = Ember.Object.extend({
+
+ /**
+ Many methods can be invoked without specifying which store should be used.
+ In those cases, the first store created will be used as the default. If
+ an application has multiple stores, it should specify which store to use
+ when performing actions, such as finding records by id.
+
+ The init method registers this store as the default if none is specified.
+ */
+ init: function() {
+ if (!get(DS, 'defaultStore') || get(this, 'isDefaultStore')) {
+ set(DS, 'defaultStore', this);
+ }
+
+ set(this, 'data', []);
+ set(this, '_typeMap', {});
+ set(this, 'models', []);
+ set(this, 'modelArrays', []);
+ set(this, 'modelArraysByClientId', {});
+ set(this, 'defaultTransaction', DS.Transaction.create({ store: this }));
+
+ return this._super();
+ },
+
+ transaction: function() {
+ return DS.Transaction.create({ store: this });
+ },
+
+ modelArraysForClientId: function(clientId) {
+ var modelArrays = get(this, 'modelArraysByClientId');
+ var ret = modelArrays[clientId];
+
+ if (!ret) {
+ ret = modelArrays[clientId] = OrderedSet.create();
+ }
+
+ return ret;
+ },
+
+ /**
+ The adapter to use to communicate to a backend server or other persistence layer.
+
+ This can be specified as an instance, a class, or a property path that specifies
+ where the adapter can be located.
+
+ @property {DS.Adapter|String}
+ */
+ adapter: null,
+
+ _adapter: Ember.computed(function() {
+ var adapter = get(this, 'adapter');
+ if (typeof adapter === 'string') {
+ return getPath(this, adapter, false) || getPath(window, adapter);
+ }
+ return adapter;
+ }).property('adapter').cacheable(),
+
+ clientIdCounter: -1,
+
+ // ....................
+ // . CREATE NEW MODEL .
+ // ....................
+
+ createRecord: function(type, hash, transaction) {
+ hash = hash || {};
+
+ var id = hash[getPath(type, 'proto.primaryKey')] || null;
+
+ var model = type.create({
+ data: hash || {},
+ store: this,
+ transaction: transaction
+ });
+
+ model.adapterDidCreate();
+
+ var data = this.clientIdToHashMap(type);
+ var models = get(this, 'models');
+
+ var clientId = this.pushHash(hash, id, type);
+
+ set(model, 'clientId', clientId);
+
+ models[clientId] = model;
+
+ this.updateModelArrays(type, clientId, hash);
+
+ return model;
+ },
+
+ // ................
+ // . DELETE MODEL .
+ // ................
+
+ deleteRecord: function(model) {
+ model.deleteRecord();
+ },
+
+ // ...............
+ // . FIND MODELS .
+ // ...............
+
+ /**
+ Finds a model by its id. If the data for that model has already been
+ loaded, an instance of DS.Model with that data will be returned
+ immediately. Otherwise, an empty DS.Model instance will be returned in
+ the loading state. As soon as the requested data is available, the model
+ will be moved into the loaded state and all of the information will be
+ available.
+
+ Note that only one DS.Model instance is ever created per unique id for a
+ given type.
+
+ Example:
+
+ var record = MyApp.store.find(MyApp.Person, 1234);
+
+ @param {DS.Model} type
+ @param {String|Number} id
+ */
+ find: function(type, id, query) {
+ if (id === undefined) {
+ return this.findMany(type, null, null);
+ }
+
+ if (query !== undefined) {
+ return this.findMany(type, id, query);
+ } else if (Ember.typeOf(id) === 'object') {
+ return this.findQuery(type, id);
+ }
+
+ if (Ember.isArray(id)) {
+ return this.findMany(type, id);
+ }
+
+ var clientId = this.clientIdForId(type, id);
+
+ return this.findByClientId(type, clientId, id);
+ },
+
+ findByClientId: function(type, clientId, id) {
+ var model;
+
+ var models = get(this, 'models');
+ var data = this.clientIdToHashMap(type);
+
+ // If there is already a clientId assigned for this
+ // type/id combination, try to find an existing
+ // model for that id and return. Otherwise,
+ // materialize a new model and set its data to the
+ // value we already have.
+ if (clientId !== undefined) {
+ model = models[clientId];
+
+ if (!model) {
+ // create a new instance of the model in the
+ // 'isLoading' state
+ model = this.createModel(type, clientId);
+
+ // immediately set its data
+ model.setData(data[clientId] || null);
+ }
+ } else {
+ clientId = this.pushHash(null, id, type);
+
+ // create a new instance of the model in the
+ // 'isLoading' state
+ model = this.createModel(type, clientId);
+
+ // let the adapter set the data, possibly async
+ var adapter = get(this, '_adapter');
+ if (adapter && adapter.find) { adapter.find(this, type, id); }
+ else { throw fmt("Adapter is either null or do not implement `find` method", this); }
+ }
+
+ return model;
+ },
+
+ /** @private
+ */
+ findMany: function(type, ids, query) {
+ var idToClientIdMap = this.idToClientIdMap(type);
+ var data = this.clientIdToHashMap(type), needed;
+
+ var clientIds = Ember.A([]);
+
+ if (ids) {
+ needed = [];
+
+ ids.forEach(function(id) {
+ var clientId = idToClientIdMap[id];
+ if (clientId === undefined || data[clientId] === undefined) {
+ clientId = this.pushHash(null, id, type);
+ needed.push(id);
+ }
+
+ clientIds.push(clientId);
+ }, this);
+ } else {
+ needed = null;
+ }
+
+ if ((needed && get(needed, 'length') > 0) || query) {
+ var adapter = get(this, '_adapter');
+ if (adapter && adapter.findMany) { adapter.findMany(this, type, needed, query); }
+ else { throw fmt("Adapter is either null or do not implement `findMany` method", this); }
+ }
+
+ return this.createModelArray(type, clientIds);
+ },
+
+ findQuery: function(type, query) {
+ var array = DS.AdapterPopulatedModelArray.create({ type: type, content: Ember.A([]), store: this });
+ var adapter = get(this, '_adapter');
+ if (adapter && adapter.findQuery) { adapter.findQuery(this, type, query, array); }
+ else { throw fmt("Adapter is either null or do not implement `findQuery` method", this); }
+ return array;
+ },
+
+ findAll: function(type) {
+
+ var typeMap = this.typeMapFor(type),
+ findAllCache = typeMap.findAllCache;
+
+ if (findAllCache) { return findAllCache; }
+
+ var array = DS.ModelArray.create({ type: type, content: Ember.A([]), store: this });
+ this.registerModelArray(array, type);
+
+ var adapter = get(this, '_adapter');
+ if (adapter && adapter.findAll) { adapter.findAll(this, type); }
+
+ typeMap.findAllCache = array;
+ return array;
+ },
+
+ filter: function(type, filter) {
+ var array = DS.FilteredModelArray.create({ type: type, content: Ember.A([]), store: this, filterFunction: filter });
+
+ this.registerModelArray(array, type, filter);
+
+ return array;
+ },
+
+ // ............
+ // . UPDATING .
+ // ............
+
+ hashWasUpdated: function(type, clientId) {
+ var clientIdToHashMap = this.clientIdToHashMap(type);
+ var hash = clientIdToHashMap[clientId];
+
+ this.updateModelArrays(type, clientId, hash);
+ },
+
+ // ..............
+ // . PERSISTING .
+ // ..............
+
+ commit: function() {
+ get(this, 'defaultTransaction').commit();
+ },
+
+ didUpdateRecords: function(array, hashes) {
+ if (arguments.length === 2) {
+ array.forEach(function(model, idx) {
+ this.didUpdateRecord(model, hashes[idx]);
+ }, this);
+ } else {
+ array.forEach(function(model) {
+ this.didUpdateRecord(model);
+ }, this);
+ }
+ },
+
+ didUpdateRecord: function(model, hash) {
+ if (arguments.length === 2) {
+ var clientId = get(model, 'clientId');
+ var data = this.clientIdToHashMap(model.constructor);
+
+ data[clientId] = hash;
+ model.set('data', hash);
+ }
+
+ model.adapterDidUpdate();
+ },
+
+ didDeleteRecords: function(array) {
+ array.forEach(function(model) {
+ model.adapterDidDelete();
+ });
+ },
+
+ didDeleteRecord: function(model) {
+ model.adapterDidDelete();
+ },
+
+ didCreateRecords: function(type, array, hashes) {
+ var id, clientId, primaryKey = getPath(type, 'proto.primaryKey');
+
+ var idToClientIdMap = this.idToClientIdMap(type);
+ var data = this.clientIdToHashMap(type);
+ var idList = this.idList(type);
+
+ for (var i=0, l=get(array, 'length'); i<l; i++) {
+ var model = array[i], hash = hashes[i];
+ id = hash[primaryKey];
+ clientId = get(model, 'clientId');
+
+ data[clientId] = hash;
+ set(model, 'data', hash);
+
+ idToClientIdMap[id] = clientId;
+ idList.push(id);
+
+ model.adapterDidUpdate();
+ }
+ },
+
+ didCreateRecord: function(model, hash) {
+ var type = model.constructor;
+
+ var id, clientId, primaryKey = getPath(type, 'proto.primaryKey');
+
+ var idToClientIdMap = this.idToClientIdMap(type);
+ var data = this.clientIdToHashMap(type);
+ var idList = this.idList(type);
+
+ id = hash[primaryKey];
+
+ clientId = get(model, 'clientId');
+ data[clientId] = hash;
+ set(model, 'data', hash);
+
+ idToClientIdMap[id] = clientId;
+ idList.push(id);
+
+ model.adapterDidUpdate();
+ },
+
+ recordWasInvalid: function(record, errors) {
+ record.wasInvalid(errors);
+ },
+
+ // ................
+ // . MODEL ARRAYS .
+ // ................
+
+ registerModelArray: function(array, type, filter) {
+ var modelArrays = get(this, 'modelArrays');
+ var idToClientIdMap = this.idToClientIdMap(type);
+
+ modelArrays.push(array);
+
+ this.updateModelArrayFilter(array, type, filter);
+ },
+
+ createModelArray: function(type, clientIds) {
+ var array = DS.ModelArray.create({ type: type, content: clientIds, store: this });
+
+ clientIds.forEach(function(clientId) {
+ var modelArrays = this.modelArraysForClientId(clientId);
+ modelArrays.add(array);
+ }, this);
+
+ return array;
+ },
+
+ updateModelArrayFilter: function(array, type, filter) {
+ var data = this.clientIdToHashMap(type);
+ var allClientIds = this.clientIdList(type);
+
+ for (var i=0, l=allClientIds.length; i<l; i++) {
+ clientId = allClientIds[i];
+
+ hash = data[clientId];
+
+ if (hash) {
+ this.updateModelArray(array, filter, type, clientId, hash);
+ }
+ }
+ },
+
+ updateModelArrays: function(type, clientId, hash) {
+ var modelArrays = get(this, 'modelArrays');
+
+ modelArrays.forEach(function(array) {
+ modelArrayType = get(array, 'type');
+ filter = get(array, 'filterFunction');
+
+ if (type !== modelArrayType) { return; }
+
+ this.updateModelArray(array, filter, type, clientId, hash);
+ }, this);
+ },
+
+ updateModelArray: function(array, filter, type, clientId, hash) {
+ var shouldBeInArray;
+
+ if (!filter) {
+ shouldBeInArray = true;
+ } else {
+ shouldBeInArray = filter(hash);
+ }
+
+ var content = get(array, 'content');
+ var alreadyInArray = content.indexOf(clientId) !== -1;
+
+ var modelArrays = this.modelArraysForClientId(clientId);
+
+ if (shouldBeInArray && !alreadyInArray) {
+ modelArrays.add(array);
+ content.pushObject(clientId);
+ } else if (!shouldBeInArray && alreadyInArray) {
+ modelArrays.remove(array);
+ content.removeObject(clientId);
+ }
+ },
+
+ removeFromModelArrays: function(model) {
+ var clientId = get(model, 'clientId');
+ var modelArrays = this.modelArraysForClientId(clientId);
+
+ modelArrays.forEach(function(array) {
+ var content = get(array, 'content');
+ content.removeObject(clientId);
+ });
+ },
+
+ // ............
+ // . TYPE MAP .
+ // ............
+
+ typeMapFor: function(type) {
+ var ids = get(this, '_typeMap');
+ var guidForType = Ember.guidFor(type);
+
+ var typeMap = ids[guidForType];
+
+ if (typeMap) {
+ return typeMap;
+ } else {
+ return (ids[guidForType] =
+ {
+ idToCid: {},
+ idList: [],
+ cidList: [],
+ cidToHash: {}
+ });
+ }
+ },
+
+ idToClientIdMap: function(type) {
+ return this.typeMapFor(type).idToCid;
+ },
+
+ idList: function(type) {
+ return this.typeMapFor(type).idList;
+ },
+
+ clientIdList: function(type) {
+ return this.typeMapFor(type).cidList;
+ },
+
+ clientIdToHashMap: function(type) {
+ return this.typeMapFor(type).cidToHash;
+ },
+
+ /** @private
+
+ For a given type and id combination, returns the client id used by the store.
+ If no client id has been assigned yet, `undefined` is returned.
+
+ @param {DS.Model} type
+ @param {String|Number} id
+ */
+ clientIdForId: function(type, id) {
+ return this.typeMapFor(type).idToCid[id];
+ },
+
+ idForHash: function(type, hash) {
+ var primaryKey = getPath(type, 'proto.primaryKey');
+
+ ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
+ return hash[primaryKey];
+ },
+
+ // ................
+ // . LOADING DATA .
+ // ................
+
+ /**
+ Load a new data hash into the store for a given id and type combination.
+ If data for that model had been loaded previously, the new information
+ overwrites the old.
+
+ If the model you are loading data for has outstanding changes that have not
+ yet been saved, an exception will be thrown.
+
+ @param {DS.Model} type
+ @param {String|Number} id
+ @param {Object} hash the data hash to load
+ */
+ load: function(type, id, hash) {
+ if (hash === undefined) {
+ hash = id;
+ var primaryKey = getPath(type, 'proto.primaryKey');
+ ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
+ id = hash[primaryKey];
+ }
+
+ var data = this.clientIdToHashMap(type);
+ var models = get(this, 'models');
+
+ var clientId = this.clientIdForId(type, id);
+
+ if (clientId !== undefined) {
+ data[clientId] = hash;
+
+ var model = models[clientId];
+ if (model) {
+ model.willLoadData();
+ model.setData(hash);
+ }
+ } else {
+ clientId = this.pushHash(hash, id, type);
+ }
+
+ this.updateModelArrays(type, clientId, hash);
+
+ return { id: id, clientId: clientId };
+ },
+
+ loadMany: function(type, ids, hashes) {
+ var clientIds = Ember.A([]);
+
+ if (hashes === undefined) {
+ hashes = ids;
+ ids = [];
+ var primaryKey = getPath(type, 'proto.primaryKey');
+
+ ids = hashes.map(function(hash) {
+ ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
+ return hash[primaryKey];
+ });
+ }
+
+ for (var i=0, l=get(ids, 'length'); i<l; i++) {
+ var loaded = this.load(type, ids[i], hashes[i]);
+ clientIds.pushObject(loaded.clientId);
+ }
+
+ return { clientIds: clientIds, ids: ids };
+ },
+
+ /** @private
+
+ Stores a data hash for the specified type and id combination and returns
+ the client id.
+
+ @param {Object} hash
+ @param {String|Number} id
+ @param {DS.Model} type
+ @returns {Number}
+ */
+ pushHash: function(hash, id, type) {
+ var idToClientIdMap = this.idToClientIdMap(type);
+ var clientIdList = this.clientIdList(type);
+ var idList = this.idList(type);
+ var data = this.clientIdToHashMap(type);
+
+ var clientId = this.incrementProperty('clientIdCounter');
+
+ data[clientId] = hash;
+
+ // if we're creating an item, this process will be done
+ // later, once the object has been persisted.
+ if (id) {
+ idToClientIdMap[id] = clientId;
+ idList.push(id);
+ }
+
+ clientIdList.push(clientId);
+
+ return clientId;
+ },
+
+ // .........................
+ // . MODEL MATERIALIZATION .
+ // .........................
+
+ createModel: function(type, clientId) {
+ var model;
+
+ get(this, 'models')[clientId] = model = type.create({ store: this, clientId: clientId });
+ set(model, 'clientId', clientId);
+ model.loadingData();
+ return model;
+ }
+});
+
+
+})({});
+
+
+(function(exports) {
+var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
+
+var stateProperty = Ember.computed(function(key) {
+ var parent = get(this, 'parentState');
+ if (parent) {
+ return get(parent, key);
+ }
+}).property();
+
+DS.State = Ember.State.extend({
+ isLoaded: stateProperty,
+ isDirty: stateProperty,
+ isSaving: stateProperty,
+ isDeleted: stateProperty,
+ isError: stateProperty,
+ isNew: stateProperty,
+ isValid: stateProperty
+});
+
+var cantLoadData = function() {
+ // TODO: get the current state name
+ throw "You cannot load data into the store when its associated model is in its current state";
+};
+
+var isEmptyObject = function(obj) {
+ for (var prop in obj) {
+ if (!obj.hasOwnProperty(prop)) { continue; }
+ return false;
+ }
+
+ return true;
+};
+
+var setProperty = function(manager, context) {
+ var key = context.key, value = context.value;
+
+ var model = get(manager, 'model'), type = model.constructor;
+ var store = get(model, 'store');
+ var data = get(model, 'data');
+
+ data[key] = value;
+
+ if (store) { store.hashWasUpdated(type, get(model, 'clientId')); }
+};
+
+// several states share extremely common functionality, so we are factoring
+// them out into a common class.
+var DirtyState = DS.State.extend({
+ // these states are virtually identical except that
+ // they (thrice) use their states name explicitly.
+ //
+ // child classes implement stateName.
+ stateName: null,
+ isDirty: true,
+ willLoadData: cantLoadData,
+
+ enter: function(manager) {
+ var stateName = get(this, 'stateName'),
+ model = get(manager, 'model');
+
+ model.withTransaction(function (t) {
+ t.modelBecameDirty(stateName, model);
+ });
+ },
+
+ exit: function(manager) {
+ var stateName = get(this, 'stateName'),
+ model = get(manager, 'model');
+
+ this.notifyModel(model);
+
+ model.withTransaction(function (t) {
+ t.modelBecameClean(stateName, model);
+ });
+ },
+
+ setProperty: setProperty,
+
+ willCommit: function(manager) {
+ manager.goToState('saving');
+ },
+
+ saving: DS.State.extend({
+ isSaving: true,
+
+ didUpdate: function(manager) {
+ manager.goToState('loaded');
+ },
+
+ wasInvalid: function(manager, errors) {
+ var model = get(manager, 'model');
+
+ set(model, 'errors', errors);
+ manager.goToState('invalid');
+ }
+ }),
+
+ invalid: DS.State.extend({
+ isValid: false,
+
+ setProperty: function(manager, context) {
+ setProperty(manager, context);
+
+ var stateName = getPath(this, 'parentState.stateName'),
+ model = get(manager, 'model'),
+ errors = get(model, 'errors'),
+ key = context.key;
+
+ delete errors[key];
+
+ if (isEmptyObject(errors)) {
+ manager.goToState(stateName);
+ }
+ }
+ })
+});
+
+var states = {
+ rootState: Ember.State.create({
+ isLoaded: false,
+ isDirty: false,
+ isSaving: false,
+ isDeleted: false,
+ isError: false,
+ isNew: false,
+ isValid: true,
+
+ willLoadData: cantLoadData,
+
+ didCreate: function(manager) {
+ manager.goToState('loaded.created');
+ },
+
+ empty: DS.State.create({
+ loadingData: function(manager) {
+ manager.goToState('loading');
+ }
+ }),
+
+ loading: DS.State.create({
+ willLoadData: Ember.K,
+
+ exit: function(manager) {
+ var model = get(manager, 'model');
+ model.didLoad();
+ },
+
+ setData: function(manager, data) {
+ var model = get(manager, 'model');
+
+ model.beginPropertyChanges();
+ model.set('data', data);
+
+ if (data !== null) {
+ manager.goToState('loaded');
+ }
+
+ model.endPropertyChanges();
+ }
+ }),
+
+ loaded: DS.State.create({
+ isLoaded: true,
+
+ willLoadData: Ember.K,
+
+ setProperty: function(manager, context) {
+ setProperty(manager, context);
+ manager.goToState('updated');
+ },
+
+ 'delete': function(manager) {
+ manager.goToState('deleted');
+ },
+
+ created: DirtyState.create({
+ stateName: 'created',
+ isNew: true,
+
+ notifyModel: function(model) {
+ model.didCreate();
+ }
+ }),
+
+ updated: DirtyState.create({
+ stateName: 'updated',
+
+ notifyModel: function(model) {
+ model.didUpdate();
+ }
+ })
+ }),
+
+ deleted: DS.State.create({
+ isDeleted: true,
+ isLoaded: true,
+ isDirty: true,
+
+ willLoadData: cantLoadData,
+
+ enter: function(manager) {
+ var model = get(manager, 'model');
+ var store = get(model, 'store');
+
+ if (store) {
+ store.removeFromModelArrays(model);
+ }
+
+ model.withTransaction(function(t) {
+ t.modelBecameDirty('deleted', model);
+ });
+ },
+
+ willCommit: function(manager) {
+ manager.goToState('saving');
+ },
+
+ saving: DS.State.create({
+ isSaving: true,
+
+ didDelete: function(manager) {
+ manager.goToState('saved');
+ },
+
+ exit: function(stateManager) {
+ var model = get(stateManager, 'model');
+
+ model.withTransaction(function(t) {
+ t.modelBecameClean('deleted', model);
+ });
+ }
+ }),
+
+ saved: DS.State.create({
+ isDirty: false
+ })
+ }),
+
+ error: DS.State.create({
+ isError: true
+ })
+ })
+};
+
+DS.StateManager = Ember.StateManager.extend({
+ model: null,
+ initialState: 'rootState',
+ states: states
+});
+
+var retrieveFromCurrentState = Ember.computed(function(key) {
+ return get(getPath(this, 'stateManager.currentState'), key);
+}).property('stateManager.currentState').cacheable();
+
+DS.Model = Ember.Object.extend({
+ isLoaded: retrieveFromCurrentState,
+ isDirty: retrieveFromCurrentState,
+ isSaving: retrieveFromCurrentState,
+ isDeleted: retrieveFromCurrentState,
+ isError: retrieveFromCurrentState,
+ isNew: retrieveFromCurrentState,
+ isValid: retrieveFromCurrentState,
+
+ clientId: null,
+
+ // because unknownProperty is used, any internal property
+ // must be initialized here.
+ primaryKey: 'id',
+ data: null,
+ transaction: null,
+
+ didLoad: Ember.K,
+ didUpdate: Ember.K,
+ didCreate: Ember.K,
+
+ init: function() {
+ var stateManager = DS.StateManager.create({
+ model: this
+ });
+
+ set(this, 'stateManager', stateManager);
+ stateManager.goToState('empty');
+ },
+
+ withTransaction: function(fn) {
+ var transaction = get(this, 'transaction') || getPath(this, 'store.defaultTransaction');
+
+ if (transaction) { fn(transaction); }
+ },
+
+ setData: function(data) {
+ var stateManager = get(this, 'stateManager');
+ stateManager.send('setData', data);
+ },
+
+ setProperty: function(key, value) {
+ var stateManager = get(this, 'stateManager');
+ stateManager.send('setProperty', { key: key, value: value });
+ },
+
+ deleteRecord: function() {
+ var stateManager = get(this, 'stateManager');
+ stateManager.send('delete');
+ },
+
+ destroy: function() {
+ this.deleteRecord();
+ this._super();
+ },
+
+ loadingData: function() {
+ var stateManager = get(this, 'stateManager');
+ stateManager.send('loadingData');
+ },
+
+ willLoadData: function() {
+ var stateManager = get(this, 'stateManager');
+ stateManager.send('willLoadData');
+ },
+
+ willCommit: function() {
+ var stateManager = get(this, 'stateManager');
+ stateManager.send('willCommit');
+ },
+
+ adapterDidUpdate: function() {
+ var stateManager = get(this, 'stateManager');
+ stateManager.send('didUpdate');
+ },
+
+ adapterDidCreate: function() {
+ var stateManager = get(this, 'stateManager');
+ stateManager.send('didCreate');
+ },
+
+ adapterDidDelete: function() {
+ var stateManager = get(this, 'stateManager');
+ stateManager.send('didDelete');
+ },
+
+ wasInvalid: function(errors) {
+ var stateManager = get(this, 'stateManager');
+ stateManager.send('wasInvalid', errors);
+ },
+
+ unknownProperty: function(key) {
+ var data = get(this, 'data');
+
+ if (data) {
+ return get(data, key);
+ }
+ },
+
+ setUnknownProperty: function(key, value) {
+ var data = get(this, 'data');
+ ember_assert("You cannot set a model attribute before its data is loaded.", !!data);
+
+ this.setProperty(key, value);
+ return value;
+ }
+});
+
+DS.Model.reopenClass({
+ typeForAssociation: function(association) {
+ var type = this.metaForProperty(association).type;
+ if (typeof type === 'string') {
+ type = getPath(this, type, false) || getPath(window, type);
+ }
+ return type;
+ }
+});
+
+DS.attr = function(type, options) {
+ var transform = DS.attr.transforms[type];
+ var transformFrom = transform.from;
+ var transformTo = transform.to;
+
+ return Ember.computed(function(key, value) {
+ var data = get(this, 'data');
+
+ key = (options && options.key) ? options.key : key;
+
+ if (value === undefined) {
+ if (!data) { return; }
+
+ return transformFrom(data[key]);
+ } else {
+ ember_assert("You cannot set a model attribute before its data is loaded.", !!data);
+
+ value = transformTo(value);
+ this.setProperty(key, value);
+ return value;
+ }
+ }).property('data');
+};
+
+var embeddedFindRecord = function(store, type, data, key, one) {
+ var association = data ? get(data, key) : one ? null : [];
+ if (one) {
+ return association ? store.load(type, association).id : null;
+ } else {
+ return association ? store.loadMany(type, association).ids : [];
+ }
+};
+
+var referencedFindRecord = function(store, type, data, key, one) {
+ return data ? get(data, key) : one ? null : [];
+};
+
+var hasAssociation = function(type, options, one) {
+ var embedded = options && options.embedded,
+ findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
+
+ return Ember.computed(function(key) {
+ var data = get(this, 'data'), ids, id, association,
+ store = get(this, 'store');
+
+ if (typeof type === 'string') {
+ type = getPath(this, type, false) || getPath(window, type);
+ }
+
+ key = (options && options.key) ? options.key : key;
+ if (one) {
+ id = findRecord(store, type, data, key, true);
+ association = id ? store.find(type, id) : null;
+ } else {
+ ids = findRecord(store, type, data, key);
+ association = store.findMany(type, ids);
+ }
+
+ return association;
+ }).property('data').cacheable().meta({ type: type });
+};
+
+DS.hasMany = function(type, options) {
+ ember_assert("The type passed to DS.hasMany must be defined", !!type);
+ return hasAssociation(type, options);
+};
+
+DS.hasOne = function(type, options) {
+ ember_assert("The type passed to DS.hasOne must be defined", !!type);
+ return hasAssociation(type, options, true);
+};
+
+DS.attr.transforms = {
+ string: {
+ from: function(serialized) {
+ return Em.none(serialized) ? null : String(serialized);
+ },
+
+ to: function(deserialized) {
+ return Em.none(deserialized) ? null : String(deserialized);
+ }
+ },
+
+ integer: {
+ from: function(serialized) {
+ return Em.none(serialized) ? null : Number(serialized);
+ },
+
+ to: function(deserialized) {
+ return Em.none(deserialized) ? null : Number(deserialized);
+ }
+ },
+
+ boolean: {
+ from: function(serialized) {
+ return Boolean(serialized);
+ },
+
+ to: function(deserialized) {
+ return Boolean(deserialized);
+ }
+ },
+
+ date: {
+ from: function(serialized) {
+ var type = typeof serialized;
+
+ if (type === "string" || type === "number") {
+ return new Date(serialized);
+ } else if (serialized === null || serialized === undefined) {
+ // if the value is not present in the data,
+ // return undefined, not null.
+ return serialized;
+ } else {
+ return null;
+ }
+ },
+
+ to: function(date) {
+ if (date instanceof Date) {
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+
+ var pad = function(num) {
+ return num < 10 ? "0"+num : ""+num;
+ };
+
+ var utcYear = date.getUTCFullYear(),
+ utcMonth = date.getUTCMonth(),
+ utcDayOfMonth = date.getUTCDate(),
+ utcDay = date.getUTCDay(),
+ utcHours = date.getUTCHours(),
+ utcMinutes = date.getUTCMinutes(),
+ utcSeconds = date.getUTCSeconds();
+
+
+ var dayOfWeek = days[utcDay];
+ var dayOfMonth = pad(utcDayOfMonth);
+ var month = months[utcMonth];
+
+ return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " +
+ pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT";
+ } else if (date === undefined) {
+ return undefined;
+ } else {
+ return null;
+ }
+ }
+ }
+};
+
+})({});
+
+
+(function(exports) {
+//Copyright (C) 2011 by Living Social, Inc.
+
+//Permission is hereby granted, free of charge, to any person obtaining a copy of
+//this software and associated documentation files (the "Software"), to deal in
+//the Software without restriction, including without limitation the rights to
+//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+//of the Software, and to permit persons to whom the Software is furnished to do
+//so, subject to the following conditions:
+
+//The above copyright notice and this permission notice shall be included in all
+//copies or substantial portions of the Software.
+
+//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+//SOFTWARE.
+})({});
Please sign in to comment.
Something went wrong with that request. Please try again.