Created
October 1, 2013 22:14
-
-
Save kieran/6786044 to your computer and use it in GitHub Desktop.
ember model with rsvp baked in
This file contains hidden or 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
(function(globals) { | |
var define, requireModule; | |
(function() { | |
var registry = {}, seen = {}; | |
define = function(name, deps, callback) { | |
registry[name] = { deps: deps, callback: callback }; | |
}; | |
requireModule = function(name) { | |
if (seen[name]) { return seen[name]; } | |
seen[name] = {}; | |
var mod = registry[name]; | |
if (!mod) { | |
throw new Error("Module '" + name + "' not found."); | |
} | |
var deps = mod.deps, | |
callback = mod.callback, | |
reified = [], | |
exports; | |
for (var i=0, l=deps.length; i<l; i++) { | |
if (deps[i] === 'exports') { | |
reified.push(exports = {}); | |
} else { | |
reified.push(requireModule(deps[i])); | |
} | |
} | |
var value = callback.apply(this, reified); | |
return seen[name] = exports || value; | |
}; | |
})(); | |
define("rsvp/all", | |
["rsvp/promise","exports"], | |
function(__dependency1__, __exports__) { | |
"use strict"; | |
var Promise = __dependency1__.Promise; | |
/* global toString */ | |
function all(promises) { | |
if (Object.prototype.toString.call(promises) !== "[object Array]") { | |
throw new TypeError('You must pass an array to all.'); | |
} | |
return new Promise(function(resolve, reject) { | |
var results = [], remaining = promises.length, | |
promise; | |
if (remaining === 0) { | |
resolve([]); | |
} | |
function resolver(index) { | |
return function(value) { | |
resolveAll(index, value); | |
}; | |
} | |
function resolveAll(index, value) { | |
results[index] = value; | |
if (--remaining === 0) { | |
resolve(results); | |
} | |
} | |
for (var i = 0; i < promises.length; i++) { | |
promise = promises[i]; | |
if (promise && typeof promise.then === 'function') { | |
promise.then(resolver(i), reject); | |
} else { | |
resolveAll(i, promise); | |
} | |
} | |
}); | |
} | |
__exports__.all = all; | |
}); | |
define("rsvp/async", | |
["exports"], | |
function(__exports__) { | |
"use strict"; | |
var browserGlobal = (typeof window !== 'undefined') ? window : {}; | |
var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; | |
var local = (typeof global !== 'undefined') ? global : this; | |
// node | |
function useNextTick() { | |
return function() { | |
process.nextTick(flush); | |
}; | |
} | |
function useMutationObserver() { | |
var observer = new BrowserMutationObserver(flush); | |
var element = document.createElement('div'); | |
observer.observe(element, { attributes: true }); | |
// Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661 | |
window.addEventListener('unload', function(){ | |
observer.disconnect(); | |
observer = null; | |
}, false); | |
return function() { | |
element.setAttribute('drainQueue', 'drainQueue'); | |
}; | |
} | |
function useSetTimeout() { | |
return function() { | |
local.setTimeout(flush, 1); | |
}; | |
} | |
var queue = []; | |
function flush() { | |
for (var i = 0; i < queue.length; i++) { | |
var tuple = queue[i]; | |
var callback = tuple[0], arg = tuple[1]; | |
callback(arg); | |
} | |
queue = []; | |
} | |
var scheduleFlush; | |
// Decide what async method to use to triggering processing of queued callbacks: | |
if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { | |
scheduleFlush = useNextTick(); | |
} else if (BrowserMutationObserver) { | |
scheduleFlush = useMutationObserver(); | |
} else { | |
scheduleFlush = useSetTimeout(); | |
} | |
function async(callback, arg) { | |
var length = queue.push([callback, arg]); | |
if (length === 1) { | |
// If length is 1, that means that we need to schedule an async flush. | |
// If additional callbacks are queued before the queue is flushed, they | |
// will be processed by this flush that we are scheduling. | |
scheduleFlush(); | |
} | |
} | |
__exports__.async = async; | |
}); | |
define("rsvp/config", | |
["rsvp/async","exports"], | |
function(__dependency1__, __exports__) { | |
"use strict"; | |
var async = __dependency1__.async; | |
var config = {}; | |
config.async = async; | |
__exports__.config = config; | |
}); | |
define("rsvp/defer", | |
["rsvp/promise","exports"], | |
function(__dependency1__, __exports__) { | |
"use strict"; | |
var Promise = __dependency1__.Promise; | |
function defer() { | |
var deferred = { | |
// pre-allocate shape | |
resolve: undefined, | |
reject: undefined, | |
promise: undefined | |
}; | |
deferred.promise = new Promise(function(resolve, reject) { | |
deferred.resolve = resolve; | |
deferred.reject = reject; | |
}); | |
return deferred; | |
} | |
__exports__.defer = defer; | |
}); | |
define("rsvp/events", | |
["exports"], | |
function(__exports__) { | |
"use strict"; | |
var Event = function(type, options) { | |
this.type = type; | |
for (var option in options) { | |
if (!options.hasOwnProperty(option)) { continue; } | |
this[option] = options[option]; | |
} | |
}; | |
var indexOf = function(callbacks, callback) { | |
for (var i=0, l=callbacks.length; i<l; i++) { | |
if (callbacks[i][0] === callback) { return i; } | |
} | |
return -1; | |
}; | |
var callbacksFor = function(object) { | |
var callbacks = object._promiseCallbacks; | |
if (!callbacks) { | |
callbacks = object._promiseCallbacks = {}; | |
} | |
return callbacks; | |
}; | |
var EventTarget = { | |
mixin: function(object) { | |
object.on = this.on; | |
object.off = this.off; | |
object.trigger = this.trigger; | |
return object; | |
}, | |
on: function(eventNames, callback, binding) { | |
var allCallbacks = callbacksFor(this), callbacks, eventName; | |
eventNames = eventNames.split(/\s+/); | |
binding = binding || this; | |
while (eventName = eventNames.shift()) { | |
callbacks = allCallbacks[eventName]; | |
if (!callbacks) { | |
callbacks = allCallbacks[eventName] = []; | |
} | |
if (indexOf(callbacks, callback) === -1) { | |
callbacks.push([callback, binding]); | |
} | |
} | |
}, | |
off: function(eventNames, callback) { | |
var allCallbacks = callbacksFor(this), callbacks, eventName, index; | |
eventNames = eventNames.split(/\s+/); | |
while (eventName = eventNames.shift()) { | |
if (!callback) { | |
allCallbacks[eventName] = []; | |
continue; | |
} | |
callbacks = allCallbacks[eventName]; | |
index = indexOf(callbacks, callback); | |
if (index !== -1) { callbacks.splice(index, 1); } | |
} | |
}, | |
trigger: function(eventName, options) { | |
var allCallbacks = callbacksFor(this), | |
callbacks, callbackTuple, callback, binding, event; | |
if (callbacks = allCallbacks[eventName]) { | |
// Don't cache the callbacks.length since it may grow | |
for (var i=0; i<callbacks.length; i++) { | |
callbackTuple = callbacks[i]; | |
callback = callbackTuple[0]; | |
binding = callbackTuple[1]; | |
if (typeof options !== 'object') { | |
options = { detail: options }; | |
} | |
event = new Event(eventName, options); | |
callback.call(binding, event); | |
} | |
} | |
} | |
}; | |
__exports__.EventTarget = EventTarget; | |
}); | |
define("rsvp/hash", | |
["rsvp/defer","exports"], | |
function(__dependency1__, __exports__) { | |
"use strict"; | |
var defer = __dependency1__.defer; | |
function size(object) { | |
var s = 0; | |
for (var prop in object) { | |
s++; | |
} | |
return s; | |
} | |
function hash(promises) { | |
var results = {}, deferred = defer(), remaining = size(promises); | |
if (remaining === 0) { | |
deferred.resolve({}); | |
} | |
var resolver = function(prop) { | |
return function(value) { | |
resolveAll(prop, value); | |
}; | |
}; | |
var resolveAll = function(prop, value) { | |
results[prop] = value; | |
if (--remaining === 0) { | |
deferred.resolve(results); | |
} | |
}; | |
var rejectAll = function(error) { | |
deferred.reject(error); | |
}; | |
for (var prop in promises) { | |
if (promises[prop] && typeof promises[prop].then === 'function') { | |
promises[prop].then(resolver(prop), rejectAll); | |
} else { | |
resolveAll(prop, promises[prop]); | |
} | |
} | |
return deferred.promise; | |
} | |
__exports__.hash = hash; | |
}); | |
define("rsvp/node", | |
["rsvp/promise","rsvp/all","exports"], | |
function(__dependency1__, __dependency2__, __exports__) { | |
"use strict"; | |
var Promise = __dependency1__.Promise; | |
var all = __dependency2__.all; | |
function makeNodeCallbackFor(resolve, reject) { | |
return function (error, value) { | |
if (error) { | |
reject(error); | |
} else if (arguments.length > 2) { | |
resolve(Array.prototype.slice.call(arguments, 1)); | |
} else { | |
resolve(value); | |
} | |
}; | |
} | |
function denodeify(nodeFunc) { | |
return function() { | |
var nodeArgs = Array.prototype.slice.call(arguments), resolve, reject; | |
var thisArg = this; | |
var promise = new Promise(function(nodeResolve, nodeReject) { | |
resolve = nodeResolve; | |
reject = nodeReject; | |
}); | |
all(nodeArgs).then(function(nodeArgs) { | |
nodeArgs.push(makeNodeCallbackFor(resolve, reject)); | |
try { | |
nodeFunc.apply(thisArg, nodeArgs); | |
} catch(e) { | |
reject(e); | |
} | |
}); | |
return promise; | |
}; | |
} | |
__exports__.denodeify = denodeify; | |
}); | |
define("rsvp/promise", | |
["rsvp/config","rsvp/events","exports"], | |
function(__dependency1__, __dependency2__, __exports__) { | |
"use strict"; | |
var config = __dependency1__.config; | |
var EventTarget = __dependency2__.EventTarget; | |
function objectOrFunction(x) { | |
return isFunction(x) || (typeof x === "object" && x !== null); | |
} | |
function isFunction(x){ | |
return typeof x === "function"; | |
} | |
var Promise = function(resolver) { | |
var promise = this, | |
resolved = false; | |
if (typeof resolver !== 'function') { | |
throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor'); | |
} | |
if (!(promise instanceof Promise)) { | |
return new Promise(resolver); | |
} | |
var resolvePromise = function(value) { | |
if (resolved) { return; } | |
resolved = true; | |
resolve(promise, value); | |
}; | |
var rejectPromise = function(value) { | |
if (resolved) { return; } | |
resolved = true; | |
reject(promise, value); | |
}; | |
this.on('promise:failed', function(event) { | |
this.trigger('error', { detail: event.detail }); | |
}, this); | |
this.on('error', onerror); | |
try { | |
resolver(resolvePromise, rejectPromise); | |
} catch(e) { | |
rejectPromise(e); | |
} | |
}; | |
function onerror(event) { | |
if (config.onerror) { | |
config.onerror(event.detail); | |
} | |
} | |
var invokeCallback = function(type, promise, callback, event) { | |
var hasCallback = isFunction(callback), | |
value, error, succeeded, failed; | |
if (hasCallback) { | |
try { | |
value = callback(event.detail); | |
succeeded = true; | |
} catch(e) { | |
failed = true; | |
error = e; | |
} | |
} else { | |
value = event.detail; | |
succeeded = true; | |
} | |
if (handleThenable(promise, value)) { | |
return; | |
} else if (hasCallback && succeeded) { | |
resolve(promise, value); | |
} else if (failed) { | |
reject(promise, error); | |
} else if (type === 'resolve') { | |
resolve(promise, value); | |
} else if (type === 'reject') { | |
reject(promise, value); | |
} | |
}; | |
Promise.prototype = { | |
constructor: Promise, | |
isRejected: undefined, | |
isFulfilled: undefined, | |
rejectedReason: undefined, | |
fulfillmentValue: undefined, | |
then: function(done, fail) { | |
this.off('error', onerror); | |
var thenPromise = new this.constructor(function() {}); | |
if (this.isFulfilled) { | |
config.async(function(promise) { | |
invokeCallback('resolve', thenPromise, done, { detail: promise.fulfillmentValue }); | |
}, this); | |
} | |
if (this.isRejected) { | |
config.async(function(promise) { | |
invokeCallback('reject', thenPromise, fail, { detail: promise.rejectedReason }); | |
}, this); | |
} | |
this.on('promise:resolved', function(event) { | |
invokeCallback('resolve', thenPromise, done, event); | |
}); | |
this.on('promise:failed', function(event) { | |
invokeCallback('reject', thenPromise, fail, event); | |
}); | |
return thenPromise; | |
}, | |
fail: function(fail) { | |
return this.then(null, fail); | |
} | |
}; | |
EventTarget.mixin(Promise.prototype); | |
function resolve(promise, value) { | |
if (promise === value) { | |
fulfill(promise, value); | |
} else if (!handleThenable(promise, value)) { | |
fulfill(promise, value); | |
} | |
} | |
function handleThenable(promise, value) { | |
var then = null, | |
resolved; | |
try { | |
if (promise === value) { | |
throw new TypeError("A promises callback cannot return that same promise."); | |
} | |
if (objectOrFunction(value)) { | |
then = value.then; | |
if (isFunction(then)) { | |
then.call(value, function(val) { | |
if (resolved) { return true; } | |
resolved = true; | |
if (value !== val) { | |
resolve(promise, val); | |
} else { | |
fulfill(promise, val); | |
} | |
}, function(val) { | |
if (resolved) { return true; } | |
resolved = true; | |
reject(promise, val); | |
}); | |
return true; | |
} | |
} | |
} catch (error) { | |
reject(promise, error); | |
return true; | |
} | |
return false; | |
} | |
function fulfill(promise, value) { | |
config.async(function() { | |
promise.trigger('promise:resolved', { detail: value }); | |
promise.isFulfilled = true; | |
promise.fulfillmentValue = value; | |
}); | |
} | |
function reject(promise, value) { | |
config.async(function() { | |
promise.trigger('promise:failed', { detail: value }); | |
promise.isRejected = true; | |
promise.rejectedReason = value; | |
}); | |
} | |
__exports__.Promise = Promise; | |
}); | |
define("rsvp/reject", | |
["rsvp/promise","exports"], | |
function(__dependency1__, __exports__) { | |
"use strict"; | |
var Promise = __dependency1__.Promise; | |
function reject(reason) { | |
return new Promise(function (resolve, reject) { | |
reject(reason); | |
}); | |
} | |
__exports__.reject = reject; | |
}); | |
define("rsvp/resolve", | |
["rsvp/promise","exports"], | |
function(__dependency1__, __exports__) { | |
"use strict"; | |
var Promise = __dependency1__.Promise; | |
function resolve(thenable) { | |
return new Promise(function(resolve, reject) { | |
resolve(thenable); | |
}); | |
} | |
__exports__.resolve = resolve; | |
}); | |
define("rsvp/rethrow", | |
["exports"], | |
function(__exports__) { | |
"use strict"; | |
var local = (typeof global === "undefined") ? this : global; | |
function rethrow(reason) { | |
local.setTimeout(function() { | |
throw reason; | |
}); | |
throw reason; | |
} | |
__exports__.rethrow = rethrow; | |
}); | |
define("rsvp", | |
["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/rethrow","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], | |
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { | |
"use strict"; | |
var EventTarget = __dependency1__.EventTarget; | |
var Promise = __dependency2__.Promise; | |
var denodeify = __dependency3__.denodeify; | |
var all = __dependency4__.all; | |
var hash = __dependency5__.hash; | |
var rethrow = __dependency6__.rethrow; | |
var defer = __dependency7__.defer; | |
var config = __dependency8__.config; | |
var resolve = __dependency9__.resolve; | |
var reject = __dependency10__.reject; | |
function configure(name, value) { | |
config[name] = value; | |
} | |
__exports__.Promise = Promise; | |
__exports__.EventTarget = EventTarget; | |
__exports__.all = all; | |
__exports__.hash = hash; | |
__exports__.rethrow = rethrow; | |
__exports__.defer = defer; | |
__exports__.denodeify = denodeify; | |
__exports__.configure = configure; | |
__exports__.resolve = resolve; | |
__exports__.reject = reject; | |
}); | |
window.NewRSVP = requireModule("rsvp"); | |
})(window); | |
(function() { | |
var VERSION = '0.0.10'; | |
if (Ember.libraries) { | |
Ember.libraries.register('Ember Model', VERSION); | |
} | |
})(); | |
(function() { | |
function mustImplement(message) { | |
var fn = function() { | |
var className = this.constructor.toString(); | |
throw new Error(message.replace('{{className}}', className)); | |
}; | |
fn.isUnimplemented = true; | |
return fn; | |
} | |
Ember.Adapter = Ember.Object.extend({ | |
find: mustImplement('{{className}} must implement find'), | |
findQuery: mustImplement('{{className}} must implement findQuery'), | |
findMany: mustImplement('{{className}} must implement findMany'), | |
findAll: mustImplement('{{className}} must implement findAll'), | |
createRecord: mustImplement('{{className}} must implement createRecord'), | |
saveRecord: mustImplement('{{className}} must implement saveRecord'), | |
deleteRecord: mustImplement('{{className}} must implement deleteRecord'), | |
load: function(record, id, data) { | |
record.load(id, data); | |
} | |
}); | |
})(); | |
(function() { | |
var get = Ember.get; | |
Ember.FixtureAdapter = Ember.Adapter.extend({ | |
_findData: function(klass, id) { | |
var fixtures = klass.FIXTURES, | |
idAsString = id.toString(), | |
primaryKey = get(klass, 'primaryKey'), | |
data = Ember.A(fixtures).find(function(el) { return (el[primaryKey]).toString() === idAsString; }); | |
return data; | |
}, | |
find: function(record, id) { | |
var data = this._findData(record.constructor, id); | |
return new NewRSVP.Promise(function(resolve, reject) { | |
Ember.run.later(this, function() { | |
Ember.run(record, record.load, id, data); | |
resolve(record); | |
}, 0); | |
}); | |
}, | |
findMany: function(klass, records, ids) { | |
var fixtures = klass.FIXTURES, | |
requestedData = []; | |
for (var i = 0, l = ids.length; i < l; i++) { | |
requestedData.push(this._findData(klass, ids[i])); | |
} | |
return new NewRSVP.Promise(function(resolve, reject) { | |
Ember.run.later(this, function() { | |
Ember.run(records, records.load, klass, requestedData); | |
resolve(records); | |
}, 0); | |
}); | |
}, | |
findAll: function(klass, records) { | |
var fixtures = klass.FIXTURES; | |
return new NewRSVP.Promise(function(resolve, reject) { | |
Ember.run.later(this, function() { | |
Ember.run(records, records.load, klass, fixtures); | |
resolve(records); | |
}, 0); | |
}); | |
}, | |
createRecord: function(record) { | |
var klass = record.constructor, | |
fixtures = klass.FIXTURES; | |
return new NewRSVP.Promise(function(resolve, reject) { | |
Ember.run.later(this, function() { | |
fixtures.push(klass.findFromCacheOrLoad(record.toJSON())); | |
record.didCreateRecord(); | |
resolve(record); | |
}, 0); | |
}); | |
}, | |
saveRecord: function(record) { | |
return new NewRSVP.Promise(function(resolve, reject) { | |
Ember.run.later(this, function() { | |
record.didSaveRecord(); | |
resolve(record); | |
}, 0); | |
}); | |
}, | |
deleteRecord: function(record) { | |
return new NewRSVP.Promise(function(resolve, reject) { | |
Ember.run.later(this, function() { | |
record.didDeleteRecord(); | |
resolve(record); | |
}, 0); | |
}); | |
} | |
}); | |
})(); | |
(function() { | |
var get = Ember.get, | |
set = Ember.set; | |
Ember.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { | |
isLoaded: false, | |
isLoading: Ember.computed.not('isLoaded'), | |
load: function(klass, data) { | |
set(this, 'content', this.materializeData(klass, data)); | |
this.notifyLoaded(); | |
}, | |
loadForFindMany: function(klass) { | |
var content = get(this, '_ids').map(function(id) { return klass.cachedRecordForId(id); }); | |
set(this, 'content', Ember.A(content)); | |
this.notifyLoaded(); | |
}, | |
notifyLoaded: function() { | |
set(this, 'isLoaded', true); | |
this.trigger('didLoad'); | |
}, | |
materializeData: function(klass, data) { | |
return Ember.A(data.map(function(el) { | |
return klass.findFromCacheOrLoad(el); // FIXME | |
})); | |
}, | |
reload: function() { | |
var modelClass = this.get('modelClass'), | |
self = this, | |
promises; | |
set(this, 'isLoaded', false); | |
if (modelClass._findAllRecordArray === this) { | |
modelClass.adapter.findAll(modelClass, this); | |
} else if (this._query) { | |
modelClass.adapter.findQuery(modelClass, this, this._query); | |
} else { | |
promises = this.map(function(record) { | |
return record.reload(); | |
}); | |
NewRSVP.all(promises).then(function(data) { | |
self.notifyLoaded(); | |
}); | |
} | |
} | |
}); | |
})(); | |
(function() { | |
var get = Ember.get; | |
Ember.FilteredRecordArray = Ember.RecordArray.extend({ | |
init: function() { | |
if (!get(this, 'modelClass')) { | |
throw new Error('FilteredRecordArrays must be created with a modelClass'); | |
} | |
if (!get(this, 'filterFunction')) { | |
throw new Error('FilteredRecordArrays must be created with a filterFunction'); | |
} | |
if (!get(this, 'filterProperties')) { | |
throw new Error('FilteredRecordArrays must be created with filterProperties'); | |
} | |
var modelClass = get(this, 'modelClass'); | |
modelClass.registerRecordArray(this); | |
this.registerObservers(); | |
this.updateFilter(); | |
this._super(); | |
}, | |
updateFilter: function() { | |
var self = this, | |
results = []; | |
get(this, 'modelClass').forEachCachedRecord(function(record) { | |
if (self.filterFunction(record)) { | |
results.push(record); | |
} | |
}); | |
this.set('content', Ember.A(results)); | |
}, | |
updateFilterForRecord: function(record) { | |
var results = get(this, 'content'); | |
if (this.filterFunction(record) && !results.contains(record)) { | |
results.pushObject(record); | |
} | |
}, | |
registerObservers: function() { | |
var self = this; | |
get(this, 'modelClass').forEachCachedRecord(function(record) { | |
self.registerObserversOnRecord(record); | |
}); | |
}, | |
registerObserversOnRecord: function(record) { | |
var self = this, | |
filterProperties = get(this, 'filterProperties'); | |
for (var i = 0, l = get(filterProperties, 'length'); i < l; i++) { | |
record.addObserver(filterProperties[i], self, 'updateFilterForRecord'); | |
} | |
} | |
}); | |
})(); | |
(function() { | |
var get = Ember.get, set = Ember.set; | |
Ember.ManyArray = Ember.RecordArray.extend({ | |
_records: null, | |
originalContent: null, | |
isDirty: function() { | |
var originalContent = get(this, 'originalContent'), | |
originalContentLength = get(originalContent, 'length'), | |
content = get(this, 'content'), | |
contentLength = get(content, 'length'); | |
if (originalContentLength !== contentLength) { return true; } | |
var isDirty = false; | |
for (var i = 0, l = contentLength; i < l; i++) { | |
if (!originalContent.contains(content[i])) { | |
isDirty = true; | |
break; | |
} | |
} | |
return isDirty; | |
}.property('content.[]', 'originalContent'), | |
objectAtContent: function(idx) { | |
var content = get(this, 'content'); | |
if (!content.length) { return; } | |
return this.materializeRecord(idx); | |
}, | |
save: function() { | |
// TODO: loop over dirty records only | |
return NewRSVP.all(this.map(function(record) { | |
return record.save(); | |
})); | |
}, | |
replaceContent: function(index, removed, added) { | |
added = Ember.EnumerableUtils.map(added, function(record) { | |
return record._reference; | |
}, this); | |
this._super(index, removed, added); | |
}, | |
_contentWillChange: function() { | |
var content = get(this, 'content'); | |
if (content) { | |
content.removeArrayObserver(this); | |
this._setupOriginalContent(content); | |
} | |
}.observesBefore('content'), | |
_contentDidChange: function() { | |
var content = get(this, 'content'); | |
if (content) { | |
content.addArrayObserver(this); | |
this.arrayDidChange(content, 0, 0, get(content, 'length')); | |
} | |
}.observes('content'), | |
arrayWillChange: function(item, idx, removedCnt, addedCnt) {}, | |
arrayDidChange: function(item, idx, removedCnt, addedCnt) { | |
var parent = get(this, 'parent'), relationshipKey = get(this, 'relationshipKey'), | |
isDirty = get(this, 'isDirty'); | |
if (isDirty) { | |
parent._relationshipBecameDirty(relationshipKey); | |
} else { | |
parent._relationshipBecameClean(relationshipKey); | |
} | |
}, | |
_setupOriginalContent: function(content) { | |
content = content || get(this, 'content'); | |
if (content) { | |
set(this, 'originalContent', content.slice()); | |
} | |
}, | |
init: function() { | |
this._super(); | |
this._setupOriginalContent(); | |
this._contentDidChange(); | |
} | |
}); | |
Ember.HasManyArray = Ember.ManyArray.extend({ | |
materializeRecord: function(idx) { | |
var klass = get(this, 'modelClass'), | |
content = get(this, 'content'), | |
reference = content.objectAt(idx), | |
record; | |
if (reference.record) { | |
record = reference.record; | |
} else { | |
record = klass.find(reference.id); | |
} | |
return record; | |
}, | |
toJSON: function() { | |
var ids = [], content = this.get('content'); | |
content.forEach(function(reference) { | |
if (reference.id) { | |
ids.push(reference.id); | |
} | |
}); | |
return ids; | |
} | |
}); | |
Ember.EmbeddedHasManyArray = Ember.ManyArray.extend({ | |
create: function(attrs) { | |
var klass = get(this, 'modelClass'), | |
record = klass.create(attrs); | |
this.pushObject(record); | |
return record; // FIXME: inject parent's id | |
}, | |
materializeRecord: function(idx) { | |
var klass = get(this, 'modelClass'), | |
primaryKey = get(klass, 'primaryKey'), | |
content = get(this, 'content'), | |
reference = content.objectAt(idx), | |
attrs = reference.data; | |
if (reference.record) { | |
return reference.record; | |
} else { | |
var record = klass.create({ _reference: reference }); | |
reference.record = record; | |
if (attrs) { | |
record.load(attrs[primaryKey], attrs); | |
} | |
return record; | |
} | |
}, | |
toJSON: function() { | |
return this.map(function(record) { | |
return record.toJSON(); | |
}); | |
} | |
}); | |
})(); | |
(function() { | |
var get = Ember.get, | |
set = Ember.set, | |
setProperties = Ember.setProperties, | |
meta = Ember.meta, | |
underscore = Ember.String.underscore; | |
function contains(array, element) { | |
for (var i = 0, l = array.length; i < l; i++) { | |
if (array[i] === element) { return true; } | |
} | |
return false; | |
} | |
function concatUnique(toArray, fromArray) { | |
var e; | |
for (var i = 0, l = fromArray.length; i < l; i++) { | |
e = fromArray[i]; | |
if (!contains(toArray, e)) { toArray.push(e); } | |
} | |
return toArray; | |
} | |
function hasCachedValue(object, key) { | |
var objectMeta = meta(object, false); | |
if (objectMeta) { | |
return key in objectMeta.cache; | |
} | |
} | |
Ember.run.queues.push('data'); | |
Ember.Model = Ember.Object.extend(Ember.Evented, { | |
isLoaded: true, | |
isLoading: Ember.computed.not('isLoaded'), | |
isNew: true, | |
isDeleted: false, | |
_dirtyAttributes: null, | |
/** | |
Called when attribute is accessed. | |
@method getAttr | |
@param key {String} key which is being accessed | |
@param value {Object} value, which will be returned from getter by default | |
*/ | |
getAttr: function(key, value) { | |
return value; | |
}, | |
isDirty: function() { | |
var dirtyAttributes = get(this, '_dirtyAttributes'); | |
return dirtyAttributes && dirtyAttributes.length !== 0 || false; | |
}.property('_dirtyAttributes.length'), | |
_relationshipBecameDirty: function(name) { | |
var dirtyAttributes = get(this, '_dirtyAttributes'); | |
if (!dirtyAttributes.contains(name)) { dirtyAttributes.pushObject(name); } | |
}, | |
_relationshipBecameClean: function(name) { | |
var dirtyAttributes = get(this, '_dirtyAttributes'); | |
dirtyAttributes.removeObject(name); | |
}, | |
dataKey: function(key) { | |
var camelizeKeys = get(this.constructor, 'camelizeKeys'); | |
var meta = this.constructor.metaForProperty(key); | |
if (meta.options && meta.options.key) { | |
return camelizeKeys ? underscore(meta.options.key) : meta.options.key; | |
} | |
return camelizeKeys ? underscore(key) : key; | |
}, | |
init: function() { | |
this._createReference(); | |
if (!this._dirtyAttributes) { | |
set(this, '_dirtyAttributes', []); | |
} | |
this._super(); | |
}, | |
_createReference: function() { | |
var reference = this._reference, | |
id = this.getPrimaryKey(); | |
if (!reference) { | |
reference = this.constructor._getOrCreateReferenceForId(id); | |
reference.record = this; | |
this._reference = reference; | |
} else if (reference.id !== id) { | |
reference.id = id; | |
this.constructor._cacheReference(reference); | |
} | |
if (!reference.id) { | |
reference.id = id; | |
} | |
return reference; | |
}, | |
getPrimaryKey: function() { | |
return get(this, get(this.constructor, 'primaryKey')); | |
}, | |
load: function(id, hash) { | |
var data = {}; | |
data[get(this.constructor, 'primaryKey')] = id; | |
set(this, '_data', Ember.merge(data, hash)); | |
// eagerly load embedded data | |
var relationships = this.constructor._relationships || [], meta = Ember.meta(this), relationshipKey, relationship, relationshipMeta, relationshipData, relationshipType; | |
for (var i = 0, l = relationships.length; i < l; i++) { | |
relationshipKey = relationships[i]; | |
relationship = meta.descs[relationshipKey]; | |
relationshipMeta = relationship.meta(); | |
if (relationshipMeta.options.embedded) { | |
relationshipType = relationshipMeta.type; | |
if (typeof relationshipType === "string") { | |
relationshipType = Ember.get(Ember.lookup, relationshipType); | |
} | |
relationshipData = data[relationshipKey]; | |
if (relationshipData) { | |
relationshipType.load(relationshipData); | |
} | |
} | |
} | |
set(this, 'isLoaded', true); | |
set(this, 'isNew', false); | |
this._createReference(); | |
this.trigger('didLoad'); | |
}, | |
didDefineProperty: function(proto, key, value) { | |
if (value instanceof Ember.Descriptor) { | |
var meta = value.meta(); | |
var klass = proto.constructor; | |
if (meta.isAttribute) { | |
if (!klass._attributes) { klass._attributes = []; } | |
klass._attributes.push(key); | |
} else if (meta.isRelationship) { | |
if (!klass._relationships) { klass._relationships = []; } | |
klass._relationships.push(key); | |
meta.relationshipKey = key; | |
} | |
} | |
}, | |
serializeHasMany: function(key, meta) { | |
return this.get(key).toJSON(); | |
}, | |
serializeBelongsTo: function(key, meta) { | |
if (meta.options.embedded) { | |
var record = this.get(key); | |
return record ? record.toJSON() : null; | |
} else { | |
var primaryKey = get(meta.getType(), 'primaryKey'); | |
return this.get(key + '.' + primaryKey); | |
} | |
}, | |
toJSON: function() { | |
var key, meta, | |
json = {}, | |
attributes = this.constructor.getAttributes(), | |
relationships = this.constructor.getRelationships(), | |
properties = attributes ? this.getProperties(attributes) : {}, | |
rootKey = get(this.constructor, 'rootKey'); | |
for (key in properties) { | |
meta = this.constructor.metaForProperty(key); | |
if (meta.type && meta.type.serialize) { | |
json[this.dataKey(key)] = meta.type.serialize(properties[key]); | |
} else if (meta.type && Ember.Model.dataTypes[meta.type]) { | |
json[this.dataKey(key)] = Ember.Model.dataTypes[meta.type].serialize(properties[key]); | |
} else { | |
json[this.dataKey(key)] = properties[key]; | |
} | |
} | |
if (relationships) { | |
var data, relationshipKey; | |
for(var i = 0; i < relationships.length; i++) { | |
key = relationships[i]; | |
meta = this.constructor.metaForProperty(key); | |
relationshipKey = meta.options.key || key; | |
if (meta.kind === 'belongsTo') { | |
data = this.serializeBelongsTo(key, meta); | |
} else { | |
data = this.serializeHasMany(key, meta); | |
} | |
json[relationshipKey] = data; | |
} | |
} | |
if (rootKey) { | |
var jsonRoot = {}; | |
jsonRoot[rootKey] = json; | |
return jsonRoot; | |
} else { | |
return json; | |
} | |
}, | |
save: function() { | |
var adapter = this.constructor.adapter; | |
set(this, 'isSaving', true); | |
if (get(this, 'isNew')) { | |
return adapter.createRecord(this); | |
} else if (get(this, 'isDirty')) { | |
return adapter.saveRecord(this); | |
} else { // noop, return a resolved promise | |
var self = this, | |
promise = new NewRSVP.Promise(function(resolve, reject) { | |
resolve(self); | |
}); | |
set(this, 'isSaving', false); | |
return promise; | |
} | |
}, | |
reload: function() { | |
return this.constructor.reload(this.get(get(this.constructor, 'primaryKey'))); | |
}, | |
revert: function() { | |
this.getWithDefault('_dirtyAttributes', []).clear(); | |
this.notifyPropertyChange('_data'); | |
}, | |
didCreateRecord: function() { | |
var primaryKey = get(this.constructor, 'primaryKey'), | |
id = get(this, primaryKey); | |
set(this, 'isNew', false); | |
set(this, '_dirtyAttributes', []); | |
this.constructor.addToRecordArrays(this); | |
this.trigger('didCreateRecord'); | |
this.didSaveRecord(); | |
}, | |
didSaveRecord: function() { | |
set(this, 'isSaving', false); | |
this.trigger('didSaveRecord'); | |
if (this.get('isDirty')) { this._copyDirtyAttributesToData(); } | |
}, | |
deleteRecord: function() { | |
return this.constructor.adapter.deleteRecord(this); | |
}, | |
didDeleteRecord: function() { | |
this.constructor.removeFromRecordArrays(this); | |
set(this, 'isDeleted', true); | |
this.trigger('didDeleteRecord'); | |
}, | |
_copyDirtyAttributesToData: function() { | |
if (!this._dirtyAttributes) { return; } | |
var dirtyAttributes = this._dirtyAttributes, | |
data = get(this, '_data'), | |
key; | |
if (!data) { | |
data = {}; | |
set(this, '_data', data); | |
} | |
for (var i = 0, l = dirtyAttributes.length; i < l; i++) { | |
// TODO: merge Object.create'd object into prototype | |
key = dirtyAttributes[i]; | |
data[this.dataKey(key)] = this.cacheFor(key); | |
} | |
set(this, '_dirtyAttributes', []); | |
}, | |
dataDidChange: Ember.observer(function() { | |
this._reloadHasManys(); | |
}, '_data'), | |
_registerHasManyArray: function(array) { | |
if (!this._hasManyArrays) { this._hasManyArrays = Ember.A([]); } | |
this._hasManyArrays.pushObject(array); | |
}, | |
_reloadHasManys: function() { | |
if (!this._hasManyArrays) { return; } | |
var i, j; | |
for (i = 0; i < this._hasManyArrays.length; i++) { | |
var array = this._hasManyArrays[i], | |
hasManyContent = this._getHasManyContent(get(array, 'key'), get(array, 'modelClass'), get(array, 'embedded')); | |
for (j = 0; j < array.get('length'); j++) { | |
if (array.objectAt(j).get('isNew')) { | |
hasManyContent.addObject(array.objectAt(j)._reference); | |
} | |
} | |
set(array, 'content', hasManyContent); | |
} | |
}, | |
_getHasManyContent: function(key, type, embedded) { | |
var content = get(this, '_data.' + key); | |
if (content) { | |
var mapFunction, primaryKey, reference; | |
if (embedded) { | |
primaryKey = get(type, 'primaryKey'); | |
mapFunction = function(attrs) { | |
reference = type._getOrCreateReferenceForId(attrs[primaryKey]); | |
reference.data = attrs; | |
return reference; | |
}; | |
} else { | |
mapFunction = function(id) { return type._getOrCreateReferenceForId(id); }; | |
} | |
content = Ember.EnumerableUtils.map(content, mapFunction); | |
} | |
return Ember.A(content || []); | |
} | |
}); | |
Ember.Model.reopenClass({ | |
primaryKey: 'id', | |
adapter: Ember.Adapter.create(), | |
_clientIdCounter: 1, | |
getAttributes: function() { | |
this.proto(); // force class "compilation" if it hasn't been done. | |
var attributes = this._attributes || []; | |
if (typeof this.superclass.getAttributes === 'function') { | |
attributes = this.superclass.getAttributes().concat(attributes); | |
} | |
return attributes; | |
}, | |
getRelationships: function() { | |
this.proto(); // force class "compilation" if it hasn't been done. | |
var relationships = this._relationships || []; | |
if (typeof this.superclass.getRelationships === 'function') { | |
relationships = this.superclass.getRelationships().concat(relationships); | |
} | |
return relationships; | |
}, | |
fetch: function(id) { | |
if (!arguments.length) { | |
return this._findFetchAll(true); | |
} else if (Ember.isArray(id)) { | |
return this._findFetchMany(id, true); | |
} else if (typeof id === 'object') { | |
return this._findFetchQuery(id, true); | |
} else { | |
return this._findFetchById(id, true); | |
} | |
}, | |
find: function(id) { | |
if (!arguments.length) { | |
return this._findFetchAll(false); | |
} else if (Ember.isArray(id)) { | |
return this._findFetchMany(id, false); | |
} else if (typeof id === 'object') { | |
return this._findFetchQuery(id, false); | |
} else { | |
return this._findFetchById(id, false); | |
} | |
}, | |
findQuery: function(params) { | |
return this._findFetchQuery(params, false); | |
}, | |
fetchQuery: function(params) { | |
return this._findFetchQuery(params, true); | |
}, | |
_findFetchQuery: function(params, isFetch) { | |
var records = Ember.RecordArray.create({modelClass: this, _query: params}); | |
var promise = this.adapter.findQuery(this, records, params); | |
return isFetch ? promise : records; | |
}, | |
findMany: function(ids) { | |
return this._findFetchMany(ids, false); | |
}, | |
fetchMany: function(ids) { | |
return this._findFetchMany(ids, true); | |
}, | |
_findFetchMany: function(ids, isFetch) { | |
Ember.assert("findFetchMany requires an array", Ember.isArray(ids)); | |
var records = Ember.RecordArray.create({_ids: ids, modelClass: this}), | |
deferred; | |
if (!this.recordArrays) { this.recordArrays = []; } | |
this.recordArrays.push(records); | |
if (this._currentBatchIds) { | |
concatUnique(this._currentBatchIds, ids); | |
this._currentBatchRecordArrays.push(records); | |
} else { | |
this._currentBatchIds = concatUnique([], ids); | |
this._currentBatchRecordArrays = [records]; | |
} | |
if (isFetch) { | |
deferred = Ember.Deferred.create(); | |
Ember.set(deferred, 'resolveWith', records); | |
if (!this._currentBatchDeferreds) { this._currentBatchDeferreds = []; } | |
this._currentBatchDeferreds.push(deferred); | |
} | |
Ember.run.scheduleOnce('data', this, this._executeBatch); | |
return isFetch ? deferred : records; | |
}, | |
findAll: function() { | |
return this._findFetchAll(false); | |
}, | |
fetchAll: function() { | |
return this._findFetchAll(true); | |
}, | |
_findFetchAll: function(isFetch) { | |
var self = this; | |
if (this._findAllRecordArray) { | |
if (isFetch) { | |
return new NewRSVP.Promise(function(resolve) { | |
resolve(self._findAllRecordArray); | |
}); | |
} else { | |
return this._findAllRecordArray; | |
} | |
} | |
var records = this._findAllRecordArray = Ember.RecordArray.create({modelClass: this}); | |
var promise = this.adapter.findAll(this, records); | |
// Remove the cached record array if the promise is rejected | |
if (promise.then) { | |
promise.then(null, function() { | |
self._findAllRecordArray = null; | |
return NewRSVP.reject.apply(null, arguments); | |
}); | |
} | |
return isFetch ? promise : records; | |
}, | |
findById: function(id) { | |
return this._findFetchById(id, false); | |
}, | |
fetchById: function(id) { | |
return this._findFetchById(id, true); | |
}, | |
_findFetchById: function(id, isFetch) { | |
var record = this.cachedRecordForId(id), | |
isLoaded = get(record, 'isLoaded'), | |
adapter = get(this, 'adapter'), | |
deferredOrPromise; | |
if (isLoaded) { | |
if (isFetch) { | |
return new NewRSVP.Promise(function(resolve, reject) { | |
resolve(record); | |
}); | |
} else { | |
return record; | |
} | |
} | |
deferredOrPromise = this._fetchById(record, id); | |
return isFetch ? deferredOrPromise : record; | |
}, | |
_currentBatchIds: null, | |
_currentBatchRecordArrays: null, | |
_currentBatchDeferreds: null, | |
reload: function(id) { | |
var record = this.cachedRecordForId(id); | |
record.set('isLoaded', false); | |
return this._fetchById(record, id); | |
}, | |
_fetchById: function(record, id) { | |
var adapter = get(this, 'adapter'), | |
deferred; | |
if (adapter.findMany && !adapter.findMany.isUnimplemented) { | |
if (this._currentBatchIds) { | |
if (!contains(this._currentBatchIds, id)) { this._currentBatchIds.push(id); } | |
} else { | |
this._currentBatchIds = [id]; | |
this._currentBatchRecordArrays = []; | |
} | |
deferred = Ember.Deferred.create(); | |
//Attached the record to the deferred so we can resolove it later. | |
Ember.set(deferred, 'resolveWith', record); | |
if (!this._currentBatchDeferreds) { this._currentBatchDeferreds = []; } | |
this._currentBatchDeferreds.push(deferred); | |
Ember.run.scheduleOnce('data', this, this._executeBatch); | |
return deferred; | |
} else { | |
return adapter.find(record, id); | |
} | |
}, | |
_executeBatch: function() { | |
var batchIds = this._currentBatchIds, | |
batchRecordArrays = this._currentBatchRecordArrays, | |
batchDeferreds = this._currentBatchDeferreds, | |
self = this, | |
requestIds = [], | |
promise, | |
i; | |
this._currentBatchIds = null; | |
this._currentBatchRecordArrays = null; | |
this._currentBatchDeferreds = null; | |
for (i = 0; i < batchIds.length; i++) { | |
if (!this.cachedRecordForId(batchIds[i]).get('isLoaded')) { | |
requestIds.push(batchIds[i]); | |
} | |
} | |
if (batchIds.length === 1) { | |
promise = get(this, 'adapter').find(this.cachedRecordForId(batchIds[0]), batchIds[0]); | |
} else { | |
var recordArray = Ember.RecordArray.create({_ids: batchIds}); | |
if (requestIds.length === 0) { | |
promise = new NewRSVP.Promise(function(resolve, reject) { resolve(recordArray); }); | |
recordArray.notifyLoaded(); | |
} else { | |
promise = get(this, 'adapter').findMany(this, recordArray, requestIds); | |
} | |
} | |
promise.then(function() { | |
for (var i = 0, l = batchRecordArrays.length; i < l; i++) { | |
batchRecordArrays[i].loadForFindMany(self); | |
} | |
if (batchDeferreds) { | |
for (i = 0, l = batchDeferreds.length; i < l; i++) { | |
var resolveWith = Ember.get(batchDeferreds[i], 'resolveWith'); | |
batchDeferreds[i].resolve(resolveWith); | |
} | |
} | |
}).then(null, function(errorXHR) { | |
if (batchDeferreds) { | |
for (var i = 0, l = batchDeferreds.length; i < l; i++) { | |
batchDeferreds[i].reject(errorXHR); | |
} | |
} | |
}); | |
}, | |
getCachedReferenceRecord: function(id){ | |
var ref = this._getReferenceById(id); | |
if(ref) return ref.record; | |
return undefined; | |
}, | |
cachedRecordForId: function(id) { | |
var record = this.getCachedReferenceRecord(id); | |
if (!record) { | |
var primaryKey = get(this, 'primaryKey'), | |
attrs = {isLoaded: false}; | |
attrs[primaryKey] = id; | |
record = this.create(attrs); | |
var sideloadedData = this.sideloadedData && this.sideloadedData[id]; | |
if (sideloadedData) { | |
record.load(id, sideloadedData); | |
} | |
} | |
return record; | |
}, | |
addToRecordArrays: function(record) { | |
if (this._findAllRecordArray) { | |
this._findAllRecordArray.pushObject(record); | |
} | |
if (this.recordArrays) { | |
this.recordArrays.forEach(function(recordArray) { | |
if (recordArray instanceof Ember.FilteredRecordArray) { | |
recordArray.registerObserversOnRecord(record); | |
recordArray.updateFilter(); | |
} else { | |
recordArray.pushObject(record); | |
} | |
}); | |
} | |
}, | |
unload: function (record) { | |
this.removeFromRecordArrays(record); | |
var primaryKey = record.get(get(this, 'primaryKey')); | |
this.removeFromCache(primaryKey); | |
}, | |
clearCache: function () { | |
this.sideloadedData = undefined; | |
this._referenceCache = undefined; | |
}, | |
removeFromCache: function (key) { | |
if (this.sideloadedData && this.sideloadedData[key]) { | |
delete this.sideloadedData[key]; | |
} | |
if(this._referenceCache && this._referenceCache[key]) { | |
delete this._referenceCache[key]; | |
} | |
}, | |
removeFromRecordArrays: function(record) { | |
if (this._findAllRecordArray) { | |
this._findAllRecordArray.removeObject(record); | |
} | |
if (this.recordArrays) { | |
this.recordArrays.forEach(function(recordArray) { | |
recordArray.removeObject(record); | |
}); | |
} | |
}, | |
// FIXME | |
findFromCacheOrLoad: function(data) { | |
var record; | |
if (!data[get(this, 'primaryKey')]) { | |
record = this.create({isLoaded: false}); | |
} else { | |
record = this.cachedRecordForId(data[get(this, 'primaryKey')]); | |
} | |
// set(record, 'data', data); | |
record.load(data[get(this, 'primaryKey')], data); | |
return record; | |
}, | |
registerRecordArray: function(recordArray) { | |
if (!this.recordArrays) { this.recordArrays = []; } | |
this.recordArrays.push(recordArray); | |
}, | |
unregisterRecordArray: function(recordArray) { | |
if (!this.recordArrays) { return; } | |
Ember.A(this.recordArrays).removeObject(recordArray); | |
}, | |
forEachCachedRecord: function(callback) { | |
var ids = Object.keys(this._referenceCache); | |
ids.map(function(id) { | |
return this._getReferenceById(id).record; | |
}, this).forEach(callback); | |
}, | |
load: function(hashes) { | |
if (Ember.typeOf(hashes) !== 'array') { hashes = [hashes]; } | |
if (!this.sideloadedData) { this.sideloadedData = {}; } | |
for (var i = 0, l = hashes.length; i < l; i++) { | |
var hash = hashes[i], | |
primaryKey = hash[get(this, 'primaryKey')], | |
record = this.getCachedReferenceRecord(primaryKey); | |
if (record) { | |
record.load(primaryKey, hash); | |
} else { | |
this.sideloadedData[primaryKey] = hash; | |
} | |
} | |
}, | |
_getReferenceById: function(id) { | |
if (!this._referenceCache) { this._referenceCache = {}; } | |
return this._referenceCache[id]; | |
}, | |
_getOrCreateReferenceForId: function(id) { | |
var reference = this._getReferenceById(id); | |
if (!reference) { | |
reference = this._createReference(id); | |
} | |
return reference; | |
}, | |
_createReference: function(id) { | |
if (!this._referenceCache) { this._referenceCache = {}; } | |
Ember.assert('The id ' + id + ' has alread been used with another record of type ' + this.toString() + '.', !id || !this._referenceCache[id]); | |
var reference = { | |
id: id, | |
clientId: this._clientIdCounter++ | |
}; | |
this._cacheReference(reference); | |
return reference; | |
}, | |
_cacheReference: function(reference) { | |
// if we're creating an item, this process will be done | |
// later, once the object has been persisted. | |
if (reference.id) { | |
this._referenceCache[reference.id] = reference; | |
} | |
} | |
}); | |
})(); | |
(function() { | |
var get = Ember.get; | |
Ember.hasMany = function(type, options) { | |
options = options || {}; | |
var meta = { type: type, isRelationship: true, options: options, kind: 'hasMany' }, | |
key = options.key; | |
return Ember.computed(function() { | |
if (typeof type === "string") { | |
type = Ember.get(Ember.lookup, type); | |
} | |
return this.getHasMany(key, type, meta); | |
}).property().meta(meta); | |
}; | |
Ember.Model.reopen({ | |
getHasMany: function(key, type, meta) { | |
var embedded = meta.options.embedded, | |
collectionClass = embedded ? Ember.EmbeddedHasManyArray : Ember.HasManyArray; | |
var collection = collectionClass.create({ | |
parent: this, | |
modelClass: type, | |
content: this._getHasManyContent(key, type, embedded), | |
embedded: embedded, | |
key: key, | |
relationshipKey: meta.relationshipKey | |
}); | |
this._registerHasManyArray(collection); | |
return collection; | |
} | |
}); | |
})(); | |
(function() { | |
var get = Ember.get, | |
set = Ember.set; | |
function getType() { | |
if (typeof this.type === "string") { | |
this.type = Ember.get(Ember.lookup, this.type); | |
} | |
return this.type; | |
} | |
Ember.belongsTo = function(type, options) { | |
options = options || {}; | |
var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo', getType: getType }, | |
relationshipKey = options.key; | |
return Ember.computed(function(key, value, oldValue) { | |
type = meta.getType(); | |
var dirtyAttributes = get(this, '_dirtyAttributes'), | |
createdDirtyAttributes = false; | |
if (!dirtyAttributes) { | |
dirtyAttributes = []; | |
createdDirtyAttributes = true; | |
} | |
if (arguments.length > 1) { | |
if (value) { | |
Ember.assert(Ember.String.fmt('Attempted to set property of type: %@ with a value of type: %@', | |
[value.constructor, type]), | |
value instanceof type); | |
if (oldValue !== value) { | |
dirtyAttributes.pushObject(key); | |
} else { | |
dirtyAttributes.removeObject(key); | |
} | |
if (createdDirtyAttributes) { | |
set(this, '_dirtyAttributes', dirtyAttributes); | |
} | |
} | |
return value === undefined ? null : value; | |
} else { | |
return this.getBelongsTo(relationshipKey, type, meta); | |
} | |
}).property('_data').meta(meta); | |
}; | |
Ember.Model.reopen({ | |
getBelongsTo: function(key, type, meta) { | |
var idOrAttrs = get(this, '_data.' + key), | |
record; | |
if (Ember.isNone(idOrAttrs)) { | |
return null; | |
} | |
if (meta.options.embedded) { | |
var primaryKey = get(type, 'primaryKey'), | |
id = idOrAttrs[primaryKey]; | |
record = type.create({ isLoaded: false, id: id }); | |
record.load(id, idOrAttrs); | |
} else { | |
record = type.find(idOrAttrs); | |
} | |
return record; | |
} | |
}); | |
})(); | |
(function() { | |
var get = Ember.get, | |
set = Ember.set, | |
meta = Ember.meta; | |
Ember.Model.dataTypes = {}; | |
Ember.Model.dataTypes[Date] = { | |
deserialize: function(string) { | |
if (!string) { return null; } | |
return new Date(string); | |
}, | |
serialize: function (date) { | |
if (!date) { return null; } | |
return date.toISOString(); | |
}, | |
isEqual: function(obj1, obj2) { | |
if (obj1 instanceof Date) { obj1 = this.serialize(obj1); } | |
if (obj2 instanceof Date) { obj2 = this.serialize(obj2); } | |
return obj1 === obj2; | |
} | |
}; | |
Ember.Model.dataTypes[Number] = { | |
deserialize: function(string) { | |
if (!string && string !== 0) { return null; } | |
return Number(string); | |
}, | |
serialize: function (number) { | |
if (!number && number !== 0) { return null; } | |
return Number(number); | |
} | |
}; | |
function deserialize(value, type) { | |
if (type && type.deserialize) { | |
return type.deserialize(value); | |
} else if (type && Ember.Model.dataTypes[type]) { | |
return Ember.Model.dataTypes[type].deserialize(value); | |
} else { | |
return value; | |
} | |
} | |
Ember.attr = function(type, options) { | |
return Ember.computed(function(key, value) { | |
var data = get(this, '_data'), | |
dataKey = this.dataKey(key), | |
dataValue = data && get(data, dataKey), | |
beingCreated = meta(this).proto === this, | |
dirtyAttributes = get(this, '_dirtyAttributes'), | |
createdDirtyAttributes = false; | |
if (!dirtyAttributes) { | |
dirtyAttributes = []; | |
createdDirtyAttributes = true; | |
} | |
if (arguments.length === 2) { | |
if (beingCreated) { | |
if (!data) { | |
data = {}; | |
set(this, '_data', data); | |
} | |
dataValue = data[dataKey] = value; | |
} | |
if (dataValue !== value) { | |
dirtyAttributes.pushObject(key); | |
} else { | |
dirtyAttributes.removeObject(key); | |
} | |
if (createdDirtyAttributes) { | |
set(this, '_dirtyAttributes', dirtyAttributes); | |
} | |
return value; | |
} | |
return this.getAttr(key, deserialize(dataValue, type)); | |
}).property('_data').meta({isAttribute: true, type: type, options: options}); | |
}; | |
})(); | |
(function() { | |
var get = Ember.get; | |
Ember.RESTAdapter = Ember.Adapter.extend({ | |
find: function(record, id) { | |
var url = this.buildURL(record.constructor, id), | |
self = this; | |
return this.ajax(url).then(function(data) { | |
self.didFind(record, id, data); | |
return record; | |
}); | |
}, | |
didFind: function(record, id, data) { | |
var rootKey = get(record.constructor, 'rootKey'), | |
dataToLoad = rootKey ? data[rootKey] : data; | |
record.load(id, dataToLoad); | |
}, | |
findAll: function(klass, records) { | |
var url = this.buildURL(klass), | |
self = this; | |
return this.ajax(url).then(function(data) { | |
self.didFindAll(klass, records, data); | |
return records; | |
}); | |
}, | |
didFindAll: function(klass, records, data) { | |
var collectionKey = get(klass, 'collectionKey'), | |
dataToLoad = collectionKey ? data[collectionKey] : data; | |
records.load(klass, dataToLoad); | |
}, | |
findQuery: function(klass, records, params) { | |
var url = this.buildURL(klass), | |
self = this; | |
return this.ajax(url, params).then(function(data) { | |
self.didFindQuery(klass, records, params, data); | |
return records; | |
}); | |
}, | |
didFindQuery: function(klass, records, params, data) { | |
var collectionKey = get(klass, 'collectionKey'), | |
dataToLoad = collectionKey ? data[collectionKey] : data; | |
records.load(klass, dataToLoad); | |
}, | |
createRecord: function(record) { | |
var url = this.buildURL(record.constructor), | |
self = this; | |
return this.ajax(url, record.toJSON(), "POST").then(function(data) { | |
self.didCreateRecord(record, data); | |
return record; | |
}); | |
}, | |
didCreateRecord: function(record, data) { | |
var rootKey = get(record.constructor, 'rootKey'), | |
primaryKey = get(record.constructor, 'primaryKey'), | |
dataToLoad = rootKey ? data[rootKey] : data; | |
record.load(dataToLoad[primaryKey], dataToLoad); | |
record.didCreateRecord(); | |
}, | |
saveRecord: function(record) { | |
var primaryKey = get(record.constructor, 'primaryKey'), | |
url = this.buildURL(record.constructor, get(record, primaryKey)), | |
self = this; | |
return this.ajax(url, record.toJSON(), "PUT").then(function(data) { // TODO: Some APIs may or may not return data | |
self.didSaveRecord(record, data); | |
return record; | |
}); | |
}, | |
didSaveRecord: function(record, data) { | |
record.didSaveRecord(); | |
}, | |
deleteRecord: function(record) { | |
var primaryKey = get(record.constructor, 'primaryKey'), | |
url = this.buildURL(record.constructor, get(record, primaryKey)), | |
self = this; | |
return this.ajax(url, record.toJSON(), "DELETE").then(function(data) { // TODO: Some APIs may or may not return data | |
self.didDeleteRecord(record, data); | |
}); | |
}, | |
didDeleteRecord: function(record, data) { | |
record.didDeleteRecord(); | |
}, | |
ajax: function(url, params, method) { | |
return this._ajax(url, params, method || "GET"); | |
}, | |
buildURL: function(klass, id) { | |
var urlRoot = get(klass, 'url'); | |
if (!urlRoot) { throw new Error('Ember.RESTAdapter requires a `url` property to be specified'); } | |
if (!Ember.isEmpty(id)) { | |
return urlRoot + "/" + id + ".json"; | |
} else { | |
return urlRoot + ".json"; | |
} | |
}, | |
ajaxSettings: function(url, method) { | |
return { | |
url: url, | |
type: method, | |
dataType: "json" | |
}; | |
}, | |
_ajax: function(url, params, method) { | |
var settings = this.ajaxSettings(url, method); | |
return new NewRSVP.Promise(function(resolve, reject) { | |
if (params) { | |
if (method === "GET") { | |
settings.data = params; | |
} else { | |
settings.contentType = "application/json; charset=utf-8"; | |
settings.data = JSON.stringify(params); | |
} | |
} | |
settings.success = function(json) { | |
Ember.run(null, resolve, json); | |
}; | |
settings.error = function(jqXHR, textStatus, errorThrown) { | |
// https://github.com/ebryn/ember-model/issues/202 | |
if (jqXHR) { | |
jqXHR.then = null; | |
} | |
Ember.run(null, reject, jqXHR); | |
}; | |
Ember.$.ajax(settings); | |
}); | |
} | |
}); | |
})(); | |
(function() { | |
var get = Ember.get; | |
Ember.LoadPromise = Ember.Object.extend(Ember.DeferredMixin, { | |
init: function() { | |
this._super.apply(this, arguments); | |
var target = get(this, 'target'); | |
if (get(target, 'isLoaded') && !get(target, 'isNew')) { | |
this.resolve(target); | |
} else { | |
target.one('didLoad', this, function() { | |
this.resolve(target); | |
}); | |
} | |
} | |
}); | |
Ember.loadPromise = function(target) { | |
if (Ember.isNone(target)) { | |
return null; | |
} else if (target.then) { | |
return target; | |
} else { | |
return Ember.LoadPromise.create({target: target}); | |
} | |
}; | |
})(); | |
(function() { | |
// This is a debug adapter for the Ember Extension, don't let the fact this is called an "adapter" confuse you. | |
// Most copied from: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/debug/debug_adapter.js | |
if (!Ember.DataAdapter) { return; } | |
var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.String.underscore; | |
var DebugAdapter = Ember.DataAdapter.extend({ | |
getFilters: function() { | |
return [ | |
{ name: 'isNew', desc: 'New' }, | |
{ name: 'isModified', desc: 'Modified' }, | |
{ name: 'isClean', desc: 'Clean' } | |
]; | |
}, | |
detect: function(klass) { | |
return klass !== Ember.Model && Ember.Model.detect(klass); | |
}, | |
columnsForType: function(type) { | |
var columns = [], count = 0, self = this; | |
Ember.A(get(type.proto(), 'attributes')).forEach(function(name, meta) { | |
if (count++ > self.attributeLimit) { return false; } | |
var desc = capitalize(underscore(name).replace('_', ' ')); | |
columns.push({ name: name, desc: desc }); | |
}); | |
return columns; | |
}, | |
getRecords: function(type) { | |
var records = []; | |
type.forEachCachedRecord(function(record) { records.push(record); }); | |
return records; | |
}, | |
getRecordColumnValues: function(record) { | |
var self = this, count = 0, | |
columnValues = { id: get(record, 'id') }; | |
record.get('attributes').forEach(function(key) { | |
if (count++ > self.attributeLimit) { | |
return false; | |
} | |
var value = get(record, key); | |
columnValues[key] = value; | |
}); | |
return columnValues; | |
}, | |
getRecordKeywords: function(record) { | |
var keywords = [], keys = Ember.A(['id']); | |
record.get('attributes').forEach(function(key) { | |
keys.push(key); | |
}); | |
keys.forEach(function(key) { | |
keywords.push(get(record, key)); | |
}); | |
return keywords; | |
}, | |
getRecordFilterValues: function(record) { | |
return { | |
isNew: record.get('isNew'), | |
isModified: record.get('isDirty') && !record.get('isNew'), | |
isClean: !record.get('isDirty') | |
}; | |
}, | |
getRecordColor: function(record) { | |
var color = 'black'; | |
if (record.get('isNew')) { | |
color = 'green'; | |
} else if (record.get('isDirty')) { | |
color = 'blue'; | |
} | |
return color; | |
}, | |
observeRecord: function(record, recordUpdated) { | |
var releaseMethods = Ember.A(), self = this, | |
keysToObserve = Ember.A(['id', 'isNew', 'isDirty']); | |
record.get('attributes').forEach(function(key) { | |
keysToObserve.push(key); | |
}); | |
keysToObserve.forEach(function(key) { | |
var handler = function() { | |
recordUpdated(self.wrapRecord(record)); | |
}; | |
Ember.addObserver(record, key, handler); | |
releaseMethods.push(function() { | |
Ember.removeObserver(record, key, handler); | |
}); | |
}); | |
var release = function() { | |
releaseMethods.forEach(function(fn) { fn(); } ); | |
}; | |
return release; | |
} | |
}); | |
Ember.onLoad('Ember.Application', function(Application) { | |
Application.initializer({ | |
name: "dataAdapter", | |
initialize: function(container, application) { | |
application.register('dataAdapter:main', DebugAdapter); | |
} | |
}); | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment