>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w<
+l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();
+(function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])},
+_doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]),
+f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f,
+m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m,
+E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/
+4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math);
+(function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a,
+this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684,
+1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})},
+decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d,
+b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}();
+(function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8,
+16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;dd||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>>
+8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t=
+d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})();
diff --git a/js/libs/blob.js b/js/libs/blob.js
new file mode 100644
index 0000000..5893afc
--- /dev/null
+++ b/js/libs/blob.js
@@ -0,0 +1,166 @@
+/* Blob.js
+ * A Blob implementation.
+ * 2013-06-20
+ *
+ * By Eli Grey, http://eligrey.com
+ * By Devin Samarin, https://github.com/eboyjr
+ * License: X11/MIT
+ * See LICENSE.md
+ */
+
+/*global self, unescape */
+/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
+ plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
+
+if (!(typeof Blob === "function" || typeof Blob === "object") || typeof URL === "undefined")
+if ((typeof Blob === "function" || typeof Blob === "object") && typeof webkitURL !== "undefined") self.URL = webkitURL;
+else var Blob = (function (view) {
+ "use strict";
+
+ var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) {
+ var
+ get_class = function(object) {
+ return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
+ }
+ , FakeBlobBuilder = function BlobBuilder() {
+ this.data = [];
+ }
+ , FakeBlob = function Blob(data, type, encoding) {
+ this.data = data;
+ this.size = data.length;
+ this.type = type;
+ this.encoding = encoding;
+ }
+ , FBB_proto = FakeBlobBuilder.prototype
+ , FB_proto = FakeBlob.prototype
+ , FileReaderSync = view.FileReaderSync
+ , FileException = function(type) {
+ this.code = this[this.name = type];
+ }
+ , file_ex_codes = (
+ "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
+ + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
+ ).split(" ")
+ , file_ex_code = file_ex_codes.length
+ , real_URL = view.URL || view.webkitURL || view
+ , real_create_object_URL = real_URL.createObjectURL
+ , real_revoke_object_URL = real_URL.revokeObjectURL
+ , URL = real_URL
+ , btoa = view.btoa
+ , atob = view.atob
+
+ , ArrayBuffer = view.ArrayBuffer
+ , Uint8Array = view.Uint8Array
+ ;
+ FakeBlob.fake = FB_proto.fake = true;
+ while (file_ex_code--) {
+ FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
+ }
+ if (!real_URL.createObjectURL) {
+ URL = view.URL = {};
+ }
+ URL.createObjectURL = function(blob) {
+ var
+ type = blob.type
+ , data_URI_header
+ ;
+ if (type === null) {
+ type = "application/octet-stream";
+ }
+ if (blob instanceof FakeBlob) {
+ data_URI_header = "data:" + type;
+ if (blob.encoding === "base64") {
+ return data_URI_header + ";base64," + blob.data;
+ } else if (blob.encoding === "URI") {
+ return data_URI_header + "," + decodeURIComponent(blob.data);
+ } if (btoa) {
+ return data_URI_header + ";base64," + btoa(blob.data);
+ } else {
+ return data_URI_header + "," + encodeURIComponent(blob.data);
+ }
+ } else if (real_create_object_URL) {
+ return real_create_object_URL.call(real_URL, blob);
+ }
+ };
+ URL.revokeObjectURL = function(object_URL) {
+ if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
+ real_revoke_object_URL.call(real_URL, object_URL);
+ }
+ };
+ FBB_proto.append = function(data/*, endings*/) {
+ var bb = this.data;
+ // decode data to a binary string
+ if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
+ var
+ str = ""
+ , buf = new Uint8Array(data)
+ , i = 0
+ , buf_len = buf.length
+ ;
+ for (; i < buf_len; i++) {
+ str += String.fromCharCode(buf[i]);
+ }
+ bb.push(str);
+ } else if (get_class(data) === "Blob" || get_class(data) === "File") {
+ if (FileReaderSync) {
+ var fr = new FileReaderSync;
+ bb.push(fr.readAsBinaryString(data));
+ } else {
+ // async FileReader won't work as BlobBuilder is sync
+ throw new FileException("NOT_READABLE_ERR");
+ }
+ } else if (data instanceof FakeBlob) {
+ if (data.encoding === "base64" && atob) {
+ bb.push(atob(data.data));
+ } else if (data.encoding === "URI") {
+ bb.push(decodeURIComponent(data.data));
+ } else if (data.encoding === "raw") {
+ bb.push(data.data);
+ }
+ } else {
+ if (typeof data !== "string") {
+ data += ""; // convert unsupported types to strings
+ }
+ // decode UTF-16 to binary string
+ bb.push(unescape(encodeURIComponent(data)));
+ }
+ };
+ FBB_proto.getBlob = function(type) {
+ if (!arguments.length) {
+ type = null;
+ }
+ return new FakeBlob(this.data.join(""), type, "raw");
+ };
+ FBB_proto.toString = function() {
+ return "[object BlobBuilder]";
+ };
+ FB_proto.slice = function(start, end, type) {
+ var args = arguments.length;
+ if (args < 3) {
+ type = null;
+ }
+ return new FakeBlob(
+ this.data.slice(start, args > 1 ? end : this.data.length)
+ , type
+ , this.encoding
+ );
+ };
+ FB_proto.toString = function() {
+ return "[object Blob]";
+ };
+ return FakeBlobBuilder;
+ }(view));
+
+ return function Blob(blobParts, options) {
+ var type = options ? (options.type || "") : "";
+ var builder = new BlobBuilder();
+ if (blobParts) {
+ for (var i = 0, len = blobParts.length; i < len; i++) {
+ builder.append(blobParts[i]);
+ }
+ }
+ return builder.getBlob(type);
+ };
+}(self));
diff --git a/js/libs/bootstrap-fileupload.js b/js/libs/bootstrap-fileupload.js
new file mode 100644
index 0000000..97a6da5
--- /dev/null
+++ b/js/libs/bootstrap-fileupload.js
@@ -0,0 +1,169 @@
+/* ===========================================================
+ * bootstrap-fileupload.js j2
+ * http://jasny.github.com/bootstrap/javascript.html#fileupload
+ * ===========================================================
+ * Copyright 2012 Jasny BV, Netherlands.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+!function ($) {
+
+ "use strict"; // jshint ;_
+
+ /* FILEUPLOAD PUBLIC CLASS DEFINITION
+ * ================================= */
+
+ var Fileupload = function (element, options) {
+ this.$element = $(element)
+ this.type = this.$element.data('uploadtype') || (this.$element.find('.thumbnail').length > 0 ? "image" : "file")
+
+ this.$input = this.$element.find(':file')
+ if (this.$input.length === 0) return
+
+ this.name = this.$input.attr('name') || options.name
+
+ this.$hidden = this.$element.find('input[type=hidden][name="'+this.name+'"]')
+ if (this.$hidden.length === 0) {
+ this.$hidden = $('')
+ this.$element.prepend(this.$hidden)
+ }
+
+ this.$preview = this.$element.find('.fileupload-preview')
+ var height = this.$preview.css('height')
+ if (this.$preview.css('display') != 'inline' && height != '0px' && height != 'none') this.$preview.css('line-height', height)
+
+ this.original = {
+ 'exists': this.$element.hasClass('fileupload-exists'),
+ 'preview': this.$preview.html(),
+ 'hiddenVal': this.$hidden.val()
+ }
+
+ this.$remove = this.$element.find('[data-dismiss="fileupload"]')
+
+ this.$element.find('[data-trigger="fileupload"]').on('click.fileupload', $.proxy(this.trigger, this))
+
+ this.listen()
+ }
+
+ Fileupload.prototype = {
+
+ listen: function() {
+ this.$input.on('change.fileupload', $.proxy(this.change, this))
+ $(this.$input[0].form).on('reset.fileupload', $.proxy(this.reset, this))
+ if (this.$remove) this.$remove.on('click.fileupload', $.proxy(this.clear, this))
+ },
+
+ change: function(e, invoked) {
+ if (invoked === 'clear') return
+
+ var file = e.target.files !== undefined ? e.target.files[0] : (e.target.value ? { name: e.target.value.replace(/^.+\\/, '') } : null)
+
+ if (!file) {
+ this.clear()
+ return
+ }
+
+ this.$hidden.val('')
+ this.$hidden.attr('name', '')
+ this.$input.attr('name', this.name)
+
+ if (this.type === "image" && this.$preview.length > 0 && (typeof file.type !== "undefined" ? file.type.match('image.*') : file.name.match(/\.(gif|png|jpe?g)$/i)) && typeof FileReader !== "undefined") {
+ var reader = new FileReader()
+ var preview = this.$preview
+ var element = this.$element
+
+ reader.onload = function(e) {
+ preview.html('
')
+ element.addClass('fileupload-exists').removeClass('fileupload-new')
+ }
+
+ reader.readAsDataURL(file)
+ } else {
+ this.$preview.text(file.name)
+ this.$element.addClass('fileupload-exists').removeClass('fileupload-new')
+ }
+ },
+
+ clear: function(e) {
+ this.$hidden.val('')
+ this.$hidden.attr('name', this.name)
+ this.$input.attr('name', '')
+
+ //ie8+ doesn't support changing the value of input with type=file so clone instead
+ if (navigator.userAgent.match(/msie/i)){
+ var inputClone = this.$input.clone(true);
+ this.$input.after(inputClone);
+ this.$input.remove();
+ this.$input = inputClone;
+ }else{
+ this.$input.val('')
+ }
+
+ this.$preview.html('')
+ this.$element.addClass('fileupload-new').removeClass('fileupload-exists')
+
+ if (e) {
+ this.$input.trigger('change', [ 'clear' ])
+ e.preventDefault()
+ }
+ },
+
+ reset: function(e) {
+ this.clear()
+
+ this.$hidden.val(this.original.hiddenVal)
+ this.$preview.html(this.original.preview)
+
+ if (this.original.exists) this.$element.addClass('fileupload-exists').removeClass('fileupload-new')
+ else this.$element.addClass('fileupload-new').removeClass('fileupload-exists')
+ },
+
+ trigger: function(e) {
+ this.$input.trigger('click')
+ e.preventDefault()
+ }
+ }
+
+
+ /* FILEUPLOAD PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.fileupload = function (options) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('fileupload')
+ if (!data) $this.data('fileupload', (data = new Fileupload(this, options)))
+ if (typeof options == 'string') data[options]()
+ })
+ }
+
+ $.fn.fileupload.Constructor = Fileupload
+
+
+ /* FILEUPLOAD DATA-API
+ * ================== */
+
+ $(document).on('click.fileupload.data-api', '[data-provides="fileupload"]', function (e) {
+ var $this = $(this)
+ if ($this.data('fileupload')) return
+ $this.fileupload($this.data())
+
+ var $target = $(e.target).closest('[data-dismiss="fileupload"],[data-trigger="fileupload"]');
+ if ($target.length > 0) {
+ $target.trigger('click.fileupload')
+ e.preventDefault()
+ }
+ })
+
+}(window.jQuery);
diff --git a/js/libs/bootstrap-popover.js b/js/libs/bootstrap-popover.js
new file mode 100755
index 0000000..924844e
--- /dev/null
+++ b/js/libs/bootstrap-popover.js
@@ -0,0 +1,95 @@
+/* ===========================================================
+ * bootstrap-popover.js v2.0.1
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function( $ ) {
+
+ "use strict"
+
+ var Popover = function ( element, options ) {
+ this.init('popover', element, options)
+ }
+
+ /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+ ========================================== */
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+ constructor: Popover
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+ , content = this.getContent()
+
+ $tip.find('.popover-title')[ $.type(title) == 'object' ? 'append' : 'html' ](title)
+ $tip.find('.popover-content > *')[ $.type(content) == 'object' ? 'append' : 'html' ](content)
+
+ $tip.removeClass('fade top bottom left right in')
+ }
+
+ , hasContent: function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ , getContent: function () {
+ var content
+ , $e = this.$element
+ , o = this.options
+
+ content = $e.attr('data-content')
+ || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
+
+ content = content.toString().replace(/(^\s*|\s*$)/, "")
+
+ return content
+ }
+
+ , tip: function() {
+ if (!this.$tip) {
+ this.$tip = $(this.options.template)
+ }
+ return this.$tip
+ }
+
+ })
+
+
+ /* POPOVER PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.popover = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('popover')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+ $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+ placement: 'right'
+ , content: ''
+ , template: ''
+ })
+
+}( window.jQuery );
\ No newline at end of file
diff --git a/js/libs/bootstrap-tooltip.js b/js/libs/bootstrap-tooltip.js
new file mode 100755
index 0000000..d76e51e
--- /dev/null
+++ b/js/libs/bootstrap-tooltip.js
@@ -0,0 +1,270 @@
+/* ===========================================================
+ * bootstrap-tooltip.js v2.0.1
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+!function( $ ) {
+
+ "use strict"
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Tooltip = function ( element, options ) {
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.prototype = {
+
+ constructor: Tooltip
+
+ , init: function ( type, element, options ) {
+ var eventIn
+ , eventOut
+
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.enabled = true
+
+ if (this.options.trigger != 'manual') {
+ eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
+ eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
+ this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this))
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ , getOptions: function ( options ) {
+ options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay
+ , hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ , enter: function ( e ) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (!self.options.delay || !self.options.delay.show) {
+ self.show()
+ } else {
+ self.hoverState = 'in'
+ setTimeout(function() {
+ if (self.hoverState == 'in') {
+ self.show()
+ }
+ }, self.options.delay.show)
+ }
+ }
+
+ , leave: function ( e ) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (!self.options.delay || !self.options.delay.hide) {
+ self.hide()
+ } else {
+ self.hoverState = 'out'
+ setTimeout(function() {
+ if (self.hoverState == 'out') {
+ self.hide()
+ }
+ }, self.options.delay.hide)
+ }
+ }
+
+ , show: function () {
+ var $tip
+ , inside
+ , pos
+ , actualWidth
+ , actualHeight
+ , placement
+ , tp
+
+ if (this.hasContent() && this.enabled) {
+ $tip = this.tip()
+ this.setContent()
+
+ if (this.options.animation) {
+ $tip.addClass('fade')
+ }
+
+ placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ inside = /in/.test(placement)
+
+ $tip
+ .remove()
+ .css({ top: 0, left: 0, display: 'block' })
+ .appendTo(inside ? this.$element : document.body)
+
+ pos = this.getPosition(inside)
+
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+
+ switch (inside ? placement.split(' ')[1] : placement) {
+ case 'bottom':
+ tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'top':
+ tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'left':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+ break
+ case 'right':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+ break
+ }
+
+ $tip
+ .css(tp)
+ .addClass(placement)
+ .addClass('in')
+ }
+ }
+
+ , setContent: function () {
+ var $tip = this.tip()
+ $tip.find('.tooltip-inner').html(this.getTitle())
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ , hide: function () {
+ var that = this
+ , $tip = this.tip()
+
+ $tip.removeClass('in')
+
+ function removeWithAnimation() {
+ var timeout = setTimeout(function () {
+ $tip.off($.support.transition.end).remove()
+ }, 500)
+
+ $tip.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ $tip.remove()
+ })
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ removeWithAnimation() :
+ $tip.remove()
+ }
+
+ , fixTitle: function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
+ }
+ }
+
+ , hasContent: function () {
+ return this.getTitle()
+ }
+
+ , getPosition: function (inside) {
+ return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
+ width: this.$element[0].offsetWidth
+ , height: this.$element[0].offsetHeight
+ })
+ }
+
+ , getTitle: function () {
+ var title
+ , $e = this.$element
+ , o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ title = title.toString().replace(/(^\s*|\s*$)/, "")
+
+ return title
+ }
+
+ , tip: function () {
+ return this.$tip = this.$tip || $(this.options.template)
+ }
+
+ , validate: function () {
+ if (!this.$element[0].parentNode) {
+ this.hide()
+ this.$element = null
+ this.options = null
+ }
+ }
+
+ , enable: function () {
+ this.enabled = true
+ }
+
+ , disable: function () {
+ this.enabled = false
+ }
+
+ , toggleEnabled: function () {
+ this.enabled = !this.enabled
+ }
+
+ , toggle: function () {
+ this[this.tip().hasClass('in') ? 'hide' : 'show']()
+ }
+
+ }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+ * ========================= */
+
+ $.fn.tooltip = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tooltip')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tooltip.Constructor = Tooltip
+
+ $.fn.tooltip.defaults = {
+ animation: true
+ , delay: 0
+ , selector: false
+ , placement: 'top'
+ , trigger: 'hover'
+ , title: ''
+ , template: ''
+ }
+
+}( window.jQuery );
\ No newline at end of file
diff --git a/js/libs/core.js b/js/libs/core.js
new file mode 100644
index 0000000..996aa07
--- /dev/null
+++ b/js/libs/core.js
@@ -0,0 +1,712 @@
+/*
+CryptoJS v3.1.2
+code.google.com/p/crypto-js
+(c) 2009-2013 by Jeff Mott. All rights reserved.
+code.google.com/p/crypto-js/wiki/License
+*/
+/**
+ * CryptoJS core components.
+ */
+var CryptoJS = CryptoJS || (function (Math, undefined) {
+ /**
+ * CryptoJS namespace.
+ */
+ var C = {};
+
+ /**
+ * Library namespace.
+ */
+ var C_lib = C.lib = {};
+
+ /**
+ * Base object for prototypal inheritance.
+ */
+ var Base = C_lib.Base = (function () {
+ function F() {}
+
+ return {
+ /**
+ * Creates a new object that inherits from this object.
+ *
+ * @param {Object} overrides Properties to copy into the new object.
+ *
+ * @return {Object} The new object.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var MyType = CryptoJS.lib.Base.extend({
+ * field: 'value',
+ *
+ * method: function () {
+ * }
+ * });
+ */
+ extend: function (overrides) {
+ // Spawn
+ F.prototype = this;
+ var subtype = new F();
+
+ // Augment
+ if (overrides) {
+ subtype.mixIn(overrides);
+ }
+
+ // Create default initializer
+ if (!subtype.hasOwnProperty('init')) {
+ subtype.init = function () {
+ subtype.$super.init.apply(this, arguments);
+ };
+ }
+
+ // Initializer's prototype is the subtype object
+ subtype.init.prototype = subtype;
+
+ // Reference supertype
+ subtype.$super = this;
+
+ return subtype;
+ },
+
+ /**
+ * Extends this object and runs the init method.
+ * Arguments to create() will be passed to init().
+ *
+ * @return {Object} The new object.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var instance = MyType.create();
+ */
+ create: function () {
+ var instance = this.extend();
+ instance.init.apply(instance, arguments);
+
+ return instance;
+ },
+
+ /**
+ * Initializes a newly created object.
+ * Override this method to add some logic when your objects are created.
+ *
+ * @example
+ *
+ * var MyType = CryptoJS.lib.Base.extend({
+ * init: function () {
+ * // ...
+ * }
+ * });
+ */
+ init: function () {
+ },
+
+ /**
+ * Copies properties into this object.
+ *
+ * @param {Object} properties The properties to mix in.
+ *
+ * @example
+ *
+ * MyType.mixIn({
+ * field: 'value'
+ * });
+ */
+ mixIn: function (properties) {
+ for (var propertyName in properties) {
+ if (properties.hasOwnProperty(propertyName)) {
+ this[propertyName] = properties[propertyName];
+ }
+ }
+
+ // IE won't copy toString using the loop above
+ if (properties.hasOwnProperty('toString')) {
+ this.toString = properties.toString;
+ }
+ },
+
+ /**
+ * Creates a copy of this object.
+ *
+ * @return {Object} The clone.
+ *
+ * @example
+ *
+ * var clone = instance.clone();
+ */
+ clone: function () {
+ return this.init.prototype.extend(this);
+ }
+ };
+ }());
+
+ /**
+ * An array of 32-bit words.
+ *
+ * @property {Array} words The array of 32-bit words.
+ * @property {number} sigBytes The number of significant bytes in this word array.
+ */
+ var WordArray = C_lib.WordArray = Base.extend({
+ /**
+ * Initializes a newly created word array.
+ *
+ * @param {Array} words (Optional) An array of 32-bit words.
+ * @param {number} sigBytes (Optional) The number of significant bytes in the words.
+ *
+ * @example
+ *
+ * var wordArray = CryptoJS.lib.WordArray.create();
+ * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]);
+ * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6);
+ */
+ init: function (words, sigBytes) {
+ words = this.words = words || [];
+
+ if (sigBytes != undefined) {
+ this.sigBytes = sigBytes;
+ } else {
+ this.sigBytes = words.length * 4;
+ }
+ },
+
+ /**
+ * Converts this word array to a string.
+ *
+ * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex
+ *
+ * @return {string} The stringified word array.
+ *
+ * @example
+ *
+ * var string = wordArray + '';
+ * var string = wordArray.toString();
+ * var string = wordArray.toString(CryptoJS.enc.Utf8);
+ */
+ toString: function (encoder) {
+ return (encoder || Hex).stringify(this);
+ },
+
+ /**
+ * Concatenates a word array to this word array.
+ *
+ * @param {WordArray} wordArray The word array to append.
+ *
+ * @return {WordArray} This word array.
+ *
+ * @example
+ *
+ * wordArray1.concat(wordArray2);
+ */
+ concat: function (wordArray) {
+ // Shortcuts
+ var thisWords = this.words;
+ var thatWords = wordArray.words;
+ var thisSigBytes = this.sigBytes;
+ var thatSigBytes = wordArray.sigBytes;
+
+ // Clamp excess bits
+ this.clamp();
+
+ // Concat
+ if (thisSigBytes % 4) {
+ // Copy one byte at a time
+ for (var i = 0; i < thatSigBytes; i++) {
+ var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
+ thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8);
+ }
+ } else if (thatWords.length > 0xffff) {
+ // Copy one word at a time
+ for (var i = 0; i < thatSigBytes; i += 4) {
+ thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2];
+ }
+ } else {
+ // Copy all words at once
+ thisWords.push.apply(thisWords, thatWords);
+ }
+ this.sigBytes += thatSigBytes;
+
+ // Chainable
+ return this;
+ },
+
+ /**
+ * Removes insignificant bits.
+ *
+ * @example
+ *
+ * wordArray.clamp();
+ */
+ clamp: function () {
+ // Shortcuts
+ var words = this.words;
+ var sigBytes = this.sigBytes;
+
+ // Clamp
+ words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8);
+ words.length = Math.ceil(sigBytes / 4);
+ },
+
+ /**
+ * Creates a copy of this word array.
+ *
+ * @return {WordArray} The clone.
+ *
+ * @example
+ *
+ * var clone = wordArray.clone();
+ */
+ clone: function () {
+ var clone = Base.clone.call(this);
+ clone.words = this.words.slice(0);
+
+ return clone;
+ },
+
+ /**
+ * Creates a word array filled with random bytes.
+ *
+ * @param {number} nBytes The number of random bytes to generate.
+ *
+ * @return {WordArray} The random word array.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var wordArray = CryptoJS.lib.WordArray.random(16);
+ */
+ random: function (nBytes) {
+ var words = [];
+ for (var i = 0; i < nBytes; i += 4) {
+ words.push((Math.random() * 0x100000000) | 0);
+ }
+
+ return new WordArray.init(words, nBytes);
+ }
+ });
+
+ /**
+ * Encoder namespace.
+ */
+ var C_enc = C.enc = {};
+
+ /**
+ * Hex encoding strategy.
+ */
+ var Hex = C_enc.Hex = {
+ /**
+ * Converts a word array to a hex string.
+ *
+ * @param {WordArray} wordArray The word array.
+ *
+ * @return {string} The hex string.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var hexString = CryptoJS.enc.Hex.stringify(wordArray);
+ */
+ stringify: function (wordArray) {
+ // Shortcuts
+ var words = wordArray.words;
+ var sigBytes = wordArray.sigBytes;
+
+ // Convert
+ var hexChars = [];
+ for (var i = 0; i < sigBytes; i++) {
+ var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
+ hexChars.push((bite >>> 4).toString(16));
+ hexChars.push((bite & 0x0f).toString(16));
+ }
+
+ return hexChars.join('');
+ },
+
+ /**
+ * Converts a hex string to a word array.
+ *
+ * @param {string} hexStr The hex string.
+ *
+ * @return {WordArray} The word array.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var wordArray = CryptoJS.enc.Hex.parse(hexString);
+ */
+ parse: function (hexStr) {
+ // Shortcut
+ var hexStrLength = hexStr.length;
+
+ // Convert
+ var words = [];
+ for (var i = 0; i < hexStrLength; i += 2) {
+ words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
+ }
+
+ return new WordArray.init(words, hexStrLength / 2);
+ }
+ };
+
+ /**
+ * Latin1 encoding strategy.
+ */
+ var Latin1 = C_enc.Latin1 = {
+ /**
+ * Converts a word array to a Latin1 string.
+ *
+ * @param {WordArray} wordArray The word array.
+ *
+ * @return {string} The Latin1 string.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray);
+ */
+ stringify: function (wordArray) {
+ // Shortcuts
+ var words = wordArray.words;
+ var sigBytes = wordArray.sigBytes;
+
+ // Convert
+ var latin1Chars = [];
+ for (var i = 0; i < sigBytes; i++) {
+ var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
+ latin1Chars.push(String.fromCharCode(bite));
+ }
+
+ return latin1Chars.join('');
+ },
+
+ /**
+ * Converts a Latin1 string to a word array.
+ *
+ * @param {string} latin1Str The Latin1 string.
+ *
+ * @return {WordArray} The word array.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var wordArray = CryptoJS.enc.Latin1.parse(latin1String);
+ */
+ parse: function (latin1Str) {
+ // Shortcut
+ var latin1StrLength = latin1Str.length;
+
+ // Convert
+ var words = [];
+ for (var i = 0; i < latin1StrLength; i++) {
+ words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
+ }
+
+ return new WordArray.init(words, latin1StrLength);
+ }
+ };
+
+ /**
+ * UTF-8 encoding strategy.
+ */
+ var Utf8 = C_enc.Utf8 = {
+ /**
+ * Converts a word array to a UTF-8 string.
+ *
+ * @param {WordArray} wordArray The word array.
+ *
+ * @return {string} The UTF-8 string.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray);
+ */
+ stringify: function (wordArray) {
+ try {
+ return decodeURIComponent(escape(Latin1.stringify(wordArray)));
+ } catch (e) {
+ throw new Error('Malformed UTF-8 data');
+ }
+ },
+
+ /**
+ * Converts a UTF-8 string to a word array.
+ *
+ * @param {string} utf8Str The UTF-8 string.
+ *
+ * @return {WordArray} The word array.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var wordArray = CryptoJS.enc.Utf8.parse(utf8String);
+ */
+ parse: function (utf8Str) {
+ return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
+ }
+ };
+
+ /**
+ * Abstract buffered block algorithm template.
+ *
+ * The property blockSize must be implemented in a concrete subtype.
+ *
+ * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0
+ */
+ var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({
+ /**
+ * Resets this block algorithm's data buffer to its initial state.
+ *
+ * @example
+ *
+ * bufferedBlockAlgorithm.reset();
+ */
+ reset: function () {
+ // Initial values
+ this._data = new WordArray.init();
+ this._nDataBytes = 0;
+ },
+
+ /**
+ * Adds new data to this block algorithm's buffer.
+ *
+ * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8.
+ *
+ * @example
+ *
+ * bufferedBlockAlgorithm._append('data');
+ * bufferedBlockAlgorithm._append(wordArray);
+ */
+ _append: function (data) {
+ // Convert string to WordArray, else assume WordArray already
+ if (typeof data == 'string') {
+ data = Utf8.parse(data);
+ }
+
+ // Append
+ this._data.concat(data);
+ this._nDataBytes += data.sigBytes;
+ },
+
+ /**
+ * Processes available data blocks.
+ *
+ * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype.
+ *
+ * @param {boolean} doFlush Whether all blocks and partial blocks should be processed.
+ *
+ * @return {WordArray} The processed data.
+ *
+ * @example
+ *
+ * var processedData = bufferedBlockAlgorithm._process();
+ * var processedData = bufferedBlockAlgorithm._process(!!'flush');
+ */
+ _process: function (doFlush) {
+ // Shortcuts
+ var data = this._data;
+ var dataWords = data.words;
+ var dataSigBytes = data.sigBytes;
+ var blockSize = this.blockSize;
+ var blockSizeBytes = blockSize * 4;
+
+ // Count blocks ready
+ var nBlocksReady = dataSigBytes / blockSizeBytes;
+ if (doFlush) {
+ // Round up to include partial blocks
+ nBlocksReady = Math.ceil(nBlocksReady);
+ } else {
+ // Round down to include only full blocks,
+ // less the number of blocks that must remain in the buffer
+ nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
+ }
+
+ // Count words ready
+ var nWordsReady = nBlocksReady * blockSize;
+
+ // Count bytes ready
+ var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);
+
+ // Process blocks
+ if (nWordsReady) {
+ for (var offset = 0; offset < nWordsReady; offset += blockSize) {
+ // Perform concrete-algorithm logic
+ this._doProcessBlock(dataWords, offset);
+ }
+
+ // Remove processed words
+ var processedWords = dataWords.splice(0, nWordsReady);
+ data.sigBytes -= nBytesReady;
+ }
+
+ // Return processed words
+ return new WordArray.init(processedWords, nBytesReady);
+ },
+
+ /**
+ * Creates a copy of this object.
+ *
+ * @return {Object} The clone.
+ *
+ * @example
+ *
+ * var clone = bufferedBlockAlgorithm.clone();
+ */
+ clone: function () {
+ var clone = Base.clone.call(this);
+ clone._data = this._data.clone();
+
+ return clone;
+ },
+
+ _minBufferSize: 0
+ });
+
+ /**
+ * Abstract hasher template.
+ *
+ * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits)
+ */
+ var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({
+ /**
+ * Configuration options.
+ */
+ cfg: Base.extend(),
+
+ /**
+ * Initializes a newly created hasher.
+ *
+ * @param {Object} cfg (Optional) The configuration options to use for this hash computation.
+ *
+ * @example
+ *
+ * var hasher = CryptoJS.algo.SHA256.create();
+ */
+ init: function (cfg) {
+ // Apply config defaults
+ this.cfg = this.cfg.extend(cfg);
+
+ // Set initial values
+ this.reset();
+ },
+
+ /**
+ * Resets this hasher to its initial state.
+ *
+ * @example
+ *
+ * hasher.reset();
+ */
+ reset: function () {
+ // Reset data buffer
+ BufferedBlockAlgorithm.reset.call(this);
+
+ // Perform concrete-hasher logic
+ this._doReset();
+ },
+
+ /**
+ * Updates this hasher with a message.
+ *
+ * @param {WordArray|string} messageUpdate The message to append.
+ *
+ * @return {Hasher} This hasher.
+ *
+ * @example
+ *
+ * hasher.update('message');
+ * hasher.update(wordArray);
+ */
+ update: function (messageUpdate) {
+ // Append
+ this._append(messageUpdate);
+
+ // Update the hash
+ this._process();
+
+ // Chainable
+ return this;
+ },
+
+ /**
+ * Finalizes the hash computation.
+ * Note that the finalize operation is effectively a destructive, read-once operation.
+ *
+ * @param {WordArray|string} messageUpdate (Optional) A final message update.
+ *
+ * @return {WordArray} The hash.
+ *
+ * @example
+ *
+ * var hash = hasher.finalize();
+ * var hash = hasher.finalize('message');
+ * var hash = hasher.finalize(wordArray);
+ */
+ finalize: function (messageUpdate) {
+ // Final message update
+ if (messageUpdate) {
+ this._append(messageUpdate);
+ }
+
+ // Perform concrete-hasher logic
+ var hash = this._doFinalize();
+
+ return hash;
+ },
+
+ blockSize: 512/32,
+
+ /**
+ * Creates a shortcut function to a hasher's object interface.
+ *
+ * @param {Hasher} hasher The hasher to create a helper for.
+ *
+ * @return {Function} The shortcut function.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256);
+ */
+ _createHelper: function (hasher) {
+ return function (message, cfg) {
+ return new hasher.init(cfg).finalize(message);
+ };
+ },
+
+ /**
+ * Creates a shortcut function to the HMAC's object interface.
+ *
+ * @param {Hasher} hasher The hasher to use in this HMAC helper.
+ *
+ * @return {Function} The shortcut function.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256);
+ */
+ _createHmacHelper: function (hasher) {
+ return function (message, key) {
+ return new C_algo.HMAC.init(hasher, key).finalize(message);
+ };
+ }
+ });
+
+ /**
+ * Algorithm namespace.
+ */
+ var C_algo = C.algo = {};
+
+ return C;
+}(Math));
diff --git a/js/libs/ember-data.js b/js/libs/ember-data.js
new file mode 100644
index 0000000..e07c21b
--- /dev/null
+++ b/js/libs/ember-data.js
@@ -0,0 +1,8431 @@
+// Last commit: 57d6c01 (2013-03-18 11:27:29 -0700)
+
+
+(function() {
+window.DS = Ember.Namespace.create({
+ // this one goes past 11
+ CURRENT_API_REVISION: 12
+});
+
+})();
+
+
+
+(function() {
+var DeferredMixin = Ember.DeferredMixin, // ember-runtime/mixins/deferred
+ Evented = Ember.Evented, // ember-runtime/mixins/evented
+ run = Ember.run, // ember-metal/run-loop
+ get = Ember.get; // ember-metal/accessors
+
+var LoadPromise = Ember.Mixin.create(Evented, DeferredMixin, {
+ init: function() {
+ this._super.apply(this, arguments);
+ this.one('didLoad', function() {
+ run(this, 'resolve', this);
+ });
+
+ if (get(this, 'isLoaded')) {
+ this.trigger('didLoad');
+ }
+ }
+});
+
+DS.LoadPromise = LoadPromise;
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+
+var LoadPromise = DS.LoadPromise; // system/mixins/load_promise
+
+/**
+ A record array is an array that contains records of a certain type. The record
+ array materializes records as needed when they are retrieved for the first
+ time. You should not create record arrays yourself. Instead, an instance of
+ DS.RecordArray or its subclasses will be returned by your application's store
+ in response to queries.
+*/
+
+DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, LoadPromise, {
+ /**
+ The model type contained by this record array.
+
+ @type DS.Model
+ */
+ type: null,
+
+ // The array of client ids backing the record array. When a
+ // record is requested from the record array, the record
+ // for the client id at the same index is materialized, if
+ // necessary, by the store.
+ content: null,
+
+ isLoaded: false,
+ isUpdating: false,
+
+ // The store that created this record array.
+ store: null,
+
+ objectAtContent: function(index) {
+ var content = get(this, 'content'),
+ reference = content.objectAt(index),
+ store = get(this, 'store');
+
+ if (reference) {
+ return store.recordForReference(reference);
+ }
+ },
+
+ materializedObjectAt: function(index) {
+ var reference = get(this, 'content').objectAt(index);
+ if (!reference) { return; }
+
+ if (get(this, 'store').recordIsMaterialized(reference)) {
+ return this.objectAt(index);
+ }
+ },
+
+ update: function() {
+ if (get(this, 'isUpdating')) { return; }
+
+ var store = get(this, 'store'),
+ type = get(this, 'type');
+
+ store.fetchAll(type, this);
+ },
+
+ addReference: function(reference) {
+ get(this, 'content').addObject(reference);
+ },
+
+ removeReference: function(reference) {
+ get(this, 'content').removeObject(reference);
+ }
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+
+DS.FilteredRecordArray = DS.RecordArray.extend({
+ filterFunction: null,
+ isLoaded: true,
+
+ replace: function() {
+ var type = get(this, 'type').toString();
+ throw new Error("The result of a client-side filter (on " + type + ") is immutable.");
+ },
+
+ updateFilter: Ember.observer(function() {
+ var store = get(this, 'store');
+ store.updateRecordArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
+ }, 'filterFunction')
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+
+DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({
+ query: null,
+
+ replace: function() {
+ var type = get(this, 'type').toString();
+ throw new Error("The result of a server query (on " + type + ") is immutable.");
+ },
+
+ load: function(references) {
+ var store = get(this, 'store'), type = get(this, 'type');
+
+ this.beginPropertyChanges();
+ set(this, 'content', Ember.A(references));
+ set(this, 'isLoaded', true);
+ this.endPropertyChanges();
+
+ var self = this;
+ // TODO: does triggering didLoad event should be the last action of the runLoop?
+ Ember.run.once(function() {
+ self.trigger('didLoad');
+ });
+ }
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+
+/**
+ A ManyArray is a RecordArray that represents the contents of a has-many
+ relationship.
+
+ The ManyArray is instantiated lazily the first time the relationship is
+ requested.
+
+ ### Inverses
+
+ Often, the relationships in Ember Data applications will have
+ an inverse. For example, imagine the following models are
+ defined:
+
+ App.Post = DS.Model.extend({
+ comments: DS.hasMany('App.Comment')
+ });
+
+ App.Comment = DS.Model.extend({
+ post: DS.belongsTo('App.Post')
+ });
+
+ If you created a new instance of `App.Post` and added
+ a `App.Comment` record to its `comments` has-many
+ relationship, you would expect the comment's `post`
+ property to be set to the post that contained
+ the has-many.
+
+ We call the record to which a relationship belongs the
+ relationship's _owner_.
+*/
+DS.ManyArray = DS.RecordArray.extend({
+ init: function() {
+ this._super.apply(this, arguments);
+ this._changesToSync = Ember.OrderedSet.create();
+ },
+
+ /**
+ @private
+
+ The record to which this relationship belongs.
+
+ @property {DS.Model}
+ */
+ owner: null,
+
+ // LOADING STATE
+
+ isLoaded: false,
+
+ loadingRecordsCount: function(count) {
+ this.loadingRecordsCount = count;
+ },
+
+ loadedRecord: function() {
+ this.loadingRecordsCount--;
+ if (this.loadingRecordsCount === 0) {
+ set(this, 'isLoaded', true);
+ this.trigger('didLoad');
+ }
+ },
+
+ fetch: function() {
+ var references = get(this, 'content'),
+ store = get(this, 'store'),
+ type = get(this, 'type'),
+ owner = get(this, 'owner');
+
+ store.fetchUnloadedReferences(type, references, owner);
+ },
+
+ // Overrides Ember.Array's replace method to implement
+ replaceContent: function(index, removed, added) {
+ // Map the array of record objects into an array of client ids.
+ added = added.map(function(record) {
+ Ember.assert("You can only add records of " + (get(this, 'type') && get(this, 'type').toString()) + " to this relationship.", !get(this, 'type') || (get(this, 'type') === record.constructor));
+ return get(record, '_reference');
+ }, this);
+
+ this._super(index, removed, added);
+ },
+
+ arrangedContentDidChange: function() {
+ this.fetch();
+ },
+
+ arrayContentWillChange: function(index, removed, added) {
+ var owner = get(this, 'owner'),
+ name = get(this, 'name');
+
+ if (!owner._suspendedRelationships) {
+ // This code is the first half of code that continues inside
+ // of arrayContentDidChange. It gets or creates a change from
+ // the child object, adds the current owner as the old
+ // parent if this is the first time the object was removed
+ // from a ManyArray, and sets `newParent` to null.
+ //
+ // Later, if the object is added to another ManyArray,
+ // the `arrayContentDidChange` will set `newParent` on
+ // the change.
+ for (var i=index; i "created.uncommitted"
+
+ The `DS.Model` states are themselves stateless. What we mean is that,
+ though each instance of a record also has a unique instance of a
+ `DS.StateManager`, the hierarchical states that each of *those* points
+ to is a shared data structure. For performance reasons, instead of each
+ record getting its own copy of the hierarchy of states, each state
+ manager points to this global, immutable shared instance. How does a
+ state know which record it should be acting on? We pass a reference to
+ the current state manager as the first parameter to every method invoked
+ on a state.
+
+ The state manager passed as the first parameter is where you should stash
+ state about the record if needed; you should never store data on the state
+ object itself. If you need access to the record being acted on, you can
+ retrieve the state manager's `record` property. For example, if you had
+ an event handler `myEvent`:
+
+ myEvent: function(manager) {
+ var record = manager.get('record');
+ record.doSomething();
+ }
+
+ For more information about state managers in general, see the Ember.js
+ documentation on `Ember.StateManager`.
+
+ ### Events, Flags, and Transitions
+
+ A state may implement zero or more events, flags, or transitions.
+
+ #### Events
+
+ Events are named functions that are invoked when sent to a record. The
+ state manager will first look for a method with the given name on the
+ current state. If no method is found, it will search the current state's
+ parent, and then its grandparent, and so on until reaching the top of
+ the hierarchy. If the root is reached without an event handler being found,
+ an exception will be raised. This can be very helpful when debugging new
+ features.
+
+ Here's an example implementation of a state with a `myEvent` event handler:
+
+ aState: DS.State.create({
+ myEvent: function(manager, param) {
+ console.log("Received myEvent with "+param);
+ }
+ })
+
+ To trigger this event:
+
+ record.send('myEvent', 'foo');
+ //=> "Received myEvent with foo"
+
+ Note that an optional parameter can be sent to a record's `send()` method,
+ which will be passed as the second parameter to the event handler.
+
+ Events should transition to a different state if appropriate. This can be
+ done by calling the state manager's `transitionTo()` method with a path to the
+ desired state. The state manager will attempt to resolve the state path
+ relative to the current state. If no state is found at that path, it will
+ attempt to resolve it relative to the current state's parent, and then its
+ parent, and so on until the root is reached. For example, imagine a hierarchy
+ like this:
+
+ * created
+ * start <-- currentState
+ * inFlight
+ * updated
+ * inFlight
+
+ If we are currently in the `start` state, calling
+ `transitionTo('inFlight')` would transition to the `created.inFlight` state,
+ while calling `transitionTo('updated.inFlight')` would transition to
+ the `updated.inFlight` state.
+
+ Remember that *only events* should ever cause a state transition. You should
+ never call `transitionTo()` from outside a state's event handler. If you are
+ tempted to do so, create a new event and send that to the state manager.
+
+ #### Flags
+
+ Flags are Boolean values that can be used to introspect a record's current
+ state in a more user-friendly way than examining its state path. For example,
+ instead of doing this:
+
+ var statePath = record.get('stateManager.currentPath');
+ if (statePath === 'created.inFlight') {
+ doSomething();
+ }
+
+ You can say:
+
+ if (record.get('isNew') && record.get('isSaving')) {
+ doSomething();
+ }
+
+ If your state does not set a value for a given flag, the value will
+ be inherited from its parent (or the first place in the state hierarchy
+ where it is defined).
+
+ The current set of flags are defined below. If you want to add a new flag,
+ in addition to the area below, you will also need to declare it in the
+ `DS.Model` class.
+
+ #### Transitions
+
+ Transitions are like event handlers but are called automatically upon
+ entering or exiting a state. To implement a transition, just call a method
+ either `enter` or `exit`:
+
+ myState: DS.State.create({
+ // Gets called automatically when entering
+ // this state.
+ enter: function(manager) {
+ console.log("Entered myState");
+ }
+ })
+
+ Note that enter and exit events are called once per transition. If the
+ current state changes, but changes to another child state of the parent,
+ the transition event on the parent will not be triggered.
+*/
+
+var stateProperty = Ember.computed(function(key) {
+ var parent = get(this, 'parentState');
+ if (parent) {
+ return get(parent, key);
+ }
+}).property();
+
+var hasDefinedProperties = function(object) {
+ for (var name in object) {
+ if (object.hasOwnProperty(name) && object[name]) { return true; }
+ }
+
+ return false;
+};
+
+var didChangeData = function(manager) {
+ var record = get(manager, 'record');
+ record.materializeData();
+};
+
+var willSetProperty = function(manager, context) {
+ context.oldValue = get(get(manager, 'record'), context.name);
+
+ var change = DS.AttributeChange.createChange(context);
+ get(manager, 'record')._changesToSync[context.attributeName] = change;
+};
+
+var didSetProperty = function(manager, context) {
+ var change = get(manager, 'record')._changesToSync[context.attributeName];
+ change.value = get(get(manager, 'record'), context.name);
+ change.sync();
+};
+
+DS.State = Ember.State.extend({
+ isLoaded: stateProperty,
+ isReloading: stateProperty,
+ isDirty: stateProperty,
+ isSaving: stateProperty,
+ isDeleted: stateProperty,
+ isError: stateProperty,
+ isNew: stateProperty,
+ isValid: stateProperty,
+
+ // For states that are substates of a
+ // DirtyState (updated or created), it is
+ // useful to be able to determine which
+ // type of dirty state it is.
+ dirtyType: stateProperty
+});
+
+// Implementation notes:
+//
+// Each state has a boolean value for all of the following flags:
+//
+// * isLoaded: The record has a populated `data` property. When a
+// record is loaded via `store.find`, `isLoaded` is false
+// until the adapter sets it. When a record is created locally,
+// its `isLoaded` property is always true.
+// * isDirty: The record has local changes that have not yet been
+// saved by the adapter. This includes records that have been
+// created (but not yet saved) or deleted.
+// * isSaving: The record's transaction has been committed, but
+// the adapter has not yet acknowledged that the changes have
+// been persisted to the backend.
+// * isDeleted: The record was marked for deletion. When `isDeleted`
+// is true and `isDirty` is true, the record is deleted locally
+// but the deletion was not yet persisted. When `isSaving` is
+// true, the change is in-flight. When both `isDirty` and
+// `isSaving` are false, the change has persisted.
+// * isError: The adapter reported that it was unable to save
+// local changes to the backend. This may also result in the
+// record having its `isValid` property become false if the
+// adapter reported that server-side validations failed.
+// * isNew: The record was created on the client and the adapter
+// did not yet report that it was successfully saved.
+// * isValid: No client-side validations have failed and the
+// adapter did not report any server-side validation failures.
+
+// The dirty state is a abstract state whose functionality is
+// shared between the `created` and `updated` states.
+//
+// The deleted state shares the `isDirty` flag with the
+// subclasses of `DirtyState`, but with a very different
+// implementation.
+//
+// Dirty states have three child states:
+//
+// `uncommitted`: the store has not yet handed off the record
+// to be saved.
+// `inFlight`: the store has handed off the record to be saved,
+// but the adapter has not yet acknowledged success.
+// `invalid`: the record has invalid information and cannot be
+// send to the adapter yet.
+var DirtyState = DS.State.extend({
+ initialState: 'uncommitted',
+
+ // FLAGS
+ isDirty: true,
+
+ // SUBSTATES
+
+ // When a record first becomes dirty, it is `uncommitted`.
+ // This means that there are local pending changes, but they
+ // have not yet begun to be saved, and are not invalid.
+ uncommitted: DS.State.extend({
+ // TRANSITIONS
+ enter: function(manager) {
+ var dirtyType = get(this, 'dirtyType'),
+ record = get(manager, 'record');
+
+ record.withTransaction(function (t) {
+ t.recordBecameDirty(dirtyType, record);
+ });
+ },
+
+ // EVENTS
+ willSetProperty: willSetProperty,
+ didSetProperty: didSetProperty,
+
+ becomeDirty: Ember.K,
+
+ willCommit: function(manager) {
+ manager.transitionTo('inFlight');
+ },
+
+ becameClean: function(manager) {
+ var record = get(manager, 'record'),
+ dirtyType = get(this, 'dirtyType');
+
+ record.withTransaction(function(t) {
+ t.recordBecameClean(dirtyType, record);
+ });
+
+ manager.transitionTo('loaded.materializing');
+ },
+
+ becameInvalid: function(manager) {
+ var dirtyType = get(this, 'dirtyType'),
+ record = get(manager, 'record');
+
+ record.withTransaction(function (t) {
+ t.recordBecameInFlight(dirtyType, record);
+ });
+
+ manager.transitionTo('invalid');
+ },
+
+ rollback: function(manager) {
+ get(manager, 'record').rollback();
+ }
+ }),
+
+ // Once a record has been handed off to the adapter to be
+ // saved, it is in the 'in flight' state. Changes to the
+ // record cannot be made during this window.
+ inFlight: DS.State.extend({
+ // FLAGS
+ isSaving: true,
+
+ // TRANSITIONS
+ enter: function(manager) {
+ var dirtyType = get(this, 'dirtyType'),
+ record = get(manager, 'record');
+
+ record.becameInFlight();
+
+ record.withTransaction(function (t) {
+ t.recordBecameInFlight(dirtyType, record);
+ });
+ },
+
+ // EVENTS
+ didCommit: function(manager) {
+ var dirtyType = get(this, 'dirtyType'),
+ record = get(manager, 'record');
+
+ record.withTransaction(function(t) {
+ t.recordBecameClean('inflight', record);
+ });
+
+ manager.transitionTo('saved');
+ manager.send('invokeLifecycleCallbacks', dirtyType);
+ },
+
+ becameInvalid: function(manager, errors) {
+ var record = get(manager, 'record');
+
+ set(record, 'errors', errors);
+
+ manager.transitionTo('invalid');
+ manager.send('invokeLifecycleCallbacks');
+ },
+
+ becameError: function(manager) {
+ manager.transitionTo('error');
+ manager.send('invokeLifecycleCallbacks');
+ }
+ }),
+
+ // A record is in the `invalid` state when its client-side
+ // invalidations have failed, or if the adapter has indicated
+ // the the record failed server-side invalidations.
+ invalid: DS.State.extend({
+ // FLAGS
+ isValid: false,
+
+ exit: function(manager) {
+ var record = get(manager, 'record');
+
+ record.withTransaction(function (t) {
+ t.recordBecameClean('inflight', record);
+ });
+ },
+
+ // EVENTS
+ deleteRecord: function(manager) {
+ manager.transitionTo('deleted');
+ get(manager, 'record').clearRelationships();
+ },
+
+ willSetProperty: willSetProperty,
+
+ didSetProperty: function(manager, context) {
+ var record = get(manager, 'record'),
+ errors = get(record, 'errors'),
+ key = context.name;
+
+ set(errors, key, null);
+
+ if (!hasDefinedProperties(errors)) {
+ manager.send('becameValid');
+ }
+
+ didSetProperty(manager, context);
+ },
+
+ becomeDirty: Ember.K,
+
+ rollback: function(manager) {
+ manager.send('becameValid');
+ manager.send('rollback');
+ },
+
+ becameValid: function(manager) {
+ manager.transitionTo('uncommitted');
+ },
+
+ invokeLifecycleCallbacks: function(manager) {
+ var record = get(manager, 'record');
+ record.trigger('becameInvalid', record);
+ }
+ })
+});
+
+// The created and updated states are created outside the state
+// chart so we can reopen their substates and add mixins as
+// necessary.
+
+var createdState = DirtyState.create({
+ dirtyType: 'created',
+
+ // FLAGS
+ isNew: true
+});
+
+var updatedState = DirtyState.create({
+ dirtyType: 'updated'
+});
+
+createdState.states.uncommitted.reopen({
+ deleteRecord: function(manager) {
+ var record = get(manager, 'record');
+
+ record.withTransaction(function(t) {
+ t.recordIsMoving('created', record);
+ });
+
+ record.clearRelationships();
+ manager.transitionTo('deleted.saved');
+ }
+});
+
+createdState.states.uncommitted.reopen({
+ rollback: function(manager) {
+ this._super(manager);
+ manager.transitionTo('deleted.saved');
+ }
+});
+
+updatedState.states.uncommitted.reopen({
+ deleteRecord: function(manager) {
+ var record = get(manager, 'record');
+
+ record.withTransaction(function(t) {
+ t.recordIsMoving('updated', record);
+ });
+
+ manager.transitionTo('deleted');
+ get(manager, 'record').clearRelationships();
+ }
+});
+
+var states = {
+ rootState: Ember.State.create({
+ // FLAGS
+ isLoaded: false,
+ isReloading: false,
+ isDirty: false,
+ isSaving: false,
+ isDeleted: false,
+ isError: false,
+ isNew: false,
+ isValid: true,
+
+ // SUBSTATES
+
+ // A record begins its lifecycle in the `empty` state.
+ // If its data will come from the adapter, it will
+ // transition into the `loading` state. Otherwise, if
+ // the record is being created on the client, it will
+ // transition into the `created` state.
+ empty: DS.State.create({
+ // EVENTS
+ loadingData: function(manager) {
+ manager.transitionTo('loading');
+ },
+
+ loadedData: function(manager) {
+ manager.transitionTo('loaded.created');
+ }
+ }),
+
+ // A record enters this state when the store askes
+ // the adapter for its data. It remains in this state
+ // until the adapter provides the requested data.
+ //
+ // Usually, this process is asynchronous, using an
+ // XHR to retrieve the data.
+ loading: DS.State.create({
+ // EVENTS
+ loadedData: didChangeData,
+
+ materializingData: function(manager) {
+ manager.transitionTo('loaded.materializing.firstTime');
+ }
+ }),
+
+ // A record enters this state when its data is populated.
+ // Most of a record's lifecycle is spent inside substates
+ // of the `loaded` state.
+ loaded: DS.State.create({
+ initialState: 'saved',
+
+ // FLAGS
+ isLoaded: true,
+
+ // SUBSTATES
+
+ materializing: DS.State.create({
+ // FLAGS
+ isLoaded: false,
+
+ // EVENTS
+ willSetProperty: Ember.K,
+ didSetProperty: Ember.K,
+
+ didChangeData: didChangeData,
+
+ finishedMaterializing: function(manager) {
+ manager.transitionTo('loaded.saved');
+ },
+
+ // SUBSTATES
+ firstTime: DS.State.create({
+ exit: function(manager) {
+ var record = get(manager, 'record');
+
+ once(function() {
+ record.trigger('didLoad');
+ });
+ }
+ })
+ }),
+
+ reloading: DS.State.create({
+ // FLAGS
+ isReloading: true,
+
+ // TRANSITIONS
+ enter: function(manager) {
+ var record = get(manager, 'record'),
+ store = get(record, 'store');
+
+ store.reloadRecord(record);
+ },
+
+ exit: function(manager) {
+ var record = get(manager, 'record');
+
+ once(record, 'trigger', 'didReload');
+ },
+
+ // EVENTS
+ loadedData: didChangeData,
+
+ materializingData: function(manager) {
+ manager.transitionTo('loaded.materializing');
+ }
+ }),
+
+ // If there are no local changes to a record, it remains
+ // in the `saved` state.
+ saved: DS.State.create({
+ // EVENTS
+ willSetProperty: willSetProperty,
+ didSetProperty: didSetProperty,
+
+ didChangeData: didChangeData,
+ loadedData: didChangeData,
+
+ reloadRecord: function(manager) {
+ manager.transitionTo('loaded.reloading');
+ },
+
+ materializingData: function(manager) {
+ manager.transitionTo('loaded.materializing');
+ },
+
+ becomeDirty: function(manager) {
+ manager.transitionTo('updated');
+ },
+
+ deleteRecord: function(manager) {
+ manager.transitionTo('deleted');
+ get(manager, 'record').clearRelationships();
+ },
+
+ unloadRecord: function(manager) {
+ manager.transitionTo('deleted.saved');
+ get(manager, 'record').clearRelationships();
+ },
+
+ invokeLifecycleCallbacks: function(manager, dirtyType) {
+ var record = get(manager, 'record');
+ if (dirtyType === 'created') {
+ record.trigger('didCreate', record);
+ } else {
+ record.trigger('didUpdate', record);
+ }
+ }
+ }),
+
+ // A record is in this state after it has been locally
+ // created but before the adapter has indicated that
+ // it has been saved.
+ created: createdState,
+
+ // A record is in this state if it has already been
+ // saved to the server, but there are new local changes
+ // that have not yet been saved.
+ updated: updatedState
+ }),
+
+ // A record is in this state if it was deleted from the store.
+ deleted: DS.State.create({
+ initialState: 'uncommitted',
+ dirtyType: 'deleted',
+
+ // FLAGS
+ isDeleted: true,
+ isLoaded: true,
+ isDirty: true,
+
+ // TRANSITIONS
+ setup: function(manager) {
+ var record = get(manager, 'record'),
+ store = get(record, 'store');
+
+ store.removeFromRecordArrays(record);
+ },
+
+ // SUBSTATES
+
+ // When a record is deleted, it enters the `start`
+ // state. It will exit this state when the record's
+ // transaction starts to commit.
+ uncommitted: DS.State.create({
+ // TRANSITIONS
+ enter: function(manager) {
+ var record = get(manager, 'record');
+
+ record.withTransaction(function(t) {
+ t.recordBecameDirty('deleted', record);
+ });
+ },
+
+ // EVENTS
+ willCommit: function(manager) {
+ manager.transitionTo('inFlight');
+ },
+
+ rollback: function(manager) {
+ get(manager, 'record').rollback();
+ },
+
+ becomeDirty: Ember.K,
+
+ becameClean: function(manager) {
+ var record = get(manager, 'record');
+
+ record.withTransaction(function(t) {
+ t.recordBecameClean('deleted', record);
+ });
+
+ manager.transitionTo('loaded.materializing');
+ }
+ }),
+
+ // After a record's transaction is committing, but
+ // before the adapter indicates that the deletion
+ // has saved to the server, a record is in the
+ // `inFlight` substate of `deleted`.
+ inFlight: DS.State.create({
+ // FLAGS
+ isSaving: true,
+
+ // TRANSITIONS
+ enter: function(manager) {
+ var record = get(manager, 'record');
+
+ record.becameInFlight();
+
+ record.withTransaction(function (t) {
+ t.recordBecameInFlight('deleted', record);
+ });
+ },
+
+ // EVENTS
+ didCommit: function(manager) {
+ var record = get(manager, 'record');
+
+ record.withTransaction(function(t) {
+ t.recordBecameClean('inflight', record);
+ });
+
+ manager.transitionTo('saved');
+
+ manager.send('invokeLifecycleCallbacks');
+ }
+ }),
+
+ // Once the adapter indicates that the deletion has
+ // been saved, the record enters the `saved` substate
+ // of `deleted`.
+ saved: DS.State.create({
+ // FLAGS
+ isDirty: false,
+
+ setup: function(manager) {
+ var record = get(manager, 'record'),
+ store = get(record, 'store');
+
+ store.dematerializeRecord(record);
+ },
+
+ invokeLifecycleCallbacks: function(manager) {
+ var record = get(manager, 'record');
+ record.trigger('didDelete', record);
+ }
+ })
+ }),
+
+ // If the adapter indicates that there was an unknown
+ // error saving a record, the record enters the `error`
+ // state.
+ error: DS.State.create({
+ isError: true,
+
+ // EVENTS
+
+ invokeLifecycleCallbacks: function(manager) {
+ var record = get(manager, 'record');
+ record.trigger('becameError', record);
+ }
+ })
+ })
+};
+
+DS.StateManager = Ember.StateManager.extend({
+ record: null,
+ initialState: 'rootState',
+ states: states,
+ unhandledEvent: function(manager, originalEvent) {
+ var record = manager.get('record'),
+ contexts = [].slice.call(arguments, 2),
+ errorMessage;
+ errorMessage = "Attempted to handle event `" + originalEvent + "` ";
+ errorMessage += "on " + record.toString() + " while in state ";
+ errorMessage += get(manager, 'currentState.path') + ". Called with ";
+ errorMessage += arrayMap.call(contexts, function(context){
+ return Ember.inspect(context);
+ }).join(', ');
+ throw new Ember.Error(errorMessage);
+ }
+});
+
+})();
+
+
+
+(function() {
+var LoadPromise = DS.LoadPromise; // system/mixins/load_promise
+
+var get = Ember.get, set = Ember.set, map = Ember.EnumerableUtils.map;
+
+var retrieveFromCurrentState = Ember.computed(function(key, value) {
+ return get(get(this, 'stateManager.currentState'), key);
+}).property('stateManager.currentState').readOnly();
+
+DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
+ isLoaded: retrieveFromCurrentState,
+ isReloading: retrieveFromCurrentState,
+ isDirty: retrieveFromCurrentState,
+ isSaving: retrieveFromCurrentState,
+ isDeleted: retrieveFromCurrentState,
+ isError: retrieveFromCurrentState,
+ isNew: retrieveFromCurrentState,
+ isValid: retrieveFromCurrentState,
+
+ clientId: null,
+ id: null,
+ transaction: null,
+ stateManager: null,
+ errors: null,
+
+ /**
+ Create a JSON representation of the record, using the serialization
+ strategy of the store's adapter.
+
+ Available options:
+
+ * `includeId`: `true` if the record's ID should be included in the
+ JSON representation.
+
+ @param {Object} options
+ @returns {Object} an object whose values are primitive JSON values only
+ */
+ serialize: function(options) {
+ var store = get(this, 'store');
+ return store.serialize(this, options);
+ },
+
+ toJSON: function() {
+ var serializer = DS.JSONSerializer.create();
+ return serializer.serialize(this);
+ },
+
+ didLoad: Ember.K,
+ didReload: Ember.K,
+ didUpdate: Ember.K,
+ didCreate: Ember.K,
+ didDelete: Ember.K,
+ becameInvalid: Ember.K,
+ becameError: Ember.K,
+
+ data: Ember.computed(function() {
+ if (!this._data) {
+ this.materializeData();
+ }
+
+ return this._data;
+ }).property(),
+
+ materializeData: function() {
+ this.send('materializingData');
+
+ get(this, 'store').materializeData(this);
+
+ this.suspendRelationshipObservers(function() {
+ this.notifyPropertyChange('data');
+ });
+ },
+
+ _data: null,
+
+ init: function() {
+ this._super();
+
+ var stateManager = DS.StateManager.create({ record: this });
+ set(this, 'stateManager', stateManager);
+
+ this._setup();
+
+ stateManager.goToState('empty');
+ },
+
+ _setup: function() {
+ this._relationshipChanges = {};
+ this._changesToSync = {};
+ },
+
+ send: function(name, context) {
+ return get(this, 'stateManager').send(name, context);
+ },
+
+ withTransaction: function(fn) {
+ var transaction = get(this, 'transaction');
+ if (transaction) { fn(transaction); }
+ },
+
+ loadingData: function() {
+ this.send('loadingData');
+ },
+
+ loadedData: function() {
+ this.send('loadedData');
+ },
+
+ didChangeData: function() {
+ this.send('didChangeData');
+ },
+
+ setProperty: function(key, value, oldValue) {
+ this.send('setProperty', { key: key, value: value, oldValue: oldValue });
+ },
+
+ /**
+ Reload the record from the adapter.
+
+ This will only work if the record has already finished loading
+ and has not yet been modified (`isLoaded` but not `isDirty`,
+ or `isSaving`).
+ */
+ reload: function() {
+ this.send('reloadRecord');
+ },
+
+ deleteRecord: function() {
+ this.send('deleteRecord');
+ },
+
+ unloadRecord: function() {
+ Ember.assert("You can only unload a loaded, non-dirty record.", !get(this, 'isDirty'));
+
+ this.send('unloadRecord');
+ },
+
+ clearRelationships: function() {
+ this.eachRelationship(function(name, relationship) {
+ if (relationship.kind === 'belongsTo') {
+ set(this, name, null);
+ } else if (relationship.kind === 'hasMany') {
+ get(this, name).clear();
+ }
+ }, this);
+ },
+
+ updateRecordArrays: function() {
+ var store = get(this, 'store');
+ if (store) {
+ store.dataWasUpdated(this.constructor, get(this, '_reference'), this);
+ }
+ },
+
+ /**
+ If the adapter did not return a hash in response to a commit,
+ merge the changed attributes and relationships into the existing
+ saved data.
+ */
+ adapterDidCommit: function() {
+ var attributes = get(this, 'data').attributes;
+
+ get(this.constructor, 'attributes').forEach(function(name, meta) {
+ attributes[name] = get(this, name);
+ }, this);
+
+ this.send('didCommit');
+ this.updateRecordArraysLater();
+ },
+
+ adapterDidDirty: function() {
+ this.send('becomeDirty');
+ this.updateRecordArraysLater();
+ },
+
+ dataDidChange: Ember.observer(function() {
+ var relationships = get(this.constructor, 'relationshipsByName');
+
+ this.updateRecordArraysLater();
+
+ relationships.forEach(function(name, relationship) {
+ if (relationship.kind === 'hasMany') {
+ this.hasManyDidChange(relationship.key);
+ }
+ }, this);
+
+ this.send('finishedMaterializing');
+ }, 'data'),
+
+ hasManyDidChange: function(key) {
+ var cachedValue = this.cacheFor(key);
+
+ if (cachedValue) {
+ var type = get(this.constructor, 'relationshipsByName').get(key).type;
+ var store = get(this, 'store');
+ var ids = this._data.hasMany[key] || [];
+
+ var references = map(ids, function(id) {
+ // if it was already a reference, return the reference
+ if (typeof id === 'object') { return id; }
+ return store.referenceForId(type, id);
+ });
+
+ set(cachedValue, 'content', Ember.A(references));
+ }
+ },
+
+ updateRecordArraysLater: function() {
+ Ember.run.once(this, this.updateRecordArrays);
+ },
+
+ setupData: function(prematerialized) {
+ this._data = {
+ attributes: {},
+ belongsTo: {},
+ hasMany: {},
+ id: null
+ };
+ },
+
+ materializeId: function(id) {
+ set(this, 'id', id);
+ },
+
+ materializeAttributes: function(attributes) {
+ Ember.assert("Must pass a hash of attributes to materializeAttributes", !!attributes);
+ this._data.attributes = attributes;
+ },
+
+ materializeAttribute: function(name, value) {
+ this._data.attributes[name] = value;
+ },
+
+ materializeHasMany: function(name, ids) {
+ this._data.hasMany[name] = ids;
+ },
+
+ materializeBelongsTo: function(name, id) {
+ this._data.belongsTo[name] = id;
+ },
+
+ rollback: function() {
+ this._setup();
+ this.send('becameClean');
+
+ this.suspendRelationshipObservers(function() {
+ this.notifyPropertyChange('data');
+ });
+ },
+
+ toStringExtension: function() {
+ return get(this, 'id');
+ },
+
+ /**
+ @private
+
+ The goal of this method is to temporarily disable specific observers
+ that take action in response to application changes.
+
+ This allows the system to make changes (such as materialization and
+ rollback) that should not trigger secondary behavior (such as setting an
+ inverse relationship or marking records as dirty).
+
+ The specific implementation will likely change as Ember proper provides
+ better infrastructure for suspending groups of observers, and if Array
+ observation becomes more unified with regular observers.
+ */
+ suspendRelationshipObservers: function(callback, binding) {
+ var observers = get(this.constructor, 'relationshipNames').belongsTo;
+ var self = this;
+
+ try {
+ this._suspendedRelationships = true;
+ Ember._suspendObservers(self, observers, null, 'belongsToDidChange', function() {
+ Ember._suspendBeforeObservers(self, observers, null, 'belongsToWillChange', function() {
+ callback.call(binding || self);
+ });
+ });
+ } finally {
+ this._suspendedRelationships = false;
+ }
+ },
+
+ becameInFlight: function() {
+ },
+
+ // FOR USE BY THE BASIC ADAPTER
+
+ save: function() {
+ this.get('store').scheduleSave(this);
+ },
+
+ // FOR USE DURING COMMIT PROCESS
+
+ adapterDidUpdateAttribute: function(attributeName, value) {
+
+ // If a value is passed in, update the internal attributes and clear
+ // the attribute cache so it picks up the new value. Otherwise,
+ // collapse the current value into the internal attributes because
+ // the adapter has acknowledged it.
+ if (value !== undefined) {
+ get(this, 'data.attributes')[attributeName] = value;
+ this.notifyPropertyChange(attributeName);
+ } else {
+ value = get(this, attributeName);
+ get(this, 'data.attributes')[attributeName] = value;
+ }
+
+ this.updateRecordArraysLater();
+ },
+
+ _reference: Ember.computed(function() {
+ return get(this, 'store').referenceForClientId(get(this, 'clientId'));
+ }),
+
+ adapterDidInvalidate: function(errors) {
+ this.send('becameInvalid', errors);
+ },
+
+ adapterDidError: function() {
+ this.send('becameError');
+ },
+
+ /**
+ @private
+
+ Override the default event firing from Ember.Evented to
+ also call methods with the given name.
+ */
+ trigger: function(name) {
+ Ember.tryInvoke(this, name, [].slice.call(arguments, 1));
+ this._super.apply(this, arguments);
+ }
+});
+
+// Helper function to generate store aliases.
+// This returns a function that invokes the named alias
+// on the default store, but injects the class as the
+// first parameter.
+var storeAlias = function(methodName) {
+ return function() {
+ var store = get(DS, 'defaultStore'),
+ args = [].slice.call(arguments);
+
+ args.unshift(this);
+ Ember.assert("Your application does not have a 'Store' property defined. Attempts to call '" + methodName + "' on model classes will fail. Please provide one as with 'YourAppName.Store = DS.Store.extend()'", !!store);
+ return store[methodName].apply(store, args);
+ };
+};
+
+DS.Model.reopenClass({
+ isLoaded: storeAlias('recordIsLoaded'),
+ find: storeAlias('find'),
+ all: storeAlias('all'),
+ query: storeAlias('findQuery'),
+ filter: storeAlias('filter'),
+
+ _create: DS.Model.create,
+
+ create: function() {
+ throw new Ember.Error("You should not call `create` on a model. Instead, call `createRecord` with the attributes you would like to set.");
+ },
+
+ createRecord: storeAlias('createRecord')
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+DS.Model.reopenClass({
+ attributes: Ember.computed(function() {
+ var map = Ember.Map.create();
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isAttribute) {
+ Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('')` from " + this.toString(), name !== 'id');
+
+ meta.name = name;
+ map.set(name, meta);
+ }
+ });
+
+ return map;
+ })
+});
+
+var AttributeChange = DS.AttributeChange = function(options) {
+ this.reference = options.reference;
+ this.store = options.store;
+ this.name = options.name;
+ this.oldValue = options.oldValue;
+};
+
+AttributeChange.createChange = function(options) {
+ return new AttributeChange(options);
+};
+
+AttributeChange.prototype = {
+ sync: function() {
+ this.store.recordAttributeDidChange(this.reference, this.name, this.value, this.oldValue);
+
+ // TODO: Use this object in the commit process
+ this.destroy();
+ },
+
+ destroy: function() {
+ delete this.store.recordForReference(this.reference)._changesToSync[this.name];
+ }
+};
+
+DS.Model.reopen({
+ eachAttribute: function(callback, binding) {
+ get(this.constructor, 'attributes').forEach(function(name, meta) {
+ callback.call(binding, name, meta);
+ }, binding);
+ },
+
+ attributeWillChange: Ember.beforeObserver(function(record, key) {
+ var reference = get(record, '_reference'),
+ store = get(record, 'store');
+
+ record.send('willSetProperty', { reference: reference, store: store, name: key });
+ }),
+
+ attributeDidChange: Ember.observer(function(record, key) {
+ record.send('didSetProperty', { name: key });
+ })
+});
+
+function getAttr(record, options, key) {
+ var attributes = get(record, 'data').attributes;
+ var value = attributes[key];
+
+ if (value === undefined) {
+ value = options.defaultValue;
+ }
+
+ return value;
+}
+
+DS.attr = function(type, options) {
+ options = options || {};
+
+ var meta = {
+ type: type,
+ isAttribute: true,
+ options: options
+ };
+
+ return Ember.computed(function(key, value, oldValue) {
+ if (arguments.length > 1) {
+ Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('')` from " + this.constructor.toString(), key !== 'id');
+ } else {
+ value = getAttr(this, options, key);
+ }
+
+ return value;
+ // `data` is never set directly. However, it may be
+ // invalidated from the state manager's setData
+ // event.
+ }).property('data').meta(meta);
+};
+
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set,
+ none = Ember.isNone;
+
+DS.belongsTo = function(type, options) {
+ Ember.assert("The first argument DS.belongsTo must be a model type or string, like DS.belongsTo(App.Person)", !!type && (typeof type === 'string' || DS.Model.detect(type)));
+
+ options = options || {};
+
+ var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo' };
+
+ return Ember.computed(function(key, value) {
+ if (arguments.length === 2) {
+ return value === undefined ? null : value;
+ }
+
+ var data = get(this, 'data').belongsTo,
+ store = get(this, 'store'), id;
+
+ if (typeof type === 'string') {
+ type = get(this, type, false) || get(Ember.lookup, type);
+ }
+
+ id = data[key];
+
+ if(!id) {
+ return null;
+ } else if (typeof id === 'object') {
+ return store.recordForReference(id);
+ } else {
+ return store.find(type, id);
+ }
+ }).property('data').meta(meta);
+};
+
+/**
+ These observers observe all `belongsTo` relationships on the record. See
+ `relationships/ext` to see how these observers get their dependencies.
+
+*/
+
+DS.Model.reopen({
+ /** @private */
+ belongsToWillChange: Ember.beforeObserver(function(record, key) {
+ if (get(record, 'isLoaded')) {
+ var oldParent = get(record, key);
+
+ var childReference = get(record, '_reference'),
+ store = get(record, 'store');
+ if (oldParent){
+ var change = DS.RelationshipChange.createChange(childReference, get(oldParent, '_reference'), store, { key: key, kind:"belongsTo", changeType: "remove" });
+ change.sync();
+ this._changesToSync[key] = change;
+ }
+ }
+ }),
+
+ /** @private */
+ belongsToDidChange: Ember.immediateObserver(function(record, key) {
+ if (get(record, 'isLoaded')) {
+ var newParent = get(record, key);
+ if(newParent){
+ var childReference = get(record, '_reference'),
+ store = get(record, 'store');
+ var change = DS.RelationshipChange.createChange(childReference, get(newParent, '_reference'), store, { key: key, kind:"belongsTo", changeType: "add" });
+ change.sync();
+ if(this._changesToSync[key]){
+ DS.OneToManyChange.ensureSameTransaction([change, this._changesToSync[key]], store);
+ }
+ }
+ }
+ delete this._changesToSync[key];
+ })
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+var hasRelationship = function(type, options) {
+ options = options || {};
+
+ var meta = { type: type, isRelationship: true, options: options, kind: 'hasMany' };
+
+ return Ember.computed(function(key, value) {
+ var data = get(this, 'data').hasMany,
+ store = get(this, 'store'),
+ ids, relationship;
+
+ if (typeof type === 'string') {
+ type = get(this, type, false) || get(Ember.lookup, type);
+ }
+
+ ids = data[key];
+ relationship = store.findMany(type, ids, this, meta);
+ set(relationship, 'owner', this);
+ set(relationship, 'name', key);
+
+ return relationship;
+ }).property().meta(meta);
+};
+
+DS.hasMany = function(type, options) {
+ Ember.assert("The type passed to DS.hasMany must be defined", !!type);
+ return hasRelationship(type, options);
+};
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+
+/**
+ @private
+
+ This file defines several extensions to the base `DS.Model` class that
+ add support for one-to-many relationships.
+*/
+
+DS.Model.reopen({
+ // This Ember.js hook allows an object to be notified when a property
+ // is defined.
+ //
+ // In this case, we use it to be notified when an Ember Data user defines a
+ // belongs-to relationship. In that case, we need to set up observers for
+ // each one, allowing us to track relationship changes and automatically
+ // reflect changes in the inverse has-many array.
+ //
+ // This hook passes the class being set up, as well as the key and value
+ // being defined. So, for example, when the user does this:
+ //
+ // DS.Model.extend({
+ // parent: DS.belongsTo(App.User)
+ // });
+ //
+ // This hook would be called with "parent" as the key and the computed
+ // property returned by `DS.belongsTo` as the value.
+ didDefineProperty: function(proto, key, value) {
+ // Check if the value being set is a computed property.
+ if (value instanceof Ember.Descriptor) {
+
+ // If it is, get the metadata for the relationship. This is
+ // populated by the `DS.belongsTo` helper when it is creating
+ // the computed property.
+ var meta = value.meta();
+
+ if (meta.isRelationship && meta.kind === 'belongsTo') {
+ Ember.addObserver(proto, key, null, 'belongsToDidChange');
+ Ember.addBeforeObserver(proto, key, null, 'belongsToWillChange');
+ }
+
+ if (meta.isAttribute) {
+ Ember.addObserver(proto, key, null, 'attributeDidChange');
+ Ember.addBeforeObserver(proto, key, null, 'attributeWillChange');
+ }
+
+ meta.parentType = proto.constructor;
+ }
+ }
+});
+
+/**
+ These DS.Model extensions add class methods that provide relationship
+ introspection abilities about relationships.
+
+ A note about the computed properties contained here:
+
+ **These properties are effectively sealed once called for the first time.**
+ To avoid repeatedly doing expensive iteration over a model's fields, these
+ values are computed once and then cached for the remainder of the runtime of
+ your application.
+
+ If your application needs to modify a class after its initial definition
+ (for example, using `reopen()` to add additional attributes), make sure you
+ do it before using your model with the store, which uses these properties
+ extensively.
+*/
+
+DS.Model.reopenClass({
+ /**
+ For a given relationship name, returns the model type of the relationship.
+
+ For example, if you define a model like this:
+
+ App.Post = DS.Model.extend({
+ comments: DS.hasMany(App.Comment)
+ });
+
+ Calling `App.Post.typeForRelationship('comments')` will return `App.Comment`.
+
+ @param {String} name the name of the relationship
+ @return {subclass of DS.Model} the type of the relationship, or undefined
+ */
+ typeForRelationship: function(name) {
+ var relationship = get(this, 'relationshipsByName').get(name);
+ return relationship && relationship.type;
+ },
+
+ /**
+ The model's relationships as a map, keyed on the type of the
+ relationship. The value of each entry is an array containing a descriptor
+ for each relationship with that type, describing the name of the relationship
+ as well as the type.
+
+ For example, given the following model definition:
+
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany(App.User),
+ owner: DS.belongsTo(App.User),
+ posts: DS.hasMany(App.Post)
+ });
+
+ This computed property would return a map describing these
+ relationships, like this:
+
+ var relationships = Ember.get(App.Blog, 'relationships');
+ associatons.get(App.User);
+ //=> [ { name: 'users', kind: 'hasMany' },
+ // { name: 'owner', kind: 'belongsTo' } ]
+ relationships.get(App.Post);
+ //=> [ { name: 'posts', kind: 'hasMany' } ]
+
+ @type Ember.Map
+ @readOnly
+ */
+ relationships: Ember.computed(function() {
+ var map = new Ember.MapWithDefault({
+ defaultValue: function() { return []; }
+ });
+
+ // Loop through each computed property on the class
+ this.eachComputedProperty(function(name, meta) {
+
+ // If the computed property is a relationship, add
+ // it to the map.
+ if (meta.isRelationship) {
+ if (typeof meta.type === 'string') {
+ meta.type = Ember.get(Ember.lookup, meta.type);
+ }
+
+ var relationshipsForType = map.get(meta.type);
+
+ relationshipsForType.push({ name: name, kind: meta.kind });
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ A hash containing lists of the model's relationships, grouped
+ by the relationship kind. For example, given a model with this
+ definition:
+
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany(App.User),
+ owner: DS.belongsTo(App.User),
+
+ posts: DS.hasMany(App.Post)
+ });
+
+ This property would contain the following:
+
+ var relationshipNames = Ember.get(App.Blog, 'relationshipNames');
+ relationshipNames.hasMany;
+ //=> ['users', 'posts']
+ relationshipNames.belongsTo;
+ //=> ['owner']
+
+ @type Object
+ @readOnly
+ */
+ relationshipNames: Ember.computed(function() {
+ var names = { hasMany: [], belongsTo: [] };
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ names[meta.kind].push(name);
+ }
+ });
+
+ return names;
+ }),
+
+ /**
+ An array of types directly related to a model. Each type will be
+ included once, regardless of the number of relationships it has with
+ the model.
+
+ For example, given a model with this definition:
+
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany(App.User),
+ owner: DS.belongsTo(App.User),
+ posts: DS.hasMany(App.Post)
+ });
+
+ This property would contain the following:
+
+ var relatedTypes = Ember.get(App.Blog, 'relatedTypes');
+ //=> [ App.User, App.Post ]
+
+ @type Ember.Array
+ @readOnly
+ */
+ relatedTypes: Ember.computed(function() {
+ var type,
+ types = Ember.A([]);
+
+ // Loop through each computed property on the class,
+ // and create an array of the unique types involved
+ // in relationships
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ type = meta.type;
+
+ if (typeof type === 'string') {
+ type = get(this, type, false) || get(Ember.lookup, type);
+ }
+
+ if (!types.contains(type)) {
+ types.push(type);
+ }
+ }
+ });
+
+ return types;
+ }),
+
+ /**
+ A map whose keys are the relationships of a model and whose values are
+ relationship descriptors.
+
+ For example, given a model with this
+ definition:
+
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany(App.User),
+ owner: DS.belongsTo(App.User),
+
+ posts: DS.hasMany(App.Post)
+ });
+
+ This property would contain the following:
+
+ var relationshipsByName = Ember.get(App.Blog, 'relationshipsByName');
+ relationshipsByName.get('users');
+ //=> { key: 'users', kind: 'hasMany', type: App.User }
+ relationshipsByName.get('owner');
+ //=> { key: 'owner', kind: 'belongsTo', type: App.User }
+
+ @type Ember.Map
+ @readOnly
+ */
+ relationshipsByName: Ember.computed(function() {
+ var map = Ember.Map.create(), type;
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ meta.key = name;
+ type = meta.type;
+
+ if (typeof type === 'string') {
+ type = get(this, type, false) || get(Ember.lookup, type);
+ meta.type = type;
+ }
+
+ map.set(name, meta);
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ A map whose keys are the fields of the model and whose values are strings
+ describing the kind of the field. A model's fields are the union of all of its
+ attributes and relationships.
+
+ For example:
+
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany(App.User),
+ owner: DS.belongsTo(App.User),
+
+ posts: DS.hasMany(App.Post),
+
+ title: DS.attr('string')
+ });
+
+ var fields = Ember.get(App.Blog, 'fields');
+ fields.forEach(function(field, kind) {
+ console.log(field, kind);
+ });
+
+ // prints:
+ // users, hasMany
+ // owner, belongsTo
+ // posts, hasMany
+ // title, attribute
+
+ @type Ember.Map
+ @readOnly
+ */
+ fields: Ember.computed(function() {
+ var map = Ember.Map.create(), type;
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ map.set(name, meta.kind);
+ } else if (meta.isAttribute) {
+ map.set(name, 'attribute');
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ Given a callback, iterates over each of the relationships in the model,
+ invoking the callback with the name of each relationship and its relationship
+ descriptor.
+
+ @param {Function} callback the callback to invoke
+ @param {any} binding the value to which the callback's `this` should be bound
+ */
+ eachRelationship: function(callback, binding) {
+ get(this, 'relationshipsByName').forEach(function(name, relationship) {
+ callback.call(binding, name, relationship);
+ });
+ },
+
+ /**
+ Given a callback, iterates over each of the types related to a model,
+ invoking the callback with the related type's class. Each type will be
+ returned just once, regardless of how many different relationships it has
+ with a model.
+
+ @param {Function} callback the callback to invoke
+ @param {any} binding the value to which the callback's `this` should be bound
+ */
+ eachRelatedType: function(callback, binding) {
+ get(this, 'relatedTypes').forEach(function(type) {
+ callback.call(binding, type);
+ });
+ }
+});
+
+DS.Model.reopen({
+ /**
+ Given a callback, iterates over each of the relationships in the model,
+ invoking the callback with the name of each relationship and its relationship
+ descriptor.
+
+ @param {Function} callback the callback to invoke
+ @param {any} binding the value to which the callback's `this` should be bound
+ */
+ eachRelationship: function(callback, binding) {
+ this.constructor.eachRelationship(callback, binding);
+ }
+});
+
+/**
+ @private
+
+ Helper method to look up the name of the inverse of a relationship.
+
+ In a has-many relationship, there are always two sides: the `belongsTo` side
+ and the `hasMany` side. When one side changes, the other side should be updated
+ automatically.
+
+ Given a model, the model of the inverse, and the kind of the relationship, this
+ helper returns the name of the relationship on the inverse.
+
+ For example, imagine the following two associated models:
+
+ App.Post = DS.Model.extend({
+ comments: DS.hasMany('App.Comment')
+ });
+
+ App.Comment = DS.Model.extend({
+ post: DS.belongsTo('App.Post')
+ });
+
+ If the `post` property of a `Comment` was modified, Ember Data would invoke
+ this helper like this:
+
+ DS._inverseNameFor(App.Comment, App.Post, 'hasMany');
+ //=> 'comments'
+
+ Ember Data uses the name of the relationship returned to reflect the changed
+ relationship on the other side.
+*/
+DS._inverseRelationshipFor = function(modelType, inverseModelType) {
+ var relationshipMap = get(modelType, 'relationships'),
+ possibleRelationships = relationshipMap.get(inverseModelType),
+ possible, actual, oldValue;
+
+ if (!possibleRelationships) { return; }
+ if (possibleRelationships.length > 1) { return; }
+ return possibleRelationships[0];
+};
+
+/**
+ @private
+
+ Given a model and a relationship name, returns the model type of
+ the named relationship.
+
+ App.Post = DS.Model.extend({
+ comments: DS.hasMany('App.Comment')
+ });
+
+ DS._inverseTypeFor(App.Post, 'comments');
+ //=> App.Comment
+ @param {DS.Model class} modelType
+ @param {String} relationshipName
+ @return {DS.Model class}
+*/
+DS._inverseTypeFor = function(modelType, relationshipName) {
+ var relationships = get(modelType, 'relationshipsByName'),
+ relationship = relationships.get(relationshipName);
+
+ if (relationship) { return relationship.type; }
+};
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+var forEach = Ember.EnumerableUtils.forEach;
+
+DS.RelationshipChange = function(options) {
+ this.parentReference = options.parentReference;
+ this.childReference = options.childReference;
+ this.firstRecordReference = options.firstRecordReference;
+ this.firstRecordKind = options.firstRecordKind;
+ this.firstRecordName = options.firstRecordName;
+ this.secondRecordReference = options.secondRecordReference;
+ this.secondRecordKind = options.secondRecordKind;
+ this.secondRecordName = options.secondRecordName;
+ this.store = options.store;
+ this.committed = {};
+ this.changeType = options.changeType;
+};
+
+DS.RelationshipChangeAdd = function(options){
+ DS.RelationshipChange.call(this, options);
+};
+
+DS.RelationshipChangeRemove = function(options){
+ DS.RelationshipChange.call(this, options);
+};
+
+/** @private */
+DS.RelationshipChange.create = function(options) {
+ return new DS.RelationshipChange(options);
+};
+
+/** @private */
+DS.RelationshipChangeAdd.create = function(options) {
+ return new DS.RelationshipChangeAdd(options);
+};
+
+/** @private */
+DS.RelationshipChangeRemove.create = function(options) {
+ return new DS.RelationshipChangeRemove(options);
+};
+
+DS.OneToManyChange = {};
+DS.OneToNoneChange = {};
+DS.ManyToNoneChange = {};
+DS.OneToOneChange = {};
+DS.ManyToManyChange = {};
+
+DS.RelationshipChange._createChange = function(options){
+ if(options.changeType === "add"){
+ return DS.RelationshipChangeAdd.create(options);
+ }
+ if(options.changeType === "remove"){
+ return DS.RelationshipChangeRemove.create(options);
+ }
+};
+
+
+DS.RelationshipChange.determineRelationshipType = function(recordType, knownSide){
+ var knownKey = knownSide.key, key, type, otherContainerType,assoc;
+ var knownContainerType = knownSide.kind;
+ var options = recordType.metaForProperty(knownKey).options;
+ var otherType = DS._inverseTypeFor(recordType, knownKey);
+
+ if(options.inverse){
+ key = options.inverse;
+ otherContainerType = get(otherType, 'relationshipsByName').get(key).kind;
+ }
+ else if(assoc = DS._inverseRelationshipFor(otherType, recordType)){
+ key = assoc.name;
+ otherContainerType = assoc.kind;
+ }
+ if(!key){
+ return knownContainerType === "belongsTo" ? "oneToNone" : "manyToNone";
+ }
+ else{
+ if(otherContainerType === "belongsTo"){
+ return knownContainerType === "belongsTo" ? "oneToOne" : "manyToOne";
+ }
+ else{
+ return knownContainerType === "belongsTo" ? "oneToMany" : "manyToMany";
+ }
+ }
+
+};
+
+DS.RelationshipChange.createChange = function(firstRecordReference, secondRecordReference, store, options){
+ // Get the type of the child based on the child's client ID
+ var firstRecordType = firstRecordReference.type, key, changeType;
+ changeType = DS.RelationshipChange.determineRelationshipType(firstRecordType, options);
+ if (changeType === "oneToMany"){
+ return DS.OneToManyChange.createChange(firstRecordReference, secondRecordReference, store, options);
+ }
+ else if (changeType === "manyToOne"){
+ return DS.OneToManyChange.createChange(secondRecordReference, firstRecordReference, store, options);
+ }
+ else if (changeType === "oneToNone"){
+ return DS.OneToNoneChange.createChange(firstRecordReference, secondRecordReference, store, options);
+ }
+ else if (changeType === "manyToNone"){
+ return DS.ManyToNoneChange.createChange(firstRecordReference, secondRecordReference, store, options);
+ }
+ else if (changeType === "oneToOne"){
+ return DS.OneToOneChange.createChange(firstRecordReference, secondRecordReference, store, options);
+ }
+ else if (changeType === "manyToMany"){
+ return DS.ManyToManyChange.createChange(firstRecordReference, secondRecordReference, store, options);
+ }
+};
+
+/** @private */
+DS.OneToNoneChange.createChange = function(childReference, parentReference, store, options) {
+ var key = options.key;
+ var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
+ firstRecordReference: childReference,
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key,
+ firstRecordKind: "belongsTo"
+ });
+
+ store.addRelationshipChangeFor(childReference, key, parentReference, null, change);
+
+ return change;
+};
+
+/** @private */
+DS.ManyToNoneChange.createChange = function(childReference, parentReference, store, options) {
+ var key = options.key;
+ var change = DS.RelationshipChange._createChange({
+ parentReference: childReference,
+ childReference: parentReference,
+ secondRecordReference: childReference,
+ store: store,
+ changeType: options.changeType,
+ secondRecordName: options.key,
+ secondRecordKind: "hasMany"
+ });
+
+ store.addRelationshipChangeFor(childReference, key, parentReference, null, change);
+ return change;
+};
+
+
+/** @private */
+DS.ManyToManyChange.createChange = function(childReference, parentReference, store, options) {
+ // Get the type of the child based on the child's client ID
+ var childType = childReference.type, key;
+
+ // If the name of the belongsTo side of the relationship is specified,
+ // use that
+ // If the type of the parent is specified, look it up on the child's type
+ // definition.
+ key = options.key;
+
+ var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
+ firstRecordReference: childReference,
+ secondRecordReference: parentReference,
+ firstRecordKind: "hasMany",
+ secondRecordKind: "hasMany",
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key
+ });
+
+ store.addRelationshipChangeFor(childReference, key, parentReference, null, change);
+
+
+ return change;
+};
+
+/** @private */
+DS.OneToOneChange.createChange = function(childReference, parentReference, store, options) {
+ // Get the type of the child based on the child's client ID
+ var childType = childReference.type, key;
+
+ // If the name of the belongsTo side of the relationship is specified,
+ // use that
+ // If the type of the parent is specified, look it up on the child's type
+ // definition.
+ if (options.parentType) {
+ key = inverseBelongsToName(options.parentType, childType, options.key);
+ //DS.OneToOneChange.maintainInvariant( options, store, childReference, key );
+ } else if (options.key) {
+ key = options.key;
+ } else {
+ Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false);
+ }
+
+ var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
+ firstRecordReference: childReference,
+ secondRecordReference: parentReference,
+ firstRecordKind: "belongsTo",
+ secondRecordKind: "belongsTo",
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key
+ });
+
+ store.addRelationshipChangeFor(childReference, key, parentReference, null, change);
+
+
+ return change;
+};
+
+DS.OneToOneChange.maintainInvariant = function(options, store, childReference, key){
+ if (options.changeType === "add" && store.recordIsMaterialized(childReference)) {
+ var child = store.recordForReference(childReference);
+ var oldParent = get(child, key);
+ if (oldParent){
+ var correspondingChange = DS.OneToOneChange.createChange(childReference, oldParent.get('_reference'), store, {
+ parentType: options.parentType,
+ hasManyName: options.hasManyName,
+ changeType: "remove",
+ key: options.key
+ });
+ store.addRelationshipChangeFor(childReference, key, options.parentReference , null, correspondingChange);
+ correspondingChange.sync();
+ }
+ }
+};
+
+/** @private */
+DS.OneToManyChange.createChange = function(childReference, parentReference, store, options) {
+ // Get the type of the child based on the child's client ID
+ var childType = childReference.type, key;
+
+ // If the name of the belongsTo side of the relationship is specified,
+ // use that
+ // If the type of the parent is specified, look it up on the child's type
+ // definition.
+ if (options.parentType) {
+ key = inverseBelongsToName(options.parentType, childType, options.key);
+ DS.OneToManyChange.maintainInvariant( options, store, childReference, key );
+ } else if (options.key) {
+ key = options.key;
+ } else {
+ Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false);
+ }
+
+ var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
+ firstRecordReference: childReference,
+ secondRecordReference: parentReference,
+ firstRecordKind: "belongsTo",
+ secondRecordKind: "hasMany",
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key
+ });
+
+ store.addRelationshipChangeFor(childReference, key, parentReference, null, change);
+
+
+ return change;
+};
+
+
+DS.OneToManyChange.maintainInvariant = function(options, store, childReference, key){
+ if (options.changeType === "add" && store.recordIsMaterialized(childReference)) {
+ var child = store.recordForReference(childReference);
+ var oldParent = get(child, key);
+ if (oldParent){
+ var correspondingChange = DS.OneToManyChange.createChange(childReference, oldParent.get('_reference'), store, {
+ parentType: options.parentType,
+ hasManyName: options.hasManyName,
+ changeType: "remove",
+ key: options.key
+ });
+ store.addRelationshipChangeFor(childReference, key, options.parentReference , null, correspondingChange);
+ correspondingChange.sync();
+ }
+ }
+};
+
+DS.OneToManyChange.ensureSameTransaction = function(changes, store){
+ var records = Ember.A();
+ forEach(changes, function(change){
+ records.addObject(change.getSecondRecord());
+ records.addObject(change.getFirstRecord());
+ });
+ var transaction = store.ensureSameTransaction(records);
+ forEach(changes, function(change){
+ change.transaction = transaction;
+ });
+};
+
+DS.RelationshipChange.prototype = {
+
+ getSecondRecordName: function() {
+ var name = this.secondRecordName, store = this.store, parent;
+
+ if (!name) {
+ parent = this.secondRecordReference;
+ if (!parent) { return; }
+
+ var childType = this.firstRecordReference.type;
+ var inverseType = DS._inverseTypeFor(childType, this.firstRecordName);
+ name = inverseHasManyName(inverseType, childType, this.firstRecordName);
+ this.secondRecordName = name;
+ }
+
+ return name;
+ },
+
+ /**
+ Get the name of the relationship on the belongsTo side.
+
+ @returns {String}
+ */
+ getFirstRecordName: function() {
+ var name = this.firstRecordName, store = this.store, parent, child;
+
+ if (!name) {
+ parent = this.secondRecordReference;
+ child = this.firstRecordReference;
+ if (!(child && parent)) { return; }
+
+ name = DS._inverseRelationshipFor(child.type, parent.type).name;
+
+ this.firstRecordName = name;
+ }
+
+ return name;
+ },
+
+ /** @private */
+ destroy: function() {
+ var childReference = this.childReference,
+ belongsToName = this.getFirstRecordName(),
+ hasManyName = this.getSecondRecordName(),
+ store = this.store,
+ child, oldParent, newParent, lastParent, transaction;
+
+ store.removeRelationshipChangeFor(childReference, belongsToName, this.parentReference, hasManyName, this.changeType);
+
+ if (transaction = this.transaction) {
+ transaction.relationshipBecameClean(this);
+ }
+ },
+
+ /** @private */
+ getByReference: function(reference) {
+ var store = this.store;
+
+ // return null or undefined if the original reference was null or undefined
+ if (!reference) { return reference; }
+
+ if (store.recordIsMaterialized(reference)) {
+ return store.recordForReference(reference);
+ }
+ },
+
+ getSecondRecord: function(){
+ return this.getByReference(this.secondRecordReference);
+ },
+
+ /** @private */
+ getFirstRecord: function() {
+ return this.getByReference(this.firstRecordReference);
+ },
+
+ /**
+ @private
+
+ Make sure that all three parts of the relationship change are part of
+ the same transaction. If any of the three records is clean and in the
+ default transaction, and the rest are in a different transaction, move
+ them all into that transaction.
+ */
+ ensureSameTransaction: function() {
+ var child = this.getFirstRecord(),
+ parentRecord = this.getSecondRecord();
+
+ var transaction = this.store.ensureSameTransaction([child, parentRecord]);
+
+ this.transaction = transaction;
+ return transaction;
+ },
+
+ callChangeEvents: function(){
+ var hasManyName = this.getSecondRecordName(),
+ belongsToName = this.getFirstRecordName(),
+ child = this.getFirstRecord(),
+ parentRecord = this.getSecondRecord();
+
+ var dirtySet = new Ember.OrderedSet();
+
+ // TODO: This implementation causes a race condition in key-value
+ // stores. The fix involves buffering changes that happen while
+ // a record is loading. A similar fix is required for other parts
+ // of ember-data, and should be done as new infrastructure, not
+ // a one-off hack. [tomhuda]
+ if (parentRecord && get(parentRecord, 'isLoaded')) {
+ this.store.recordHasManyDidChange(dirtySet, parentRecord, this);
+ }
+
+ if (child) {
+ this.store.recordBelongsToDidChange(dirtySet, child, this);
+ }
+
+ dirtySet.forEach(function(record) {
+ record.adapterDidDirty();
+ });
+ },
+
+ coalesce: function(){
+ var relationshipPairs = this.store.relationshipChangePairsFor(this.firstRecordReference);
+ forEach(relationshipPairs, function(pair){
+ var addedChange = pair["add"];
+ var removedChange = pair["remove"];
+ if(addedChange && removedChange) {
+ addedChange.destroy();
+ removedChange.destroy();
+ }
+ });
+ }
+};
+
+DS.RelationshipChangeAdd.prototype = Ember.create(DS.RelationshipChange.create({}));
+DS.RelationshipChangeRemove.prototype = Ember.create(DS.RelationshipChange.create({}));
+
+DS.RelationshipChangeAdd.prototype.changeType = "add";
+DS.RelationshipChangeAdd.prototype.sync = function() {
+ var secondRecordName = this.getSecondRecordName(),
+ firstRecordName = this.getFirstRecordName(),
+ firstRecord = this.getFirstRecord(),
+ secondRecord = this.getSecondRecord();
+
+ //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName);
+ //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName);
+
+ var transaction = this.ensureSameTransaction();
+ transaction.relationshipBecameDirty(this);
+
+ this.callChangeEvents();
+
+ if (secondRecord && firstRecord) {
+ if(this.secondRecordKind === "belongsTo"){
+ secondRecord.suspendRelationshipObservers(function(){
+ set(secondRecord, secondRecordName, firstRecord);
+ });
+
+ }
+ else if(this.secondRecordKind === "hasMany"){
+ secondRecord.suspendRelationshipObservers(function(){
+ get(secondRecord, secondRecordName).addObject(firstRecord);
+ });
+ }
+ }
+
+ if (firstRecord && secondRecord && get(firstRecord, firstRecordName) !== secondRecord) {
+ if(this.firstRecordKind === "belongsTo"){
+ firstRecord.suspendRelationshipObservers(function(){
+ set(firstRecord, firstRecordName, secondRecord);
+ });
+ }
+ else if(this.firstdRecordKind === "hasMany"){
+ firstRecord.suspendRelationshipObservers(function(){
+ get(firstRecord, firstRecordName).addObject(secondRecord);
+ });
+ }
+ }
+
+ this.coalesce();
+};
+
+DS.RelationshipChangeRemove.prototype.changeType = "remove";
+DS.RelationshipChangeRemove.prototype.sync = function() {
+ var secondRecordName = this.getSecondRecordName(),
+ firstRecordName = this.getFirstRecordName(),
+ firstRecord = this.getFirstRecord(),
+ secondRecord = this.getSecondRecord();
+
+ //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName);
+ //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName);
+
+ var transaction = this.ensureSameTransaction(firstRecord, secondRecord, secondRecordName, firstRecordName);
+ transaction.relationshipBecameDirty(this);
+
+ this.callChangeEvents();
+
+ if (secondRecord && firstRecord) {
+ if(this.secondRecordKind === "belongsTo"){
+ secondRecord.suspendRelationshipObservers(function(){
+ set(secondRecord, secondRecordName, null);
+ });
+ }
+ else if(this.secondRecordKind === "hasMany"){
+ secondRecord.suspendRelationshipObservers(function(){
+ get(secondRecord, secondRecordName).removeObject(firstRecord);
+ });
+ }
+ }
+
+ if (firstRecord && get(firstRecord, firstRecordName)) {
+ if(this.firstRecordKind === "belongsTo"){
+ firstRecord.suspendRelationshipObservers(function(){
+ set(firstRecord, firstRecordName, null);
+ });
+ }
+ else if(this.firstdRecordKind === "hasMany"){
+ firstRecord.suspendRelationshipObservers(function(){
+ get(firstRecord, firstRecordName).removeObject(secondRecord);
+ });
+ }
+ }
+
+ this.coalesce();
+};
+
+function inverseBelongsToName(parentType, childType, hasManyName) {
+ // Get the options passed to the parent's DS.hasMany()
+ var options = parentType.metaForProperty(hasManyName).options;
+ var belongsToName;
+
+ if (belongsToName = options.inverse) {
+ return belongsToName;
+ }
+
+ return DS._inverseRelationshipFor(childType, parentType).name;
+}
+
+function inverseHasManyName(parentType, childType, belongsToName) {
+ var options = childType.metaForProperty(belongsToName).options;
+ var hasManyName;
+
+ if (hasManyName = options.inverse) {
+ return hasManyName;
+ }
+
+ return DS._inverseRelationshipFor(parentType, childType).name;
+}
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var set = Ember.set;
+
+/**
+ This code registers an injection for Ember.Application.
+
+ If an Ember.js developer defines a subclass of DS.Store on their application,
+ this code will automatically instantiate it and make it available on the
+ router.
+
+ Additionally, after an application's controllers have been injected, they will
+ each have the store made available to them.
+
+ For example, imagine an Ember.js application with the following classes:
+
+ App.Store = DS.Store.extend({
+ adapter: 'App.MyCustomAdapter'
+ });
+
+ App.PostsController = Ember.ArrayController.extend({
+ // ...
+ });
+
+ When the application is initialized, `App.Store` will automatically be
+ instantiated, and the instance of `App.PostsController` will have its `store`
+ property set to that instance.
+
+ Note that this code will only be run if the `ember-application` package is
+ loaded. If Ember Data is being used in an environment other than a
+ typical application (e.g., node.js where only `ember-runtime` is available),
+ this code will be ignored.
+*/
+
+Ember.onLoad('Ember.Application', function(Application) {
+ if (Application.registerInjection) {
+ Application.registerInjection({
+ name: "store",
+ before: "controllers",
+
+ // If a store subclass is defined, like App.Store,
+ // instantiate it and inject it into the router.
+ injection: function(app, stateManager, property) {
+ if (!stateManager) { return; }
+ if (property === 'Store') {
+ set(stateManager, 'store', app[property].create());
+ }
+ }
+ });
+
+ Application.registerInjection({
+ name: "giveStoreToControllers",
+ after: ['store','controllers'],
+
+ // For each controller, set its `store` property
+ // to the DS.Store instance we created above.
+ injection: function(app, stateManager, property) {
+ if (!stateManager) { return; }
+ if (/^[A-Z].*Controller$/.test(property)) {
+ var controllerName = property.charAt(0).toLowerCase() + property.substr(1);
+ var store = stateManager.get('store');
+ var controller = stateManager.get(controllerName);
+ if(!controller) { return; }
+
+ controller.set('store', store);
+ }
+ }
+ });
+ } else if (Application.initializer) {
+ Application.initializer({
+ name: "store",
+
+ initialize: function(container, application) {
+ application.register('store:main', application.Store);
+
+ // Eagerly generate the store so defaultStore is populated.
+ // TODO: Do this in a finisher hook
+ container.lookup('store:main');
+ }
+ });
+
+ Application.initializer({
+ name: "injectStore",
+
+ initialize: function(container, application) {
+ application.inject('controller', 'store', 'store:main');
+ application.inject('route', 'store', 'store:main');
+ }
+ });
+ }
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set, map = Ember.ArrayPolyfills.map, isNone = Ember.isNone;
+
+function mustImplement(name) {
+ return function() {
+ throw new Ember.Error("Your serializer " + this.toString() + " does not implement the required method " + name);
+ };
+}
+
+/**
+ A serializer is responsible for serializing and deserializing a group of
+ records.
+
+ `DS.Serializer` is an abstract base class designed to help you build a
+ serializer that can read to and write from any serialized form. While most
+ applications will use `DS.JSONSerializer`, which reads and writes JSON, the
+ serializer architecture allows your adapter to transmit things like XML,
+ strings, or custom binary data.
+
+ Typically, your application's `DS.Adapter` is responsible for both creating a
+ serializer as well as calling the appropriate methods when it needs to
+ materialize data or serialize a record.
+
+ The serializer API is designed as a series of layered hooks that you can
+ override to customize any of the individual steps of serialization and
+ deserialization.
+
+ The hooks are organized by the three responsibilities of the serializer:
+
+ 1. Determining naming conventions
+ 2. Serializing records into a serialized form
+ 3. Deserializing records from a serialized form
+
+ Because Ember Data lazily materializes records, the deserialization
+ step, and therefore the hooks you implement, are split into two phases:
+
+ 1. Extraction, where the serialized forms for multiple records are
+ extracted from a single payload. The IDs of each record are also
+ extracted for indexing.
+ 2. Materialization, where a newly-created record has its attributes
+ and relationships initialized based on the serialized form loaded
+ by the adapter.
+
+ Additionally, a serializer can convert values from their JavaScript
+ versions into their serialized versions via a declarative API.
+
+ ## Naming Conventions
+
+ One of the most common uses of the serializer is to map attribute names
+ from the serialized form to your `DS.Model`. For example, in your model,
+ you may have an attribute called `firstName`:
+
+ ```javascript
+ App.Person = DS.Model.extend({
+ firstName: DS.attr('string')
+ });
+ ```
+
+ However, because the web API your adapter is communicating with is
+ legacy, it calls this attribute `FIRST_NAME`.
+
+ You can determine the attribute name used in the serialized form
+ by implementing `keyForAttributeName`:
+
+ ```javascript
+ keyForAttributeName: function(type, name) {
+ return name.underscore.toUpperCase();
+ }
+ ```
+
+ If your attribute names are not predictable, you can re-map them
+ one-by-one using the adapter's `map` API:
+
+ ```javascript
+ App.Adapter.map('App.Person', {
+ firstName: { key: '*API_USER_FIRST_NAME*' }
+ });
+ ```
+
+ This API will also work for relationships and primary keys. For
+ example:
+
+ ```javascript
+ App.Adapter.map('App.Person', {
+ primaryKey: '_id'
+ });
+ ```
+
+ ## Serialization
+
+ During the serialization process, a record or records are converted
+ from Ember.js objects into their serialized form.
+
+ These methods are designed in layers, like a delicious 7-layer
+ cake (but with fewer layers).
+
+ The main entry point for serialization is the `serialize`
+ method, which takes the record and options.
+
+ The `serialize` method is responsible for:
+
+ * turning the record's attributes (`DS.attr`) into
+ attributes on the JSON object.
+ * optionally adding the record's ID onto the hash
+ * adding relationships (`DS.hasMany` and `DS.belongsTo`)
+ to the JSON object.
+
+ Depending on the backend, the serializer can choose
+ whether to include the `hasMany` or `belongsTo`
+ relationships on the JSON hash.
+
+ For very custom serialization, you can implement your
+ own `serialize` method. In general, however, you will want
+ to override the hooks described below.
+
+ ### Adding the ID
+
+ The default `serialize` will optionally call your serializer's
+ `addId` method with the JSON hash it is creating, the
+ record's type, and the record's ID. The `serialize` method
+ will not call `addId` if the record's ID is undefined.
+
+ Your adapter must specifically request ID inclusion by
+ passing `{ includeId: true }` as an option to `serialize`.
+
+ NOTE: You may not want to include the ID when updating an
+ existing record, because your server will likely disallow
+ changing an ID after it is created, and the PUT request
+ itself will include the record's identification.
+
+ By default, `addId` will:
+
+ 1. Get the primary key name for the record by calling
+ the serializer's `primaryKey` with the record's type.
+ Unless you override the `primaryKey` method, this
+ will be `'id'`.
+ 2. Assign the record's ID to the primary key in the
+ JSON hash being built.
+
+ If your backend expects a JSON object with the primary
+ key at the root, you can just override the `primaryKey`
+ method on your serializer subclass.
+
+ Otherwise, you can override the `addId` method for
+ more specialized handling.
+
+ ### Adding Attributes
+
+ By default, the serializer's `serialize` method will call
+ `addAttributes` with the JSON object it is creating
+ and the record to serialize.
+
+ The `addAttributes` method will then call `addAttribute`
+ in turn, with the JSON object, the record to serialize,
+ the attribute's name and its type.
+
+ Finally, the `addAttribute` method will serialize the
+ attribute:
+
+ 1. It will call `keyForAttributeName` to determine
+ the key to use in the JSON hash.
+ 2. It will get the value from the record.
+ 3. It will call `serializeValue` with the attribute's
+ value and attribute type to convert it into a
+ JSON-compatible value. For example, it will convert a
+ Date into a String.
+
+ If your backend expects a JSON object with attributes as
+ keys at the root, you can just override the `serializeValue`
+ and `keyForAttributeName` methods in your serializer
+ subclass and let the base class do the heavy lifting.
+
+ If you need something more specialized, you can probably
+ override `addAttribute` and let the default `addAttributes`
+ handle the nitty gritty.
+
+ ### Adding Relationships
+
+ By default, `serialize` will call your serializer's
+ `addRelationships` method with the JSON object that is
+ being built and the record being serialized. The default
+ implementation of this method is to loop over all of the
+ relationships defined on your record type and:
+
+ * If the relationship is a `DS.hasMany` relationship,
+ call `addHasMany` with the JSON object, the record
+ and a description of the relationship.
+ * If the relationship is a `DS.belongsTo` relationship,
+ call `addBelongsTo` with the JSON object, the record
+ and a description of the relationship.
+
+ The relationship description has the following keys:
+
+ * `type`: the class of the associated information (the
+ first parameter to `DS.hasMany` or `DS.belongsTo`)
+ * `kind`: either `hasMany` or `belongsTo`
+
+ The relationship description may get additional
+ information in the future if more capabilities or
+ relationship types are added. However, it will
+ remain backwards-compatible, so the mere existence
+ of new features should not break existing adapters.
+*/
+DS.Serializer = Ember.Object.extend({
+ init: function() {
+ this.mappings = Ember.Map.create();
+ this.configurations = Ember.Map.create();
+ this.globalConfigurations = {};
+ },
+
+ extract: mustImplement('extract'),
+ extractMany: mustImplement('extractMany'),
+
+ extractRecordRepresentation: function(loader, type, json, shouldSideload) {
+ var mapping = this.mappingForType(type);
+ var embeddedData, prematerialized = {}, reference;
+
+ if (shouldSideload) {
+ reference = loader.sideload(type, json);
+ } else {
+ reference = loader.load(type, json);
+ }
+
+ this.eachEmbeddedHasMany(type, function(name, relationship) {
+ var embeddedData = json[this.keyFor(relationship)];
+ if (!isNone(embeddedData)) {
+ this.extractEmbeddedHasMany(loader, relationship, embeddedData, reference, prematerialized);
+ }
+ }, this);
+
+ this.eachEmbeddedBelongsTo(type, function(name, relationship) {
+ var embeddedData = json[this.keyFor(relationship)];
+ if (!isNone(embeddedData)) {
+ this.extractEmbeddedBelongsTo(loader, relationship, embeddedData, reference, prematerialized);
+ }
+ }, this);
+
+ loader.prematerialize(reference, prematerialized);
+
+ return reference;
+ },
+
+ extractEmbeddedHasMany: function(loader, relationship, array, parent, prematerialized) {
+ var references = map.call(array, function(item) {
+ if (!item) { return; }
+
+ var reference = this.extractRecordRepresentation(loader, relationship.type, item, true);
+
+ // If the embedded record should also be saved back when serializing the parent,
+ // make sure we set its parent since it will not have an ID.
+ var embeddedType = this.embeddedType(parent.type, relationship.key);
+ if (embeddedType === 'always') {
+ reference.parent = parent;
+ }
+
+ return reference;
+ }, this);
+
+ prematerialized[relationship.key] = references;
+ },
+
+ extractEmbeddedBelongsTo: function(loader, relationship, data, parent, prematerialized) {
+ var reference = this.extractRecordRepresentation(loader, relationship.type, data, true);
+ prematerialized[relationship.key] = reference;
+
+ // If the embedded record should also be saved back when serializing the parent,
+ // make sure we set its parent since it will not have an ID.
+ var embeddedType = this.embeddedType(parent.type, relationship.key);
+ if (embeddedType === 'always') {
+ reference.parent = parent;
+ }
+ },
+
+ //.......................
+ //. SERIALIZATION HOOKS
+ //.......................
+
+ /**
+ The main entry point for serializing a record. While you can consider this
+ a hook that can be overridden in your serializer, you will have to manually
+ handle serialization. For most cases, there are more granular hooks that you
+ can override.
+
+ If overriding this method, these are the responsibilities that you will need
+ to implement yourself:
+
+ * If the option hash contains `includeId`, add the record's ID to the serialized form.
+ By default, `serialize` calls `addId` if appropriate.
+ * Add the record's attributes to the serialized form. By default, `serialize` calls
+ `addAttributes`.
+ * Add the record's relationships to the serialized form. By default, `serialize` calls
+ `addRelationships`.
+
+ @param {DS.Model} record the record to serialize
+ @param {Object} [options] a hash of options
+ @returns {any} the serialized form of the record
+ */
+ serialize: function(record, options) {
+ options = options || {};
+
+ var serialized = this.createSerializedForm(), id;
+
+ if (options.includeId) {
+ if (id = get(record, 'id')) {
+ this._addId(serialized, record.constructor, id);
+ }
+ }
+
+ this.addAttributes(serialized, record);
+ this.addRelationships(serialized, record);
+
+ return serialized;
+ },
+
+ /**
+ @private
+
+ Given an attribute type and value, convert the value into the
+ serialized form using the transform registered for that type.
+
+ @param {any} value the value to convert to the serialized form
+ @param {String} attributeType the registered type (e.g. `string`
+ or `boolean`)
+ @returns {any} the serialized form of the value
+ */
+ serializeValue: function(value, attributeType) {
+ var transform = this.transforms ? this.transforms[attributeType] : null;
+
+ Ember.assert("You tried to use an attribute type (" + attributeType + ") that has not been registered", transform);
+ return transform.serialize(value);
+ },
+
+ /**
+ A hook you can use to normalize IDs before adding them to the
+ serialized representation.
+
+ Because the store coerces all IDs to strings for consistency,
+ this is the opportunity for the serializer to, for example,
+ convert numerical IDs back into number form.
+
+ @param {String} id the id from the record
+ @returns {any} the serialized representation of the id
+ */
+ serializeId: function(id) {
+ if (isNaN(id)) { return id; }
+ return +id;
+ },
+
+ /**
+ A hook you can use to change how attributes are added to the serialized
+ representation of a record.
+
+ By default, `addAttributes` simply loops over all of the attributes of the
+ passed record, maps the attribute name to the key for the serialized form,
+ and invokes any registered transforms on the value. It then invokes the
+ more granular `addAttribute` with the key and transformed value.
+
+ Since you can override `keyForAttributeName`, `addAttribute`, and register
+ custom tranforms, you should rarely need to override this hook.
+
+ @param {any} data the serialized representation that is being built
+ @param {DS.Model} record the record to serialize
+ */
+ addAttributes: function(data, record) {
+ record.eachAttribute(function(name, attribute) {
+ this._addAttribute(data, record, name, attribute.type);
+ }, this);
+ },
+
+ /**
+ A hook you can use to customize how the key/value pair is added to
+ the serialized data.
+
+ @param {any} serialized the serialized form being built
+ @param {String} key the key to add to the serialized data
+ @param {any} value the value to add to the serialized data
+ */
+ addAttribute: Ember.K,
+
+ /**
+ A hook you can use to customize how the record's id is added to
+ the serialized data.
+
+ The `addId` hook is called with:
+
+ * the serialized representation being built
+ * the resolved primary key (taking configurations and the
+ `primaryKey` hook into consideration)
+ * the serialized id (after calling the `serializeId` hook)
+
+ @param {any} data the serialized representation that is being built
+ @param {String} key the resolved primary key
+ @param {id} id the serialized id
+ */
+ addId: Ember.K,
+
+ /**
+ A hook you can use to change how relationships are added to the serialized
+ representation of a record.
+
+ By default, `addAttributes` loops over all of the relationships of the
+ passed record, maps the relationship names to the key for the serialized form,
+ and then invokes the public `addBelongsTo` and `addHasMany` hooks.
+
+ Since you can override `keyForBelongsTo`, `keyForHasMany`, `addBelongsTo`,
+ `addHasMany`, and register mappings, you should rarely need to override this
+ hook.
+
+ @param {any} data the serialized representation that is being built
+ @param {DS.Model} record the record to serialize
+ */
+ addRelationships: function(data, record) {
+ record.eachRelationship(function(name, relationship) {
+ if (relationship.kind === 'belongsTo') {
+ this._addBelongsTo(data, record, name, relationship);
+ } else if (relationship.kind === 'hasMany') {
+ this._addHasMany(data, record, name, relationship);
+ }
+ }, this);
+ },
+
+ /**
+ A hook you can use to add a `belongsTo` relationship to the
+ serialized representation.
+
+ The specifics of this hook are very adapter-specific, so there
+ is no default implementation. You can see `DS.JSONSerializer`
+ for an example of an implementation of the `addBelongsTo` hook.
+
+ The `belongsTo` relationship object has the following properties:
+
+ * **type** a subclass of DS.Model that is the type of the
+ relationship. This is the first parameter to DS.belongsTo
+ * **options** the options passed to the call to DS.belongsTo
+ * **kind** always `belongsTo`
+
+ Additional properties may be added in the future.
+
+ @param {any} data the serialized representation that is being built
+ @param {DS.Model} record the record to serialize
+ @param {String} key the key for the serialized object
+ @param {Object} relationship an object representing the relationship
+ */
+ addBelongsTo: Ember.K,
+
+ /**
+ A hook you can use to add a `hasMany` relationship to the
+ serialized representation.
+
+ The specifics of this hook are very adapter-specific, so there
+ is no default implementation. You may not need to implement this,
+ for example, if your backend only expects relationships on the
+ child of a one to many relationship.
+
+ The `hasMany` relationship object has the following properties:
+
+ * **type** a subclass of DS.Model that is the type of the
+ relationship. This is the first parameter to DS.hasMany
+ * **options** the options passed to the call to DS.hasMany
+ * **kind** always `hasMany`
+
+ Additional properties may be added in the future.
+
+ @param {any} data the serialized representation that is being built
+ @param {DS.Model} record the record to serialize
+ @param {String} key the key for the serialized object
+ @param {Object} relationship an object representing the relationship
+ */
+ addHasMany: Ember.K,
+
+ /**
+ NAMING CONVENTIONS
+
+ The most commonly overridden APIs of the serializer are
+ the naming convention methods:
+
+ * `keyForAttributeName`: converts a camelized attribute name
+ into a key in the adapter-provided data hash. For example,
+ if the model's attribute name was `firstName`, and the
+ server used underscored names, you would return `first_name`.
+ * `primaryKey`: returns the key that should be used to
+ extract the id from the adapter-provided data hash. It is
+ also used when serializing a record.
+ */
+
+ /**
+ A hook you can use in your serializer subclass to customize
+ how an unmapped attribute name is converted into a key.
+
+ By default, this method returns the `name` parameter.
+
+ For example, if the attribute names in your JSON are underscored,
+ you will want to convert them into JavaScript conventional
+ camelcase:
+
+ ```javascript
+ App.MySerializer = DS.Serializer.extend({
+ // ...
+
+ keyForAttributeName: function(type, name) {
+ return name.camelize();
+ }
+ });
+ ```
+
+ @param {DS.Model subclass} type the type of the record with
+ the attribute name `name`
+ @param {String} name the attribute name to convert into a key
+
+ @returns {String} the key
+ */
+ keyForAttributeName: function(type, name) {
+ return name;
+ },
+
+ /**
+ A hook you can use in your serializer to specify a conventional
+ primary key.
+
+ By default, this method will return the string `id`.
+
+ In general, you should not override this hook to specify a special
+ primary key for an individual type; use `configure` instead.
+
+ For example, if your primary key is always `__id__`:
+
+ ```javascript
+ App.MySerializer = DS.Serializer.extend({
+ // ...
+ primaryKey: function(type) {
+ return '__id__';
+ }
+ });
+ ```
+
+ In another example, if the primary key always includes the
+ underscored version of the type before the string `id`:
+
+ ```javascript
+ App.MySerializer = DS.Serializer.extend({
+ // ...
+ primaryKey: function(type) {
+ // If the type is `BlogPost`, this will return
+ // `blog_post_id`.
+ var typeString = type.toString().split(".")[1].underscore();
+ return typeString + "_id";
+ }
+ });
+ ```
+
+ @param {DS.Model subclass} type
+ @returns {String} the primary key for the type
+ */
+ primaryKey: function(type) {
+ return "id";
+ },
+
+ /**
+ A hook you can use in your serializer subclass to customize
+ how an unmapped `belongsTo` relationship is converted into
+ a key.
+
+ By default, this method calls `keyForAttributeName`, so if
+ your naming convention is uniform across attributes and
+ relationships, you can use the default here and override
+ just `keyForAttributeName` as needed.
+
+ For example, if the `belongsTo` names in your JSON always
+ begin with `BT_` (e.g. `BT_posts`), you can strip out the
+ `BT_` prefix:"
+
+ ```javascript
+ App.MySerializer = DS.Serializer.extend({
+ // ...
+ keyForBelongsTo: function(type, name) {
+ return name.match(/^BT_(.*)$/)[1].camelize();
+ }
+ });
+ ```
+
+ @param {DS.Model subclass} type the type of the record with
+ the `belongsTo` relationship.
+ @param {String} name the relationship name to convert into a key
+
+ @returns {String} the key
+ */
+ keyForBelongsTo: function(type, name) {
+ return this.keyForAttributeName(type, name);
+ },
+
+ /**
+ A hook you can use in your serializer subclass to customize
+ how an unmapped `hasMany` relationship is converted into
+ a key.
+
+ By default, this method calls `keyForAttributeName`, so if
+ your naming convention is uniform across attributes and
+ relationships, you can use the default here and override
+ just `keyForAttributeName` as needed.
+
+ For example, if the `hasMany` names in your JSON always
+ begin with the "table name" for the current type (e.g.
+ `post_comments`), you can strip out the prefix:"
+
+ ```javascript
+ App.MySerializer = DS.Serializer.extend({
+ // ...
+ keyForHasMany: function(type, name) {
+ // if your App.BlogPost has many App.BlogComment, the key from
+ // the server would look like: `blog_post_blog_comments`
+ //
+ // 1. Convert the type into a string and underscore the
+ // second part (App.BlogPost -> blog_post)
+ // 2. Extract the part after `blog_post_` (`blog_comments`)
+ // 3. Underscore it, to become `blogComments`
+ var typeString = type.toString().split(".")[1].underscore();
+ return name.match(new RegExp("^" + typeString + "_(.*)$"))[1].camelize();
+ }
+ });
+ ```
+
+ @param {DS.Model subclass} type the type of the record with
+ the `belongsTo` relationship.
+ @param {String} name the relationship name to convert into a key
+
+ @returns {String} the key
+ */
+ keyForHasMany: function(type, name) {
+ return this.keyForAttributeName(type, name);
+ },
+
+ //.........................
+ //. MATERIALIZATION HOOKS
+ //.........................
+
+ materialize: function(record, serialized, prematerialized) {
+ var id;
+ if (Ember.isNone(get(record, 'id'))) {
+ if (prematerialized && prematerialized.hasOwnProperty('id')) {
+ id = prematerialized.id;
+ } else {
+ id = this.extractId(record.constructor, serialized);
+ }
+ record.materializeId(id);
+ }
+
+ this.materializeAttributes(record, serialized, prematerialized);
+ this.materializeRelationships(record, serialized, prematerialized);
+ },
+
+ deserializeValue: function(value, attributeType) {
+ var transform = this.transforms ? this.transforms[attributeType] : null;
+
+ Ember.assert("You tried to use a attribute type (" + attributeType + ") that has not been registered", transform);
+ return transform.deserialize(value);
+ },
+
+ materializeAttributes: function(record, serialized, prematerialized) {
+ record.eachAttribute(function(name, attribute) {
+ if (prematerialized && prematerialized.hasOwnProperty(name)) {
+ record.materializeAttribute(name, prematerialized[name]);
+ } else {
+ this.materializeAttribute(record, serialized, name, attribute.type);
+ }
+ }, this);
+ },
+
+ materializeAttribute: function(record, serialized, attributeName, attributeType) {
+ var value = this.extractAttribute(record.constructor, serialized, attributeName);
+ value = this.deserializeValue(value, attributeType);
+
+ record.materializeAttribute(attributeName, value);
+ },
+
+ materializeRelationships: function(record, hash, prematerialized) {
+ record.eachRelationship(function(name, relationship) {
+ if (relationship.kind === 'hasMany') {
+ if (prematerialized && prematerialized.hasOwnProperty(name)) {
+ record.materializeHasMany(name, prematerialized[name]);
+ } else {
+ this.materializeHasMany(name, record, hash, relationship, prematerialized);
+ }
+ } else if (relationship.kind === 'belongsTo') {
+ if (prematerialized && prematerialized.hasOwnProperty(name)) {
+ record.materializeBelongsTo(name, prematerialized[name]);
+ } else {
+ this.materializeBelongsTo(name, record, hash, relationship, prematerialized);
+ }
+ }
+ }, this);
+ },
+
+ materializeHasMany: function(name, record, hash, relationship) {
+ var key = this._keyForHasMany(record.constructor, relationship.key);
+ record.materializeHasMany(name, this.extractHasMany(record.constructor, hash, key));
+ },
+
+ materializeBelongsTo: function(name, record, hash, relationship) {
+ var key = this._keyForBelongsTo(record.constructor, relationship.key);
+ record.materializeBelongsTo(name, this.extractBelongsTo(record.constructor, hash, key));
+ },
+
+ _extractEmbeddedRelationship: function(type, hash, name, relationshipType) {
+ var key = this['_keyFor' + relationshipType](type, name);
+
+ if (this.embeddedType(type, name)) {
+ return this['extractEmbedded' + relationshipType](type, hash, key);
+ }
+ },
+
+ _extractEmbeddedBelongsTo: function(type, hash, name) {
+ return this._extractEmbeddedRelationship(type, hash, name, 'BelongsTo');
+ },
+
+ _extractEmbeddedHasMany: function(type, hash, name) {
+ return this._extractEmbeddedRelationship(type, hash, name, 'HasMany');
+ },
+
+ /**
+ @private
+
+ This method is called to get the primary key for a given
+ type.
+
+ If a primary key configuration exists for this type, this
+ method will return the configured value. Otherwise, it will
+ call the public `primaryKey` hook.
+
+ @param {DS.Model subclass} type
+ @returns {String} the primary key for the type
+ */
+ _primaryKey: function(type) {
+ var config = this.configurationForType(type),
+ primaryKey = config && config.primaryKey;
+
+ if (primaryKey) {
+ return primaryKey;
+ } else {
+ return this.primaryKey(type);
+ }
+ },
+
+ /**
+ @private
+
+ This method looks up the key for the attribute name and transforms the
+ attribute's value using registered transforms.
+
+ Specifically:
+
+ 1. Look up the key for the attribute name. If available, this will use
+ any registered mappings. Otherwise, it will invoke the public
+ `keyForAttributeName` hook.
+ 2. Get the value from the record using the `attributeName`.
+ 3. Transform the value using registered transforms for the `attributeType`.
+ 4. Invoke the public `addAttribute` hook with the hash, key, and
+ transformed value.
+
+ @param {any} data the serialized representation being built
+ @param {DS.Model} record the record to serialize
+ @param {String} attributeName the name of the attribute on the record
+ @param {String} attributeType the type of the attribute (e.g. `string`
+ or `boolean`)
+ */
+ _addAttribute: function(data, record, attributeName, attributeType) {
+ var key = this._keyForAttributeName(record.constructor, attributeName);
+ var value = get(record, attributeName);
+
+ this.addAttribute(data, key, this.serializeValue(value, attributeType));
+ },
+
+ /**
+ @private
+
+ This method looks up the primary key for the `type` and invokes
+ `serializeId` on the `id`.
+
+ It then invokes the public `addId` hook with the primary key and
+ the serialized id.
+
+ @param {any} data the serialized representation that is being built
+ @param {Ember.Model subclass} type
+ @param {any} id the materialized id from the record
+ */
+ _addId: function(hash, type, id) {
+ var primaryKey = this._primaryKey(type);
+
+ this.addId(hash, primaryKey, this.serializeId(id));
+ },
+
+ /**
+ @private
+
+ This method is called to get a key used in the data from
+ an attribute name. It first checks for any mappings before
+ calling the public hook `keyForAttributeName`.
+
+ @param {DS.Model subclass} type the type of the record with
+ the attribute name `name`
+ @param {String} name the attribute name to convert into a key
+
+ @returns {String} the key
+ */
+ _keyForAttributeName: function(type, name) {
+ return this._keyFromMappingOrHook('keyForAttributeName', type, name);
+ },
+
+ /**
+ @private
+
+ This method is called to get a key used in the data from
+ a belongsTo relationship. It first checks for any mappings before
+ calling the public hook `keyForBelongsTo`.
+
+ @param {DS.Model subclass} type the type of the record with
+ the `belongsTo` relationship.
+ @param {String} name the relationship name to convert into a key
+
+ @returns {String} the key
+ */
+ _keyForBelongsTo: function(type, name) {
+ return this._keyFromMappingOrHook('keyForBelongsTo', type, name);
+ },
+
+ keyFor: function(description) {
+ var type = description.parentType,
+ name = description.key;
+
+ switch (description.kind) {
+ case 'belongsTo':
+ return this._keyForBelongsTo(type, name);
+ case 'hasMany':
+ return this._keyForHasMany(type, name);
+ }
+ },
+
+ /**
+ @private
+
+ This method is called to get a key used in the data from
+ a hasMany relationship. It first checks for any mappings before
+ calling the public hook `keyForHasMany`.
+
+ @param {DS.Model subclass} type the type of the record with
+ the `hasMany` relationship.
+ @param {String} name the relationship name to convert into a key
+
+ @returns {String} the key
+ */
+ _keyForHasMany: function(type, name) {
+ return this._keyFromMappingOrHook('keyForHasMany', type, name);
+ },
+ /**
+ @private
+
+ This method converts the relationship name to a key for serialization,
+ and then invokes the public `addBelongsTo` hook.
+
+ @param {any} data the serialized representation that is being built
+ @param {DS.Model} record the record to serialize
+ @param {String} name the relationship name
+ @param {Object} relationship an object representing the relationship
+ */
+ _addBelongsTo: function(data, record, name, relationship) {
+ var key = this._keyForBelongsTo(record.constructor, name);
+ this.addBelongsTo(data, record, key, relationship);
+ },
+
+ /**
+ @private
+
+ This method converts the relationship name to a key for serialization,
+ and then invokes the public `addHasMany` hook.
+
+ @param {any} data the serialized representation that is being built
+ @param {DS.Model} record the record to serialize
+ @param {String} name the relationship name
+ @param {Object} relationship an object representing the relationship
+ */
+ _addHasMany: function(data, record, name, relationship) {
+ var key = this._keyForHasMany(record.constructor, name);
+ this.addHasMany(data, record, key, relationship);
+ },
+
+ /**
+ @private
+
+ An internal method that handles checking whether a mapping
+ exists for a particular attribute or relationship name before
+ calling the public hooks.
+
+ If a mapping is found, and the mapping has a key defined,
+ use that instead of invoking the hook.
+
+ @param {String} publicMethod the public hook to invoke if
+ a mapping is not found (e.g. `keyForAttributeName`)
+ @param {DS.Model subclass} type the type of the record with
+ the attribute or relationship name.
+ @param {String} name the attribute or relationship name to
+ convert into a key
+ */
+ _keyFromMappingOrHook: function(publicMethod, type, name) {
+ var key = this.mappingOption(type, name, 'key');
+
+ if (key) {
+ return key;
+ } else {
+ return this[publicMethod](type, name);
+ }
+ },
+
+ /**
+ TRANSFORMS
+ */
+
+ registerTransform: function(type, transform) {
+ this.transforms[type] = transform;
+ },
+
+ registerEnumTransform: function(type, objects) {
+ var transform = {
+ deserialize: function(deserialized) {
+ return Ember.A(objects).objectAt(deserialized);
+ },
+ serialize: function(serialized) {
+ return Ember.EnumerableUtils.indexOf(objects, serialized);
+ },
+ values: objects
+ };
+ this.registerTransform(type, transform);
+ },
+
+ /**
+ MAPPING CONVENIENCE
+ */
+
+ map: function(type, mappings) {
+ this.mappings.set(type, mappings);
+ },
+
+ configure: function(type, configuration) {
+ if (type && !configuration) {
+ Ember.merge(this.globalConfigurations, type);
+ return;
+ }
+
+ var config = Ember.create(this.globalConfigurations);
+ Ember.merge(config, configuration);
+
+ this.configurations.set(type, config);
+ },
+
+ mappingForType: function(type) {
+ this._reifyMappings();
+ return this.mappings.get(type) || {};
+ },
+
+ configurationForType: function(type) {
+ this._reifyConfigurations();
+ return this.configurations.get(type) || this.globalConfigurations;
+ },
+
+ _reifyMappings: function() {
+ if (this._didReifyMappings) { return; }
+
+ var mappings = this.mappings,
+ reifiedMappings = Ember.Map.create();
+
+ mappings.forEach(function(key, mapping) {
+ if (typeof key === 'string') {
+ var type = Ember.get(Ember.lookup, key);
+ Ember.assert("Could not find model at path " + key, type);
+
+ reifiedMappings.set(type, mapping);
+ } else {
+ reifiedMappings.set(key, mapping);
+ }
+ });
+
+ this.mappings = reifiedMappings;
+
+ this._didReifyMappings = true;
+ },
+
+ _reifyConfigurations: function() {
+ if (this._didReifyConfigurations) { return; }
+
+ var configurations = this.configurations,
+ reifiedConfigurations = Ember.Map.create();
+
+ configurations.forEach(function(key, mapping) {
+ if (typeof key === 'string' && key !== 'plurals') {
+ var type = Ember.get(Ember.lookup, key);
+ Ember.assert("Could not find model at path " + key, type);
+
+ reifiedConfigurations.set(type, mapping);
+ } else {
+ reifiedConfigurations.set(key, mapping);
+ }
+ });
+
+ this.configurations = reifiedConfigurations;
+
+ this._didReifyConfigurations = true;
+ },
+
+ mappingOption: function(type, name, option) {
+ var mapping = this.mappingForType(type)[name];
+
+ return mapping && mapping[option];
+ },
+
+ configOption: function(type, option) {
+ var config = this.configurationForType(type);
+
+ return config[option];
+ },
+
+ // EMBEDDED HELPERS
+
+ embeddedType: function(type, name) {
+ return this.mappingOption(type, name, 'embedded');
+ },
+
+ eachEmbeddedRecord: function(record, callback, binding) {
+ this.eachEmbeddedBelongsToRecord(record, callback, binding);
+ this.eachEmbeddedHasManyRecord(record, callback, binding);
+ },
+
+ eachEmbeddedBelongsToRecord: function(record, callback, binding) {
+ var type = record.constructor;
+
+ this.eachEmbeddedBelongsTo(record.constructor, function(name, relationship, embeddedType) {
+ var embeddedRecord = get(record, name);
+ if (embeddedRecord) { callback.call(binding, embeddedRecord, embeddedType); }
+ });
+ },
+
+ eachEmbeddedHasManyRecord: function(record, callback, binding) {
+ var type = record.constructor;
+
+ this.eachEmbeddedHasMany(record.constructor, function(name, relationship, embeddedType) {
+ var array = get(record, name);
+ for (var i=0, l=get(array, 'length'); i types) before sideloading.
+ // We can't do this conversion immediately here, because `configure`
+ // may be called before certain types have been defined.
+ this.sideloadMapping.normalized = false;
+
+ delete configuration.sideloadAs;
+ }
+
+ this._super.apply(this, arguments);
+ },
+
+ addId: function(data, key, id) {
+ data[key] = id;
+ },
+
+ /**
+ A hook you can use to customize how the key/value pair is added to
+ the serialized data.
+
+ @param {any} hash the JSON hash being built
+ @param {String} key the key to add to the serialized data
+ @param {any} value the value to add to the serialized data
+ */
+ addAttribute: function(hash, key, value) {
+ hash[key] = value;
+ },
+
+ /**
+ @private
+
+ Creates an empty hash that will be filled in by the hooks called from the
+ `serialize()` method.
+
+ @return {Object}
+ */
+ createSerializedForm: function() {
+ return {};
+ },
+
+ extractAttribute: function(type, hash, attributeName) {
+ var key = this._keyForAttributeName(type, attributeName);
+ return hash[key];
+ },
+
+ extractId: function(type, hash) {
+ var primaryKey = this._primaryKey(type);
+
+ if (hash.hasOwnProperty(primaryKey)) {
+ // Ensure that we coerce IDs to strings so that record
+ // IDs remain consistent between application runs; especially
+ // if the ID is serialized and later deserialized from the URL,
+ // when type information will have been lost.
+ return hash[primaryKey]+'';
+ } else {
+ return null;
+ }
+ },
+
+ extractHasMany: function(type, hash, key) {
+ return hash[key];
+ },
+
+ extractBelongsTo: function(type, hash, key) {
+ return hash[key];
+ },
+
+ addBelongsTo: function(hash, record, key, relationship) {
+ var type = record.constructor,
+ name = relationship.key,
+ value = null,
+ embeddedChild;
+
+ if (this.embeddedType(type, name)) {
+ if (embeddedChild = get(record, name)) {
+ value = this.serialize(embeddedChild, { includeId: true });
+ }
+
+ hash[key] = value;
+ } else {
+ var id = get(record, relationship.key+'.id');
+ if (!Ember.isNone(id)) { hash[key] = id; }
+ }
+ },
+
+ /**
+ Adds a has-many relationship to the JSON hash being built.
+
+ The default REST semantics are to only add a has-many relationship if it
+ is embedded. If the relationship was initially loaded by ID, we assume that
+ that was done as a performance optimization, and that changes to the
+ has-many should be saved as foreign key changes on the child's belongs-to
+ relationship.
+
+ @param {Object} hash the JSON being built
+ @param {DS.Model} record the record being serialized
+ @param {String} key the JSON key into which the serialized relationship
+ should be saved
+ @param {Object} relationship metadata about the relationship being serialized
+ */
+ addHasMany: function(hash, record, key, relationship) {
+ var type = record.constructor,
+ name = relationship.key,
+ serializedHasMany = [],
+ manyArray, embeddedType;
+
+ // If the has-many is not embedded, there is nothing to do.
+ embeddedType = this.embeddedType(type, name);
+ if (embeddedType !== 'always') { return; }
+
+ // Get the DS.ManyArray for the relationship off the record
+ manyArray = get(record, name);
+
+ // Build up the array of serialized records
+ manyArray.forEach(function (record) {
+ serializedHasMany.push(this.serialize(record, { includeId: true }));
+ }, this);
+
+ // Set the appropriate property of the serialized JSON to the
+ // array of serialized embedded records
+ hash[key] = serializedHasMany;
+ },
+
+ // EXTRACTION
+
+ extract: function(loader, json, type, record) {
+ var root = this.rootForType(type);
+
+ this.sideload(loader, type, json, root);
+ this.extractMeta(loader, type, json);
+
+ if (json[root]) {
+ if (record) { loader.updateId(record, json[root]); }
+ this.extractRecordRepresentation(loader, type, json[root]);
+ }
+ },
+
+ extractMany: function(loader, json, type, records) {
+ var root = this.rootForType(type);
+ root = this.pluralize(root);
+
+ this.sideload(loader, type, json, root);
+ this.extractMeta(loader, type, json);
+
+ if (json[root]) {
+ var objects = json[root], references = [];
+ if (records) { records = records.toArray(); }
+
+ for (var i = 0; i < objects.length; i++) {
+ if (records) { loader.updateId(records[i], objects[i]); }
+ var reference = this.extractRecordRepresentation(loader, type, objects[i]);
+ references.push(reference);
+ }
+
+ loader.populateArray(references);
+ }
+ },
+
+ extractMeta: function(loader, type, json) {
+ var meta = json[this.configOption(type, 'meta')], since;
+ if (!meta) { return; }
+
+ if (since = meta[this.configOption(type, 'since')]) {
+ loader.sinceForType(type, since);
+ }
+ },
+
+ /**
+ @private
+
+ Iterates over the `json` payload and attempts to load any data
+ included alongside `root`.
+
+ The keys expected for sideloaded data are based upon the types related
+ to the root model. Recursion is used to ensure that types related to
+ related types can be loaded as well. Any custom keys specified by
+ `sideloadAs` mappings will also be respected.
+
+ @param {DS.Store subclass} loader
+ @param {DS.Model subclass} type
+ @param {Object} json
+ @param {String} root
+ */
+ sideload: function(loader, type, json, root) {
+ var sideloadedType;
+
+ this.normalizeSideloadMappings();
+ this.configureSideloadMappingForType(type);
+
+ for (var prop in json) {
+ if (!json.hasOwnProperty(prop) ||
+ prop === root ||
+ prop === this.configOption(type, 'meta')) {
+ continue;
+ }
+
+ sideloadedType = this.sideloadMapping.get(prop);
+ Ember.assert("Your server returned a hash with the key " + prop +
+ " but you have no mapping for it",
+ !!sideloadedType);
+
+ this.loadValue(loader, sideloadedType, json[prop]);
+ }
+ },
+
+ /**
+ @private
+
+ Iterates over all the `sideloadAs` mappings and converts any that are
+ strings to their equivalent types.
+
+ This is an optimization used to avoid performing lookups for every
+ call to `sideload`.
+ */
+ normalizeSideloadMappings: function() {
+ if (! this.sideloadMapping.normalized) {
+ this.sideloadMapping.forEach(function(key, value) {
+ if (typeof value === 'string') {
+ this.sideloadMapping.set(key, get(Ember.lookup, value));
+ }
+ }, this);
+ this.sideloadMapping.normalized = true;
+ }
+ },
+
+ /**
+ @private
+
+ Configures possible sideload mappings for the types related to a
+ particular model. This recursive method ensures that sideloading
+ works for related models as well.
+
+ @param {DS.Model subclass} type
+ @param {Ember.A} configured an array of types that have already been configured
+ */
+ configureSideloadMappingForType: function(type, configured) {
+ if (!configured) {configured = Ember.A([]);}
+ configured.pushObject(type);
+
+ type.eachRelatedType(function(relatedType) {
+ if (!configured.contains(relatedType)) {
+ var root = this.sideloadMappingForType(relatedType);
+ if (!root) {
+ root = this.defaultSideloadRootForType(relatedType);
+ this.sideloadMapping.set(root, relatedType);
+ }
+ this.configureSideloadMappingForType(relatedType, configured);
+ }
+ }, this);
+ },
+
+ loadValue: function(loader, type, value) {
+ if (value instanceof Array) {
+ for (var i=0; i < value.length; i++) {
+ loader.sideload(type, value[i]);
+ }
+ } else {
+ loader.sideload(type, value);
+ }
+ },
+
+ // HELPERS
+
+ // define a plurals hash in your subclass to define
+ // special-case pluralization
+ pluralize: function(name) {
+ var plurals = this.configurations.get('plurals');
+ return (plurals && plurals[name]) || name + "s";
+ },
+
+ // use the same plurals hash to determine
+ // special-case singularization
+ singularize: function(name) {
+ var plurals = this.configurations.get('plurals');
+ if (plurals) {
+ for (var i in plurals) {
+ if (plurals[i] === name) {
+ return i;
+ }
+ }
+ }
+ if (name.lastIndexOf('s') === name.length - 1) {
+ return name.substring(0, name.length - 1);
+ } else {
+ return name;
+ }
+ },
+
+ /**
+ @private
+
+ Determines the singular root name for a particular type.
+
+ This is an underscored, lowercase version of the model name.
+ For example, the type `App.UserGroup` will have the root
+ `user_group`.
+
+ @param {DS.Model subclass} type
+ @returns {String} name of the root element
+ */
+ rootForType: function(type) {
+ var typeString = type.toString();
+
+ Ember.assert("Your model must not be anonymous. It was " + type, typeString.charAt(0) !== '(');
+
+ // use the last part of the name as the URL
+ var parts = typeString.split(".");
+ var name = parts[parts.length - 1];
+ return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1);
+ },
+
+ /**
+ @private
+
+ Determines the root name mapped to a particular sideloaded type.
+
+ @param {DS.Model subclass} type
+ @returns {String} name of the root element, if any is registered
+ */
+ sideloadMappingForType: function(type) {
+ this.sideloadMapping.forEach(function(key, value) {
+ if (type === value) {
+ return key;
+ }
+ });
+ },
+
+ /**
+ @private
+
+ The default root name for a particular sideloaded type.
+
+ @param {DS.Model subclass} type
+ @returns {String} name of the root element
+ */
+ defaultSideloadRootForType: function(type) {
+ return this.pluralize(this.rootForType(type));
+ }
+});
+
+})();
+
+
+
+(function() {
+function loaderFor(store) {
+ return {
+ load: function(type, data, prematerialized) {
+ return store.load(type, data, prematerialized);
+ },
+
+ loadMany: function(type, array) {
+ return store.loadMany(type, array);
+ },
+
+ updateId: function(record, data) {
+ return store.updateId(record, data);
+ },
+
+ populateArray: Ember.K,
+
+ sideload: function(type, data) {
+ return store.load(type, data);
+ },
+
+ sideloadMany: function(type, array) {
+ return store.loadMany(type, array);
+ },
+
+ prematerialize: function(reference, prematerialized) {
+ store.prematerialize(reference, prematerialized);
+ },
+
+ sinceForType: function(type, since) {
+ store.sinceForType(type, since);
+ }
+ };
+}
+
+DS.loaderFor = loaderFor;
+
+/**
+ An adapter is an object that receives requests from a store and
+ translates them into the appropriate action to take against your
+ persistence layer. The persistence layer is usually an HTTP API, but may
+ be anything, such as the browser's local storage.
+
+ ### Creating an Adapter
+
+ First, create a new subclass of `DS.Adapter`:
+
+ App.MyAdapter = DS.Adapter.extend({
+ // ...your code here
+ });
+
+ To tell your store which adapter to use, set its `adapter` property:
+
+ App.store = DS.Store.create({
+ revision: 3,
+ adapter: App.MyAdapter.create()
+ });
+
+ `DS.Adapter` is an abstract base class that you should override in your
+ application to customize it for your backend. The minimum set of methods
+ that you should implement is:
+
+ * `find()`
+ * `createRecord()`
+ * `updateRecord()`
+ * `deleteRecord()`
+
+ To improve the network performance of your application, you can optimize
+ your adapter by overriding these lower-level methods:
+
+ * `findMany()`
+ * `createRecords()`
+ * `updateRecords()`
+ * `deleteRecords()`
+ * `commit()`
+*/
+
+var get = Ember.get, set = Ember.set, merge = Ember.merge;
+
+DS.Adapter = Ember.Object.extend(DS._Mappable, {
+
+ init: function() {
+ var serializer = get(this, 'serializer');
+
+ if (Ember.Object.detect(serializer)) {
+ serializer = serializer.create();
+ set(this, 'serializer', serializer);
+ }
+
+ this._attributesMap = this.createInstanceMapFor('attributes');
+ this._configurationsMap = this.createInstanceMapFor('configurations');
+
+ this._outstandingOperations = new Ember.MapWithDefault({
+ defaultValue: function() { return 0; }
+ });
+
+ this._dependencies = new Ember.MapWithDefault({
+ defaultValue: function() { return new Ember.OrderedSet(); }
+ });
+
+ this.registerSerializerTransforms(this.constructor, serializer, {});
+ this.registerSerializerMappings(serializer);
+ },
+
+ /**
+ Loads a payload for a record into the store.
+
+ This method asks the serializer to break the payload into
+ constituent parts, and then loads them into the store. For example,
+ if you have a payload that contains embedded records, they will be
+ extracted by the serializer and loaded into the store.
+
+ For example:
+
+ ```javascript
+ adapter.load(store, App.Person, {
+ id: 123,
+ firstName: "Yehuda",
+ lastName: "Katz",
+ occupations: [{
+ id: 345,
+ title: "Tricycle Mechanic"
+ }]
+ });
+ ```
+
+ This will load the payload for the `App.Person` with ID `123` and
+ the embedded `App.Occupation` with ID `345`.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {any} payload
+ */
+ load: function(store, type, payload) {
+ var loader = loaderFor(store);
+ get(this, 'serializer').extractRecordRepresentation(loader, type, payload);
+ },
+
+ /**
+ Acknowledges that the adapter has finished creating a record.
+
+ Your adapter should call this method from `createRecord` when
+ it has saved a new record to its persistent storage and received
+ an acknowledgement.
+
+ If the persistent storage returns a new payload in response to the
+ creation, and you want to update the existing record with the
+ new information, pass the payload as the fourth parameter.
+
+ For example, the `RESTAdapter` saves newly created records by
+ making an Ajax request. When the server returns, the adapter
+ calls didCreateRecord. If the server returns a response body,
+ it is passed as the payload.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @param {any} payload
+ */
+ didCreateRecord: function(store, type, record, payload) {
+ store.didSaveRecord(record);
+
+ if (payload) {
+ var loader = DS.loaderFor(store);
+
+ loader.load = function(type, data, prematerialized) {
+ store.updateId(record, data);
+ return store.load(type, data, prematerialized);
+ };
+
+ get(this, 'serializer').extract(loader, payload, type);
+ }
+ },
+
+ /**
+ Acknowledges that the adapter has finished creating several records.
+
+ Your adapter should call this method from `createRecords` when it
+ has saved multiple created records to its persistent storage
+ received an acknowledgement.
+
+ If the persistent storage returns a new payload in response to the
+ creation, and you want to update the existing record with the
+ new information, pass the payload as the fourth parameter.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @param {any} payload
+ */
+ didCreateRecords: function(store, type, records, payload) {
+ records.forEach(function(record) {
+ store.didSaveRecord(record);
+ }, this);
+
+ if (payload) {
+ var loader = DS.loaderFor(store);
+ get(this, 'serializer').extractMany(loader, payload, type, records);
+ }
+ },
+
+ /**
+ @private
+
+ Acknowledges that the adapter has finished updating or deleting a record.
+
+ Your adapter should call this method from `updateRecord` or `deleteRecord`
+ when it has updated or deleted a record to its persistent storage and
+ received an acknowledgement.
+
+ If the persistent storage returns a new payload in response to the
+ update or delete, and you want to update the existing record with the
+ new information, pass the payload as the fourth parameter.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @param {any} payload
+ */
+ didSaveRecord: function(store, type, record, payload) {
+ store.didSaveRecord(record);
+
+ var serializer = get(this, 'serializer'),
+ mappings = serializer.mappingForType(type);
+
+ serializer.eachEmbeddedRecord(record, function(embeddedRecord, embeddedType) {
+ if (embeddedType === 'load') { return; }
+
+ this.didSaveRecord(store, embeddedRecord.constructor, embeddedRecord);
+ }, this);
+
+ if (payload) {
+ var loader = DS.loaderFor(store);
+ serializer.extract(loader, payload, type);
+ }
+ },
+
+ /**
+ Acknowledges that the adapter has finished updating a record.
+
+ Your adapter should call this method from `updateRecord` when it
+ has updated a record to its persistent storage and received an
+ acknowledgement.
+
+ If the persistent storage returns a new payload in response to the
+ update, pass the payload as the fourth parameter.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @param {any} payload
+ */
+ didUpdateRecord: function() {
+ this.didSaveRecord.apply(this, arguments);
+ },
+
+ /**
+ Acknowledges that the adapter has finished deleting a record.
+
+ Your adapter should call this method from `deleteRecord` when it
+ has deleted a record from its persistent storage and received an
+ acknowledgement.
+
+ If the persistent storage returns a new payload in response to the
+ deletion, pass the payload as the fourth parameter.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @param {any} payload
+ */
+ didDeleteRecord: function() {
+ this.didSaveRecord.apply(this, arguments);
+ },
+
+ /**
+ Acknowledges that the adapter has finished updating or deleting
+ multiple records.
+
+ Your adapter should call this method from its `updateRecords` or
+ `deleteRecords` when it has updated or deleted multiple records
+ to its persistent storage and received an acknowledgement.
+
+ If the persistent storage returns a new payload in response to the
+ creation, pass the payload as the fourth parameter.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} records
+ @param {any} payload
+ */
+ didSaveRecords: function(store, type, records, payload) {
+ records.forEach(function(record) {
+ store.didSaveRecord(record);
+ }, this);
+
+ if (payload) {
+ var loader = DS.loaderFor(store);
+ get(this, 'serializer').extractMany(loader, payload, type);
+ }
+ },
+
+ /**
+ Acknowledges that the adapter has finished updating multiple records.
+
+ Your adapter should call this method from its `updateRecords` when
+ it has updated multiple records to its persistent storage and
+ received an acknowledgement.
+
+ If the persistent storage returns a new payload in response to the
+ update, pass the payload as the fourth parameter.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} records
+ @param {any} payload
+ */
+ didUpdateRecords: function() {
+ this.didSaveRecords.apply(this, arguments);
+ },
+
+ /**
+ Acknowledges that the adapter has finished updating multiple records.
+
+ Your adapter should call this method from its `deleteRecords` when
+ it has deleted multiple records to its persistent storage and
+ received an acknowledgement.
+
+ If the persistent storage returns a new payload in response to the
+ deletion, pass the payload as the fourth parameter.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} records
+ @param {any} payload
+ */
+ didDeleteRecords: function() {
+ this.didSaveRecords.apply(this, arguments);
+ },
+
+ /**
+ Loads the response to a request for a record by ID.
+
+ Your adapter should call this method from its `find` method
+ with the response from the backend.
+
+ You should pass the same ID to this method that was given
+ to your find method so that the store knows which record
+ to associate the new data with.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {any} payload
+ @param {String} id
+ */
+ didFindRecord: function(store, type, payload, id) {
+ var loader = DS.loaderFor(store);
+
+ loader.load = function(type, data, prematerialized) {
+ prematerialized = prematerialized || {};
+ prematerialized.id = id;
+
+ return store.load(type, data, prematerialized);
+ };
+
+ get(this, 'serializer').extract(loader, payload, type);
+ },
+
+ /**
+ Loads the response to a request for all records by type.
+
+ You adapter should call this method from its `findAll`
+ method with the response from the backend.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {any} payload
+ */
+ didFindAll: function(store, type, payload) {
+ var loader = DS.loaderFor(store),
+ serializer = get(this, 'serializer');
+
+ store.didUpdateAll(type);
+
+ serializer.extractMany(loader, payload, type);
+ },
+
+ /**
+ Loads the response to a request for records by query.
+
+ Your adapter should call this method from its `findQuery`
+ method with the response from the backend.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {any} payload
+ @param {DS.AdapterPopulatedRecordArray} recordArray
+ */
+ didFindQuery: function(store, type, payload, recordArray) {
+ var loader = DS.loaderFor(store);
+
+ loader.populateArray = function(data) {
+ recordArray.load(data);
+ };
+
+ get(this, 'serializer').extractMany(loader, payload, type);
+ },
+
+ /**
+ Loads the response to a request for many records by ID.
+
+ You adapter should call this method from its `findMany`
+ method with the response from the backend.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {any} payload
+ */
+ didFindMany: function(store, type, payload) {
+ var loader = DS.loaderFor(store);
+
+ get(this, 'serializer').extractMany(loader, payload, type);
+ },
+
+ /**
+ Notifies the store that a request to the backend returned
+ an error.
+
+ Your adapter should call this method to indicate that the
+ backend returned an error for a request.
+
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ */
+ didError: function(store, type, record) {
+ store.recordWasError(record);
+ },
+
+ dirtyRecordsForAttributeChange: function(dirtySet, record, attributeName, newValue, oldValue) {
+ if (newValue !== oldValue) {
+ // If this record is embedded, add its parent
+ // to the dirty set.
+ this.dirtyRecordsForRecordChange(dirtySet, record);
+ }
+ },
+
+ dirtyRecordsForRecordChange: function(dirtySet, record) {
+ dirtySet.add(record);
+ },
+
+ dirtyRecordsForBelongsToChange: function(dirtySet, child) {
+ this.dirtyRecordsForRecordChange(dirtySet, child);
+ },
+
+ dirtyRecordsForHasManyChange: function(dirtySet, parent) {
+ this.dirtyRecordsForRecordChange(dirtySet, parent);
+ },
+
+ /**
+ @private
+
+ This method recursively climbs the superclass hierarchy and
+ registers any class-registered transforms on the adapter's
+ serializer.
+
+ Once it registers a transform for a given type, it ignores
+ subsequent transforms for the same attribute type.
+
+ @param {Class} klass the DS.Adapter subclass to extract the
+ transforms from
+ @param {DS.Serializer} serializer the serializer to register
+ the transforms onto
+ @param {Object} seen a hash of attributes already seen
+ */
+ registerSerializerTransforms: function(klass, serializer, seen) {
+ var transforms = klass._registeredTransforms, superclass, prop;
+
+ for (prop in transforms) {
+ if (!transforms.hasOwnProperty(prop) || prop in seen) { continue; }
+ seen[prop] = true;
+
+ serializer.registerTransform(prop, transforms[prop]);
+ }
+
+ if (superclass = klass.superclass) {
+ this.registerSerializerTransforms(superclass, serializer, seen);
+ }
+ },
+
+ /**
+ @private
+
+ This method recursively climbs the superclass hierarchy and
+ registers any class-registered mappings on the adapter's
+ serializer.
+
+ @param {Class} klass the DS.Adapter subclass to extract the
+ transforms from
+ @param {DS.Serializer} serializer the serializer to register the
+ mappings onto
+ */
+ registerSerializerMappings: function(serializer) {
+ var mappings = this._attributesMap,
+ configurations = this._configurationsMap;
+
+ mappings.forEach(serializer.map, serializer);
+ configurations.forEach(serializer.configure, serializer);
+ },
+
+ /**
+ The `find()` method is invoked when the store is asked for a record that
+ has not previously been loaded. In response to `find()` being called, you
+ should query your persistence layer for a record with the given ID. Once
+ found, you can asynchronously call the store's `load()` method to load
+ the record.
+
+ Here is an example `find` implementation:
+
+ find: function(store, type, id) {
+ var url = type.url;
+ url = url.fmt(id);
+
+ jQuery.getJSON(url, function(data) {
+ // data is a hash of key/value pairs. If your server returns a
+ // root, simply do something like:
+ // store.load(type, id, data.person)
+ store.load(type, id, data);
+ });
+ }
+ */
+ find: null,
+
+ serializer: DS.JSONSerializer,
+
+ registerTransform: function(attributeType, transform) {
+ get(this, 'serializer').registerTransform(attributeType, transform);
+ },
+
+ /**
+ A public method that allows you to register an enumerated
+ type on your adapter. This is useful if you want to utilize
+ a text representation of an integer value.
+
+ Eg: Say you want to utilize "low","medium","high" text strings
+ in your app, but you want to persist those as 0,1,2 in your backend.
+ You would first register the transform on your adapter instance:
+
+ adapter.registerEnumTransform('priority', ['low', 'medium', 'high']);
+
+ You would then refer to the 'priority' DS.attr in your model:
+ App.Task = DS.Model.extend({
+ priority: DS.attr('priority')
+ });
+
+ And lastly, you would set/get the text representation on your model instance,
+ but the transformed result will be the index number of the type.
+
+ App: myTask.get('priority') => 'low'
+ Server Response / Load: { myTask: {priority: 0} }
+
+ @param {String} type of the transform
+ @param {Array} array of String objects to use for the enumerated values.
+ This is an ordered list and the index values will be used for the transform.
+ */
+ registerEnumTransform: function(attributeType, objects) {
+ get(this, 'serializer').registerEnumTransform(attributeType, objects);
+ },
+
+ /**
+ If the globally unique IDs for your records should be generated on the client,
+ implement the `generateIdForRecord()` method. This method will be invoked
+ each time you create a new record, and the value returned from it will be
+ assigned to the record's `primaryKey`.
+
+ Most traditional REST-like HTTP APIs will not use this method. Instead, the ID
+ of the record will be set by the server, and your adapter will update the store
+ with the new ID when it calls `didCreateRecord()`. Only implement this method if
+ you intend to generate record IDs on the client-side.
+
+ The `generateIdForRecord()` method will be invoked with the requesting store as
+ the first parameter and the newly created record as the second parameter:
+
+ generateIdForRecord: function(store, record) {
+ var uuid = App.generateUUIDWithStatisticallyLowOddsOfCollision();
+ return uuid;
+ }
+ */
+ generateIdForRecord: null,
+
+ materialize: function(record, data, prematerialized) {
+ get(this, 'serializer').materialize(record, data, prematerialized);
+ },
+
+ serialize: function(record, options) {
+ return get(this, 'serializer').serialize(record, options);
+ },
+
+ extractId: function(type, data) {
+ return get(this, 'serializer').extractId(type, data);
+ },
+
+ groupByType: function(enumerable) {
+ var map = Ember.MapWithDefault.create({
+ defaultValue: function() { return Ember.OrderedSet.create(); }
+ });
+
+ enumerable.forEach(function(item) {
+ map.get(item.constructor).add(item);
+ });
+
+ return map;
+ },
+
+ commit: function(store, commitDetails) {
+ this.save(store, commitDetails);
+ },
+
+ save: function(store, commitDetails) {
+ var adapter = this;
+
+ function filter(records) {
+ var filteredSet = Ember.OrderedSet.create();
+
+ records.forEach(function(record) {
+ if (adapter.shouldSave(record)) {
+ filteredSet.add(record);
+ }
+ });
+
+ return filteredSet;
+ }
+
+ this.groupByType(commitDetails.created).forEach(function(type, set) {
+ this.createRecords(store, type, filter(set));
+ }, this);
+
+ this.groupByType(commitDetails.updated).forEach(function(type, set) {
+ this.updateRecords(store, type, filter(set));
+ }, this);
+
+ this.groupByType(commitDetails.deleted).forEach(function(type, set) {
+ this.deleteRecords(store, type, filter(set));
+ }, this);
+ },
+
+ shouldSave: Ember.K,
+
+ createRecords: function(store, type, records) {
+ records.forEach(function(record) {
+ this.createRecord(store, type, record);
+ }, this);
+ },
+
+ updateRecords: function(store, type, records) {
+ records.forEach(function(record) {
+ this.updateRecord(store, type, record);
+ }, this);
+ },
+
+ deleteRecords: function(store, type, records) {
+ records.forEach(function(record) {
+ this.deleteRecord(store, type, record);
+ }, this);
+ },
+
+ findMany: function(store, type, ids) {
+ ids.forEach(function(id) {
+ this.find(store, type, id);
+ }, this);
+ }
+});
+
+DS.Adapter.reopenClass({
+ registerTransform: function(attributeType, transform) {
+ var registeredTransforms = this._registeredTransforms || {};
+
+ registeredTransforms[attributeType] = transform;
+
+ this._registeredTransforms = registeredTransforms;
+ },
+
+ map: DS._Mappable.generateMapFunctionFor('attributes', function(key, newValue, map) {
+ var existingValue = map.get(key);
+
+ merge(existingValue, newValue);
+ }),
+
+ configure: DS._Mappable.generateMapFunctionFor('configurations', function(key, newValue, map) {
+ var existingValue = map.get(key);
+
+ // If a mapping configuration is provided, peel it off and apply it
+ // using the DS.Adapter.map API.
+ var mappings = newValue && newValue.mappings;
+ if (mappings) {
+ this.map(key, mappings);
+ delete newValue.mappings;
+ }
+
+ merge(existingValue, newValue);
+ }),
+
+ resolveMapConflict: function(oldValue, newValue, mappingsKey) {
+ merge(newValue, oldValue);
+
+ return newValue;
+ }
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+
+DS.FixtureSerializer = DS.Serializer.extend({
+ deserializeValue: function(value, attributeType) {
+ return value;
+ },
+
+ serializeValue: function(value, attributeType) {
+ return value;
+ },
+
+ addId: function(data, key, id) {
+ data[key] = id;
+ },
+
+ addAttribute: function(hash, key, value) {
+ hash[key] = value;
+ },
+
+ addBelongsTo: function(hash, record, key, relationship) {
+ var id = get(record, relationship.key+'.id');
+ if (!Ember.isNone(id)) { hash[key] = id; }
+ },
+
+ addHasMany: function(hash, record, key, relationship) {
+ var ids = get(record, relationship.key).map(function(item) {
+ return item.get('id');
+ });
+
+ hash[relationship.key] = ids;
+ },
+
+ /**
+ @private
+
+ Creates an empty hash that will be filled in by the hooks called from the
+ `serialize()` method.
+
+ @return {Object}
+ */
+ createSerializedForm: function() {
+ return {};
+ },
+
+ extract: function(loader, fixture, type, record) {
+ if (record) { loader.updateId(record, fixture); }
+ this.extractRecordRepresentation(loader, type, fixture);
+ },
+
+ extractMany: function(loader, fixtures, type, records) {
+ var objects = fixtures, references = [];
+ if (records) { records = records.toArray(); }
+
+ for (var i = 0; i < objects.length; i++) {
+ if (records) { loader.updateId(records[i], objects[i]); }
+ var reference = this.extractRecordRepresentation(loader, type, objects[i]);
+ references.push(reference);
+ }
+
+ loader.populateArray(references);
+ },
+
+ extractId: function(type, hash) {
+ var primaryKey = this._primaryKey(type);
+
+ if (hash.hasOwnProperty(primaryKey)) {
+ // Ensure that we coerce IDs to strings so that record
+ // IDs remain consistent between application runs; especially
+ // if the ID is serialized and later deserialized from the URL,
+ // when type information will have been lost.
+ return hash[primaryKey]+'';
+ } else {
+ return null;
+ }
+ },
+
+ extractAttribute: function(type, hash, attributeName) {
+ var key = this._keyForAttributeName(type, attributeName);
+ return hash[key];
+ },
+
+ extractHasMany: function(type, hash, key) {
+ return hash[key];
+ },
+
+ extractBelongsTo: function(type, hash, key) {
+ return hash[key];
+ }
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, fmt = Ember.String.fmt,
+ dump = Ember.get(window, 'JSON.stringify') || function(object) { return object.toString(); };
+
+/**
+ `DS.FixtureAdapter` is an adapter that loads records from memory.
+ Its primarily used for development and testing. You can also use
+ `DS.FixtureAdapter` while working on the API but are not ready to
+ integrate yet. It is a fully functioning adapter. All CRUD methods
+ are implemented. You can also implement query logic that a remote
+ system would do. Its possible to do develop your entire application
+ with `DS.FixtureAdapter`.
+
+*/
+DS.FixtureAdapter = DS.Adapter.extend({
+
+ simulateRemoteResponse: true,
+
+ latency: 50,
+
+ serializer: DS.FixtureSerializer,
+
+ /*
+ Implement this method in order to provide data associated with a type
+ */
+ fixturesForType: function(type) {
+ if (type.FIXTURES) {
+ var fixtures = Ember.A(type.FIXTURES);
+ return fixtures.map(function(fixture){
+ if(!fixture.id){
+ throw new Error(fmt('the id property must be defined for fixture %@', [dump(fixture)]));
+ }
+ fixture.id = fixture.id + '';
+ return fixture;
+ });
+ }
+ return null;
+ },
+
+ /*
+ Implement this method in order to query fixtures data
+ */
+ queryFixtures: function(fixtures, query, type) {
+ Ember.assert('Not implemented: You must override the DS.FixtureAdapter::queryFixtures method to support querying the fixture store.');
+ },
+
+ updateFixtures: function(type, fixture) {
+ if(!type.FIXTURES) {
+ type.FIXTURES = [];
+ }
+
+ var fixtures = type.FIXTURES;
+
+ this.deleteLoadedFixture(type, fixture);
+
+ fixtures.push(fixture);
+ },
+
+ /*
+ Implement this method in order to provide provide json for CRUD methods
+ */
+ mockJSON: function(type, record) {
+ return this.serialize(record, { includeId: true });
+ },
+
+ /*
+ Adapter methods
+ */
+ generateIdForRecord: function(store, record) {
+ return Ember.guidFor(record);
+ },
+
+ find: function(store, type, id) {
+ var fixtures = this.fixturesForType(type),
+ fixture;
+
+ Ember.warn("Unable to find fixtures for model type " + type.toString(), fixtures);
+
+ if (fixtures) {
+ fixture = Ember.A(fixtures).findProperty('id', id);
+ }
+
+ if (fixture) {
+ this.simulateRemoteCall(function() {
+ this.didFindRecord(store, type, fixture, id);
+ }, this);
+ }
+ },
+
+ findMany: function(store, type, ids) {
+ var fixtures = this.fixturesForType(type);
+
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
+
+ if (fixtures) {
+ fixtures = fixtures.filter(function(item) {
+ return ids.indexOf(item.id) !== -1;
+ });
+ }
+
+ if (fixtures) {
+ this.simulateRemoteCall(function() {
+ this.didFindMany(store, type, fixtures);
+ }, this);
+ }
+ },
+
+ findAll: function(store, type) {
+ var fixtures = this.fixturesForType(type);
+
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
+
+ this.simulateRemoteCall(function() {
+ this.didFindAll(store, type, fixtures);
+ }, this);
+ },
+
+ findQuery: function(store, type, query, array) {
+ var fixtures = this.fixturesForType(type);
+
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
+
+ fixtures = this.queryFixtures(fixtures, query, type);
+
+ if (fixtures) {
+ this.simulateRemoteCall(function() {
+ this.didFindQuery(store, type, fixtures, array);
+ }, this);
+ }
+ },
+
+ createRecord: function(store, type, record) {
+ var fixture = this.mockJSON(type, record);
+
+ this.updateFixtures(type, fixture);
+
+ this.simulateRemoteCall(function() {
+ this.didCreateRecord(store, type, record, fixture);
+ }, this);
+ },
+
+ updateRecord: function(store, type, record) {
+ var fixture = this.mockJSON(type, record);
+
+ this.updateFixtures(type, fixture);
+
+ this.simulateRemoteCall(function() {
+ this.didUpdateRecord(store, type, record, fixture);
+ }, this);
+ },
+
+ deleteRecord: function(store, type, record) {
+ var fixture = this.mockJSON(type, record);
+
+ this.deleteLoadedFixture(type, fixture);
+
+ this.simulateRemoteCall(function() {
+ this.didDeleteRecord(store, type, record);
+ }, this);
+ },
+
+ /*
+ @private
+ */
+ deleteLoadedFixture: function(type, record) {
+ var id = this.extractId(type, record);
+
+ var existingFixture = this.findExistingFixture(type, record);
+
+ if(existingFixture) {
+ var index = type.FIXTURES.indexOf(existingFixture);
+ type.FIXTURES.splice(index, 1);
+ return true;
+ }
+ },
+
+ findExistingFixture: function(type, record) {
+ var fixtures = this.fixturesForType(type);
+ var id = this.extractId(type, record);
+
+ return this.findFixtureById(fixtures, id);
+ },
+
+ findFixtureById: function(fixtures, id) {
+ var adapter = this;
+
+ return Ember.A(fixtures).find(function(r) {
+ if(''+get(r, 'id') === ''+id) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+ },
+
+ simulateRemoteCall: function(callback, context) {
+ if (get(this, 'simulateRemoteResponse')) {
+ // Schedule with setTimeout
+ Ember.run.later(context, callback, get(this, 'latency'));
+ } else {
+ // Asynchronous, but at the of the runloop with zero latency
+ Ember.run.once(context, callback);
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+DS.RESTSerializer = DS.JSONSerializer.extend({
+ keyForAttributeName: function(type, name) {
+ return Ember.String.decamelize(name);
+ },
+
+ keyForBelongsTo: function(type, name) {
+ var key = this.keyForAttributeName(type, name);
+
+ if (this.embeddedType(type, name)) {
+ return key;
+ }
+
+ return key + "_id";
+ },
+
+ keyForHasMany: function(type, name) {
+ var key = this.keyForAttributeName(type, name);
+
+ if (this.embeddedType(type, name)) {
+ return key;
+ }
+
+ return this.singularize(key) + "_ids";
+ }
+});
+
+})();
+
+
+
+(function() {
+/*global jQuery*/
+
+var get = Ember.get, set = Ember.set, merge = Ember.merge;
+
+/**
+ The REST adapter allows your store to communicate with an HTTP server by
+ transmitting JSON via XHR. Most Ember.js apps that consume a JSON API
+ should use the REST adapter.
+
+ This adapter is designed around the idea that the JSON exchanged with
+ the server should be conventional.
+
+ ## JSON Structure
+
+ The REST adapter expects the JSON returned from your server to follow
+ these conventions.
+
+ ### Object Root
+
+ The JSON payload should be an object that contains the record inside a
+ root property. For example, in response to a `GET` request for
+ `/posts/1`, the JSON should look like this:
+
+ ```js
+ {
+ "post": {
+ title: "I'm Running to Reform the W3C's Tag",
+ author: "Yehuda Katz"
+ }
+ }
+ ```
+
+ ### Conventional Names
+
+ Attribute names in your JSON payload should be the underscored versions of
+ the attributes in your Ember.js models.
+
+ For example, if you have a `Person` model:
+
+ ```js
+ App.Person = DS.Model.extend({
+ firstName: DS.attr('string'),
+ lastName: DS.attr('string'),
+ occupation: DS.attr('string')
+ });
+ ```
+
+ The JSON returned should look like this:
+
+ ```js
+ {
+ "person": {
+ "first_name": "Barack",
+ "last_name": "Obama",
+ "occupation": "President"
+ }
+ }
+ ```
+*/
+DS.RESTAdapter = DS.Adapter.extend({
+ bulkCommit: false,
+ since: 'since',
+
+ serializer: DS.RESTSerializer,
+
+ init: function() {
+ this._super.apply(this, arguments);
+ },
+
+ shouldSave: function(record) {
+ var reference = get(record, '_reference');
+
+ return !reference.parent;
+ },
+
+ createRecord: function(store, type, record) {
+ var root = this.rootForType(type);
+
+ var data = {};
+ data[root] = this.serialize(record, { includeId: true });
+
+ this.ajax(this.buildURL(root), "POST", {
+ data: data,
+ context: this,
+ success: function(json) {
+ Ember.run(this, function(){
+ this.didCreateRecord(store, type, record, json);
+ });
+ },
+ error: function(xhr) {
+ this.didError(store, type, record, xhr);
+ }
+ });
+ },
+
+ dirtyRecordsForRecordChange: function(dirtySet, record) {
+ this._dirtyTree(dirtySet, record);
+ },
+
+ dirtyRecordsForHasManyChange: function(dirtySet, record, relationship) {
+ var embeddedType = get(this, 'serializer').embeddedType(record.constructor, relationship.secondRecordName);
+
+ if (embeddedType === 'always') {
+ relationship.childReference.parent = relationship.parentReference;
+ this._dirtyTree(dirtySet, record);
+ }
+ },
+
+ _dirtyTree: function(dirtySet, record) {
+ dirtySet.add(record);
+
+ get(this, 'serializer').eachEmbeddedRecord(record, function(embeddedRecord, embeddedType) {
+ if (embeddedType !== 'always') { return; }
+ if (dirtySet.has(embeddedRecord)) { return; }
+ this._dirtyTree(dirtySet, embeddedRecord);
+ }, this);
+
+ var reference = record.get('_reference');
+
+ if (reference.parent) {
+ var store = get(record, 'store');
+ var parent = store.recordForReference(reference.parent);
+ this._dirtyTree(dirtySet, parent);
+ }
+ },
+
+ createRecords: function(store, type, records) {
+ if (get(this, 'bulkCommit') === false) {
+ return this._super(store, type, records);
+ }
+
+ var root = this.rootForType(type),
+ plural = this.pluralize(root);
+
+ var data = {};
+ data[plural] = [];
+ records.forEach(function(record) {
+ data[plural].push(this.serialize(record, { includeId: true }));
+ }, this);
+
+ this.ajax(this.buildURL(root), "POST", {
+ data: data,
+ context: this,
+ success: function(json) {
+ Ember.run(this, function(){
+ this.didCreateRecords(store, type, records, json);
+ });
+ }
+ });
+ },
+
+ updateRecord: function(store, type, record) {
+ var id = get(record, 'id');
+ var root = this.rootForType(type);
+
+ var data = {};
+ data[root] = this.serialize(record);
+
+ this.ajax(this.buildURL(root, id), "PUT", {
+ data: data,
+ context: this,
+ success: function(json) {
+ Ember.run(this, function(){
+ this.didSaveRecord(store, type, record, json);
+ });
+ },
+ error: function(xhr) {
+ this.didError(store, type, record, xhr);
+ }
+ });
+ },
+
+ updateRecords: function(store, type, records) {
+ if (get(this, 'bulkCommit') === false) {
+ return this._super(store, type, records);
+ }
+
+ var root = this.rootForType(type),
+ plural = this.pluralize(root);
+
+ var data = {};
+ data[plural] = [];
+ records.forEach(function(record) {
+ data[plural].push(this.serialize(record, { includeId: true }));
+ }, this);
+
+ this.ajax(this.buildURL(root, "bulk"), "PUT", {
+ data: data,
+ context: this,
+ success: function(json) {
+ Ember.run(this, function(){
+ this.didSaveRecords(store, type, records, json);
+ });
+ }
+ });
+ },
+
+ deleteRecord: function(store, type, record) {
+ var id = get(record, 'id');
+ var root = this.rootForType(type);
+
+ this.ajax(this.buildURL(root, id), "DELETE", {
+ context: this,
+ success: function(json) {
+ Ember.run(this, function(){
+ this.didSaveRecord(store, type, record, json);
+ });
+ }
+ });
+ },
+
+ deleteRecords: function(store, type, records) {
+ if (get(this, 'bulkCommit') === false) {
+ return this._super(store, type, records);
+ }
+
+ var root = this.rootForType(type),
+ plural = this.pluralize(root),
+ serializer = get(this, 'serializer');
+
+ var data = {};
+ data[plural] = [];
+ records.forEach(function(record) {
+ data[plural].push(serializer.serializeId( get(record, 'id') ));
+ });
+
+ this.ajax(this.buildURL(root, 'bulk'), "DELETE", {
+ data: data,
+ context: this,
+ success: function(json) {
+ Ember.run(this, function(){
+ this.didSaveRecords(store, type, records, json);
+ });
+ }
+ });
+ },
+
+ find: function(store, type, id) {
+ var root = this.rootForType(type);
+
+ this.ajax(this.buildURL(root, id), "GET", {
+ success: function(json) {
+ Ember.run(this, function(){
+ this.didFindRecord(store, type, json, id);
+ });
+ }
+ });
+ },
+
+ findAll: function(store, type, since) {
+ var root = this.rootForType(type);
+
+ this.ajax(this.buildURL(root), "GET", {
+ data: this.sinceQuery(since),
+ success: function(json) {
+ Ember.run(this, function(){
+ this.didFindAll(store, type, json);
+ });
+ }
+ });
+ },
+
+ findQuery: function(store, type, query, recordArray) {
+ var root = this.rootForType(type);
+
+ this.ajax(this.buildURL(root), "GET", {
+ data: query,
+ success: function(json) {
+ Ember.run(this, function(){
+ this.didFindQuery(store, type, json, recordArray);
+ });
+ }
+ });
+ },
+
+ findMany: function(store, type, ids, owner) {
+ var root = this.rootForType(type);
+ ids = this.serializeIds(ids);
+
+ this.ajax(this.buildURL(root), "GET", {
+ data: {ids: ids},
+ success: function(json) {
+ Ember.run(this, function(){
+ this.didFindMany(store, type, json);
+ });
+ }
+ });
+ },
+
+ /**
+ @private
+
+ This method serializes a list of IDs using `serializeId`
+
+ @returns {Array} an array of serialized IDs
+ */
+ serializeIds: function(ids) {
+ var serializer = get(this, 'serializer');
+
+ return Ember.EnumerableUtils.map(ids, function(id) {
+ return serializer.serializeId(id);
+ });
+ },
+
+ didError: function(store, type, record, xhr) {
+ if (xhr.status === 422) {
+ var data = JSON.parse(xhr.responseText);
+ store.recordWasInvalid(record, data['errors']);
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+
+ ajax: function(url, type, hash) {
+ hash.url = url;
+ hash.type = type;
+ hash.dataType = 'json';
+ hash.contentType = 'application/json; charset=utf-8';
+ hash.context = this;
+
+ if (hash.data && type !== 'GET') {
+ hash.data = JSON.stringify(hash.data);
+ }
+
+ jQuery.ajax(hash);
+ },
+
+ url: "",
+
+ rootForType: function(type) {
+ var serializer = get(this, 'serializer');
+ return serializer.rootForType(type);
+ },
+
+ pluralize: function(string) {
+ var serializer = get(this, 'serializer');
+ return serializer.pluralize(string);
+ },
+
+ buildURL: function(record, suffix) {
+ var url = [this.url];
+
+ Ember.assert("Namespace URL (" + this.namespace + ") must not start with slash", !this.namespace || this.namespace.toString().charAt(0) !== "/");
+ Ember.assert("Record URL (" + record + ") must not start with slash", !record || record.toString().charAt(0) !== "/");
+ Ember.assert("URL suffix (" + suffix + ") must not start with slash", !suffix || suffix.toString().charAt(0) !== "/");
+
+ if (this.namespace !== undefined) {
+ url.push(this.namespace);
+ }
+
+ url.push(this.pluralize(record));
+ if (suffix !== undefined) {
+ url.push(suffix);
+ }
+
+ return url.join("/");
+ },
+
+ sinceQuery: function(since) {
+ var query = {};
+ query[get(this, 'since')] = since;
+ return since ? query : null;
+ }
+});
+
+
+})();
+
+
+
+(function() {
+var camelize = Ember.String.camelize,
+ capitalize = Ember.String.capitalize,
+ get = Ember.get,
+ map = Ember.ArrayPolyfills.map,
+ registeredTransforms;
+
+var passthruTransform = {
+ serialize: function(value) { return value; },
+ deserialize: function(value) { return value; }
+};
+
+var defaultTransforms = {
+ string: passthruTransform,
+ boolean: passthruTransform,
+ number: passthruTransform
+};
+
+function camelizeKeys(json) {
+ var value;
+
+ for (var prop in json) {
+ value = json[prop];
+ delete json[prop];
+ json[camelize(prop)] = value;
+ }
+}
+
+function munge(json, callback) {
+ callback(json);
+}
+
+function applyTransforms(json, type, transformType) {
+ var transforms = registeredTransforms[transformType];
+
+ Ember.assert("You are trying to apply the '" + transformType + "' transforms, but you didn't register any transforms with that name", transforms);
+
+ get(type, 'attributes').forEach(function(name, attribute) {
+ var attributeType = attribute.type,
+ value = json[name];
+
+ var transform = transforms[attributeType] || defaultTransforms[attributeType];
+
+ Ember.assert("Your model specified the '" + attributeType + "' type for the '" + name + "' attribute, but no transform for that type was registered", transform);
+
+ json[name] = transform.deserialize(value);
+ });
+}
+
+function ObjectProcessor(json, type, store) {
+ this.json = json;
+ this.type = type;
+ this.store = store;
+}
+
+ObjectProcessor.prototype = {
+ camelizeKeys: function() {
+ camelizeKeys(this.json);
+ return this;
+ },
+
+ munge: function(callback) {
+ munge(this.json, callback);
+ return this;
+ },
+
+ applyTransforms: function(transformType) {
+ applyTransforms(this.json, this.type, transformType);
+ return this;
+ }
+};
+
+function LoadObjectProcessor() {
+ ObjectProcessor.apply(this, arguments);
+}
+
+LoadObjectProcessor.prototype = Ember.create(ObjectProcessor.prototype);
+
+LoadObjectProcessor.prototype.load = function() {
+ this.store.load(this.type, {}, this.json);
+};
+
+function loadObjectProcessorFactory(store, type) {
+ return function(json) {
+ return new LoadObjectProcessor(json, type, store);
+ };
+}
+
+function ArrayProcessor(json, type, array, store) {
+ this.json = json;
+ this.type = type;
+ this.array = array;
+ this.store = store;
+}
+
+ArrayProcessor.prototype = {
+ load: function() {
+ var store = this.store,
+ type = this.type;
+
+ var references = this.json.map(function(object) {
+ return store.load(type, {}, object);
+ });
+
+ this.array.load(references);
+ },
+
+ camelizeKeys: function() {
+ this.json.forEach(camelizeKeys);
+ return this;
+ },
+
+ munge: function(callback) {
+ this.json.forEach(function(object) {
+ munge(object, callback);
+ });
+ return this;
+ },
+
+ applyTransforms: function(transformType) {
+ var type = this.type;
+
+ this.json.forEach(function(object) {
+ applyTransforms(object, type, transformType);
+ });
+
+ return this;
+ }
+};
+
+function arrayProcessorFactory(store, type, array) {
+ return function(json) {
+ return new ArrayProcessor(json, type, array, store);
+ };
+}
+
+var HasManyProcessor = function(json, store, record, relationship) {
+ this.json = json;
+ this.store = store;
+ this.record = record;
+ this.type = record.constructor;
+ this.relationship = relationship;
+};
+
+HasManyProcessor.prototype = Ember.create(ArrayProcessor.prototype);
+
+HasManyProcessor.prototype.load = function() {
+ var store = this.store;
+ var ids = map.call(this.json, function(obj) { return obj.id; });
+
+ store.loadMany(this.relationship.type, this.json);
+ store.loadHasMany(this.record, this.relationship.key, ids);
+};
+
+function hasManyProcessorFactory(store, record, relationship) {
+ return function(json) {
+ return new HasManyProcessor(json, store, record, relationship);
+ };
+}
+
+function CreateProcessor(record, store, type) {
+ this.record = record;
+ ObjectProcessor.call(this, record.toJSON(), type, store);
+}
+
+CreateProcessor.prototype = Ember.create(ObjectProcessor.prototype);
+
+CreateProcessor.prototype.save = function() {};
+
+function createProcessorFactory(store, type) {
+ return function(record) {
+ return new CreateProcessor(record, store, type);
+ };
+}
+
+DS.BasicAdapter = DS.Adapter.extend({
+ find: function(store, type, id) {
+ var sync = type.sync;
+
+ Ember.assert("You are trying to use the BasicAdapter to find id '" + id + "' of " + type + " but " + type + ".sync was not found", sync);
+ Ember.assert("The sync code on " + type + " does not implement find(), but you are trying to find id '" + id + "'.", sync.find);
+
+ sync.find(id, loadObjectProcessorFactory(store, type));
+ },
+
+ findQuery: function(store, type, query, recordArray) {
+ var sync = type.sync;
+
+ Ember.assert("You are trying to use the BasicAdapter to query " + type + " but " + type + ".sync was not found", sync);
+ Ember.assert("The sync code on " + type + " does not implement query(), but you are trying to query " + type + ".", sync.query);
+
+ sync.query(query, arrayProcessorFactory(store, type, recordArray));
+ },
+
+ findHasMany: function(store, record, relationship, data) {
+ var name = capitalize(relationship.key),
+ sync = record.constructor.sync,
+ processor = hasManyProcessorFactory(store, record, relationship);
+
+ var options = {
+ relationship: relationship.key,
+ data: data
+ };
+
+ if (sync['find'+name]) {
+ sync['find' + name](record, options, processor);
+ } else if (sync.findHasMany) {
+ sync.findHasMany(record, options, processor);
+ } else {
+ Ember.assert("You are trying to use the BasicAdapter to find the " + relationship.key + " has-many relationship, but " + record.constructor + ".sync did not implement findHasMany or find" + name + ".", false);
+ }
+ },
+
+ createRecord: function(store, type, record) {
+ var sync = type.sync;
+
+ sync.createRecord(record, createProcessorFactory(store, type));
+ }
+});
+
+DS.registerTransforms = function(kind, object) {
+ registeredTransforms[kind] = object;
+};
+
+DS.clearTransforms = function() {
+ registeredTransforms = {};
+};
+
+DS.clearTransforms();
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+//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.
+
+})();
+
diff --git a/js/libs/ember.js b/js/libs/ember.js
new file mode 100644
index 0000000..7376897
--- /dev/null
+++ b/js/libs/ember.js
@@ -0,0 +1,29735 @@
+// Version: v1.0.0-rc.1-223-g5934230
+// Last commit: 5934230 (2013-03-22 22:26:47 -0700)
+
+
+(function() {
+/*global __fail__*/
+
+/**
+Ember Debug
+
+@module ember
+@submodule ember-debug
+*/
+
+/**
+@class Ember
+*/
+
+if ('undefined' === typeof Ember) {
+ Ember = {};
+
+ if ('undefined' !== typeof window) {
+ window.Em = window.Ember = Em = Ember;
+ }
+}
+
+Ember.ENV = 'undefined' === typeof ENV ? {} : ENV;
+
+if (!('MANDATORY_SETTER' in Ember.ENV)) {
+ Ember.ENV.MANDATORY_SETTER = true; // default to true for debug dist
+}
+
+/**
+ Define an assertion that will throw an exception if the condition is not
+ met. Ember build tools will remove any calls to `Ember.assert()` when
+ doing a production build. Example:
+
+ ```javascript
+ // Test for truthiness
+ Ember.assert('Must pass a valid object', obj);
+ // Fail unconditionally
+ Ember.assert('This code path should never be run')
+ ```
+
+ @method assert
+ @param {String} desc A description of the assertion. This will become
+ the text of the Error thrown if the assertion fails.
+ @param {Boolean} test Must be truthy for the assertion to pass. If
+ falsy, an exception will be thrown.
+*/
+Ember.assert = function(desc, test) {
+ if (!test) throw new Error("assertion failed: "+desc);
+};
+
+
+/**
+ Display a warning with the provided message. Ember build tools will
+ remove any calls to `Ember.warn()` when doing a production build.
+
+ @method warn
+ @param {String} message A warning to display.
+ @param {Boolean} test An optional boolean. If falsy, the warning
+ will be displayed.
+*/
+Ember.warn = function(message, test) {
+ if (!test) {
+ Ember.Logger.warn("WARNING: "+message);
+ if ('trace' in Ember.Logger) Ember.Logger.trace();
+ }
+};
+
+/**
+ Display a debug notice. Ember build tools will remove any calls to
+ `Ember.debug()` when doing a production build.
+
+ ```javascript
+ Ember.debug("I'm a debug notice!");
+ ```
+
+ @method debug
+ @param {String} message A debug message to display.
+*/
+Ember.debug = function(message) {
+ Ember.Logger.debug("DEBUG: "+message);
+};
+
+/**
+ Display a deprecation warning with the provided message and a stack trace
+ (Chrome and Firefox only). Ember build tools will remove any calls to
+ `Ember.deprecate()` when doing a production build.
+
+ @method deprecate
+ @param {String} message A description of the deprecation.
+ @param {Boolean} test An optional boolean. If falsy, the deprecation
+ will be displayed.
+*/
+Ember.deprecate = function(message, test) {
+ if (Ember && Ember.TESTING_DEPRECATION) { return; }
+
+ if (arguments.length === 1) { test = false; }
+ if (test) { return; }
+
+ if (Ember && Ember.ENV.RAISE_ON_DEPRECATION) { throw new Error(message); }
+
+ var error;
+
+ // When using new Error, we can't do the arguments check for Chrome. Alternatives are welcome
+ try { __fail__.fail(); } catch (e) { error = e; }
+
+ if (Ember.LOG_STACKTRACE_ON_DEPRECATION && error.stack) {
+ var stack, stackStr = '';
+ if (error['arguments']) {
+ // Chrome
+ stack = error.stack.replace(/^\s+at\s+/gm, '').
+ replace(/^([^\(]+?)([\n$])/gm, '{anonymous}($1)$2').
+ replace(/^Object.\s*\(([^\)]+)\)/gm, '{anonymous}($1)').split('\n');
+ stack.shift();
+ } else {
+ // Firefox
+ stack = error.stack.replace(/(?:\n@:0)?\s+$/m, '').
+ replace(/^\(/gm, '{anonymous}(').split('\n');
+ }
+
+ stackStr = "\n " + stack.slice(2).join("\n ");
+ message = message + stackStr;
+ }
+
+ Ember.Logger.warn("DEPRECATION: "+message);
+};
+
+
+
+/**
+ Display a deprecation warning with the provided message and a stack trace
+ (Chrome and Firefox only) when the wrapped method is called.
+
+ Ember build tools will not remove calls to `Ember.deprecateFunc()`, though
+ no warnings will be shown in production.
+
+ @method deprecateFunc
+ @param {String} message A description of the deprecation.
+ @param {Function} func The function to be deprecated.
+ @return {Function} a new function that wrapped the original function with a deprecation warning
+*/
+Ember.deprecateFunc = function(message, func) {
+ return function() {
+ Ember.deprecate(message);
+ return func.apply(this, arguments);
+ };
+};
+
+})();
+
+// Version: v1.0.0-rc.1-223-g5934230
+// Last commit: 5934230 (2013-03-22 22:26:47 -0700)
+
+
+(function() {
+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],
+ deps = mod.deps,
+ callback = mod.callback,
+ reified = [],
+ exports;
+
+ for (var i=0, l=deps.length; i= 0) {
+ intersection.push(element);
+ }
+ });
+
+ return intersection;
+ }
+};
+
+})();
+
+
+
+(function() {
+/*jshint newcap:false*/
+/**
+@module ember-metal
+*/
+
+// NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new`
+// as being ok unless both `newcap:false` and not `use strict`.
+// https://github.com/jshint/jshint/issues/392
+
+// Testing this is not ideal, but we want to use native functions
+// if available, but not to use versions created by libraries like Prototype
+var isNativeFunc = function(func) {
+ // This should probably work in all browsers likely to have ES5 array methods
+ return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1;
+};
+
+// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
+var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) {
+ //"use strict";
+
+ if (this === void 0 || this === null) {
+ throw new TypeError();
+ }
+
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (typeof fun !== "function") {
+ throw new TypeError();
+ }
+
+ var res = new Array(len);
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (i in t) {
+ res[i] = fun.call(thisp, t[i], i, t);
+ }
+ }
+
+ return res;
+};
+
+// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
+var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) {
+ //"use strict";
+
+ if (this === void 0 || this === null) {
+ throw new TypeError();
+ }
+
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (typeof fun !== "function") {
+ throw new TypeError();
+ }
+
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (i in t) {
+ fun.call(thisp, t[i], i, t);
+ }
+ }
+};
+
+var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) {
+ if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
+ else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
+ for (var i = fromIndex, j = this.length; i < j; i++) {
+ if (this[i] === obj) { return i; }
+ }
+ return -1;
+};
+
+Ember.ArrayPolyfills = {
+ map: arrayMap,
+ forEach: arrayForEach,
+ indexOf: arrayIndexOf
+};
+
+if (Ember.SHIM_ES5) {
+ if (!Array.prototype.map) {
+ Array.prototype.map = arrayMap;
+ }
+
+ if (!Array.prototype.forEach) {
+ Array.prototype.forEach = arrayForEach;
+ }
+
+ if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = arrayIndexOf;
+ }
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+/*
+ JavaScript (before ES6) does not have a Map implementation. Objects,
+ which are often used as dictionaries, may only have Strings as keys.
+
+ Because Ember has a way to get a unique identifier for every object
+ via `Ember.guidFor`, we can implement a performant Map with arbitrary
+ keys. Because it is commonly used in low-level bookkeeping, Map is
+ implemented as a pure JavaScript object for performance.
+
+ This implementation follows the current iteration of the ES6 proposal for
+ maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets),
+ with two exceptions. First, because we need our implementation to be pleasant
+ on older browsers, we do not use the `delete` name (using `remove` instead).
+ Second, as we do not have the luxury of in-VM iteration, we implement a
+ forEach method for iteration.
+
+ Map is mocked out to look like an Ember object, so you can do
+ `Ember.Map.create()` for symmetry with other Ember classes.
+*/
+var guidFor = Ember.guidFor,
+ indexOf = Ember.ArrayPolyfills.indexOf;
+
+var copy = function(obj) {
+ var output = {};
+
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) { output[prop] = obj[prop]; }
+ }
+
+ return output;
+};
+
+var copyMap = function(original, newObject) {
+ var keys = original.keys.copy(),
+ values = copy(original.values);
+
+ newObject.keys = keys;
+ newObject.values = values;
+
+ return newObject;
+};
+
+/**
+ This class is used internally by Ember and Ember Data.
+ Please do not use it at this time. We plan to clean it up
+ and add many tests soon.
+
+ @class OrderedSet
+ @namespace Ember
+ @constructor
+ @private
+*/
+var OrderedSet = Ember.OrderedSet = function() {
+ this.clear();
+};
+
+/**
+ @method create
+ @static
+ @return {Ember.OrderedSet}
+*/
+OrderedSet.create = function() {
+ return new OrderedSet();
+};
+
+
+OrderedSet.prototype = {
+ /**
+ @method clear
+ */
+ clear: function() {
+ this.presenceSet = {};
+ this.list = [];
+ },
+
+ /**
+ @method add
+ @param obj
+ */
+ add: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet,
+ list = this.list;
+
+ if (guid in presenceSet) { return; }
+
+ presenceSet[guid] = true;
+ list.push(obj);
+ },
+
+ /**
+ @method remove
+ @param obj
+ */
+ remove: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet,
+ list = this.list;
+
+ delete presenceSet[guid];
+
+ var index = indexOf.call(list, obj);
+ if (index > -1) {
+ list.splice(index, 1);
+ }
+ },
+
+ /**
+ @method isEmpty
+ @return {Boolean}
+ */
+ isEmpty: function() {
+ return this.list.length === 0;
+ },
+
+ /**
+ @method has
+ @param obj
+ @return {Boolean}
+ */
+ has: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet;
+
+ return guid in presenceSet;
+ },
+
+ /**
+ @method forEach
+ @param {Function} fn
+ @param self
+ */
+ forEach: function(fn, self) {
+ // allow mutation during iteration
+ var list = this.list.slice();
+
+ for (var i = 0, j = list.length; i < j; i++) {
+ fn.call(self, list[i]);
+ }
+ },
+
+ /**
+ @method toArray
+ @return {Array}
+ */
+ toArray: function() {
+ return this.list.slice();
+ },
+
+ /**
+ @method copy
+ @return {Ember.OrderedSet}
+ */
+ copy: function() {
+ var set = new OrderedSet();
+
+ set.presenceSet = copy(this.presenceSet);
+ set.list = this.list.slice();
+
+ return set;
+ }
+};
+
+/**
+ A Map stores values indexed by keys. Unlike JavaScript's
+ default Objects, the keys of a Map can be any JavaScript
+ object.
+
+ Internally, a Map has two data structures:
+
+ 1. `keys`: an OrderedSet of all of the existing keys
+ 2. `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`.
+
+ @class Map
+ @namespace Ember
+ @private
+ @constructor
+*/
+var Map = Ember.Map = function() {
+ this.keys = Ember.OrderedSet.create();
+ this.values = {};
+};
+
+/**
+ @method create
+ @static
+*/
+Map.create = function() {
+ return new Map();
+};
+
+Map.prototype = {
+ /**
+ Retrieve the value associated with a given key.
+
+ @method get
+ @param {anything} key
+ @return {anything} the value associated with the key, or `undefined`
+ */
+ get: function(key) {
+ var values = this.values,
+ guid = guidFor(key);
+
+ return values[guid];
+ },
+
+ /**
+ Adds a value to the map. If a value for the given key has already been
+ provided, the new value will replace the old value.
+
+ @method set
+ @param {anything} key
+ @param {anything} value
+ */
+ set: function(key, value) {
+ var keys = this.keys,
+ values = this.values,
+ guid = guidFor(key);
+
+ keys.add(key);
+ values[guid] = value;
+ },
+
+ /**
+ Removes a value from the map for an associated key.
+
+ @method remove
+ @param {anything} key
+ @return {Boolean} true if an item was removed, false otherwise
+ */
+ remove: function(key) {
+ // don't use ES6 "delete" because it will be annoying
+ // to use in browsers that are not ES6 friendly;
+ var keys = this.keys,
+ values = this.values,
+ guid = guidFor(key),
+ value;
+
+ if (values.hasOwnProperty(guid)) {
+ keys.remove(key);
+ value = values[guid];
+ delete values[guid];
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ Check whether a key is present.
+
+ @method has
+ @param {anything} key
+ @return {Boolean} true if the item was present, false otherwise
+ */
+ has: function(key) {
+ var values = this.values,
+ guid = guidFor(key);
+
+ return values.hasOwnProperty(guid);
+ },
+
+ /**
+ Iterate over all the keys and values. Calls the function once
+ for each key, passing in the key and value, in that order.
+
+ The keys are guaranteed to be iterated over in insertion order.
+
+ @method forEach
+ @param {Function} callback
+ @param {anything} self if passed, the `this` value inside the
+ callback. By default, `this` is the map.
+ */
+ forEach: function(callback, self) {
+ var keys = this.keys,
+ values = this.values;
+
+ keys.forEach(function(key) {
+ var guid = guidFor(key);
+ callback.call(self, key, values[guid]);
+ });
+ },
+
+ /**
+ @method copy
+ @return {Ember.Map}
+ */
+ copy: function() {
+ return copyMap(this, new Map());
+ }
+};
+
+/**
+ @class MapWithDefault
+ @namespace Ember
+ @extends Ember.Map
+ @private
+ @constructor
+ @param [options]
+ @param {anything} [options.defaultValue]
+*/
+var MapWithDefault = Ember.MapWithDefault = function(options) {
+ Map.call(this);
+ this.defaultValue = options.defaultValue;
+};
+
+/**
+ @method create
+ @static
+ @param [options]
+ @param {anything} [options.defaultValue]
+ @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
+ `Ember.MapWithDefault` otherwise returns `Ember.Map`
+*/
+MapWithDefault.create = function(options) {
+ if (options) {
+ return new MapWithDefault(options);
+ } else {
+ return new Map();
+ }
+};
+
+MapWithDefault.prototype = Ember.create(Map.prototype);
+
+/**
+ Retrieve the value associated with a given key.
+
+ @method get
+ @param {anything} key
+ @return {anything} the value associated with the key, or the default value
+*/
+MapWithDefault.prototype.get = function(key) {
+ var hasValue = this.has(key);
+
+ if (hasValue) {
+ return Map.prototype.get.call(this, key);
+ } else {
+ var defaultValue = this.defaultValue(key);
+ this.set(key, defaultValue);
+ return defaultValue;
+ }
+};
+
+/**
+ @method copy
+ @return {Ember.MapWithDefault}
+*/
+MapWithDefault.prototype.copy = function() {
+ return copyMap(this, new MapWithDefault({
+ defaultValue: this.defaultValue
+ }));
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var META_KEY = Ember.META_KEY, get, set;
+
+var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
+
+var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
+var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
+var HAS_THIS = /^this[\.\*]/;
+var FIRST_KEY = /^([^\.\*]+)/;
+
+// ..........................................................
+// GET AND SET
+//
+// If we are on a platform that supports accessors we can use those.
+// Otherwise simulate accessors by looking up the property directly on the
+// object.
+
+/**
+ Gets the value of a property on an object. If the property is computed,
+ the function will be invoked. If the property is not defined but the
+ object implements the `unknownProperty` method then that will be invoked.
+
+ If you plan to run on IE8 and older browsers then you should use this
+ method anytime you want to retrieve a property on an object that you don't
+ know for sure is private. (Properties beginning with an underscore '_'
+ are considered private.)
+
+ On all newer browsers, you only need to use this method to retrieve
+ properties if the property might not be defined on the object and you want
+ to respect the `unknownProperty` handler. Otherwise you can ignore this
+ method.
+
+ Note that if the object itself is `undefined`, this method will throw
+ an error.
+
+ @method get
+ @for Ember
+ @param {Object} obj The object to retrieve from.
+ @param {String} keyName The property key to retrieve
+ @return {Object} the property value or `null`.
+*/
+get = function get(obj, keyName) {
+ // Helpers that operate with 'this' within an #each
+ if (keyName === '') {
+ return obj;
+ }
+
+ if (!keyName && 'string'===typeof obj) {
+ keyName = obj;
+ obj = null;
+ }
+
+ if (!obj || keyName.indexOf('.') !== -1) {
+ Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);
+ return getPath(obj, keyName);
+ }
+
+ Ember.assert("You need to provide an object and key to `get`.", !!obj && keyName);
+
+ var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
+ if (desc) {
+ return desc.get(obj, keyName);
+ } else {
+ if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) {
+ ret = meta.values[keyName];
+ } else {
+ ret = obj[keyName];
+ }
+
+ if (ret === undefined &&
+ 'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
+ return obj.unknownProperty(keyName);
+ }
+
+ return ret;
+ }
+};
+
+/**
+ Sets the value of a property on an object, respecting computed properties
+ and notifying observers and other listeners of the change. If the
+ property is not defined but the object implements the `unknownProperty`
+ method then that will be invoked as well.
+
+ If you plan to run on IE8 and older browsers then you should use this
+ method anytime you want to set a property on an object that you don't
+ know for sure is private. (Properties beginning with an underscore '_'
+ are considered private.)
+
+ On all newer browsers, you only need to use this method to set
+ properties if the property might not be defined on the object and you want
+ to respect the `unknownProperty` handler. Otherwise you can ignore this
+ method.
+
+ @method set
+ @for Ember
+ @param {Object} obj The object to modify.
+ @param {String} keyName The property key to set
+ @param {Object} value The value to set
+ @return {Object} the passed value.
+*/
+set = function set(obj, keyName, value, tolerant) {
+ if (typeof obj === 'string') {
+ Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
+ value = keyName;
+ keyName = obj;
+ obj = null;
+ }
+
+ if (!obj || keyName.indexOf('.') !== -1) {
+ return setPath(obj, keyName, value, tolerant);
+ }
+
+ Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
+ Ember.assert('calling set on destroyed object', !obj.isDestroyed);
+
+ var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
+ isUnknown, currentValue;
+ if (desc) {
+ desc.set(obj, keyName, value);
+ } else {
+ isUnknown = 'object' === typeof obj && !(keyName in obj);
+
+ // setUnknownProperty is called if `obj` is an object,
+ // the property does not already exist, and the
+ // `setUnknownProperty` method exists on the object
+ if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
+ obj.setUnknownProperty(keyName, value);
+ } else if (meta && meta.watching[keyName] > 0) {
+ if (MANDATORY_SETTER) {
+ currentValue = meta.values[keyName];
+ } else {
+ currentValue = obj[keyName];
+ }
+ // only trigger a change if the value has changed
+ if (value !== currentValue) {
+ Ember.propertyWillChange(obj, keyName);
+ if (MANDATORY_SETTER) {
+ if (currentValue === undefined && !(keyName in obj)) {
+ Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
+ } else {
+ meta.values[keyName] = value;
+ }
+ } else {
+ obj[keyName] = value;
+ }
+ Ember.propertyDidChange(obj, keyName);
+ }
+ } else {
+ obj[keyName] = value;
+ }
+ }
+ return value;
+};
+
+// Currently used only by Ember Data tests
+if (Ember.config.overrideAccessors) {
+ Ember.get = get;
+ Ember.set = set;
+ Ember.config.overrideAccessors();
+ get = Ember.get;
+ set = Ember.set;
+}
+
+function firstKey(path) {
+ return path.match(FIRST_KEY)[0];
+}
+
+// assumes path is already normalized
+function normalizeTuple(target, path) {
+ var hasThis = HAS_THIS.test(path),
+ isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
+ key;
+
+ if (!target || isGlobal) target = Ember.lookup;
+ if (hasThis) path = path.slice(5);
+
+ if (target === Ember.lookup) {
+ key = firstKey(path);
+ target = get(target, key);
+ path = path.slice(key.length+1);
+ }
+
+ // must return some kind of path to be valid else other things will break.
+ if (!path || path.length===0) throw new Error('Invalid Path');
+
+ return [ target, path ];
+}
+
+function getPath(root, path) {
+ var hasThis, parts, tuple, idx, len;
+
+ // If there is no root and path is a key name, return that
+ // property from the global object.
+ // E.g. get('Ember') -> Ember
+ if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); }
+
+ // detect complicated paths and normalize them
+ hasThis = HAS_THIS.test(path);
+
+ if (!root || hasThis) {
+ tuple = normalizeTuple(root, path);
+ root = tuple[0];
+ path = tuple[1];
+ tuple.length = 0;
+ }
+
+ parts = path.split(".");
+ len = parts.length;
+ for (idx=0; root && idx 0;
+
+ if (existingDesc instanceof Ember.Descriptor) {
+ existingDesc.teardown(obj, keyName);
+ }
+
+ if (desc instanceof Ember.Descriptor) {
+ value = desc;
+
+ descs[keyName] = desc;
+ if (MANDATORY_SETTER && watching) {
+ objectDefineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: undefined // make enumerable
+ });
+ } else {
+ obj[keyName] = undefined; // make enumerable
+ }
+ desc.setup(obj, keyName);
+ } else {
+ descs[keyName] = undefined; // shadow descriptor in proto
+ if (desc == null) {
+ value = data;
+
+ if (MANDATORY_SETTER && watching) {
+ meta.values[keyName] = data;
+ objectDefineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ set: MANDATORY_SETTER_FUNCTION,
+ get: DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ } else {
+ obj[keyName] = data;
+ }
+ } else {
+ value = desc;
+
+ // compatibility with ES5
+ objectDefineProperty(obj, keyName, desc);
+ }
+ }
+
+ // if key is being watched, override chains that
+ // were initialized with the prototype
+ if (watching) { Ember.overrideChains(obj, keyName, meta); }
+
+ // The `value` passed to the `didDefineProperty` hook is
+ // either the descriptor or data, whichever was passed.
+ if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }
+
+ return this;
+};
+
+
+})();
+
+
+
+(function() {
+// Ember.tryFinally
+/**
+@module ember-metal
+*/
+
+var AFTER_OBSERVERS = ':change';
+var BEFORE_OBSERVERS = ':before';
+
+var guidFor = Ember.guidFor;
+
+var deferred = 0;
+
+/*
+ this.observerSet = {
+ [senderGuid]: { // variable name: `keySet`
+ [keyName]: listIndex
+ }
+ },
+ this.observers = [
+ {
+ sender: obj,
+ keyName: keyName,
+ eventName: eventName,
+ listeners: [
+ [target, method, onceFlag, suspendedFlag]
+ ]
+ },
+ ...
+ ]
+*/
+function ObserverSet() {
+ this.clear();
+}
+
+ObserverSet.prototype.add = function(sender, keyName, eventName) {
+ var observerSet = this.observerSet,
+ observers = this.observers,
+ senderGuid = Ember.guidFor(sender),
+ keySet = observerSet[senderGuid],
+ index;
+
+ if (!keySet) {
+ observerSet[senderGuid] = keySet = {};
+ }
+ index = keySet[keyName];
+ if (index === undefined) {
+ index = observers.push({
+ sender: sender,
+ keyName: keyName,
+ eventName: eventName,
+ listeners: []
+ }) - 1;
+ keySet[keyName] = index;
+ }
+ return observers[index].listeners;
+};
+
+ObserverSet.prototype.flush = function() {
+ var observers = this.observers, i, len, observer, sender;
+ this.clear();
+ for (i=0, len=observers.length; i < len; ++i) {
+ observer = observers[i];
+ sender = observer.sender;
+ if (sender.isDestroying || sender.isDestroyed) { continue; }
+ Ember.sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
+ }
+};
+
+ObserverSet.prototype.clear = function() {
+ this.observerSet = {};
+ this.observers = [];
+};
+
+var beforeObserverSet = new ObserverSet(), observerSet = new ObserverSet();
+
+/**
+ @method beginPropertyChanges
+ @chainable
+*/
+Ember.beginPropertyChanges = function() {
+ deferred++;
+};
+
+/**
+ @method endPropertyChanges
+*/
+Ember.endPropertyChanges = function() {
+ deferred--;
+ if (deferred<=0) {
+ beforeObserverSet.clear();
+ observerSet.flush();
+ }
+};
+
+/**
+ Make a series of property changes together in an
+ exception-safe way.
+
+ ```javascript
+ Ember.changeProperties(function() {
+ obj1.set('foo', mayBlowUpWhenSet);
+ obj2.set('bar', baz);
+ });
+ ```
+
+ @method changeProperties
+ @param {Function} callback
+ @param [binding]
+*/
+Ember.changeProperties = function(cb, binding){
+ Ember.beginPropertyChanges();
+ Ember.tryFinally(cb, Ember.endPropertyChanges, binding);
+};
+
+/**
+ Set a list of properties on an object. These properties are set inside
+ a single `beginPropertyChanges` and `endPropertyChanges` batch, so
+ observers will be buffered.
+
+ @method setProperties
+ @param target
+ @param {Hash} properties
+ @return target
+*/
+Ember.setProperties = function(self, hash) {
+ Ember.changeProperties(function(){
+ for(var prop in hash) {
+ if (hash.hasOwnProperty(prop)) Ember.set(self, prop, hash[prop]);
+ }
+ });
+ return self;
+};
+
+
+function changeEvent(keyName) {
+ return keyName+AFTER_OBSERVERS;
+}
+
+function beforeEvent(keyName) {
+ return keyName+BEFORE_OBSERVERS;
+}
+
+/**
+ @method addObserver
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.addObserver = function(obj, path, target, method) {
+ Ember.addListener(obj, changeEvent(path), target, method);
+ Ember.watch(obj, path);
+ return this;
+};
+
+Ember.observersFor = function(obj, path) {
+ return Ember.listenersFor(obj, changeEvent(path));
+};
+
+/**
+ @method removeObserver
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.removeObserver = function(obj, path, target, method) {
+ Ember.unwatch(obj, path);
+ Ember.removeListener(obj, changeEvent(path), target, method);
+ return this;
+};
+
+/**
+ @method addBeforeObserver
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.addBeforeObserver = function(obj, path, target, method) {
+ Ember.addListener(obj, beforeEvent(path), target, method);
+ Ember.watch(obj, path);
+ return this;
+};
+
+// Suspend observer during callback.
+//
+// This should only be used by the target of the observer
+// while it is setting the observed path.
+Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
+};
+
+Ember._suspendObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
+};
+
+var map = Ember.ArrayPolyfills.map;
+
+Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, beforeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
+
+Ember._suspendObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, changeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
+
+Ember.beforeObserversFor = function(obj, path) {
+ return Ember.listenersFor(obj, beforeEvent(path));
+};
+
+/**
+ @method removeBeforeObserver
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.removeBeforeObserver = function(obj, path, target, method) {
+ Ember.unwatch(obj, path);
+ Ember.removeListener(obj, beforeEvent(path), target, method);
+ return this;
+};
+
+Ember.notifyBeforeObservers = function(obj, keyName) {
+ if (obj.isDestroying) { return; }
+
+ var eventName = beforeEvent(keyName), listeners, listenersDiff;
+ if (deferred) {
+ listeners = beforeObserverSet.add(obj, keyName, eventName);
+ listenersDiff = Ember.listenersDiff(obj, eventName, listeners);
+ Ember.sendEvent(obj, eventName, [obj, keyName], listenersDiff);
+ } else {
+ Ember.sendEvent(obj, eventName, [obj, keyName]);
+ }
+};
+
+Ember.notifyObservers = function(obj, keyName) {
+ if (obj.isDestroying) { return; }
+
+ var eventName = changeEvent(keyName), listeners;
+ if (deferred) {
+ listeners = observerSet.add(obj, keyName, eventName);
+ Ember.listenersUnion(obj, eventName, listeners);
+ } else {
+ Ember.sendEvent(obj, eventName, [obj, keyName]);
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var guidFor = Ember.guidFor, // utils.js
+ metaFor = Ember.meta, // utils.js
+ get = Ember.get, // accessors.js
+ set = Ember.set, // accessors.js
+ normalizeTuple = Ember.normalizeTuple, // accessors.js
+ GUID_KEY = Ember.GUID_KEY, // utils.js
+ META_KEY = Ember.META_KEY, // utils.js
+ // circular reference observer depends on Ember.watch
+ // we should move change events to this file or its own property_events.js
+ forEach = Ember.ArrayPolyfills.forEach, // array.js
+ FIRST_KEY = /^([^\.\*]+)/,
+ IS_PATH = /[\.\*]/;
+
+var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+o_defineProperty = Ember.platform.defineProperty;
+
+function firstKey(path) {
+ return path.match(FIRST_KEY)[0];
+}
+
+// returns true if the passed path is just a keyName
+function isKeyName(path) {
+ return path==='*' || !IS_PATH.test(path);
+}
+
+// ..........................................................
+// DEPENDENT KEYS
+//
+
+function iterDeps(method, obj, depKey, seen, meta) {
+
+ var guid = guidFor(obj);
+ if (!seen[guid]) seen[guid] = {};
+ if (seen[guid][depKey]) return;
+ seen[guid][depKey] = true;
+
+ var deps = meta.deps;
+ deps = deps && deps[depKey];
+ if (deps) {
+ for(var key in deps) {
+ var desc = meta.descs[key];
+ if (desc && desc._suspended === obj) continue;
+ method(obj, key);
+ }
+ }
+}
+
+
+var WILL_SEEN, DID_SEEN;
+
+// called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
+function dependentKeysWillChange(obj, depKey, meta) {
+ if (obj.isDestroying) { return; }
+
+ var seen = WILL_SEEN, top = !seen;
+ if (top) { seen = WILL_SEEN = {}; }
+ iterDeps(propertyWillChange, obj, depKey, seen, meta);
+ if (top) { WILL_SEEN = null; }
+}
+
+// called whenever a property has just changed to update dependent keys
+function dependentKeysDidChange(obj, depKey, meta) {
+ if (obj.isDestroying) { return; }
+
+ var seen = DID_SEEN, top = !seen;
+ if (top) { seen = DID_SEEN = {}; }
+ iterDeps(propertyDidChange, obj, depKey, seen, meta);
+ if (top) { DID_SEEN = null; }
+}
+
+// ..........................................................
+// CHAIN
+//
+
+function addChainWatcher(obj, keyName, node) {
+ if (!obj || ('object' !== typeof obj)) { return; } // nothing to do
+
+ var m = metaFor(obj), nodes = m.chainWatchers;
+
+ if (!m.hasOwnProperty('chainWatchers')) {
+ nodes = m.chainWatchers = {};
+ }
+
+ if (!nodes[keyName]) { nodes[keyName] = []; }
+ nodes[keyName].push(node);
+ Ember.watch(obj, keyName);
+}
+
+function removeChainWatcher(obj, keyName, node) {
+ if (!obj || 'object' !== typeof obj) { return; } // nothing to do
+
+ var m = metaFor(obj, false);
+ if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
+
+ var nodes = m.chainWatchers;
+
+ if (nodes[keyName]) {
+ nodes = nodes[keyName];
+ for (var i = 0, l = nodes.length; i < l; i++) {
+ if (nodes[i] === node) { nodes.splice(i, 1); }
+ }
+ }
+ Ember.unwatch(obj, keyName);
+}
+
+var pendingQueue = [];
+
+// attempts to add the pendingQueue chains again. If some of them end up
+// back in the queue and reschedule is true, schedules a timeout to try
+// again.
+function flushPendingChains() {
+ if (pendingQueue.length === 0) { return; } // nothing to do
+
+ var queue = pendingQueue;
+ pendingQueue = [];
+
+ forEach.call(queue, function(q) { q[0].add(q[1]); });
+
+ Ember.warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0);
+}
+
+function isProto(pvalue) {
+ return metaFor(pvalue, false).proto === pvalue;
+}
+
+// A ChainNode watches a single key on an object. If you provide a starting
+// value for the key then the node won't actually watch it. For a root node
+// pass null for parent and key and object for value.
+var ChainNode = function(parent, key, value) {
+ var obj;
+ this._parent = parent;
+ this._key = key;
+
+ // _watching is true when calling get(this._parent, this._key) will
+ // return the value of this node.
+ //
+ // It is false for the root of a chain (because we have no parent)
+ // and for global paths (because the parent node is the object with
+ // the observer on it)
+ this._watching = value===undefined;
+
+ this._value = value;
+ this._paths = {};
+ if (this._watching) {
+ this._object = parent.value();
+ if (this._object) { addChainWatcher(this._object, this._key, this); }
+ }
+
+ // Special-case: the EachProxy relies on immediate evaluation to
+ // establish its observers.
+ //
+ // TODO: Replace this with an efficient callback that the EachProxy
+ // can implement.
+ if (this._parent && this._parent._key === '@each') {
+ this.value();
+ }
+};
+
+var ChainNodePrototype = ChainNode.prototype;
+
+ChainNodePrototype.value = function() {
+ if (this._value === undefined && this._watching) {
+ var obj = this._parent.value();
+ this._value = (obj && !isProto(obj)) ? get(obj, this._key) : undefined;
+ }
+ return this._value;
+};
+
+ChainNodePrototype.destroy = function() {
+ if (this._watching) {
+ var obj = this._object;
+ if (obj) { removeChainWatcher(obj, this._key, this); }
+ this._watching = false; // so future calls do nothing
+ }
+};
+
+// copies a top level object only
+ChainNodePrototype.copy = function(obj) {
+ var ret = new ChainNode(null, null, obj),
+ paths = this._paths, path;
+ for (path in paths) {
+ if (paths[path] <= 0) { continue; } // this check will also catch non-number vals.
+ ret.add(path);
+ }
+ return ret;
+};
+
+// called on the root node of a chain to setup watchers on the specified
+// path.
+ChainNodePrototype.add = function(path) {
+ var obj, tuple, key, src, paths;
+
+ paths = this._paths;
+ paths[path] = (paths[path] || 0) + 1;
+
+ obj = this.value();
+ tuple = normalizeTuple(obj, path);
+
+ // the path was a local path
+ if (tuple[0] && tuple[0] === obj) {
+ path = tuple[1];
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+
+ // global path, but object does not exist yet.
+ // put into a queue and try to connect later.
+ } else if (!tuple[0]) {
+ pendingQueue.push([this, path]);
+ tuple.length = 0;
+ return;
+
+ // global path, and object already exists
+ } else {
+ src = tuple[0];
+ key = path.slice(0, 0-(tuple[1].length+1));
+ path = tuple[1];
+ }
+
+ tuple.length = 0;
+ this.chain(key, path, src);
+};
+
+// called on the root node of a chain to teardown watcher on the specified
+// path
+ChainNodePrototype.remove = function(path) {
+ var obj, tuple, key, src, paths;
+
+ paths = this._paths;
+ if (paths[path] > 0) { paths[path]--; }
+
+ obj = this.value();
+ tuple = normalizeTuple(obj, path);
+ if (tuple[0] === obj) {
+ path = tuple[1];
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ } else {
+ src = tuple[0];
+ key = path.slice(0, 0-(tuple[1].length+1));
+ path = tuple[1];
+ }
+
+ tuple.length = 0;
+ this.unchain(key, path);
+};
+
+ChainNodePrototype.count = 0;
+
+ChainNodePrototype.chain = function(key, path, src) {
+ var chains = this._chains, node;
+ if (!chains) { chains = this._chains = {}; }
+
+ node = chains[key];
+ if (!node) { node = chains[key] = new ChainNode(this, key, src); }
+ node.count++; // count chains...
+
+ // chain rest of path if there is one
+ if (path && path.length>0) {
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ node.chain(key, path); // NOTE: no src means it will observe changes...
+ }
+};
+
+ChainNodePrototype.unchain = function(key, path) {
+ var chains = this._chains, node = chains[key];
+
+ // unchain rest of path first...
+ if (path && path.length>1) {
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ node.unchain(key, path);
+ }
+
+ // delete node if needed.
+ node.count--;
+ if (node.count<=0) {
+ delete chains[node._key];
+ node.destroy();
+ }
+
+};
+
+ChainNodePrototype.willChange = function() {
+ var chains = this._chains;
+ if (chains) {
+ for(var key in chains) {
+ if (!chains.hasOwnProperty(key)) { continue; }
+ chains[key].willChange();
+ }
+ }
+
+ if (this._parent) { this._parent.chainWillChange(this, this._key, 1); }
+};
+
+ChainNodePrototype.chainWillChange = function(chain, path, depth) {
+ if (this._key) { path = this._key + '.' + path; }
+
+ if (this._parent) {
+ this._parent.chainWillChange(this, path, depth+1);
+ } else {
+ if (depth > 1) { Ember.propertyWillChange(this.value(), path); }
+ path = 'this.' + path;
+ if (this._paths[path] > 0) { Ember.propertyWillChange(this.value(), path); }
+ }
+};
+
+ChainNodePrototype.chainDidChange = function(chain, path, depth) {
+ if (this._key) { path = this._key + '.' + path; }
+ if (this._parent) {
+ this._parent.chainDidChange(this, path, depth+1);
+ } else {
+ if (depth > 1) { Ember.propertyDidChange(this.value(), path); }
+ path = 'this.' + path;
+ if (this._paths[path] > 0) { Ember.propertyDidChange(this.value(), path); }
+ }
+};
+
+ChainNodePrototype.didChange = function(suppressEvent) {
+ // invalidate my own value first.
+ if (this._watching) {
+ var obj = this._parent.value();
+ if (obj !== this._object) {
+ removeChainWatcher(this._object, this._key, this);
+ this._object = obj;
+ addChainWatcher(obj, this._key, this);
+ }
+ this._value = undefined;
+
+ // Special-case: the EachProxy relies on immediate evaluation to
+ // establish its observers.
+ if (this._parent && this._parent._key === '@each')
+ this.value();
+ }
+
+ // then notify chains...
+ var chains = this._chains;
+ if (chains) {
+ for(var key in chains) {
+ if (!chains.hasOwnProperty(key)) { continue; }
+ chains[key].didChange(suppressEvent);
+ }
+ }
+
+ if (suppressEvent) { return; }
+
+ // and finally tell parent about my path changing...
+ if (this._parent) { this._parent.chainDidChange(this, this._key, 1); }
+};
+
+// get the chains for the current object. If the current object has
+// chains inherited from the proto they will be cloned and reconfigured for
+// the current object.
+function chainsFor(obj) {
+ var m = metaFor(obj), ret = m.chains;
+ if (!ret) {
+ ret = m.chains = new ChainNode(null, null, obj);
+ } else if (ret.value() !== obj) {
+ ret = m.chains = ret.copy(obj);
+ }
+ return ret;
+}
+
+Ember.overrideChains = function(obj, keyName, m) {
+ chainsDidChange(obj, keyName, m, true);
+};
+
+function chainsWillChange(obj, keyName, m, arg) {
+ if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
+
+ var nodes = m.chainWatchers;
+
+ nodes = nodes[keyName];
+ if (!nodes) { return; }
+
+ for(var i = 0, l = nodes.length; i < l; i++) {
+ nodes[i].willChange(arg);
+ }
+}
+
+function chainsDidChange(obj, keyName, m, arg) {
+ if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
+
+ var nodes = m.chainWatchers;
+
+ nodes = nodes[keyName];
+ if (!nodes) { return; }
+
+ // looping in reverse because the chainWatchers array can be modified inside didChange
+ for (var i = nodes.length - 1; i >= 0; i--) {
+ nodes[i].didChange(arg);
+ }
+}
+
+// ..........................................................
+// WATCH
+//
+
+/**
+ @private
+
+ Starts watching a property on an object. Whenever the property changes,
+ invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
+ primitive used by observers and dependent keys; usually you will never call
+ this method directly but instead use higher level methods like
+ `Ember.addObserver()`
+
+ @method watch
+ @for Ember
+ @param obj
+ @param {String} keyName
+*/
+Ember.watch = function(obj, keyName) {
+ // can't watch length on Array - it is special...
+ if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
+
+ var m = metaFor(obj), watching = m.watching, desc;
+
+ // activate watching first time
+ if (!watching[keyName]) {
+ watching[keyName] = 1;
+ if (isKeyName(keyName)) {
+ desc = m.descs[keyName];
+ if (desc && desc.willWatch) { desc.willWatch(obj, keyName); }
+
+ if ('function' === typeof obj.willWatchProperty) {
+ obj.willWatchProperty(keyName);
+ }
+
+ if (MANDATORY_SETTER && keyName in obj) {
+ m.values[keyName] = obj[keyName];
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ set: Ember.MANDATORY_SETTER_FUNCTION,
+ get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ }
+ } else {
+ chainsFor(obj).add(keyName);
+ }
+
+ } else {
+ watching[keyName] = (watching[keyName] || 0) + 1;
+ }
+ return this;
+};
+
+Ember.isWatching = function isWatching(obj, key) {
+ var meta = obj[META_KEY];
+ return (meta && meta.watching[key]) > 0;
+};
+
+Ember.watch.flushPending = flushPendingChains;
+
+Ember.unwatch = function(obj, keyName) {
+ // can't watch length on Array - it is special...
+ if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
+
+ var m = metaFor(obj), watching = m.watching, desc;
+
+ if (watching[keyName] === 1) {
+ watching[keyName] = 0;
+
+ if (isKeyName(keyName)) {
+ desc = m.descs[keyName];
+ if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
+
+ if ('function' === typeof obj.didUnwatchProperty) {
+ obj.didUnwatchProperty(keyName);
+ }
+
+ if (MANDATORY_SETTER && keyName in obj) {
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: m.values[keyName]
+ });
+ delete m.values[keyName];
+ }
+ } else {
+ chainsFor(obj).remove(keyName);
+ }
+
+ } else if (watching[keyName]>1) {
+ watching[keyName]--;
+ }
+
+ return this;
+};
+
+/**
+ @private
+
+ Call on an object when you first beget it from another object. This will
+ setup any chained watchers on the object instance as needed. This method is
+ safe to call multiple times.
+
+ @method rewatch
+ @for Ember
+ @param obj
+*/
+Ember.rewatch = function(obj) {
+ var m = metaFor(obj, false), chains = m.chains;
+
+ // make sure the object has its own guid.
+ if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
+ Ember.generateGuid(obj, 'ember');
+ }
+
+ // make sure any chained watchers update.
+ if (chains && chains.value() !== obj) {
+ m.chains = chains.copy(obj);
+ }
+
+ return this;
+};
+
+Ember.finishChains = function(obj) {
+ var m = metaFor(obj, false), chains = m.chains;
+ if (chains) {
+ if (chains.value() !== obj) {
+ m.chains = chains = chains.copy(obj);
+ }
+ chains.didChange(true);
+ }
+};
+
+// ..........................................................
+// PROPERTY CHANGES
+//
+
+/**
+ This function is called just before an object property is about to change.
+ It will notify any before observers and prepare caches among other things.
+
+ Normally you will not need to call this method directly but if for some
+ reason you can't directly watch a property you can invoke this method
+ manually along with `Ember.propertyDidChange()` which you should call just
+ after the property value changes.
+
+ @method propertyWillChange
+ @for Ember
+ @param {Object} obj The object with the property that will change
+ @param {String} keyName The property key (or path) that will change.
+ @return {void}
+*/
+function propertyWillChange(obj, keyName) {
+ var m = metaFor(obj, false),
+ watching = m.watching[keyName] > 0 || keyName === 'length',
+ proto = m.proto,
+ desc = m.descs[keyName];
+
+ if (!watching) { return; }
+ if (proto === obj) { return; }
+ if (desc && desc.willChange) { desc.willChange(obj, keyName); }
+ dependentKeysWillChange(obj, keyName, m);
+ chainsWillChange(obj, keyName, m);
+ Ember.notifyBeforeObservers(obj, keyName);
+}
+
+Ember.propertyWillChange = propertyWillChange;
+
+/**
+ This function is called just after an object property has changed.
+ It will notify any observers and clear caches among other things.
+
+ Normally you will not need to call this method directly but if for some
+ reason you can't directly watch a property you can invoke this method
+ manually along with `Ember.propertyWilLChange()` which you should call just
+ before the property value changes.
+
+ @method propertyDidChange
+ @for Ember
+ @param {Object} obj The object with the property that will change
+ @param {String} keyName The property key (or path) that will change.
+ @return {void}
+*/
+function propertyDidChange(obj, keyName) {
+ var m = metaFor(obj, false),
+ watching = m.watching[keyName] > 0 || keyName === 'length',
+ proto = m.proto,
+ desc = m.descs[keyName];
+
+ if (proto === obj) { return; }
+
+ // shouldn't this mean that we're watching this key?
+ if (desc && desc.didChange) { desc.didChange(obj, keyName); }
+ if (!watching && keyName !== 'length') { return; }
+
+ dependentKeysDidChange(obj, keyName, m);
+ chainsDidChange(obj, keyName, m);
+ Ember.notifyObservers(obj, keyName);
+}
+
+Ember.propertyDidChange = propertyDidChange;
+
+var NODE_STACK = [];
+
+/**
+ Tears down the meta on an object so that it can be garbage collected.
+ Multiple calls will have no effect.
+
+ @method destroy
+ @for Ember
+ @param {Object} obj the object to destroy
+ @return {void}
+*/
+Ember.destroy = function (obj) {
+ var meta = obj[META_KEY], node, nodes, key, nodeObject;
+ if (meta) {
+ obj[META_KEY] = null;
+ // remove chainWatchers to remove circular references that would prevent GC
+ node = meta.chains;
+ if (node) {
+ NODE_STACK.push(node);
+ // process tree
+ while (NODE_STACK.length > 0) {
+ node = NODE_STACK.pop();
+ // push children
+ nodes = node._chains;
+ if (nodes) {
+ for (key in nodes) {
+ if (nodes.hasOwnProperty(key)) {
+ NODE_STACK.push(nodes[key]);
+ }
+ }
+ }
+ // remove chainWatcher in node object
+ if (node._watching) {
+ nodeObject = node._object;
+ if (nodeObject) {
+ removeChainWatcher(nodeObject, node._key, node);
+ }
+ }
+ }
+ }
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false);
+
+
+var get = Ember.get,
+ set = Ember.set,
+ metaFor = Ember.meta,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ META_KEY = Ember.META_KEY,
+ watch = Ember.watch,
+ unwatch = Ember.unwatch;
+
+// ..........................................................
+// DEPENDENT KEYS
+//
+
+// data structure:
+// meta.deps = {
+// 'depKey': {
+// 'keyName': count,
+// }
+// }
+
+/*
+ This function returns a map of unique dependencies for a
+ given object and key.
+*/
+function keysForDep(obj, depsMeta, depKey) {
+ var keys = depsMeta[depKey];
+ if (!keys) {
+ // if there are no dependencies yet for a the given key
+ // create a new empty list of dependencies for the key
+ keys = depsMeta[depKey] = {};
+ } else if (!depsMeta.hasOwnProperty(depKey)) {
+ // otherwise if the dependency list is inherited from
+ // a superclass, clone the hash
+ keys = depsMeta[depKey] = o_create(keys);
+ }
+ return keys;
+}
+
+function metaForDeps(obj, meta) {
+ return keysForDep(obj, meta, 'deps');
+}
+
+function addDependentKeys(desc, obj, keyName, meta) {
+ // the descriptor has a list of dependent keys, so
+ // add all of its dependent keys.
+ var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
+ if (!depKeys) return;
+
+ depsMeta = metaForDeps(obj, meta);
+
+ for(idx = 0, len = depKeys.length; idx < len; idx++) {
+ depKey = depKeys[idx];
+ // Lookup keys meta for depKey
+ keys = keysForDep(obj, depsMeta, depKey);
+ // Increment the number of times depKey depends on keyName.
+ keys[keyName] = (keys[keyName] || 0) + 1;
+ // Watch the depKey
+ watch(obj, depKey);
+ }
+}
+
+function removeDependentKeys(desc, obj, keyName, meta) {
+ // the descriptor has a list of dependent keys, so
+ // add all of its dependent keys.
+ var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
+ if (!depKeys) return;
+
+ depsMeta = metaForDeps(obj, meta);
+
+ for(idx = 0, len = depKeys.length; idx < len; idx++) {
+ depKey = depKeys[idx];
+ // Lookup keys meta for depKey
+ keys = keysForDep(obj, depsMeta, depKey);
+ // Increment the number of times depKey depends on keyName.
+ keys[keyName] = (keys[keyName] || 0) - 1;
+ // Watch the depKey
+ unwatch(obj, depKey);
+ }
+}
+
+// ..........................................................
+// COMPUTED PROPERTY
+//
+
+/**
+ @class ComputedProperty
+ @namespace Ember
+ @extends Ember.Descriptor
+ @constructor
+*/
+function ComputedProperty(func, opts) {
+ this.func = func;
+
+ this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
+ this._dependentKeys = opts && opts.dependentKeys;
+ this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
+}
+
+Ember.ComputedProperty = ComputedProperty;
+ComputedProperty.prototype = new Ember.Descriptor();
+
+var ComputedPropertyPrototype = ComputedProperty.prototype;
+
+/**
+ Call on a computed property to set it into cacheable mode. When in this
+ mode the computed property will automatically cache the return value of
+ your function until one of the dependent keys changes.
+
+ ```javascript
+ MyApp.president = Ember.Object.create({
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // After calculating the value of this function, Ember will
+ // return that value without re-executing this function until
+ // one of the dependent properties change.
+ }.property('firstName', 'lastName')
+ });
+ ```
+
+ Properties are cacheable by default.
+
+ @method cacheable
+ @param {Boolean} aFlag optional set to `false` to disable caching
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.cacheable = function(aFlag) {
+ this._cacheable = aFlag !== false;
+ return this;
+};
+
+/**
+ Call on a computed property to set it into non-cached mode. When in this
+ mode the computed property will not automatically cache the return value.
+
+ ```javascript
+ MyApp.outsideService = Ember.Object.create({
+ value: function() {
+ return OutsideService.getValue();
+ }.property().volatile()
+ });
+ ```
+
+ @method volatile
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.volatile = function() {
+ return this.cacheable(false);
+};
+
+/**
+ Call on a computed property to set it into read-only mode. When in this
+ mode the computed property will throw an error when set.
+
+ ```javascript
+ MyApp.person = Ember.Object.create({
+ guid: function() {
+ return 'guid-guid-guid';
+ }.property().readOnly()
+ });
+
+ MyApp.person.set('guid', 'new-guid'); // will throw an exception
+ ```
+
+ @method readOnly
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.readOnly = function(readOnly) {
+ this._readOnly = readOnly === undefined || !!readOnly;
+ return this;
+};
+
+/**
+ Sets the dependent keys on this computed property. Pass any number of
+ arguments containing key paths that this computed property depends on.
+
+ ```javascript
+ MyApp.president = Ember.Object.create({
+ fullName: Ember.computed(function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Tell Ember that this computed property depends on firstName
+ // and lastName
+ }).property('firstName', 'lastName')
+ });
+ ```
+
+ @method property
+ @param {String} path* zero or more property paths
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.property = function() {
+ var args = [];
+ for (var i = 0, l = arguments.length; i < l; i++) {
+ args.push(arguments[i]);
+ }
+ this._dependentKeys = args;
+ return this;
+};
+
+/**
+ In some cases, you may want to annotate computed properties with additional
+ metadata about how they function or what values they operate on. For example,
+ computed property functions may close over variables that are then no longer
+ available for introspection.
+
+ You can pass a hash of these values to a computed property like this:
+
+ ```
+ person: function() {
+ var personId = this.get('personId');
+ return App.Person.create({ id: personId });
+ }.property().meta({ type: App.Person })
+ ```
+
+ The hash that you pass to the `meta()` function will be saved on the
+ computed property descriptor under the `_meta` key. Ember runtime
+ exposes a public API for retrieving these values from classes,
+ via the `metaForProperty()` function.
+
+ @method meta
+ @param {Hash} meta
+ @chainable
+*/
+
+ComputedPropertyPrototype.meta = function(meta) {
+ if (arguments.length === 0) {
+ return this._meta || {};
+ } else {
+ this._meta = meta;
+ return this;
+ }
+};
+
+/* impl descriptor API */
+ComputedPropertyPrototype.willWatch = function(obj, keyName) {
+ // watch already creates meta for this instance
+ var meta = obj[META_KEY];
+ Ember.assert('watch should have setup meta to be writable', meta.source === obj);
+ if (!(keyName in meta.cache)) {
+ addDependentKeys(this, obj, keyName, meta);
+ }
+};
+
+ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
+ var meta = obj[META_KEY];
+ Ember.assert('unwatch should have setup meta to be writable', meta.source === obj);
+ if (!(keyName in meta.cache)) {
+ // unwatch already creates meta for this instance
+ removeDependentKeys(this, obj, keyName, meta);
+ }
+};
+
+/* impl descriptor API */
+ComputedPropertyPrototype.didChange = function(obj, keyName) {
+ // _suspended is set via a CP.set to ensure we don't clear
+ // the cached value set by the setter
+ if (this._cacheable && this._suspended !== obj) {
+ var meta = metaFor(obj);
+ if (keyName in meta.cache) {
+ delete meta.cache[keyName];
+ if (!meta.watching[keyName]) {
+ removeDependentKeys(this, obj, keyName, meta);
+ }
+ }
+ }
+};
+
+/* impl descriptor API */
+ComputedPropertyPrototype.get = function(obj, keyName) {
+ var ret, cache, meta;
+ if (this._cacheable) {
+ meta = metaFor(obj);
+ cache = meta.cache;
+ if (keyName in cache) { return cache[keyName]; }
+ ret = cache[keyName] = this.func.call(obj, keyName);
+ if (!meta.watching[keyName]) {
+ addDependentKeys(this, obj, keyName, meta);
+ }
+ } else {
+ ret = this.func.call(obj, keyName);
+ }
+ return ret;
+};
+
+/* impl descriptor API */
+ComputedPropertyPrototype.set = function(obj, keyName, value) {
+ var cacheable = this._cacheable,
+ func = this.func,
+ meta = metaFor(obj, cacheable),
+ watched = meta.watching[keyName],
+ oldSuspended = this._suspended,
+ hadCachedValue = false,
+ cache = meta.cache,
+ cachedValue, ret;
+
+ if (this._readOnly) {
+ throw new Error('Cannot Set: ' + keyName + ' on: ' + obj.toString() );
+ }
+
+ this._suspended = obj;
+
+ try {
+
+ if (cacheable && cache.hasOwnProperty(keyName)) {
+ cachedValue = cache[keyName];
+ hadCachedValue = true;
+ }
+
+ // Check if the CP has been wrapped
+ if (func.wrappedFunction) { func = func.wrappedFunction; }
+
+ // For backwards-compatibility with computed properties
+ // that check for arguments.length === 2 to determine if
+ // they are being get or set, only pass the old cached
+ // value if the computed property opts into a third
+ // argument.
+ if (func.length === 3) {
+ ret = func.call(obj, keyName, value, cachedValue);
+ } else if (func.length === 2) {
+ ret = func.call(obj, keyName, value);
+ } else {
+ Ember.defineProperty(obj, keyName, null, cachedValue);
+ Ember.set(obj, keyName, value);
+ return;
+ }
+
+ if (hadCachedValue && cachedValue === ret) { return; }
+
+ if (watched) { Ember.propertyWillChange(obj, keyName); }
+
+ if (hadCachedValue) {
+ delete cache[keyName];
+ }
+
+ if (cacheable) {
+ if (!watched && !hadCachedValue) {
+ addDependentKeys(this, obj, keyName, meta);
+ }
+ cache[keyName] = ret;
+ }
+
+ if (watched) { Ember.propertyDidChange(obj, keyName); }
+ } finally {
+ this._suspended = oldSuspended;
+ }
+ return ret;
+};
+
+/* called when property is defined */
+ComputedPropertyPrototype.setup = function(obj, keyName) {
+ var meta = obj[META_KEY];
+ if (meta && meta.watching[keyName]) {
+ addDependentKeys(this, obj, keyName, metaFor(obj));
+ }
+};
+
+/* called before property is overridden */
+ComputedPropertyPrototype.teardown = function(obj, keyName) {
+ var meta = metaFor(obj);
+
+ if (meta.watching[keyName] || keyName in meta.cache) {
+ removeDependentKeys(this, obj, keyName, meta);
+ }
+
+ if (this._cacheable) { delete meta.cache[keyName]; }
+
+ return null; // no value to restore
+};
+
+
+/**
+ This helper returns a new property descriptor that wraps the passed
+ computed property function. You can use this helper to define properties
+ with mixins or via `Ember.defineProperty()`.
+
+ The function you pass will be used to both get and set property values.
+ The function should accept two parameters, key and value. If value is not
+ undefined you should set the value first. In either case return the
+ current value of the property.
+
+ @method computed
+ @for Ember
+ @param {Function} func The computed property function.
+ @return {Ember.ComputedProperty} property descriptor instance
+*/
+Ember.computed = function(func) {
+ var args;
+
+ if (arguments.length > 1) {
+ args = a_slice.call(arguments, 0, -1);
+ func = a_slice.call(arguments, -1)[0];
+ }
+
+ if ( typeof func !== "function" ) {
+ throw new Error("Computed Property declared without a property function");
+ }
+
+ var cp = new ComputedProperty(func);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+/**
+ Returns the cached value for a property, if one exists.
+ This can be useful for peeking at the value of a computed
+ property that is generated lazily, without accidentally causing
+ it to be created.
+
+ @method cacheFor
+ @for Ember
+ @param {Object} obj the object whose property you want to check
+ @param {String} key the name of the property whose cached value you want
+ to return
+ @return {any} the cached value
+*/
+Ember.cacheFor = function cacheFor(obj, key) {
+ var cache = metaFor(obj, false).cache;
+
+ if (cache && key in cache) {
+ return cache[key];
+ }
+};
+
+/**
+ @method computed.not
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which negate the original value for property
+*/
+Ember.computed.not = function(dependentKey) {
+ return Ember.computed(dependentKey, function(key) {
+ return !get(this, dependentKey);
+ });
+};
+
+/**
+ @method computed.none
+ @for Ember
+ @param {String} dependentKey
+*/
+Ember.computed.none = function(dependentKey) {
+ return Ember.computed(dependentKey, function(key) {
+ var val = get(this, dependentKey);
+ return Ember.isNone(val);
+ });
+};
+
+/**
+ @method computed.empty
+ @for Ember
+ @param {String} dependentKey
+*/
+Ember.computed.empty = function(dependentKey) {
+ return Ember.computed(dependentKey, function(key) {
+ var val = get(this, dependentKey);
+ return Ember.isEmpty(val);
+ });
+};
+
+/**
+ @method computed.bool
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which convert to boolean the original value for property
+*/
+Ember.computed.bool = function(dependentKey) {
+ return Ember.computed(dependentKey, function(key) {
+ return !!get(this, dependentKey);
+ });
+};
+
+/**
+ @method computed.alias
+ @for Ember
+
+ @param {String} dependentKey
+*/
+Ember.computed.alias = function(dependentKey) {
+ return Ember.computed(dependentKey, function(key, value){
+ if (arguments.length > 1) {
+ set(this, dependentKey, value);
+ return value;
+ } else {
+ return get(this, dependentKey);
+ }
+ });
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var o_create = Ember.create,
+ metaFor = Ember.meta,
+ META_KEY = Ember.META_KEY;
+
+/*
+ The event system uses a series of nested hashes to store listeners on an
+ object. When a listener is registered, or when an event arrives, these
+ hashes are consulted to determine which target and action pair to invoke.
+
+ The hashes are stored in the object's meta hash, and look like this:
+
+ // Object's meta hash
+ {
+ listeners: { // variable name: `listenerSet`
+ "foo:changed": [ // variable name: `actions`
+ [target, method, onceFlag, suspendedFlag]
+ ]
+ }
+ }
+
+*/
+
+function indexOf(array, target, method) {
+ var index = -1;
+ for (var i = 0, l = array.length; i < l; i++) {
+ if (target === array[i][0] && method === array[i][1]) { index = i; break; }
+ }
+ return index;
+}
+
+function actionsFor(obj, eventName) {
+ var meta = metaFor(obj, true),
+ actions;
+
+ if (!meta.listeners) { meta.listeners = {}; }
+
+ if (!meta.hasOwnProperty('listeners')) {
+ // setup inherited copy of the listeners object
+ meta.listeners = o_create(meta.listeners);
+ }
+
+ actions = meta.listeners[eventName];
+
+ // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype
+ if (actions && !meta.listeners.hasOwnProperty(eventName)) {
+ actions = meta.listeners[eventName] = meta.listeners[eventName].slice();
+ } else if (!actions) {
+ actions = meta.listeners[eventName] = [];
+ }
+
+ return actions;
+}
+
+function actionsUnion(obj, eventName, otherActions) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 1; i >= 0; i--) {
+ var target = actions[i][0],
+ method = actions[i][1],
+ once = actions[i][2],
+ suspended = actions[i][3],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex === -1) {
+ otherActions.push([target, method, once, suspended]);
+ }
+ }
+}
+
+function actionsDiff(obj, eventName, otherActions) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName],
+ diffActions = [];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 1; i >= 0; i--) {
+ var target = actions[i][0],
+ method = actions[i][1],
+ once = actions[i][2],
+ suspended = actions[i][3],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex !== -1) { continue; }
+
+ otherActions.push([target, method, once, suspended]);
+ diffActions.push([target, method, once, suspended]);
+ }
+
+ return diffActions;
+}
+
+/**
+ Add an event listener
+
+ @method addListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Boolean} once A flag whether a function should only be called once
+*/
+function addListener(obj, eventName, target, method, once) {
+ Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
+
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method);
+
+ if (actionIndex !== -1) { return; }
+
+ actions.push([target, method, once, undefined]);
+
+ if ('function' === typeof obj.didAddListener) {
+ obj.didAddListener(eventName, target, method);
+ }
+}
+
+/**
+ Remove an event listener
+
+ Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}}
+
+ @method removeListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+*/
+function removeListener(obj, eventName, target, method) {
+ Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
+
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ function _removeListener(target, method, once) {
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method);
+
+ // action doesn't exist, give up silently
+ if (actionIndex === -1) { return; }
+
+ actions.splice(actionIndex, 1);
+
+ if ('function' === typeof obj.didRemoveListener) {
+ obj.didRemoveListener(eventName, target, method);
+ }
+ }
+
+ if (method) {
+ _removeListener(target, method);
+ } else {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 1; i >= 0; i--) {
+ _removeListener(actions[i][0], actions[i][1]);
+ }
+ }
+}
+
+/**
+ @private
+
+ Suspend listener during callback.
+
+ This should only be used by the target of the event listener
+ when it is taking an action that would cause the event, e.g.
+ an object might suspend its property change listener while it is
+ setting that property.
+
+ @method suspendListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Function} callback
+*/
+function suspendListener(obj, eventName, target, method, callback) {
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method),
+ action;
+
+ if (actionIndex !== -1) {
+ action = actions[actionIndex].slice(); // copy it, otherwise we're modifying a shared object
+ action[3] = true; // mark the action as suspended
+ actions[actionIndex] = action; // replace the shared object with our copy
+ }
+
+ function tryable() { return callback.call(target); }
+ function finalizer() { if (action) { action[3] = undefined; } }
+
+ return Ember.tryFinally(tryable, finalizer);
+}
+
+/**
+ @private
+
+ Suspend listener during callback.
+
+ This should only be used by the target of the event listener
+ when it is taking an action that would cause the event, e.g.
+ an object might suspend its property change listener while it is
+ setting that property.
+
+ @method suspendListener
+ @for Ember
+ @param obj
+ @param {Array} eventName Array of event names
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Function} callback
+*/
+function suspendListeners(obj, eventNames, target, method, callback) {
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var suspendedActions = [],
+ eventName, actions, action, i, l;
+
+ for (i=0, l=eventNames.length; i= 0; i--) { // looping in reverse for once listeners
+ if (!actions[i] || actions[i][3] === true) { continue; }
+
+ var target = actions[i][0],
+ method = actions[i][1],
+ once = actions[i][2];
+
+ if (once) { removeListener(obj, eventName, target, method); }
+ if (!target) { target = obj; }
+ if ('string' === typeof method) { method = target[method]; }
+ if (params) {
+ method.apply(target, params);
+ } else {
+ method.call(target);
+ }
+ }
+ return true;
+}
+
+/**
+ @private
+ @method hasListeners
+ @for Ember
+ @param obj
+ @param {String} eventName
+*/
+function hasListeners(obj, eventName) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ return !!(actions && actions.length);
+}
+
+/**
+ @private
+ @method listenersFor
+ @for Ember
+ @param obj
+ @param {String} eventName
+*/
+function listenersFor(obj, eventName) {
+ var ret = [];
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return ret; }
+
+ for (var i = 0, l = actions.length; i < l; i++) {
+ var target = actions[i][0],
+ method = actions[i][1];
+ ret.push([target, method]);
+ }
+
+ return ret;
+}
+
+Ember.addListener = addListener;
+Ember.removeListener = removeListener;
+Ember._suspendListener = suspendListener;
+Ember._suspendListeners = suspendListeners;
+Ember.sendEvent = sendEvent;
+Ember.hasListeners = hasListeners;
+Ember.watchedEvents = watchedEvents;
+Ember.listenersFor = listenersFor;
+Ember.listenersDiff = actionsDiff;
+Ember.listenersUnion = actionsUnion;
+
+})();
+
+
+
+(function() {
+// Ember.Logger
+// Ember.watch.flushPending
+// Ember.beginPropertyChanges, Ember.endPropertyChanges
+// Ember.guidFor, Ember.tryFinally
+
+/**
+@module ember-metal
+*/
+
+// ..........................................................
+// HELPERS
+//
+
+var slice = [].slice,
+ forEach = Ember.ArrayPolyfills.forEach;
+
+// invokes passed params - normalizing so you can pass target/func,
+// target/string or just func
+function invoke(target, method, args, ignore) {
+
+ if (method === undefined) {
+ method = target;
+ target = undefined;
+ }
+
+ if ('string' === typeof method) { method = target[method]; }
+ if (args && ignore > 0) {
+ args = args.length > ignore ? slice.call(args, ignore) : null;
+ }
+
+ return Ember.handleErrors(function() {
+ // IE8's Function.prototype.apply doesn't accept undefined/null arguments.
+ return method.apply(target || this, args || []);
+ }, this);
+}
+
+
+// ..........................................................
+// RUNLOOP
+//
+
+var timerMark; // used by timers...
+
+/**
+Ember RunLoop (Private)
+
+@class RunLoop
+@namespace Ember
+@private
+@constructor
+*/
+var RunLoop = function(prev) {
+ this._prev = prev || null;
+ this.onceTimers = {};
+};
+
+RunLoop.prototype = {
+ /**
+ @method end
+ */
+ end: function() {
+ this.flush();
+ },
+
+ /**
+ @method prev
+ */
+ prev: function() {
+ return this._prev;
+ },
+
+ // ..........................................................
+ // Delayed Actions
+ //
+
+ /**
+ @method schedule
+ @param {String} queueName
+ @param target
+ @param method
+ */
+ schedule: function(queueName, target, method) {
+ var queues = this._queues, queue;
+ if (!queues) { queues = this._queues = {}; }
+ queue = queues[queueName];
+ if (!queue) { queue = queues[queueName] = []; }
+
+ var args = arguments.length > 3 ? slice.call(arguments, 3) : null;
+ queue.push({ target: target, method: method, args: args });
+ return this;
+ },
+
+ /**
+ @method flush
+ @param {String} queueName
+ */
+ flush: function(queueName) {
+ var queueNames, idx, len, queue, log;
+
+ if (!this._queues) { return this; } // nothing to do
+
+ function iter(item) {
+ invoke(item.target, item.method, item.args);
+ }
+
+ function tryable() {
+ forEach.call(queue, iter);
+ }
+
+ Ember.watch.flushPending(); // make sure all chained watchers are setup
+
+ if (queueName) {
+ while (this._queues && (queue = this._queues[queueName])) {
+ this._queues[queueName] = null;
+
+ // the sync phase is to allow property changes to propagate. don't
+ // invoke observers until that is finished.
+ if (queueName === 'sync') {
+ log = Ember.LOG_BINDINGS;
+ if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
+
+ Ember.beginPropertyChanges();
+
+ Ember.tryFinally(tryable, Ember.endPropertyChanges);
+
+ if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
+
+ } else {
+ forEach.call(queue, iter);
+ }
+ }
+
+ } else {
+ queueNames = Ember.run.queues;
+ len = queueNames.length;
+ idx = 0;
+
+ outerloop:
+ while (idx < len) {
+ queueName = queueNames[idx];
+ queue = this._queues && this._queues[queueName];
+ delete this._queues[queueName];
+
+ if (queue) {
+ // the sync phase is to allow property changes to propagate. don't
+ // invoke observers until that is finished.
+ if (queueName === 'sync') {
+ log = Ember.LOG_BINDINGS;
+ if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
+
+ Ember.beginPropertyChanges();
+
+ Ember.tryFinally(tryable, Ember.endPropertyChanges);
+
+ if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
+ } else {
+ forEach.call(queue, iter);
+ }
+ }
+
+ // Loop through prior queues
+ for (var i = 0; i <= idx; i++) {
+ if (this._queues && this._queues[queueNames[i]]) {
+ // Start over at the first queue with contents
+ idx = i;
+ continue outerloop;
+ }
+ }
+
+ idx++;
+ }
+ }
+
+ timerMark = null;
+
+ return this;
+ }
+
+};
+
+Ember.RunLoop = RunLoop;
+
+// ..........................................................
+// Ember.run - this is ideally the only public API the dev sees
+//
+
+/**
+ Runs the passed target and method inside of a RunLoop, ensuring any
+ deferred actions including bindings and views updates are flushed at the
+ end.
+
+ Normally you should not need to invoke this method yourself. However if
+ you are implementing raw event handlers when interfacing with other
+ libraries or plugins, you should probably wrap all of your code inside this
+ call.
+
+ ```javascript
+ Ember.run(function(){
+ // code to be execute within a RunLoop
+ });
+ ```
+
+ @class run
+ @namespace Ember
+ @static
+ @constructor
+ @param {Object} [target] target of method to call
+ @param {Function|String} method Method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Any additional arguments you wish to pass to the method.
+ @return {Object} return value from invoking the passed function.
+*/
+Ember.run = function(target, method) {
+ var args = arguments;
+ run.begin();
+
+ function tryable() {
+ if (target || method) {
+ return invoke(target, method, args, 2);
+ }
+ }
+
+ return Ember.tryFinally(tryable, run.end);
+};
+
+var run = Ember.run;
+
+
+/**
+ Begins a new RunLoop. Any deferred actions invoked after the begin will
+ be buffered until you invoke a matching call to `Ember.run.end()`. This is
+ a lower-level way to use a RunLoop instead of using `Ember.run()`.
+
+ ```javascript
+ Ember.run.begin();
+ // code to be execute within a RunLoop
+ Ember.run.end();
+ ```
+
+ @method begin
+ @return {void}
+*/
+Ember.run.begin = function() {
+ run.currentRunLoop = new RunLoop(run.currentRunLoop);
+};
+
+/**
+ Ends a RunLoop. This must be called sometime after you call
+ `Ember.run.begin()` to flush any deferred actions. This is a lower-level way
+ to use a RunLoop instead of using `Ember.run()`.
+
+ ```javascript
+ Ember.run.begin();
+ // code to be execute within a RunLoop
+ Ember.run.end();
+ ```
+
+ @method end
+ @return {void}
+*/
+Ember.run.end = function() {
+ Ember.assert('must have a current run loop', run.currentRunLoop);
+
+ function tryable() { run.currentRunLoop.end(); }
+ function finalizer() { run.currentRunLoop = run.currentRunLoop.prev(); }
+
+ Ember.tryFinally(tryable, finalizer);
+};
+
+/**
+ Array of named queues. This array determines the order in which queues
+ are flushed at the end of the RunLoop. You can define your own queues by
+ simply adding the queue name to this array. Normally you should not need
+ to inspect or modify this property.
+
+ @property queues
+ @type Array
+ @default ['sync', 'actions', 'destroy']
+*/
+Ember.run.queues = ['sync', 'actions', 'destroy'];
+
+/**
+ Adds the passed target/method and any optional arguments to the named
+ queue to be executed at the end of the RunLoop. If you have not already
+ started a RunLoop when calling this method one will be started for you
+ automatically.
+
+ At the end of a RunLoop, any methods scheduled in this way will be invoked.
+ Methods will be invoked in an order matching the named queues defined in
+ the `run.queues` property.
+
+ ```javascript
+ Ember.run.schedule('sync', this, function(){
+ // this will be executed in the first RunLoop queue, when bindings are synced
+ console.log("scheduled on sync queue");
+ });
+
+ Ember.run.schedule('actions', this, function(){
+ // this will be executed in the 'actions' queue, after bindings have synced.
+ console.log("scheduled on actions queue");
+ });
+
+ // Note the functions will be run in order based on the run queues order. Output would be:
+ // scheduled on sync queue
+ // scheduled on actions queue
+ ```
+
+ @method schedule
+ @param {String} queue The name of the queue to schedule against.
+ Default queues are 'sync' and 'actions'
+ @param {Object} [target] target object to use as the context when invoking a method.
+ @param {String|Function} method The method to invoke. If you pass a string it
+ will be resolved on the target object at the time the scheduled item is
+ invoked allowing you to change the target function.
+ @param {Object} [arguments*] Optional arguments to be passed to the queued method.
+ @return {void}
+*/
+Ember.run.schedule = function(queue, target, method) {
+ var loop = run.autorun();
+ loop.schedule.apply(loop, arguments);
+};
+
+var scheduledAutorun;
+function autorun() {
+ scheduledAutorun = null;
+ if (run.currentRunLoop) { run.end(); }
+}
+
+// Used by global test teardown
+Ember.run.hasScheduledTimers = function() {
+ return !!(scheduledAutorun || scheduledLater);
+};
+
+// Used by global test teardown
+Ember.run.cancelTimers = function () {
+ if (scheduledAutorun) {
+ clearTimeout(scheduledAutorun);
+ scheduledAutorun = null;
+ }
+ if (scheduledLater) {
+ clearTimeout(scheduledLater);
+ scheduledLater = null;
+ }
+ timers = {};
+};
+
+/**
+ Begins a new RunLoop if necessary and schedules a timer to flush the
+ RunLoop at a later time. This method is used by parts of Ember to
+ ensure the RunLoop always finishes. You normally do not need to call this
+ method directly. Instead use `Ember.run()`
+
+ @method autorun
+ @example
+ Ember.run.autorun();
+ @return {Ember.RunLoop} the new current RunLoop
+*/
+Ember.run.autorun = function() {
+ if (!run.currentRunLoop) {
+ Ember.assert("You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run", !Ember.testing);
+
+ run.begin();
+
+ if (!scheduledAutorun) {
+ scheduledAutorun = setTimeout(autorun, 1);
+ }
+ }
+
+ return run.currentRunLoop;
+};
+
+/**
+ Immediately flushes any events scheduled in the 'sync' queue. Bindings
+ use this queue so this method is a useful way to immediately force all
+ bindings in the application to sync.
+
+ You should call this method anytime you need any changed state to propagate
+ throughout the app immediately without repainting the UI (which happens
+ in the later 'render' queue added by the `ember-views` package).
+
+ ```javascript
+ Ember.run.sync();
+ ```
+
+ @method sync
+ @return {void}
+*/
+Ember.run.sync = function() {
+ run.autorun();
+ run.currentRunLoop.flush('sync');
+};
+
+// ..........................................................
+// TIMERS
+//
+
+var timers = {}; // active timers...
+
+var scheduledLater, scheduledLaterExpires;
+function invokeLaterTimers() {
+ scheduledLater = null;
+ run(function() {
+ var now = (+ new Date()), earliest = -1;
+ for (var key in timers) {
+ if (!timers.hasOwnProperty(key)) { continue; }
+ var timer = timers[key];
+ if (timer && timer.expires) {
+ if (now >= timer.expires) {
+ delete timers[key];
+ invoke(timer.target, timer.method, timer.args, 2);
+ } else {
+ if (earliest < 0 || (timer.expires < earliest)) { earliest = timer.expires; }
+ }
+ }
+ }
+
+ // schedule next timeout to fire when the earliest timer expires
+ if (earliest > 0) {
+ scheduledLater = setTimeout(invokeLaterTimers, earliest - now);
+ scheduledLaterExpires = earliest;
+ }
+ });
+}
+
+/**
+ Invokes the passed target/method and optional arguments after a specified
+ period if time. The last parameter of this method must always be a number
+ of milliseconds.
+
+ You should use this method whenever you need to run some action after a
+ period of time instead of using `setTimeout()`. This method will ensure that
+ items that expire during the same script execution cycle all execute
+ together, which is often more efficient than using a real setTimeout.
+
+ ```javascript
+ Ember.run.later(myContext, function(){
+ // code here will execute within a RunLoop in about 500ms with this == myContext
+ }, 500);
+ ```
+
+ @method later
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @param {Number} wait Number of milliseconds to wait.
+ @return {String} a string you can use to cancel the timer in
+ {{#crossLink "Ember/run.cancel"}}{{/crossLink}} later.
+*/
+Ember.run.later = function(target, method) {
+ var args, expires, timer, guid, wait;
+
+ // setTimeout compatibility...
+ if (arguments.length===2 && 'function' === typeof target) {
+ wait = method;
+ method = target;
+ target = undefined;
+ args = [target, method];
+ } else {
+ args = slice.call(arguments);
+ wait = args.pop();
+ }
+
+ expires = (+ new Date()) + wait;
+ timer = { target: target, method: method, expires: expires, args: args };
+ guid = Ember.guidFor(timer);
+ timers[guid] = timer;
+
+ if(scheduledLater && expires < scheduledLaterExpires) {
+ // Cancel later timer (then reschedule earlier timer below)
+ clearTimeout(scheduledLater);
+ scheduledLater = null;
+ }
+
+ if (!scheduledLater) {
+ // Schedule later timers to be run.
+ scheduledLater = setTimeout(invokeLaterTimers, wait);
+ scheduledLaterExpires = expires;
+ }
+
+ return guid;
+};
+
+function invokeOnceTimer(guid, onceTimers) {
+ if (onceTimers[this.tguid]) { delete onceTimers[this.tguid][this.mguid]; }
+ if (timers[guid]) { invoke(this.target, this.method, this.args); }
+ delete timers[guid];
+}
+
+function scheduleOnce(queue, target, method, args) {
+ var tguid = Ember.guidFor(target),
+ mguid = Ember.guidFor(method),
+ onceTimers = run.autorun().onceTimers,
+ guid = onceTimers[tguid] && onceTimers[tguid][mguid],
+ timer;
+
+ if (guid && timers[guid]) {
+ timers[guid].args = args; // replace args
+ } else {
+ timer = {
+ target: target,
+ method: method,
+ args: args,
+ tguid: tguid,
+ mguid: mguid
+ };
+
+ guid = Ember.guidFor(timer);
+ timers[guid] = timer;
+ if (!onceTimers[tguid]) { onceTimers[tguid] = {}; }
+ onceTimers[tguid][mguid] = guid; // so it isn't scheduled more than once
+
+ run.schedule(queue, timer, invokeOnceTimer, guid, onceTimers);
+ }
+
+ return guid;
+}
+
+/**
+ Schedules an item to run one time during the current RunLoop. Calling
+ this method with the same target/method combination will have no effect.
+
+ Note that although you can pass optional arguments these will not be
+ considered when looking for duplicates. New arguments will replace previous
+ calls.
+
+ ```javascript
+ Ember.run(function(){
+ var doFoo = function() { foo(); }
+ Ember.run.once(myContext, doFoo);
+ Ember.run.once(myContext, doFoo);
+ // doFoo will only be executed once at the end of the RunLoop
+ });
+ ```
+
+ Also note that passing an anonymous function to `Ember.run.once` will
+ not prevent additional calls with an identical anonymous function from
+ scheduling the items multiple times, e.g.:
+
+ ```javascript
+ function scheduleIt() {
+ Ember.run.once(myContext, function() { console.log("Closure"); });
+ }
+ scheduleIt();
+ scheduleIt();
+ // "Closure" will print twice, even though we're using `Ember.run.once`,
+ // because the function we pass to it is anonymous and won't match the
+ // previously scheduled operation.
+ ```
+
+ @method once
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @return {Object} timer
+*/
+Ember.run.once = function(target, method) {
+ return scheduleOnce('actions', target, method, slice.call(arguments, 2));
+};
+
+Ember.run.scheduleOnce = function(queue, target, method, args) {
+ return scheduleOnce(queue, target, method, slice.call(arguments, 3));
+};
+
+/**
+ Schedules an item to run from within a separate run loop, after
+ control has been returned to the system. This is equivalent to calling
+ `Ember.run.later` with a wait time of 1ms.
+
+ ```javascript
+ Ember.run.next(myContext, function(){
+ // code to be executed in the next run loop, which will be scheduled after the current one
+ });
+ ```
+
+ Multiple operations scheduled with `Ember.run.next` will coalesce
+ into the same later run loop, along with any other operations
+ scheduled by `Ember.run.later` that expire right around the same
+ time that `Ember.run.next` operations will fire.
+
+ Note that there are often alternatives to using `Ember.run.next`.
+ For instance, if you'd like to schedule an operation to happen
+ after all DOM element operations have completed within the current
+ run loop, you can make use of the `afterRender` run loop queue (added
+ by the `ember-views` package, along with the preceding `render` queue
+ where all the DOM element operations happen). Example:
+
+ ```javascript
+ App.MyCollectionView = Ember.CollectionView.extend({
+ didInsertElement: function() {
+ Ember.run.scheduleOnce('afterRender', this, 'processChildElements');
+ },
+ processChildElements: function() {
+ // ... do something with collectionView's child view
+ // elements after they've finished rendering, which
+ // can't be done within the CollectionView's
+ // `didInsertElement` hook because that gets run
+ // before the child elements have been added to the DOM.
+ }
+ });
+ ```
+
+ One benefit of the above approach compared to using `Ember.run.next` is
+ that you will be able to perform DOM/CSS operations before unprocessed
+ elements are rendered to the screen, which may prevent flickering or
+ other artifacts caused by delaying processing until after rendering.
+
+ The other major benefit to the above approach is that `Ember.run.next`
+ introduces an element of non-determinism, which can make things much
+ harder to test, due to its reliance on `setTimeout`; it's much harder
+ to guarantee the order of scheduled operations when they are scheduled
+ outside of the current run loop, i.e. with `Ember.run.next`.
+
+ @method next
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @return {Object} timer
+*/
+Ember.run.next = function() {
+ var args = slice.call(arguments);
+ args.push(1); // 1 millisecond wait
+ return run.later.apply(this, args);
+};
+
+/**
+ Cancels a scheduled item. Must be a value returned by `Ember.run.later()`,
+ `Ember.run.once()`, or `Ember.run.next()`.
+
+ ```javascript
+ var runNext = Ember.run.next(myContext, function(){
+ // will not be executed
+ });
+ Ember.run.cancel(runNext);
+
+ var runLater = Ember.run.later(myContext, function(){
+ // will not be executed
+ }, 500);
+ Ember.run.cancel(runLater);
+
+ var runOnce = Ember.run.once(myContext, function(){
+ // will not be executed
+ });
+ Ember.run.cancel(runOnce);
+ ```
+
+ @method cancel
+ @param {Object} timer Timer object to cancel
+ @return {void}
+*/
+Ember.run.cancel = function(timer) {
+ delete timers[timer];
+};
+
+})();
+
+
+
+(function() {
+// Ember.Logger
+// get, set, trySet
+// guidFor, isArray, meta
+// addObserver, removeObserver
+// Ember.run.schedule
+/**
+@module ember-metal
+*/
+
+// ..........................................................
+// CONSTANTS
+//
+
+/**
+ Debug parameter you can turn on. This will log all bindings that fire to
+ the console. This should be disabled in production code. Note that you
+ can also enable this from the console or temporarily.
+
+ @property LOG_BINDINGS
+ @for Ember
+ @type Boolean
+ @default false
+*/
+Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS;
+
+var get = Ember.get,
+ set = Ember.set,
+ guidFor = Ember.guidFor,
+ isGlobalPath = Ember.isGlobalPath;
+
+
+function getWithGlobals(obj, path) {
+ return get(isGlobalPath(path) ? Ember.lookup : obj, path);
+}
+
+// ..........................................................
+// BINDING
+//
+
+var Binding = function(toPath, fromPath) {
+ this._direction = 'fwd';
+ this._from = fromPath;
+ this._to = toPath;
+ this._directionMap = Ember.Map.create();
+};
+
+/**
+@class Binding
+@namespace Ember
+*/
+
+Binding.prototype = {
+ /**
+ This copies the Binding so it can be connected to another object.
+
+ @method copy
+ @return {Ember.Binding}
+ */
+ copy: function () {
+ var copy = new Binding(this._to, this._from);
+ if (this._oneWay) { copy._oneWay = true; }
+ return copy;
+ },
+
+ // ..........................................................
+ // CONFIG
+ //
+
+ /**
+ This will set `from` property path to the specified value. It will not
+ attempt to resolve this property path to an actual object until you
+ connect the binding.
+
+ The binding will search for the property path starting at the root object
+ you pass when you `connect()` the binding. It follows the same rules as
+ `get()` - see that method for more information.
+
+ @method from
+ @param {String} path the property path to connect to
+ @return {Ember.Binding} `this`
+ */
+ from: function(path) {
+ this._from = path;
+ return this;
+ },
+
+ /**
+ This will set the `to` property path to the specified value. It will not
+ attempt to resolve this property path to an actual object until you
+ connect the binding.
+
+ The binding will search for the property path starting at the root object
+ you pass when you `connect()` the binding. It follows the same rules as
+ `get()` - see that method for more information.
+
+ @method to
+ @param {String|Tuple} path A property path or tuple
+ @return {Ember.Binding} `this`
+ */
+ to: function(path) {
+ this._to = path;
+ return this;
+ },
+
+ /**
+ Configures the binding as one way. A one-way binding will relay changes
+ on the `from` side to the `to` side, but not the other way around. This
+ means that if you change the `to` side directly, the `from` side may have
+ a different value.
+
+ @method oneWay
+ @return {Ember.Binding} `this`
+ */
+ oneWay: function() {
+ this._oneWay = true;
+ return this;
+ },
+
+ /**
+ @method toString
+ @return {String} string representation of binding
+ */
+ toString: function() {
+ var oneWay = this._oneWay ? '[oneWay]' : '';
+ return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay;
+ },
+
+ // ..........................................................
+ // CONNECT AND SYNC
+ //
+
+ /**
+ Attempts to connect this binding instance so that it can receive and relay
+ changes. This method will raise an exception if you have not set the
+ from/to properties yet.
+
+ @method connect
+ @param {Object} obj The root object for this binding.
+ @return {Ember.Binding} `this`
+ */
+ connect: function(obj) {
+ Ember.assert('Must pass a valid object to Ember.Binding.connect()', !!obj);
+
+ var fromPath = this._from, toPath = this._to;
+ Ember.trySet(obj, toPath, getWithGlobals(obj, fromPath));
+
+ // add an observer on the object to be notified when the binding should be updated
+ Ember.addObserver(obj, fromPath, this, this.fromDidChange);
+
+ // if the binding is a two-way binding, also set up an observer on the target
+ if (!this._oneWay) { Ember.addObserver(obj, toPath, this, this.toDidChange); }
+
+ this._readyToSync = true;
+
+ return this;
+ },
+
+ /**
+ Disconnects the binding instance. Changes will no longer be relayed. You
+ will not usually need to call this method.
+
+ @method disconnect
+ @param {Object} obj The root object you passed when connecting the binding.
+ @return {Ember.Binding} `this`
+ */
+ disconnect: function(obj) {
+ Ember.assert('Must pass a valid object to Ember.Binding.disconnect()', !!obj);
+
+ var twoWay = !this._oneWay;
+
+ // remove an observer on the object so we're no longer notified of
+ // changes that should update bindings.
+ Ember.removeObserver(obj, this._from, this, this.fromDidChange);
+
+ // if the binding is two-way, remove the observer from the target as well
+ if (twoWay) { Ember.removeObserver(obj, this._to, this, this.toDidChange); }
+
+ this._readyToSync = false; // disable scheduled syncs...
+ return this;
+ },
+
+ // ..........................................................
+ // PRIVATE
+ //
+
+ /* called when the from side changes */
+ fromDidChange: function(target) {
+ this._scheduleSync(target, 'fwd');
+ },
+
+ /* called when the to side changes */
+ toDidChange: function(target) {
+ this._scheduleSync(target, 'back');
+ },
+
+ _scheduleSync: function(obj, dir) {
+ var directionMap = this._directionMap;
+ var existingDir = directionMap.get(obj);
+
+ // if we haven't scheduled the binding yet, schedule it
+ if (!existingDir) {
+ Ember.run.schedule('sync', this, this._sync, obj);
+ directionMap.set(obj, dir);
+ }
+
+ // If both a 'back' and 'fwd' sync have been scheduled on the same object,
+ // default to a 'fwd' sync so that it remains deterministic.
+ if (existingDir === 'back' && dir === 'fwd') {
+ directionMap.set(obj, 'fwd');
+ }
+ },
+
+ _sync: function(obj) {
+ var log = Ember.LOG_BINDINGS;
+
+ // don't synchronize destroyed objects or disconnected bindings
+ if (obj.isDestroyed || !this._readyToSync) { return; }
+
+ // get the direction of the binding for the object we are
+ // synchronizing from
+ var directionMap = this._directionMap;
+ var direction = directionMap.get(obj);
+
+ var fromPath = this._from, toPath = this._to;
+
+ directionMap.remove(obj);
+
+ // if we're synchronizing from the remote object...
+ if (direction === 'fwd') {
+ var fromValue = getWithGlobals(obj, this._from);
+ if (log) {
+ Ember.Logger.log(' ', this.toString(), '->', fromValue, obj);
+ }
+ if (this._oneWay) {
+ Ember.trySet(obj, toPath, fromValue);
+ } else {
+ Ember._suspendObserver(obj, toPath, this, this.toDidChange, function () {
+ Ember.trySet(obj, toPath, fromValue);
+ });
+ }
+ // if we're synchronizing *to* the remote object
+ } else if (direction === 'back') {
+ var toValue = get(obj, this._to);
+ if (log) {
+ Ember.Logger.log(' ', this.toString(), '<-', toValue, obj);
+ }
+ Ember._suspendObserver(obj, fromPath, this, this.fromDidChange, function () {
+ Ember.trySet(Ember.isGlobalPath(fromPath) ? Ember.lookup : obj, fromPath, toValue);
+ });
+ }
+ }
+
+};
+
+function mixinProperties(to, from) {
+ for (var key in from) {
+ if (from.hasOwnProperty(key)) {
+ to[key] = from[key];
+ }
+ }
+}
+
+mixinProperties(Binding, {
+
+ /**
+ See {{#crossLink "Ember.Binding/from"}}{{/crossLink}}
+
+ @method from
+ @static
+ */
+ from: function() {
+ var C = this, binding = new C();
+ return binding.from.apply(binding, arguments);
+ },
+
+ /**
+ See {{#crossLink "Ember.Binding/to"}}{{/crossLink}}
+
+ @method to
+ @static
+ */
+ to: function() {
+ var C = this, binding = new C();
+ return binding.to.apply(binding, arguments);
+ },
+
+ /**
+ Creates a new Binding instance and makes it apply in a single direction.
+ A one-way binding will relay changes on the `from` side object (supplied
+ as the `from` argument) the `to` side, but not the other way around.
+ This means that if you change the "to" side directly, the "from" side may have
+ a different value.
+
+ See {{#crossLink "Binding/oneWay"}}{{/crossLink}}
+
+ @method oneWay
+ @param {String} from from path.
+ @param {Boolean} [flag] (Optional) passing nothing here will make the
+ binding `oneWay`. You can instead pass `false` to disable `oneWay`, making the
+ binding two way again.
+ */
+ oneWay: function(from, flag) {
+ var C = this, binding = new C(null, from);
+ return binding.oneWay(flag);
+ }
+
+});
+
+/**
+ An `Ember.Binding` connects the properties of two objects so that whenever
+ the value of one property changes, the other property will be changed also.
+
+ ## Automatic Creation of Bindings with `/^*Binding/`-named Properties
+
+ You do not usually create Binding objects directly but instead describe
+ bindings in your class or object definition using automatic binding
+ detection.
+
+ Properties ending in a `Binding` suffix will be converted to `Ember.Binding`
+ instances. The value of this property should be a string representing a path
+ to another object or a custom binding instanced created using Binding helpers
+ (see "Customizing Your Bindings"):
+
+ ```
+ valueBinding: "MyApp.someController.title"
+ ```
+
+ This will create a binding from `MyApp.someController.title` to the `value`
+ property of your object instance automatically. Now the two values will be
+ kept in sync.
+
+ ## One Way Bindings
+
+ One especially useful binding customization you can use is the `oneWay()`
+ helper. This helper tells Ember that you are only interested in
+ receiving changes on the object you are binding from. For example, if you
+ are binding to a preference and you want to be notified if the preference
+ has changed, but your object will not be changing the preference itself, you
+ could do:
+
+ ```
+ bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")
+ ```
+
+ This way if the value of `MyApp.preferencesController.bigTitles` changes the
+ `bigTitles` property of your object will change also. However, if you
+ change the value of your `bigTitles` property, it will not update the
+ `preferencesController`.
+
+ One way bindings are almost twice as fast to setup and twice as fast to
+ execute because the binding only has to worry about changes to one side.
+
+ You should consider using one way bindings anytime you have an object that
+ may be created frequently and you do not intend to change a property; only
+ to monitor it for changes. (such as in the example above).
+
+ ## Adding Bindings Manually
+
+ All of the examples above show you how to configure a custom binding, but the
+ result of these customizations will be a binding template, not a fully active
+ Binding instance. The binding will actually become active only when you
+ instantiate the object the binding belongs to. It is useful however, to
+ understand what actually happens when the binding is activated.
+
+ For a binding to function it must have at least a `from` property and a `to`
+ property. The `from` property path points to the object/key that you want to
+ bind from while the `to` path points to the object/key you want to bind to.
+
+ When you define a custom binding, you are usually describing the property
+ you want to bind from (such as `MyApp.someController.value` in the examples
+ above). When your object is created, it will automatically assign the value
+ you want to bind `to` based on the name of your binding key. In the
+ examples above, during init, Ember objects will effectively call
+ something like this on your binding:
+
+ ```javascript
+ binding = Ember.Binding.from(this.valueBinding).to("value");
+ ```
+
+ This creates a new binding instance based on the template you provide, and
+ sets the to path to the `value` property of the new object. Now that the
+ binding is fully configured with a `from` and a `to`, it simply needs to be
+ connected to become active. This is done through the `connect()` method:
+
+ ```javascript
+ binding.connect(this);
+ ```
+
+ Note that when you connect a binding you pass the object you want it to be
+ connected to. This object will be used as the root for both the from and
+ to side of the binding when inspecting relative paths. This allows the
+ binding to be automatically inherited by subclassed objects as well.
+
+ Now that the binding is connected, it will observe both the from and to side
+ and relay changes.
+
+ If you ever needed to do so (you almost never will, but it is useful to
+ understand this anyway), you could manually create an active binding by
+ using the `Ember.bind()` helper method. (This is the same method used by
+ to setup your bindings on objects):
+
+ ```javascript
+ Ember.bind(MyApp.anotherObject, "value", "MyApp.someController.value");
+ ```
+
+ Both of these code fragments have the same effect as doing the most friendly
+ form of binding creation like so:
+
+ ```javascript
+ MyApp.anotherObject = Ember.Object.create({
+ valueBinding: "MyApp.someController.value",
+
+ // OTHER CODE FOR THIS OBJECT...
+ });
+ ```
+
+ Ember's built in binding creation method makes it easy to automatically
+ create bindings for you. You should always use the highest-level APIs
+ available, even if you understand how it works underneath.
+
+ @class Binding
+ @namespace Ember
+ @since Ember 0.9
+*/
+Ember.Binding = Binding;
+
+
+/**
+ Global helper method to create a new binding. Just pass the root object
+ along with a `to` and `from` path to create and connect the binding.
+
+ @method bind
+ @for Ember
+ @param {Object} obj The root object of the transform.
+ @param {String} to The path to the 'to' side of the binding.
+ Must be relative to obj.
+ @param {String} from The path to the 'from' side of the binding.
+ Must be relative to obj or a global path.
+ @return {Ember.Binding} binding instance
+*/
+Ember.bind = function(obj, to, from) {
+ return new Ember.Binding(to, from).connect(obj);
+};
+
+/**
+ @method oneWay
+ @for Ember
+ @param {Object} obj The root object of the transform.
+ @param {String} to The path to the 'to' side of the binding.
+ Must be relative to obj.
+ @param {String} from The path to the 'from' side of the binding.
+ Must be relative to obj or a global path.
+ @return {Ember.Binding} binding instance
+*/
+Ember.oneWay = function(obj, to, from) {
+ return new Ember.Binding(to, from).oneWay().connect(obj);
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var Mixin, REQUIRED, Alias,
+ a_map = Ember.ArrayPolyfills.map,
+ a_indexOf = Ember.ArrayPolyfills.indexOf,
+ a_forEach = Ember.ArrayPolyfills.forEach,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ defineProperty = Ember.defineProperty,
+ guidFor = Ember.guidFor;
+
+function mixinsMeta(obj) {
+ var m = Ember.meta(obj, true), ret = m.mixins;
+ if (!ret) {
+ ret = m.mixins = {};
+ } else if (!m.hasOwnProperty('mixins')) {
+ ret = m.mixins = o_create(ret);
+ }
+ return ret;
+}
+
+function initMixin(mixin, args) {
+ if (args && args.length > 0) {
+ mixin.mixins = a_map.call(args, function(x) {
+ if (x instanceof Mixin) { return x; }
+
+ // Note: Manually setup a primitive mixin here. This is the only
+ // way to actually get a primitive mixin. This way normal creation
+ // of mixins will give you combined mixins...
+ var mixin = new Mixin();
+ mixin.properties = x;
+ return mixin;
+ });
+ }
+ return mixin;
+}
+
+function isMethod(obj) {
+ return 'function' === typeof obj &&
+ obj.isMethod !== false &&
+ obj !== Boolean && obj !== Object && obj !== Number && obj !== Array && obj !== Date && obj !== String;
+}
+
+var CONTINUE = {};
+
+function mixinProperties(mixinsMeta, mixin) {
+ var guid;
+
+ if (mixin instanceof Mixin) {
+ guid = guidFor(mixin);
+ if (mixinsMeta[guid]) { return CONTINUE; }
+ mixinsMeta[guid] = mixin;
+ return mixin.properties;
+ } else {
+ return mixin; // apply anonymous mixin properties
+ }
+}
+
+function concatenatedProperties(props, values, base) {
+ var concats;
+
+ // reset before adding each new mixin to pickup concats from previous
+ concats = values.concatenatedProperties || base.concatenatedProperties;
+ if (props.concatenatedProperties) {
+ concats = concats ? concats.concat(props.concatenatedProperties) : props.concatenatedProperties;
+ }
+
+ return concats;
+}
+
+function giveDescriptorSuper(meta, key, property, values, descs) {
+ var superProperty;
+
+ // Computed properties override methods, and do not call super to them
+ if (values[key] === undefined) {
+ // Find the original descriptor in a parent mixin
+ superProperty = descs[key];
+ }
+
+ // If we didn't find the original descriptor in a parent mixin, find
+ // it on the original object.
+ superProperty = superProperty || meta.descs[key];
+
+ if (!superProperty || !(superProperty instanceof Ember.ComputedProperty)) {
+ return property;
+ }
+
+ // Since multiple mixins may inherit from the same parent, we need
+ // to clone the computed property so that other mixins do not receive
+ // the wrapped version.
+ property = o_create(property);
+ property.func = Ember.wrap(property.func, superProperty.func);
+
+ return property;
+}
+
+function giveMethodSuper(obj, key, method, values, descs) {
+ var superMethod;
+
+ // Methods overwrite computed properties, and do not call super to them.
+ if (descs[key] === undefined) {
+ // Find the original method in a parent mixin
+ superMethod = values[key];
+ }
+
+ // If we didn't find the original value in a parent mixin, find it in
+ // the original object
+ superMethod = superMethod || obj[key];
+
+ // Only wrap the new method if the original method was a function
+ if ('function' !== typeof superMethod) {
+ return method;
+ }
+
+ return Ember.wrap(method, superMethod);
+}
+
+function applyConcatenatedProperties(obj, key, value, values) {
+ var baseValue = values[key] || obj[key];
+
+ if (baseValue) {
+ if ('function' === typeof baseValue.concat) {
+ return baseValue.concat(value);
+ } else {
+ return Ember.makeArray(baseValue).concat(value);
+ }
+ } else {
+ return Ember.makeArray(value);
+ }
+}
+
+function addNormalizedProperty(base, key, value, meta, descs, values, concats) {
+ if (value instanceof Ember.Descriptor) {
+ if (value === REQUIRED && descs[key]) { return CONTINUE; }
+
+ // Wrap descriptor function to implement
+ // _super() if needed
+ if (value.func) {
+ value = giveDescriptorSuper(meta, key, value, values, descs);
+ }
+
+ descs[key] = value;
+ values[key] = undefined;
+ } else {
+ // impl super if needed...
+ if (isMethod(value)) {
+ value = giveMethodSuper(base, key, value, values, descs);
+ } else if ((concats && a_indexOf.call(concats, key) >= 0) || key === 'concatenatedProperties') {
+ value = applyConcatenatedProperties(base, key, value, values);
+ }
+
+ descs[key] = undefined;
+ values[key] = value;
+ }
+}
+
+function mergeMixins(mixins, m, descs, values, base) {
+ var mixin, props, key, concats, meta;
+
+ function removeKeys(keyName) {
+ delete descs[keyName];
+ delete values[keyName];
+ }
+
+ for(var i=0, l=mixins.length; i= 0) {
+ if (_detect(mixins[loc], targetMixin, seen)) { return true; }
+ }
+ return false;
+}
+
+/**
+ @method detect
+ @param obj
+ @return {Boolean}
+*/
+MixinPrototype.detect = function(obj) {
+ if (!obj) { return false; }
+ if (obj instanceof Mixin) { return _detect(obj, this, {}); }
+ var mixins = Ember.meta(obj, false).mixins;
+ if (mixins) {
+ return !!mixins[guidFor(this)];
+ }
+ return false;
+};
+
+MixinPrototype.without = function() {
+ var ret = new Mixin(this);
+ ret._without = a_slice.call(arguments);
+ return ret;
+};
+
+function _keys(ret, mixin, seen) {
+ if (seen[guidFor(mixin)]) { return; }
+ seen[guidFor(mixin)] = true;
+
+ if (mixin.properties) {
+ var props = mixin.properties;
+ for (var key in props) {
+ if (props.hasOwnProperty(key)) { ret[key] = true; }
+ }
+ } else if (mixin.mixins) {
+ a_forEach.call(mixin.mixins, function(x) { _keys(ret, x, seen); });
+ }
+}
+
+MixinPrototype.keys = function() {
+ var keys = {}, seen = {}, ret = [];
+ _keys(keys, this, seen);
+ for(var key in keys) {
+ if (keys.hasOwnProperty(key)) { ret.push(key); }
+ }
+ return ret;
+};
+
+// returns the mixins currently applied to the specified object
+// TODO: Make Ember.mixin
+Mixin.mixins = function(obj) {
+ var mixins = Ember.meta(obj, false).mixins, ret = [];
+
+ if (!mixins) { return ret; }
+
+ for (var key in mixins) {
+ var mixin = mixins[key];
+
+ // skip primitive mixins since these are always anonymous
+ if (!mixin.properties) { ret.push(mixin); }
+ }
+
+ return ret;
+};
+
+REQUIRED = new Ember.Descriptor();
+REQUIRED.toString = function() { return '(Required Property)'; };
+
+/**
+ Denotes a required property for a mixin
+
+ @method required
+ @for Ember
+*/
+Ember.required = function() {
+ return REQUIRED;
+};
+
+Alias = function(methodName) {
+ this.methodName = methodName;
+};
+Alias.prototype = new Ember.Descriptor();
+
+/**
+ Makes a property or method available via an additional name.
+
+ ```javascript
+ App.PaintSample = Ember.Object.extend({
+ color: 'red',
+ colour: Ember.alias('color'),
+ name: function(){
+ return "Zed";
+ },
+ moniker: Ember.alias("name")
+ });
+
+ var paintSample = App.PaintSample.create()
+ paintSample.get('colour'); // 'red'
+ paintSample.moniker(); // 'Zed'
+ ```
+
+ @method alias
+ @for Ember
+ @param {String} methodName name of the method or property to alias
+ @return {Ember.Descriptor}
+ @deprecated Use `Ember.aliasMethod` or `Ember.computed.alias` instead
+*/
+Ember.alias = function(methodName) {
+ return new Alias(methodName);
+};
+
+Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.", Ember.alias);
+
+/**
+ Makes a method available via an additional name.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ name: function(){
+ return 'Tomhuda Katzdale';
+ },
+ moniker: Ember.aliasMethod('name')
+ });
+
+ var goodGuy = App.Person.create()
+ ```
+
+ @method aliasMethod
+ @for Ember
+ @param {String} methodName name of the method to alias
+ @return {Ember.Descriptor}
+*/
+Ember.aliasMethod = function(methodName) {
+ return new Alias(methodName);
+};
+
+// ..........................................................
+// OBSERVER HELPER
+//
+
+/**
+ @method observer
+ @for Ember
+ @param {Function} func
+ @param {String} propertyNames*
+ @return func
+*/
+Ember.observer = function(func) {
+ var paths = a_slice.call(arguments, 1);
+ func.__ember_observes__ = paths;
+ return func;
+};
+
+// If observers ever become asynchronous, Ember.immediateObserver
+// must remain synchronous.
+/**
+ @method immediateObserver
+ @for Ember
+ @param {Function} func
+ @param {String} propertyNames*
+ @return func
+*/
+Ember.immediateObserver = function() {
+ for (var i=0, l=arguments.length; i w.
+*/
+Ember.compare = function compare(v, w) {
+ if (v === w) { return 0; }
+
+ var type1 = Ember.typeOf(v);
+ var type2 = Ember.typeOf(w);
+
+ var Comparable = Ember.Comparable;
+ if (Comparable) {
+ if (type1==='instance' && Comparable.detect(v.constructor)) {
+ return v.constructor.compare(v, w);
+ }
+
+ if (type2 === 'instance' && Comparable.detect(w.constructor)) {
+ return 1-w.constructor.compare(w, v);
+ }
+ }
+
+ // If we haven't yet generated a reverse-mapping of Ember.ORDER_DEFINITION,
+ // do so now.
+ var mapping = Ember.ORDER_DEFINITION_MAPPING;
+ if (!mapping) {
+ var order = Ember.ORDER_DEFINITION;
+ mapping = Ember.ORDER_DEFINITION_MAPPING = {};
+ var idx, len;
+ for (idx = 0, len = order.length; idx < len; ++idx) {
+ mapping[order[idx]] = idx;
+ }
+
+ // We no longer need Ember.ORDER_DEFINITION.
+ delete Ember.ORDER_DEFINITION;
+ }
+
+ var type1Index = mapping[type1];
+ var type2Index = mapping[type2];
+
+ if (type1Index < type2Index) { return -1; }
+ if (type1Index > type2Index) { return 1; }
+
+ // types are equal - so we have to check values now
+ switch (type1) {
+ case 'boolean':
+ case 'number':
+ if (v < w) { return -1; }
+ if (v > w) { return 1; }
+ return 0;
+
+ case 'string':
+ var comp = v.localeCompare(w);
+ if (comp < 0) { return -1; }
+ if (comp > 0) { return 1; }
+ return 0;
+
+ case 'array':
+ var vLen = v.length;
+ var wLen = w.length;
+ var l = Math.min(vLen, wLen);
+ var r = 0;
+ var i = 0;
+ while (r === 0 && i < l) {
+ r = compare(v[i],w[i]);
+ i++;
+ }
+ if (r !== 0) { return r; }
+
+ // all elements are equal now
+ // shorter array should be ordered first
+ if (vLen < wLen) { return -1; }
+ if (vLen > wLen) { return 1; }
+ // arrays are equal now
+ return 0;
+
+ case 'instance':
+ if (Ember.Comparable && Ember.Comparable.detect(v)) {
+ return v.compare(v, w);
+ }
+ return 0;
+
+ case 'date':
+ var vNum = v.getTime();
+ var wNum = w.getTime();
+ if (vNum < wNum) { return -1; }
+ if (vNum > wNum) { return 1; }
+ return 0;
+
+ default:
+ return 0;
+ }
+};
+
+function _copy(obj, deep, seen, copies) {
+ var ret, loc, key;
+
+ // primitive data types are immutable, just return them.
+ if ('object' !== typeof obj || obj===null) return obj;
+
+ // avoid cyclical loops
+ if (deep && (loc=indexOf(seen, obj))>=0) return copies[loc];
+
+ Ember.assert('Cannot clone an Ember.Object that does not implement Ember.Copyable', !(obj instanceof Ember.Object) || (Ember.Copyable && Ember.Copyable.detect(obj)));
+
+ // IMPORTANT: this specific test will detect a native array only. Any other
+ // object will need to implement Copyable.
+ if (Ember.typeOf(obj) === 'array') {
+ ret = obj.slice();
+ if (deep) {
+ loc = ret.length;
+ while(--loc>=0) ret[loc] = _copy(ret[loc], deep, seen, copies);
+ }
+ } else if (Ember.Copyable && Ember.Copyable.detect(obj)) {
+ ret = obj.copy(deep, seen, copies);
+ } else {
+ ret = {};
+ for(key in obj) {
+ if (!obj.hasOwnProperty(key)) continue;
+
+ // Prevents browsers that don't respect non-enumerability from
+ // copying internal Ember properties
+ if (key.substring(0,2) === '__') continue;
+
+ ret[key] = deep ? _copy(obj[key], deep, seen, copies) : obj[key];
+ }
+ }
+
+ if (deep) {
+ seen.push(obj);
+ copies.push(ret);
+ }
+
+ return ret;
+}
+
+/**
+ Creates a clone of the passed object. This function can take just about
+ any type of object and create a clone of it, including primitive values
+ (which are not actually cloned because they are immutable).
+
+ If the passed object implements the `clone()` method, then this function
+ will simply call that method and return the result.
+
+ @method copy
+ @for Ember
+ @param {Object} obj The object to clone
+ @param {Boolean} deep If true, a deep copy of the object is made
+ @return {Object} The cloned object
+*/
+Ember.copy = function(obj, deep) {
+ // fast paths
+ if ('object' !== typeof obj || obj===null) return obj; // can't copy primitives
+ if (Ember.Copyable && Ember.Copyable.detect(obj)) return obj.copy(deep);
+ return _copy(obj, deep, deep ? [] : null, deep ? [] : null);
+};
+
+/**
+ Convenience method to inspect an object. This method will attempt to
+ convert the object into a useful string description.
+
+ It is a pretty simple implementation. If you want something more robust,
+ use something like JSDump: https://github.com/NV/jsDump
+
+ @method inspect
+ @for Ember
+ @param {Object} obj The object you want to inspect.
+ @return {String} A description of the object
+*/
+Ember.inspect = function(obj) {
+ if (typeof obj !== 'object' || obj === null) {
+ return obj + '';
+ }
+
+ var v, ret = [];
+ for(var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ v = obj[key];
+ if (v === 'toString') { continue; } // ignore useless items
+ if (Ember.typeOf(v) === 'function') { v = "function() { ... }"; }
+ ret.push(key + ": " + v);
+ }
+ }
+ return "{" + ret.join(", ") + "}";
+};
+
+/**
+ Compares two objects, returning true if they are logically equal. This is
+ a deeper comparison than a simple triple equal. For sets it will compare the
+ internal objects. For any other object that implements `isEqual()` it will
+ respect that method.
+
+ ```javascript
+ Ember.isEqual('hello', 'hello'); // true
+ Ember.isEqual(1, 2); // false
+ Ember.isEqual([4,2], [4,2]); // false
+ ```
+
+ @method isEqual
+ @for Ember
+ @param {Object} a first object to compare
+ @param {Object} b second object to compare
+ @return {Boolean}
+*/
+Ember.isEqual = function(a, b) {
+ if (a && 'function'===typeof a.isEqual) return a.isEqual(b);
+ return a === b;
+};
+
+// Used by Ember.compare
+Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [
+ 'undefined',
+ 'null',
+ 'boolean',
+ 'number',
+ 'string',
+ 'array',
+ 'object',
+ 'instance',
+ 'function',
+ 'class',
+ 'date'
+];
+
+/**
+ Returns all of the keys defined on an object or hash. This is useful
+ when inspecting objects for debugging. On browsers that support it, this
+ uses the native `Object.keys` implementation.
+
+ @method keys
+ @for Ember
+ @param {Object} obj
+ @return {Array} Array containing keys of obj
+*/
+Ember.keys = Object.keys;
+
+if (!Ember.keys) {
+ Ember.keys = function(obj) {
+ var ret = [];
+ for(var key in obj) {
+ if (obj.hasOwnProperty(key)) { ret.push(key); }
+ }
+ return ret;
+ };
+}
+
+// ..........................................................
+// ERROR
+//
+
+var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
+
+/**
+ A subclass of the JavaScript Error object for use in Ember.
+
+ @class Error
+ @namespace Ember
+ @extends Error
+ @constructor
+*/
+Ember.Error = function() {
+ var tmp = Error.prototype.constructor.apply(this, arguments);
+
+ // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
+ for (var idx = 0; idx < errorProps.length; idx++) {
+ this[errorProps[idx]] = tmp[errorProps[idx]];
+ }
+};
+
+Ember.Error.prototype = Ember.create(Error.prototype);
+
+})();
+
+
+
+(function() {
+/**
+ Expose RSVP implementation
+
+ @class RSVP
+ @namespace Ember
+ @constructor
+*/
+Ember.RSVP = requireModule('rsvp');
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var STRING_DASHERIZE_REGEXP = (/[ _]/g);
+var STRING_DASHERIZE_CACHE = {};
+var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g);
+var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g);
+var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g);
+var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g);
+
+/**
+ Defines the hash of localized strings for the current language. Used by
+ the `Ember.String.loc()` helper. To localize, add string values to this
+ hash.
+
+ @property STRINGS
+ @for Ember
+ @type Hash
+*/
+Ember.STRINGS = {};
+
+/**
+ Defines string helper methods including string formatting and localization.
+ Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be
+ added to the `String.prototype` as well.
+
+ @class String
+ @namespace Ember
+ @static
+*/
+Ember.String = {
+
+ /**
+ Apply formatting options to the string. This will look for occurrences
+ of "%@" in your string and substitute them with the arguments you pass into
+ this method. If you want to control the specific order of replacement,
+ you can add a number after the key as well to indicate which argument
+ you want to insert.
+
+ Ordered insertions are most useful when building loc strings where values
+ you need to insert may appear in different orders.
+
+ ```javascript
+ "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe"
+ "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John"
+ ```
+
+ @method fmt
+ @param {Object...} [args]
+ @return {String} formatted string
+ */
+ fmt: function(str, formats) {
+ // first, replace any ORDERED replacements.
+ var idx = 0; // the current index for non-numerical replacements
+ return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
+ argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ;
+ s = formats[argIndex];
+ return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString();
+ }) ;
+ },
+
+ /**
+ Formats the passed string, but first looks up the string in the localized
+ strings hash. This is a convenient way to localize text. See
+ `Ember.String.fmt()` for more information on formatting.
+
+ Note that it is traditional but not required to prefix localized string
+ keys with an underscore or other character so you can easily identify
+ localized strings.
+
+ ```javascript
+ Ember.STRINGS = {
+ '_Hello World': 'Bonjour le monde',
+ '_Hello %@ %@': 'Bonjour %@ %@'
+ };
+
+ Ember.String.loc("_Hello World"); // 'Bonjour le monde';
+ Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith";
+ ```
+
+ @method loc
+ @param {String} str The string to format
+ @param {Array} formats Optional array of parameters to interpolate into string.
+ @return {String} formatted string
+ */
+ loc: function(str, formats) {
+ str = Ember.STRINGS[str] || str;
+ return Ember.String.fmt(str, formats) ;
+ },
+
+ /**
+ Splits a string into separate units separated by spaces, eliminating any
+ empty strings in the process. This is a convenience method for split that
+ is mostly useful when applied to the `String.prototype`.
+
+ ```javascript
+ Ember.String.w("alpha beta gamma").forEach(function(key) {
+ console.log(key);
+ });
+
+ // > alpha
+ // > beta
+ // > gamma
+ ```
+
+ @method w
+ @param {String} str The string to split
+ @return {String} split string
+ */
+ w: function(str) { return str.split(/\s+/); },
+
+ /**
+ Converts a camelized string into all lower case separated by underscores.
+
+ ```javascript
+ 'innerHTML'.decamelize(); // 'inner_html'
+ 'action_name'.decamelize(); // 'action_name'
+ 'css-class-name'.decamelize(); // 'css-class-name'
+ 'my favorite items'.decamelize(); // 'my favorite items'
+ ```
+
+ @method decamelize
+ @param {String} str The string to decamelize.
+ @return {String} the decamelized string.
+ */
+ decamelize: function(str) {
+ return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();
+ },
+
+ /**
+ Replaces underscores or spaces with dashes.
+
+ ```javascript
+ 'innerHTML'.dasherize(); // 'inner-html'
+ 'action_name'.dasherize(); // 'action-name'
+ 'css-class-name'.dasherize(); // 'css-class-name'
+ 'my favorite items'.dasherize(); // 'my-favorite-items'
+ ```
+
+ @method dasherize
+ @param {String} str The string to dasherize.
+ @return {String} the dasherized string.
+ */
+ dasherize: function(str) {
+ var cache = STRING_DASHERIZE_CACHE,
+ hit = cache.hasOwnProperty(str),
+ ret;
+
+ if (hit) {
+ return cache[str];
+ } else {
+ ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
+ cache[str] = ret;
+ }
+
+ return ret;
+ },
+
+ /**
+ Returns the lowerCamelCase form of a string.
+
+ ```javascript
+ 'innerHTML'.camelize(); // 'innerHTML'
+ 'action_name'.camelize(); // 'actionName'
+ 'css-class-name'.camelize(); // 'cssClassName'
+ 'my favorite items'.camelize(); // 'myFavoriteItems'
+ 'My Favorite Items'.camelize(); // 'myFavoriteItems'
+ ```
+
+ @method camelize
+ @param {String} str The string to camelize.
+ @return {String} the camelized string.
+ */
+ camelize: function(str) {
+ return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) {
+ return chr ? chr.toUpperCase() : '';
+ }).replace(/^([A-Z])/, function(match, separator, chr) {
+ return match.toLowerCase();
+ });
+ },
+
+ /**
+ Returns the UpperCamelCase form of a string.
+
+ ```javascript
+ 'innerHTML'.classify(); // 'InnerHTML'
+ 'action_name'.classify(); // 'ActionName'
+ 'css-class-name'.classify(); // 'CssClassName'
+ 'my favorite items'.classify(); // 'MyFavoriteItems'
+ ```
+
+ @method classify
+ @param {String} str the string to classify
+ @return {String} the classified string
+ */
+ classify: function(str) {
+ var parts = str.split("."),
+ out = [];
+
+ for (var i=0, l=parts.length; i1) args = a_slice.call(arguments, 1);
+
+ this.forEach(function(x, idx) {
+ var method = x && x[methodName];
+ if ('function' === typeof method) {
+ ret[idx] = args ? method.apply(x, args) : method.call(x);
+ }
+ }, this);
+
+ return ret;
+ },
+
+ /**
+ Simply converts the enumerable into a genuine array. The order is not
+ guaranteed. Corresponds to the method implemented by Prototype.
+
+ @method toArray
+ @return {Array} the enumerable as an array.
+ */
+ toArray: function() {
+ var ret = Ember.A([]);
+ this.forEach(function(o, idx) { ret[idx] = o; });
+ return ret ;
+ },
+
+ /**
+ Returns a copy of the array with all null and undefined elements removed.
+
+ ```javascript
+ var arr = ["a", null, "c", undefined];
+ arr.compact(); // ["a", "c"]
+ ```
+
+ @method compact
+ @return {Array} the array without null and undefined elements.
+ */
+ compact: function() {
+ return this.filter(function(value) { return value != null; });
+ },
+
+ /**
+ Returns a new enumerable that excludes the passed value. The default
+ implementation returns an array regardless of the receiver type unless
+ the receiver does not contain the value.
+
+ ```javascript
+ var arr = ["a", "b", "a", "c"];
+ arr.without("a"); // ["b", "c"]
+ ```
+
+ @method without
+ @param {Object} value
+ @return {Ember.Enumerable}
+ */
+ without: function(value) {
+ if (!this.contains(value)) return this; // nothing to do
+ var ret = Ember.A([]);
+ this.forEach(function(k) {
+ if (k !== value) ret[ret.length] = k;
+ }) ;
+ return ret ;
+ },
+
+ /**
+ Returns a new enumerable that contains only unique values. The default
+ implementation returns an array regardless of the receiver type.
+
+ ```javascript
+ var arr = ["a", "a", "b", "b"];
+ arr.uniq(); // ["a", "b"]
+ ```
+
+ @method uniq
+ @return {Ember.Enumerable}
+ */
+ uniq: function() {
+ var ret = Ember.A([]);
+ this.forEach(function(k){
+ if (a_indexOf(ret, k)<0) ret.push(k);
+ });
+ return ret;
+ },
+
+ /**
+ This property will trigger anytime the enumerable's content changes.
+ You can observe this property to be notified of changes to the enumerables
+ content.
+
+ For plain enumerables, this property is read only. `Ember.Array` overrides
+ this method.
+
+ @property []
+ @type Ember.Array
+ @return this
+ */
+ '[]': Ember.computed(function(key, value) {
+ return this;
+ }),
+
+ // ..........................................................
+ // ENUMERABLE OBSERVERS
+ //
+
+ /**
+ Registers an enumerable observer. Must implement `Ember.EnumerableObserver`
+ mixin.
+
+ @method addEnumerableObserver
+ @param {Object} target
+ @param {Hash} [opts]
+ @return this
+ */
+ addEnumerableObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'enumerableWillChange',
+ didChange = (opts && opts.didChange) || 'enumerableDidChange';
+
+ var hasObservers = get(this, 'hasEnumerableObservers');
+ if (!hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
+ Ember.addListener(this, '@enumerable:before', target, willChange);
+ Ember.addListener(this, '@enumerable:change', target, didChange);
+ if (!hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
+ return this;
+ },
+
+ /**
+ Removes a registered enumerable observer.
+
+ @method removeEnumerableObserver
+ @param {Object} target
+ @param {Hash} [opts]
+ @return this
+ */
+ removeEnumerableObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'enumerableWillChange',
+ didChange = (opts && opts.didChange) || 'enumerableDidChange';
+
+ var hasObservers = get(this, 'hasEnumerableObservers');
+ if (hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
+ Ember.removeListener(this, '@enumerable:before', target, willChange);
+ Ember.removeListener(this, '@enumerable:change', target, didChange);
+ if (hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
+ return this;
+ },
+
+ /**
+ Becomes true whenever the array currently has observers watching changes
+ on the array.
+
+ @property hasEnumerableObservers
+ @type Boolean
+ */
+ hasEnumerableObservers: Ember.computed(function() {
+ return Ember.hasListeners(this, '@enumerable:change') || Ember.hasListeners(this, '@enumerable:before');
+ }),
+
+
+ /**
+ Invoke this method just before the contents of your enumerable will
+ change. You can either omit the parameters completely or pass the objects
+ to be removed or added if available or just a count.
+
+ @method enumerableContentWillChange
+ @param {Ember.Enumerable|Number} removing An enumerable of the objects to
+ be removed or the number of items to be removed.
+ @param {Ember.Enumerable|Number} adding An enumerable of the objects to be
+ added or the number of items to be added.
+ @chainable
+ */
+ enumerableContentWillChange: function(removing, adding) {
+
+ var removeCnt, addCnt, hasDelta;
+
+ if ('number' === typeof removing) removeCnt = removing;
+ else if (removing) removeCnt = get(removing, 'length');
+ else removeCnt = removing = -1;
+
+ if ('number' === typeof adding) addCnt = adding;
+ else if (adding) addCnt = get(adding,'length');
+ else addCnt = adding = -1;
+
+ hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
+
+ if (removing === -1) removing = null;
+ if (adding === -1) adding = null;
+
+ Ember.propertyWillChange(this, '[]');
+ if (hasDelta) Ember.propertyWillChange(this, 'length');
+ Ember.sendEvent(this, '@enumerable:before', [this, removing, adding]);
+
+ return this;
+ },
+
+ /**
+ Invoke this method when the contents of your enumerable has changed.
+ This will notify any observers watching for content changes. If your are
+ implementing an ordered enumerable (such as an array), also pass the
+ start and end values where the content changed so that it can be used to
+ notify range observers.
+
+ @method enumerableContentDidChange
+ @param {Number} [start] optional start offset for the content change.
+ For unordered enumerables, you should always pass -1.
+ @param {Ember.Enumerable|Number} removing An enumerable of the objects to
+ be removed or the number of items to be removed.
+ @param {Ember.Enumerable|Number} adding An enumerable of the objects to
+ be added or the number of items to be added.
+ @chainable
+ */
+ enumerableContentDidChange: function(removing, adding) {
+ var removeCnt, addCnt, hasDelta;
+
+ if ('number' === typeof removing) removeCnt = removing;
+ else if (removing) removeCnt = get(removing, 'length');
+ else removeCnt = removing = -1;
+
+ if ('number' === typeof adding) addCnt = adding;
+ else if (adding) addCnt = get(adding, 'length');
+ else addCnt = adding = -1;
+
+ hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
+
+ if (removing === -1) removing = null;
+ if (adding === -1) adding = null;
+
+ Ember.sendEvent(this, '@enumerable:change', [this, removing, adding]);
+ if (hasDelta) Ember.propertyDidChange(this, 'length');
+ Ember.propertyDidChange(this, '[]');
+
+ return this ;
+ }
+
+}) ;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+// ..........................................................
+// HELPERS
+//
+
+var get = Ember.get, set = Ember.set, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
+
+function none(obj) { return obj===null || obj===undefined; }
+
+// ..........................................................
+// ARRAY
+//
+/**
+ This module implements Observer-friendly Array-like behavior. This mixin is
+ picked up by the Array class as well as other controllers, etc. that want to
+ appear to be arrays.
+
+ Unlike `Ember.Enumerable,` this mixin defines methods specifically for
+ collections that provide index-ordered access to their contents. When you
+ are designing code that needs to accept any kind of Array-like object, you
+ should use these methods instead of Array primitives because these will
+ properly notify observers of changes to the array.
+
+ Although these methods are efficient, they do add a layer of indirection to
+ your application so it is a good idea to use them only when you need the
+ flexibility of using both true JavaScript arrays and "virtual" arrays such
+ as controllers and collections.
+
+ You can use the methods defined in this module to access and modify array
+ contents in a KVO-friendly way. You can also be notified whenever the
+ membership if an array changes by changing the syntax of the property to
+ `.observes('*myProperty.[]')`.
+
+ To support `Ember.Array` in your own class, you must override two
+ primitives to use it: `replace()` and `objectAt()`.
+
+ Note that the Ember.Array mixin also incorporates the `Ember.Enumerable`
+ mixin. All `Ember.Array`-like objects are also enumerable.
+
+ @class Array
+ @namespace Ember
+ @extends Ember.Mixin
+ @uses Ember.Enumerable
+ @since Ember 0.9.0
+*/
+Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.prototype */ {
+
+ // compatibility
+ isSCArray: true,
+
+ /**
+ Your array must support the `length` property. Your replace methods should
+ set this property whenever it changes.
+
+ @property {Number} length
+ */
+ length: Ember.required(),
+
+ /**
+ Returns the object at the given `index`. If the given `index` is negative
+ or is greater or equal than the array length, returns `undefined`.
+
+ This is one of the primitives you must implement to support `Ember.Array`.
+ If your object supports retrieving the value of an array item using `get()`
+ (i.e. `myArray.get(0)`), then you do not need to implement this method
+ yourself.
+
+ ```javascript
+ var arr = ['a', 'b', 'c', 'd'];
+ arr.objectAt(0); // "a"
+ arr.objectAt(3); // "d"
+ arr.objectAt(-1); // undefined
+ arr.objectAt(4); // undefined
+ arr.objectAt(5); // undefined
+ ```
+
+ @method objectAt
+ @param {Number} idx The index of the item to return.
+ @return {any} item at index or undefined
+ */
+ objectAt: function(idx) {
+ if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
+ return get(this, idx);
+ },
+
+ /**
+ This returns the objects at the specified indexes, using `objectAt`.
+
+ ```javascript
+ var arr = ['a', 'b', 'c', 'd'];
+ arr.objectsAt([0, 1, 2]); // ["a", "b", "c"]
+ arr.objectsAt([2, 3, 4]); // ["c", "d", undefined]
+ ```
+
+ @method objectsAt
+ @param {Array} indexes An array of indexes of items to return.
+ @return {Array}
+ */
+ objectsAt: function(indexes) {
+ var self = this;
+ return map(indexes, function(idx){ return self.objectAt(idx); });
+ },
+
+ // overrides Ember.Enumerable version
+ nextObject: function(idx) {
+ return this.objectAt(idx);
+ },
+
+ /**
+ This is the handler for the special array content property. If you get
+ this property, it will return this. If you set this property it a new
+ array, it will replace the current content.
+
+ This property overrides the default property defined in `Ember.Enumerable`.
+
+ @property []
+ @return this
+ */
+ '[]': Ember.computed(function(key, value) {
+ if (value !== undefined) this.replace(0, get(this, 'length'), value) ;
+ return this ;
+ }),
+
+ firstObject: Ember.computed(function() {
+ return this.objectAt(0);
+ }),
+
+ lastObject: Ember.computed(function() {
+ return this.objectAt(get(this, 'length')-1);
+ }),
+
+ // optimized version from Enumerable
+ contains: function(obj){
+ return this.indexOf(obj) >= 0;
+ },
+
+ // Add any extra methods to Ember.Array that are native to the built-in Array.
+ /**
+ Returns a new array that is a slice of the receiver. This implementation
+ uses the observable array methods to retrieve the objects for the new
+ slice.
+
+ ```javascript
+ var arr = ['red', 'green', 'blue'];
+ arr.slice(0); // ['red', 'green', 'blue']
+ arr.slice(0, 2); // ['red', 'green']
+ arr.slice(1, 100); // ['green', 'blue']
+ ```
+
+ @method slice
+ @param {Integer} beginIndex (Optional) index to begin slicing from.
+ @param {Integer} endIndex (Optional) index to end the slice at.
+ @return {Array} New array with specified slice
+ */
+ slice: function(beginIndex, endIndex) {
+ var ret = Ember.A([]);
+ var length = get(this, 'length') ;
+ if (none(beginIndex)) beginIndex = 0 ;
+ if (none(endIndex) || (endIndex > length)) endIndex = length ;
+
+ if (beginIndex < 0) beginIndex = length + beginIndex;
+ if (endIndex < 0) endIndex = length + endIndex;
+
+ while(beginIndex < endIndex) {
+ ret[ret.length] = this.objectAt(beginIndex++) ;
+ }
+ return ret ;
+ },
+
+ /**
+ Returns the index of the given object's first occurrence.
+ If no `startAt` argument is given, the starting location to
+ search is 0. If it's negative, will count backward from
+ the end of the array. Returns -1 if no match is found.
+
+ ```javascript
+ var arr = ["a", "b", "c", "d", "a"];
+ arr.indexOf("a"); // 0
+ arr.indexOf("z"); // -1
+ arr.indexOf("a", 2); // 4
+ arr.indexOf("a", -1); // 4
+ arr.indexOf("b", 3); // -1
+ arr.indexOf("a", 100); // -1
+ ```
+
+ @method indexOf
+ @param {Object} object the item to search for
+ @param {Number} startAt optional starting location to search, default 0
+ @return {Number} index or -1 if not found
+ */
+ indexOf: function(object, startAt) {
+ var idx, len = get(this, 'length');
+
+ if (startAt === undefined) startAt = 0;
+ if (startAt < 0) startAt += len;
+
+ for(idx=startAt;idx= len) startAt = len-1;
+ if (startAt < 0) startAt += len;
+
+ for(idx=startAt;idx>=0;idx--) {
+ if (this.objectAt(idx) === object) return idx ;
+ }
+ return -1;
+ },
+
+ // ..........................................................
+ // ARRAY OBSERVERS
+ //
+
+ /**
+ Adds an array observer to the receiving array. The array observer object
+ normally must implement two methods:
+
+ * `arrayWillChange(start, removeCount, addCount)` - This method will be
+ called just before the array is modified.
+ * `arrayDidChange(start, removeCount, addCount)` - This method will be
+ called just after the array is modified.
+
+ Both callbacks will be passed the starting index of the change as well a
+ a count of the items to be removed and added. You can use these callbacks
+ to optionally inspect the array during the change, clear caches, or do
+ any other bookkeeping necessary.
+
+ In addition to passing a target, you can also include an options hash
+ which you can use to override the method names that will be invoked on the
+ target.
+
+ @method addArrayObserver
+ @param {Object} target The observer object.
+ @param {Hash} opts Optional hash of configuration options including
+ `willChange` and `didChange` option.
+ @return {Ember.Array} receiver
+ */
+ addArrayObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'arrayWillChange',
+ didChange = (opts && opts.didChange) || 'arrayDidChange';
+
+ var hasObservers = get(this, 'hasArrayObservers');
+ if (!hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
+ Ember.addListener(this, '@array:before', target, willChange);
+ Ember.addListener(this, '@array:change', target, didChange);
+ if (!hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
+ return this;
+ },
+
+ /**
+ Removes an array observer from the object if the observer is current
+ registered. Calling this method multiple times with the same object will
+ have no effect.
+
+ @method removeArrayObserver
+ @param {Object} target The object observing the array.
+ @param {Hash} opts Optional hash of configuration options including
+ `willChange` and `didChange` option.
+ @return {Ember.Array} receiver
+ */
+ removeArrayObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'arrayWillChange',
+ didChange = (opts && opts.didChange) || 'arrayDidChange';
+
+ var hasObservers = get(this, 'hasArrayObservers');
+ if (hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
+ Ember.removeListener(this, '@array:before', target, willChange);
+ Ember.removeListener(this, '@array:change', target, didChange);
+ if (hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
+ return this;
+ },
+
+ /**
+ Becomes true whenever the array currently has observers watching changes
+ on the array.
+
+ @property Boolean
+ */
+ hasArrayObservers: Ember.computed(function() {
+ return Ember.hasListeners(this, '@array:change') || Ember.hasListeners(this, '@array:before');
+ }),
+
+ /**
+ If you are implementing an object that supports `Ember.Array`, call this
+ method just before the array content changes to notify any observers and
+ invalidate any related properties. Pass the starting index of the change
+ as well as a delta of the amounts to change.
+
+ @method arrayContentWillChange
+ @param {Number} startIdx The starting index in the array that will change.
+ @param {Number} removeAmt The number of items that will be removed. If you
+ pass `null` assumes 0
+ @param {Number} addAmt The number of items that will be added If you
+ pass `null` assumes 0.
+ @return {Ember.Array} receiver
+ */
+ arrayContentWillChange: function(startIdx, removeAmt, addAmt) {
+
+ // if no args are passed assume everything changes
+ if (startIdx===undefined) {
+ startIdx = 0;
+ removeAmt = addAmt = -1;
+ } else {
+ if (removeAmt === undefined) removeAmt=-1;
+ if (addAmt === undefined) addAmt=-1;
+ }
+
+ // Make sure the @each proxy is set up if anyone is observing @each
+ if (Ember.isWatching(this, '@each')) { get(this, '@each'); }
+
+ Ember.sendEvent(this, '@array:before', [this, startIdx, removeAmt, addAmt]);
+
+ var removing, lim;
+ if (startIdx>=0 && removeAmt>=0 && get(this, 'hasEnumerableObservers')) {
+ removing = [];
+ lim = startIdx+removeAmt;
+ for(var idx=startIdx;idx=0 && addAmt>=0 && get(this, 'hasEnumerableObservers')) {
+ adding = [];
+ lim = startIdx+addAmt;
+ for(var idx=startIdx;idx b`
+
+ Default implementation raises an exception.
+
+ @method compare
+ @param a {Object} the first object to compare
+ @param b {Object} the second object to compare
+ @return {Integer} the result of the comparison
+ */
+ compare: Ember.required(Function)
+
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ Implements some standard methods for copying an object. Add this mixin to
+ any object you create that can create a copy of itself. This mixin is
+ added automatically to the built-in array.
+
+ You should generally implement the `copy()` method to return a copy of the
+ receiver.
+
+ Note that `frozenCopy()` will only work if you also implement
+ `Ember.Freezable`.
+
+ @class Copyable
+ @namespace Ember
+ @extends Ember.Mixin
+ @since Ember 0.9
+*/
+Ember.Copyable = Ember.Mixin.create(
+/** @scope Ember.Copyable.prototype */ {
+
+ /**
+ Override to return a copy of the receiver. Default implementation raises
+ an exception.
+
+ @method copy
+ @param {Boolean} deep if `true`, a deep copy of the object should be made
+ @return {Object} copy of receiver
+ */
+ copy: Ember.required(Function),
+
+ /**
+ If the object implements `Ember.Freezable`, then this will return a new
+ copy if the object is not frozen and the receiver if the object is frozen.
+
+ Raises an exception if you try to call this method on a object that does
+ not support freezing.
+
+ You should use this method whenever you want a copy of a freezable object
+ since a freezable object can simply return itself without actually
+ consuming more memory.
+
+ @method frozenCopy
+ @return {Object} copy of receiver or receiver
+ */
+ frozenCopy: function() {
+ if (Ember.Freezable && Ember.Freezable.detect(this)) {
+ return get(this, 'isFrozen') ? this : this.copy().freeze();
+ } else {
+ throw new Error(Ember.String.fmt("%@ does not support freezing", [this]));
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ The `Ember.Freezable` mixin implements some basic methods for marking an
+ object as frozen. Once an object is frozen it should be read only. No changes
+ may be made the internal state of the object.
+
+ ## Enforcement
+
+ To fully support freezing in your subclass, you must include this mixin and
+ override any method that might alter any property on the object to instead
+ raise an exception. You can check the state of an object by checking the
+ `isFrozen` property.
+
+ Although future versions of JavaScript may support language-level freezing
+ object objects, that is not the case today. Even if an object is freezable,
+ it is still technically possible to modify the object, even though it could
+ break other parts of your application that do not expect a frozen object to
+ change. It is, therefore, very important that you always respect the
+ `isFrozen` property on all freezable objects.
+
+ ## Example Usage
+
+ The example below shows a simple object that implement the `Ember.Freezable`
+ protocol.
+
+ ```javascript
+ Contact = Ember.Object.extend(Ember.Freezable, {
+ firstName: null,
+ lastName: null,
+
+ // swaps the names
+ swapNames: function() {
+ if (this.get('isFrozen')) throw Ember.FROZEN_ERROR;
+ var tmp = this.get('firstName');
+ this.set('firstName', this.get('lastName'));
+ this.set('lastName', tmp);
+ return this;
+ }
+
+ });
+
+ c = Context.create({ firstName: "John", lastName: "Doe" });
+ c.swapNames(); // returns c
+ c.freeze();
+ c.swapNames(); // EXCEPTION
+ ```
+
+ ## Copying
+
+ Usually the `Ember.Freezable` protocol is implemented in cooperation with the
+ `Ember.Copyable` protocol, which defines a `frozenCopy()` method that will
+ return a frozen object, if the object implements this method as well.
+
+ @class Freezable
+ @namespace Ember
+ @extends Ember.Mixin
+ @since Ember 0.9
+*/
+Ember.Freezable = Ember.Mixin.create(
+/** @scope Ember.Freezable.prototype */ {
+
+ /**
+ Set to `true` when the object is frozen. Use this property to detect
+ whether your object is frozen or not.
+
+ @property isFrozen
+ @type Boolean
+ */
+ isFrozen: false,
+
+ /**
+ Freezes the object. Once this method has been called the object should
+ no longer allow any properties to be edited.
+
+ @method freeze
+ @return {Object} receiver
+ */
+ freeze: function() {
+ if (get(this, 'isFrozen')) return this;
+ set(this, 'isFrozen', true);
+ return this;
+ }
+
+});
+
+Ember.FROZEN_ERROR = "Frozen object cannot be modified.";
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var forEach = Ember.EnumerableUtils.forEach;
+
+/**
+ This mixin defines the API for modifying generic enumerables. These methods
+ can be applied to an object regardless of whether it is ordered or
+ unordered.
+
+ Note that an Enumerable can change even if it does not implement this mixin.
+ For example, a MappedEnumerable cannot be directly modified but if its
+ underlying enumerable changes, it will change also.
+
+ ## Adding Objects
+
+ To add an object to an enumerable, use the `addObject()` method. This
+ method will only add the object to the enumerable if the object is not
+ already present and the object if of a type supported by the enumerable.
+
+ ```javascript
+ set.addObject(contact);
+ ```
+
+ ## Removing Objects
+
+ To remove an object form an enumerable, use the `removeObject()` method. This
+ will only remove the object if it is already in the enumerable, otherwise
+ this method has no effect.
+
+ ```javascript
+ set.removeObject(contact);
+ ```
+
+ ## Implementing In Your Own Code
+
+ If you are implementing an object and want to support this API, just include
+ this mixin in your class and implement the required methods. In your unit
+ tests, be sure to apply the Ember.MutableEnumerableTests to your object.
+
+ @class MutableEnumerable
+ @namespace Ember
+ @extends Ember.Mixin
+ @uses Ember.Enumerable
+*/
+Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, {
+
+ /**
+ __Required.__ You must implement this method to apply this mixin.
+
+ Attempts to add the passed object to the receiver if the object is not
+ already present in the collection. If the object is present, this method
+ has no effect.
+
+ If the passed object is of a type not supported by the receiver
+ then this method should raise an exception.
+
+ @method addObject
+ @param {Object} object The object to add to the enumerable.
+ @return {Object} the passed object
+ */
+ addObject: Ember.required(Function),
+
+ /**
+ Adds each object in the passed enumerable to the receiver.
+
+ @method addObjects
+ @param {Ember.Enumerable} objects the objects to add.
+ @return {Object} receiver
+ */
+ addObjects: function(objects) {
+ Ember.beginPropertyChanges(this);
+ forEach(objects, function(obj) { this.addObject(obj); }, this);
+ Ember.endPropertyChanges(this);
+ return this;
+ },
+
+ /**
+ __Required.__ You must implement this method to apply this mixin.
+
+ Attempts to remove the passed object from the receiver collection if the
+ object is in present in the collection. If the object is not present,
+ this method has no effect.
+
+ If the passed object is of a type not supported by the receiver
+ then this method should raise an exception.
+
+ @method removeObject
+ @param {Object} object The object to remove from the enumerable.
+ @return {Object} the passed object
+ */
+ removeObject: Ember.required(Function),
+
+
+ /**
+ Removes each objects in the passed enumerable from the receiver.
+
+ @method removeObjects
+ @param {Ember.Enumerable} objects the objects to remove
+ @return {Object} receiver
+ */
+ removeObjects: function(objects) {
+ Ember.beginPropertyChanges(this);
+ forEach(objects, function(obj) { this.removeObject(obj); }, this);
+ Ember.endPropertyChanges(this);
+ return this;
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+// ..........................................................
+// CONSTANTS
+//
+
+var OUT_OF_RANGE_EXCEPTION = "Index out of range" ;
+var EMPTY = [];
+
+// ..........................................................
+// HELPERS
+//
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ This mixin defines the API for modifying array-like objects. These methods
+ can be applied only to a collection that keeps its items in an ordered set.
+
+ Note that an Array can change even if it does not implement this mixin.
+ For example, one might implement a SparseArray that cannot be directly
+ modified, but if its underlying enumerable changes, it will change also.
+
+ @class MutableArray
+ @namespace Ember
+ @extends Ember.Mixin
+ @uses Ember.Array
+ @uses Ember.MutableEnumerable
+*/
+Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
+ /** @scope Ember.MutableArray.prototype */ {
+
+ /**
+ __Required.__ You must implement this method to apply this mixin.
+
+ This is one of the primitives you must implement to support `Ember.Array`.
+ You should replace amt objects started at idx with the objects in the
+ passed array. You should also call `this.enumerableContentDidChange()`
+
+ @method replace
+ @param {Number} idx Starting index in the array to replace. If
+ idx >= length, then append to the end of the array.
+ @param {Number} amt Number of elements that should be removed from
+ the array, starting at *idx*.
+ @param {Array} objects An array of zero or more objects that should be
+ inserted into the array at *idx*
+ */
+ replace: Ember.required(),
+
+ /**
+ Remove all elements from self. This is useful if you
+ want to reuse an existing array without having to recreate it.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ color.length(); // 3
+ colors.clear(); // []
+ colors.length(); // 0
+ ```
+
+ @method clear
+ @return {Ember.Array} An empty Array.
+ */
+ clear: function () {
+ var len = get(this, 'length');
+ if (len === 0) return this;
+ this.replace(0, len, EMPTY);
+ return this;
+ },
+
+ /**
+ This will use the primitive `replace()` method to insert an object at the
+ specified index.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.insertAt(2, "yellow"); // ["red", "green", "yellow", "blue"]
+ colors.insertAt(5, "orange"); // Error: Index out of range
+ ```
+
+ @method insertAt
+ @param {Number} idx index of insert the object at.
+ @param {Object} object object to insert
+ @return this
+ */
+ insertAt: function(idx, object) {
+ if (idx > get(this, 'length')) throw new Error(OUT_OF_RANGE_EXCEPTION) ;
+ this.replace(idx, 0, [object]) ;
+ return this ;
+ },
+
+ /**
+ Remove an object at the specified index using the `replace()` primitive
+ method. You can pass either a single index, or a start and a length.
+
+ If you pass a start and length that is beyond the
+ length this method will throw an `Ember.OUT_OF_RANGE_EXCEPTION`
+
+ ```javascript
+ var colors = ["red", "green", "blue", "yellow", "orange"];
+ colors.removeAt(0); // ["green", "blue", "yellow", "orange"]
+ colors.removeAt(2, 2); // ["green", "blue"]
+ colors.removeAt(4, 2); // Error: Index out of range
+ ```
+
+ @method removeAt
+ @param {Number} start index, start of range
+ @param {Number} len length of passing range
+ @return {Object} receiver
+ */
+ removeAt: function(start, len) {
+ if ('number' === typeof start) {
+
+ if ((start < 0) || (start >= get(this, 'length'))) {
+ throw new Error(OUT_OF_RANGE_EXCEPTION);
+ }
+
+ // fast case
+ if (len === undefined) len = 1;
+ this.replace(start, len, EMPTY);
+ }
+
+ return this ;
+ },
+
+ /**
+ Push the object onto the end of the array. Works just like `push()` but it
+ is KVO-compliant.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.pushObject("black"); // ["red", "green", "blue", "black"]
+ colors.pushObject(["yellow", "orange"]); // ["red", "green", "blue", "black", ["yellow", "orange"]]
+ ```
+
+ @method pushObject
+ @param {anything} obj object to push
+ @return {any} the same obj passed as param
+ */
+ pushObject: function(obj) {
+ this.insertAt(get(this, 'length'), obj) ;
+ return obj ;
+ },
+
+ /**
+ Add the objects in the passed numerable to the end of the array. Defers
+ notifying observers of the change until all objects are added.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.pushObjects("black"); // ["red", "green", "blue", "black"]
+ colors.pushObjects(["yellow", "orange"]); // ["red", "green", "blue", "black", "yellow", "orange"]
+ ```
+
+ @method pushObjects
+ @param {Ember.Enumerable} objects the objects to add
+ @return {Ember.Array} receiver
+ */
+ pushObjects: function(objects) {
+ this.replace(get(this, 'length'), 0, objects);
+ return this;
+ },
+
+ /**
+ Pop object from array or nil if none are left. Works just like `pop()` but
+ it is KVO-compliant.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.popObject(); // "blue"
+ console.log(colors); // ["red", "green"]
+ ```
+
+ @method popObject
+ @return object
+ */
+ popObject: function() {
+ var len = get(this, 'length') ;
+ if (len === 0) return null ;
+
+ var ret = this.objectAt(len-1) ;
+ this.removeAt(len-1, 1) ;
+ return ret ;
+ },
+
+ /**
+ Shift an object from start of array or nil if none are left. Works just
+ like `shift()` but it is KVO-compliant.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.shiftObject(); // "red"
+ console.log(colors); // ["green", "blue"]
+ ```
+
+ @method shiftObject
+ @return object
+ */
+ shiftObject: function() {
+ if (get(this, 'length') === 0) return null ;
+ var ret = this.objectAt(0) ;
+ this.removeAt(0) ;
+ return ret ;
+ },
+
+ /**
+ Unshift an object to start of array. Works just like `unshift()` but it is
+ KVO-compliant.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.unshiftObject("yellow"); // ["yellow", "red", "green", "blue"]
+ colors.unshiftObject(["black", "white"]); // [["black", "white"], "yellow", "red", "green", "blue"]
+ ```
+
+ @method unshiftObject
+ @param {anything} obj object to unshift
+ @return {any} the same obj passed as param
+ */
+ unshiftObject: function(obj) {
+ this.insertAt(0, obj) ;
+ return obj ;
+ },
+
+ /**
+ Adds the named objects to the beginning of the array. Defers notifying
+ observers until all objects have been added.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.unshiftObjects(["black", "white"]); // ["black", "white", "red", "green", "blue"]
+ colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function
+ ```
+
+ @method unshiftObjects
+ @param {Ember.Enumerable} objects the objects to add
+ @return {Ember.Array} receiver
+ */
+ unshiftObjects: function(objects) {
+ this.replace(0, 0, objects);
+ return this;
+ },
+
+ /**
+ Reverse objects in the array. Works just like `reverse()` but it is
+ KVO-compliant.
+
+ @method reverseObjects
+ @return {Ember.Array} receiver
+ */
+ reverseObjects: function() {
+ var len = get(this, 'length');
+ if (len === 0) return this;
+ var objects = this.toArray().reverse();
+ this.replace(0, len, objects);
+ return this;
+ },
+
+ /**
+ Replace all the the receiver's content with content of the argument.
+ If argument is an empty array receiver will be cleared.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.setObjects(["black", "white"]); // ["black", "white"]
+ colors.setObjects([]); // []
+ ```
+
+ @method setObjects
+ @param {Ember.Array} objects array whose content will be used for replacing
+ the content of the receiver
+ @return {Ember.Array} receiver with the new content
+ */
+ setObjects: function(objects) {
+ if (objects.length === 0) return this.clear();
+
+ var len = get(this, 'length');
+ this.replace(0, len, objects);
+ return this;
+ },
+
+ // ..........................................................
+ // IMPLEMENT Ember.MutableEnumerable
+ //
+
+ removeObject: function(obj) {
+ var loc = get(this, 'length') || 0;
+ while(--loc >= 0) {
+ var curObject = this.objectAt(loc) ;
+ if (curObject === obj) this.removeAt(loc) ;
+ }
+ return this ;
+ },
+
+ addObject: function(obj) {
+ if (!this.contains(obj)) this.pushObject(obj);
+ return this ;
+ }
+
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ ## Overview
+
+ This mixin provides properties and property observing functionality, core
+ features of the Ember object model.
+
+ Properties and observers allow one object to observe changes to a
+ property on another object. This is one of the fundamental ways that
+ models, controllers and views communicate with each other in an Ember
+ application.
+
+ Any object that has this mixin applied can be used in observer
+ operations. That includes `Ember.Object` and most objects you will
+ interact with as you write your Ember application.
+
+ Note that you will not generally apply this mixin to classes yourself,
+ but you will use the features provided by this module frequently, so it
+ is important to understand how to use it.
+
+ ## Using `get()` and `set()`
+
+ Because of Ember's support for bindings and observers, you will always
+ access properties using the get method, and set properties using the
+ set method. This allows the observing objects to be notified and
+ computed properties to be handled properly.
+
+ More documentation about `get` and `set` are below.
+
+ ## Observing Property Changes
+
+ You typically observe property changes simply by adding the `observes`
+ call to the end of your method declarations in classes that you write.
+ For example:
+
+ ```javascript
+ Ember.Object.create({
+ valueObserver: function() {
+ // Executes whenever the "value" property changes
+ }.observes('value')
+ });
+ ```
+
+ Although this is the most common way to add an observer, this capability
+ is actually built into the `Ember.Object` class on top of two methods
+ defined in this mixin: `addObserver` and `removeObserver`. You can use
+ these two methods to add and remove observers yourself if you need to
+ do so at runtime.
+
+ To add an observer for a property, call:
+
+ ```javascript
+ object.addObserver('propertyKey', targetObject, targetAction)
+ ```
+
+ This will call the `targetAction` method on the `targetObject` to be called
+ whenever the value of the `propertyKey` changes.
+
+ Note that if `propertyKey` is a computed property, the observer will be
+ called when any of the property dependencies are changed, even if the
+ resulting value of the computed property is unchanged. This is necessary
+ because computed properties are not computed until `get` is called.
+
+ @class Observable
+ @namespace Ember
+ @extends Ember.Mixin
+*/
+Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
+
+ /**
+ Retrieves the value of a property from the object.
+
+ This method is usually similar to using `object[keyName]` or `object.keyName`,
+ however it supports both computed properties and the unknownProperty
+ handler.
+
+ Because `get` unifies the syntax for accessing all these kinds
+ of properties, it can make many refactorings easier, such as replacing a
+ simple property with a computed property, or vice versa.
+
+ ### Computed Properties
+
+ Computed properties are methods defined with the `property` modifier
+ declared at the end, such as:
+
+ ```javascript
+ fullName: function() {
+ return this.getEach('firstName', 'lastName').compact().join(' ');
+ }.property('firstName', 'lastName')
+ ```
+
+ When you call `get` on a computed property, the function will be
+ called and the return value will be returned instead of the function
+ itself.
+
+ ### Unknown Properties
+
+ Likewise, if you try to call `get` on a property whose value is
+ `undefined`, the `unknownProperty()` method will be called on the object.
+ If this method returns any value other than `undefined`, it will be returned
+ instead. This allows you to implement "virtual" properties that are
+ not defined upfront.
+
+ @method get
+ @param {String} keyName The property to retrieve
+ @return {Object} The property value or undefined.
+ */
+ get: function(keyName) {
+ return get(this, keyName);
+ },
+
+ /**
+ To get multiple properties at once, call `getProperties`
+ with a list of strings or an array:
+
+ ```javascript
+ record.getProperties('firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
+
+ is equivalent to:
+
+ ```javascript
+ record.getProperties(['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
+
+ @method getProperties
+ @param {String...|Array} list of keys to get
+ @return {Hash}
+ */
+ getProperties: function() {
+ var ret = {};
+ var propertyNames = arguments;
+ if (arguments.length === 1 && Ember.typeOf(arguments[0]) === 'array') {
+ propertyNames = arguments[0];
+ }
+ for(var i = 0; i < propertyNames.length; i++) {
+ ret[propertyNames[i]] = get(this, propertyNames[i]);
+ }
+ return ret;
+ },
+
+ /**
+ Sets the provided key or path to the value.
+
+ This method is generally very similar to calling `object[key] = value` or
+ `object.key = value`, except that it provides support for computed
+ properties, the `unknownProperty()` method and property observers.
+
+ ### Computed Properties
+
+ If you try to set a value on a key that has a computed property handler
+ defined (see the `get()` method for an example), then `set()` will call
+ that method, passing both the value and key instead of simply changing
+ the value itself. This is useful for those times when you need to
+ implement a property that is composed of one or more member
+ properties.
+
+ ### Unknown Properties
+
+ If you try to set a value on a key that is undefined in the target
+ object, then the `unknownProperty()` handler will be called instead. This
+ gives you an opportunity to implement complex "virtual" properties that
+ are not predefined on the object. If `unknownProperty()` returns
+ undefined, then `set()` will simply set the value on the object.
+
+ ### Property Observers
+
+ In addition to changing the property, `set()` will also register a property
+ change with the object. Unless you have placed this call inside of a
+ `beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers
+ (i.e. observer methods declared on the same object), will be called
+ immediately. Any "remote" observers (i.e. observer methods declared on
+ another object) will be placed in a queue and called at a later time in a
+ coalesced manner.
+
+ ### Chaining
+
+ In addition to property changes, `set()` returns the value of the object
+ itself so you can do chaining like this:
+
+ ```javascript
+ record.set('firstName', 'Charles').set('lastName', 'Jolley');
+ ```
+
+ @method set
+ @param {String} keyName The property to set
+ @param {Object} value The value to set or `null`.
+ @return {Ember.Observable}
+ */
+ set: function(keyName, value) {
+ set(this, keyName, value);
+ return this;
+ },
+
+ /**
+ To set multiple properties at once, call `setProperties`
+ with a Hash:
+
+ ```javascript
+ record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
+ ```
+
+ @method setProperties
+ @param {Hash} hash the hash of keys and values to set
+ @return {Ember.Observable}
+ */
+ setProperties: function(hash) {
+ return Ember.setProperties(this, hash);
+ },
+
+ /**
+ Begins a grouping of property changes.
+
+ You can use this method to group property changes so that notifications
+ will not be sent until the changes are finished. If you plan to make a
+ large number of changes to an object at one time, you should call this
+ method at the beginning of the changes to begin deferring change
+ notifications. When you are done making changes, call
+ `endPropertyChanges()` to deliver the deferred change notifications and end
+ deferring.
+
+ @method beginPropertyChanges
+ @return {Ember.Observable}
+ */
+ beginPropertyChanges: function() {
+ Ember.beginPropertyChanges();
+ return this;
+ },
+
+ /**
+ Ends a grouping of property changes.
+
+ You can use this method to group property changes so that notifications
+ will not be sent until the changes are finished. If you plan to make a
+ large number of changes to an object at one time, you should call
+ `beginPropertyChanges()` at the beginning of the changes to defer change
+ notifications. When you are done making changes, call this method to
+ deliver the deferred change notifications and end deferring.
+
+ @method endPropertyChanges
+ @return {Ember.Observable}
+ */
+ endPropertyChanges: function() {
+ Ember.endPropertyChanges();
+ return this;
+ },
+
+ /**
+ Notify the observer system that a property is about to change.
+
+ Sometimes you need to change a value directly or indirectly without
+ actually calling `get()` or `set()` on it. In this case, you can use this
+ method and `propertyDidChange()` instead. Calling these two methods
+ together will notify all observers that the property has potentially
+ changed value.
+
+ Note that you must always call `propertyWillChange` and `propertyDidChange`
+ as a pair. If you do not, it may get the property change groups out of
+ order and cause notifications to be delivered more often than you would
+ like.
+
+ @method propertyWillChange
+ @param {String} keyName The property key that is about to change.
+ @return {Ember.Observable}
+ */
+ propertyWillChange: function(keyName){
+ Ember.propertyWillChange(this, keyName);
+ return this;
+ },
+
+ /**
+ Notify the observer system that a property has just changed.
+
+ Sometimes you need to change a value directly or indirectly without
+ actually calling `get()` or `set()` on it. In this case, you can use this
+ method and `propertyWillChange()` instead. Calling these two methods
+ together will notify all observers that the property has potentially
+ changed value.
+
+ Note that you must always call `propertyWillChange` and `propertyDidChange`
+ as a pair. If you do not, it may get the property change groups out of
+ order and cause notifications to be delivered more often than you would
+ like.
+
+ @method propertyDidChange
+ @param {String} keyName The property key that has just changed.
+ @return {Ember.Observable}
+ */
+ propertyDidChange: function(keyName) {
+ Ember.propertyDidChange(this, keyName);
+ return this;
+ },
+
+ /**
+ Convenience method to call `propertyWillChange` and `propertyDidChange` in
+ succession.
+
+ @method notifyPropertyChange
+ @param {String} keyName The property key to be notified about.
+ @return {Ember.Observable}
+ */
+ notifyPropertyChange: function(keyName) {
+ this.propertyWillChange(keyName);
+ this.propertyDidChange(keyName);
+ return this;
+ },
+
+ addBeforeObserver: function(key, target, method) {
+ Ember.addBeforeObserver(this, key, target, method);
+ },
+
+ /**
+ Adds an observer on a property.
+
+ This is the core method used to register an observer for a property.
+
+ Once you call this method, anytime the key's value is set, your observer
+ will be notified. Note that the observers are triggered anytime the
+ value is set, regardless of whether it has actually changed. Your
+ observer should be prepared to handle that.
+
+ You can also pass an optional context parameter to this method. The
+ context will be passed to your observer method whenever it is triggered.
+ Note that if you add the same target/method pair on a key multiple times
+ with different context parameters, your observer will only be called once
+ with the last context you passed.
+
+ ### Observer Methods
+
+ Observer methods you pass should generally have the following signature if
+ you do not pass a `context` parameter:
+
+ ```javascript
+ fooDidChange: function(sender, key, value, rev) { };
+ ```
+
+ The sender is the object that changed. The key is the property that
+ changes. The value property is currently reserved and unused. The rev
+ is the last property revision of the object when it changed, which you can
+ use to detect if the key value has really changed or not.
+
+ If you pass a `context` parameter, the context will be passed before the
+ revision like so:
+
+ ```javascript
+ fooDidChange: function(sender, key, value, context, rev) { };
+ ```
+
+ Usually you will not need the value, context or revision parameters at
+ the end. In this case, it is common to write observer methods that take
+ only a sender and key value as parameters or, if you aren't interested in
+ any of these values, to write an observer that has no parameters at all.
+
+ @method addObserver
+ @param {String} key The key to observer
+ @param {Object} target The target object to invoke
+ @param {String|Function} method The method to invoke.
+ @return {Ember.Object} self
+ */
+ addObserver: function(key, target, method) {
+ Ember.addObserver(this, key, target, method);
+ },
+
+ /**
+ Remove an observer you have previously registered on this object. Pass
+ the same key, target, and method you passed to `addObserver()` and your
+ target will no longer receive notifications.
+
+ @method removeObserver
+ @param {String} key The key to observer
+ @param {Object} target The target object to invoke
+ @param {String|Function} method The method to invoke.
+ @return {Ember.Observable} receiver
+ */
+ removeObserver: function(key, target, method) {
+ Ember.removeObserver(this, key, target, method);
+ },
+
+ /**
+ Returns `true` if the object currently has observers registered for a
+ particular key. You can use this method to potentially defer performing
+ an expensive action until someone begins observing a particular property
+ on the object.
+
+ @method hasObserverFor
+ @param {String} key Key to check
+ @return {Boolean}
+ */
+ hasObserverFor: function(key) {
+ return Ember.hasListeners(this, key+':change');
+ },
+
+ /**
+ @deprecated
+ @method getPath
+ @param {String} path The property path to retrieve
+ @return {Object} The property value or undefined.
+ */
+ getPath: function(path) {
+ Ember.deprecate("getPath is deprecated since get now supports paths");
+ return this.get(path);
+ },
+
+ /**
+ @deprecated
+ @method setPath
+ @param {String} path The path to the property that will be set
+ @param {Object} value The value to set or `null`.
+ @return {Ember.Observable}
+ */
+ setPath: function(path, value) {
+ Ember.deprecate("setPath is deprecated since set now supports paths");
+ return this.set(path, value);
+ },
+
+ /**
+ Retrieves the value of a property, or a default value in the case that the
+ property returns `undefined`.
+
+ ```javascript
+ person.getWithDefault('lastName', 'Doe');
+ ```
+
+ @method getWithDefault
+ @param {String} keyName The name of the property to retrieve
+ @param {Object} defaultValue The value to return if the property value is undefined
+ @return {Object} The property value or the defaultValue.
+ */
+ getWithDefault: function(keyName, defaultValue) {
+ return Ember.getWithDefault(this, keyName, defaultValue);
+ },
+
+ /**
+ Set the value of a property to the current value plus some amount.
+
+ ```javascript
+ person.incrementProperty('age');
+ team.incrementProperty('score', 2);
+ ```
+
+ @method incrementProperty
+ @param {String} keyName The name of the property to increment
+ @param {Object} increment The amount to increment by. Defaults to 1
+ @return {Object} The new property value
+ */
+ incrementProperty: function(keyName, increment) {
+ if (!increment) { increment = 1; }
+ set(this, keyName, (get(this, keyName) || 0)+increment);
+ return get(this, keyName);
+ },
+
+ /**
+ Set the value of a property to the current value minus some amount.
+
+ ```javascript
+ player.decrementProperty('lives');
+ orc.decrementProperty('health', 5);
+ ```
+
+ @method decrementProperty
+ @param {String} keyName The name of the property to decrement
+ @param {Object} increment The amount to decrement by. Defaults to 1
+ @return {Object} The new property value
+ */
+ decrementProperty: function(keyName, increment) {
+ if (!increment) { increment = 1; }
+ set(this, keyName, (get(this, keyName) || 0)-increment);
+ return get(this, keyName);
+ },
+
+ /**
+ Set the value of a boolean property to the opposite of it's
+ current value.
+
+ ```javascript
+ starship.toggleProperty('warpDriveEnaged');
+ ```
+
+ @method toggleProperty
+ @param {String} keyName The name of the property to toggle
+ @return {Object} The new property value
+ */
+ toggleProperty: function(keyName) {
+ set(this, keyName, !get(this, keyName));
+ return get(this, keyName);
+ },
+
+ /**
+ Returns the cached value of a computed property, if it exists.
+ This allows you to inspect the value of a computed property
+ without accidentally invoking it if it is intended to be
+ generated lazily.
+
+ @method cacheFor
+ @param {String} keyName
+ @return {Object} The cached value of the computed property, if any
+ */
+ cacheFor: function(keyName) {
+ return Ember.cacheFor(this, keyName);
+ },
+
+ // intended for debugging purposes
+ observersForKey: function(keyName) {
+ return Ember.observersFor(this, keyName);
+ }
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+@class TargetActionSupport
+@namespace Ember
+@extends Ember.Mixin
+*/
+Ember.TargetActionSupport = Ember.Mixin.create({
+ target: null,
+ action: null,
+
+ targetObject: Ember.computed(function() {
+ var target = get(this, 'target');
+
+ if (Ember.typeOf(target) === "string") {
+ var value = get(this, target);
+ if (value === undefined) { value = get(Ember.lookup, target); }
+ return value;
+ } else {
+ return target;
+ }
+ }).property('target'),
+
+ triggerAction: function() {
+ var action = get(this, 'action'),
+ target = get(this, 'targetObject');
+
+ if (target && action) {
+ var ret;
+
+ if (typeof target.send === 'function') {
+ ret = target.send(action, this);
+ } else {
+ if (typeof action === 'string') {
+ action = target[action];
+ }
+ ret = action.call(target, this);
+ }
+ if (ret !== false) ret = true;
+
+ return ret;
+ } else {
+ return false;
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ This mixin allows for Ember objects to subscribe to and emit events.
+
+ ```javascript
+ App.Person = Ember.Object.extend(Ember.Evented, {
+ greet: function() {
+ // ...
+ this.trigger('greet');
+ }
+ });
+
+ var person = App.Person.create();
+
+ person.on('greet', function() {
+ console.log('Our person has greeted');
+ });
+
+ person.greet();
+
+ // outputs: 'Our person has greeted'
+ ```
+
+ You can also chain multiple event subscriptions:
+
+ ```javascript
+ person.on('greet', function() {
+ console.log('Our person has greeted');
+ }).one('greet', function() {
+ console.log('Offer one-time special');
+ }).off('event', this, forgetThis);
+ ```
+
+ @class Evented
+ @namespace Ember
+ @extends Ember.Mixin
+ */
+Ember.Evented = Ember.Mixin.create({
+
+ /**
+ Subscribes to a named event with given function.
+
+ ```javascript
+ person.on('didLoad', function() {
+ // fired once the person has loaded
+ });
+ ```
+
+ An optional target can be passed in as the 2nd argument that will
+ be set as the "this" for the callback. This is a good way to give your
+ function access to the object triggering the event. When the target
+ parameter is used the callback becomes the third argument.
+
+ @method on
+ @param {String} name The name of the event
+ @param {Object} [target] The "this" binding for the callback
+ @param {Function} method The callback to execute
+ @return this
+ */
+ on: function(name, target, method) {
+ Ember.addListener(this, name, target, method);
+ return this;
+ },
+
+ /**
+ Subscribes a function to a named event and then cancels the subscription
+ after the first time the event is triggered. It is good to use ``one`` when
+ you only care about the first time an event has taken place.
+
+ This function takes an optional 2nd argument that will become the "this"
+ value for the callback. If this argument is passed then the 3rd argument
+ becomes the function.
+
+ @method one
+ @param {String} name The name of the event
+ @param {Object} [target] The "this" binding for the callback
+ @param {Function} method The callback to execute
+ @return this
+ */
+ one: function(name, target, method) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ Ember.addListener(this, name, target, method, true);
+ return this;
+ },
+
+ /**
+ Triggers a named event for the object. Any additional arguments
+ will be passed as parameters to the functions that are subscribed to the
+ event.
+
+ ```javascript
+ person.on('didEat', function(food) {
+ console.log('person ate some ' + food);
+ });
+
+ person.trigger('didEat', 'broccoli');
+
+ // outputs: person ate some broccoli
+ ```
+ @method trigger
+ @param {String} name The name of the event
+ @param {Object...} args Optional arguments to pass on
+ */
+ trigger: function(name) {
+ var args = [], i, l;
+ for (i = 1, l = arguments.length; i < l; i++) {
+ args.push(arguments[i]);
+ }
+ Ember.sendEvent(this, name, args);
+ },
+
+ fire: function(name) {
+ Ember.deprecate("Ember.Evented#fire() has been deprecated in favor of trigger() for compatibility with jQuery. It will be removed in 1.0. Please update your code to call trigger() instead.");
+ this.trigger.apply(this, arguments);
+ },
+
+ /**
+ Cancels subscription for give name, target, and method.
+
+ @method off
+ @param {String} name The name of the event
+ @param {Object} target The target of the subscription
+ @param {Function} method The function of the subscription
+ @return this
+ */
+ off: function(name, target, method) {
+ Ember.removeListener(this, name, target, method);
+ return this;
+ },
+
+ /**
+ Checks to see if object has any subscriptions for named event.
+
+ @method has
+ @param {String} name The name of the event
+ @return {Boolean} does the object have a subscription for event
+ */
+ has: function(name) {
+ return Ember.hasListeners(this, name);
+ }
+});
+
+})();
+
+
+
+(function() {
+var RSVP = requireModule("rsvp");
+
+RSVP.async = function(callback, binding) {
+ Ember.run.schedule('actions', binding, callback);
+};
+
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get;
+
+/**
+ @class Deferred
+ @namespace Ember
+ @extends Ember.Mixin
+ */
+Ember.DeferredMixin = Ember.Mixin.create({
+ /**
+ Add handlers to be called when the Deferred object is resolved or rejected.
+
+ @method then
+ @param {Function} doneCallback a callback function to be called when done
+ @param {Function} failCallback a callback function to be called when failed
+ */
+ then: function(doneCallback, failCallback) {
+ var promise = get(this, 'promise');
+ return promise.then.apply(promise, arguments);
+ },
+
+ /**
+ Resolve a Deferred object and call any `doneCallbacks` with the given args.
+
+ @method resolve
+ */
+ resolve: function(value) {
+ get(this, 'promise').resolve(value);
+ },
+
+ /**
+ Reject a Deferred object and call any `failCallbacks` with the given args.
+
+ @method reject
+ */
+ reject: function(value) {
+ get(this, 'promise').reject(value);
+ },
+
+ promise: Ember.computed(function() {
+ return new RSVP.Promise();
+ })
+});
+
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+Ember.Container = requireModule('container');
+Ember.Container.set = Ember.set;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+// NOTE: this object should never be included directly. Instead use Ember.
+// Ember.Object. We only define this separately so that Ember.Set can depend on it
+
+
+var set = Ember.set, get = Ember.get,
+ o_create = Ember.create,
+ o_defineProperty = Ember.platform.defineProperty,
+ GUID_KEY = Ember.GUID_KEY,
+ guidFor = Ember.guidFor,
+ generateGuid = Ember.generateGuid,
+ meta = Ember.meta,
+ rewatch = Ember.rewatch,
+ finishChains = Ember.finishChains,
+ destroy = Ember.destroy,
+ schedule = Ember.run.schedule,
+ Mixin = Ember.Mixin,
+ applyMixin = Mixin._apply,
+ finishPartial = Mixin.finishPartial,
+ reopen = Mixin.prototype.reopen,
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ indexOf = Ember.EnumerableUtils.indexOf;
+
+var undefinedDescriptor = {
+ configurable: true,
+ writable: true,
+ enumerable: false,
+ value: undefined
+};
+
+function makeCtor() {
+
+ // Note: avoid accessing any properties on the object since it makes the
+ // method a lot faster. This is glue code so we want it to be as fast as
+ // possible.
+
+ var wasApplied = false, initMixins, initProperties;
+
+ var Class = function() {
+ if (!wasApplied) {
+ Class.proto(); // prepare prototype...
+ }
+ o_defineProperty(this, GUID_KEY, undefinedDescriptor);
+ o_defineProperty(this, '_super', undefinedDescriptor);
+ var m = meta(this);
+ m.proto = this;
+ if (initMixins) {
+ // capture locally so we can clear the closed over variable
+ var mixins = initMixins;
+ initMixins = null;
+ this.reopen.apply(this, mixins);
+ }
+ if (initProperties) {
+ // capture locally so we can clear the closed over variable
+ var props = initProperties;
+ initProperties = null;
+
+ var concatenatedProperties = this.concatenatedProperties;
+
+ for (var i = 0, l = props.length; i < l; i++) {
+ var properties = props[i];
+ for (var keyName in properties) {
+ if (!properties.hasOwnProperty(keyName)) { continue; }
+
+ var value = properties[keyName],
+ IS_BINDING = Ember.IS_BINDING;
+
+ if (IS_BINDING.test(keyName)) {
+ var bindings = m.bindings;
+ if (!bindings) {
+ bindings = m.bindings = {};
+ } else if (!m.hasOwnProperty('bindings')) {
+ bindings = m.bindings = o_create(m.bindings);
+ }
+ bindings[keyName] = value;
+ }
+
+ var desc = m.descs[keyName];
+
+ Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
+ Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
+
+ if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
+ var baseValue = this[keyName];
+
+ if (baseValue) {
+ if ('function' === typeof baseValue.concat) {
+ value = baseValue.concat(value);
+ } else {
+ value = Ember.makeArray(baseValue).concat(value);
+ }
+ } else {
+ value = Ember.makeArray(value);
+ }
+ }
+
+ if (desc) {
+ desc.set(this, keyName, value);
+ } else {
+ if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
+ this.setUnknownProperty(keyName, value);
+ } else if (MANDATORY_SETTER) {
+ Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
+ } else {
+ this[keyName] = value;
+ }
+ }
+ }
+ }
+ }
+ finishPartial(this, m);
+ delete m.proto;
+ finishChains(this);
+ this.init.apply(this, arguments);
+ };
+
+ Class.toString = Mixin.prototype.toString;
+ Class.willReopen = function() {
+ if (wasApplied) {
+ Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
+ }
+
+ wasApplied = false;
+ };
+ Class._initMixins = function(args) { initMixins = args; };
+ Class._initProperties = function(args) { initProperties = args; };
+
+ Class.proto = function() {
+ var superclass = Class.superclass;
+ if (superclass) { superclass.proto(); }
+
+ if (!wasApplied) {
+ wasApplied = true;
+ Class.PrototypeMixin.applyPartial(Class.prototype);
+ rewatch(Class.prototype);
+ }
+
+ return this.prototype;
+ };
+
+ return Class;
+
+}
+
+var CoreObject = makeCtor();
+CoreObject.toString = function() { return "Ember.CoreObject"; };
+
+CoreObject.PrototypeMixin = Mixin.create({
+ reopen: function() {
+ applyMixin(this, arguments, true);
+ return this;
+ },
+
+ isInstance: true,
+
+ /**
+ An overridable method called when objects are instantiated. By default,
+ does nothing unless it is overridden during class definition.
+
+ Example:
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ init: function() {
+ this._super();
+ alert('Name is ' + this.get('name'));
+ }
+ });
+
+ var steve = App.Person.create({
+ name: "Steve"
+ });
+
+ // alerts 'Name is Steve'.
+ ```
+
+ NOTE: If you do override `init` for a framework class like `Ember.View` or
+ `Ember.ArrayController`, be sure to call `this._super()` in your
+ `init` declaration! If you don't, Ember may not have an opportunity to
+ do important setup work, and you'll see strange behavior in your
+ application.
+
+ ```
+
+ @method init
+ */
+ init: function() {},
+
+ /**
+ Defines the properties that will be concatenated from the superclass
+ (instead of overridden).
+
+ By default, when you extend an Ember class a property defined in
+ the subclass overrides a property with the same name that is defined
+ in the superclass. However, there are some cases where it is preferable
+ to build up a property's value by combining the superclass' property
+ value with the subclass' value. An example of this in use within Ember
+ is the `classNames` property of `Ember.View`.
+
+ Here is some sample code showing the difference between a concatenated
+ property and a normal one:
+
+ ```javascript
+ App.BarView = Ember.View.extend({
+ someNonConcatenatedProperty: ['bar'],
+ classNames: ['bar']
+ });
+
+ App.FooBarView = App.BarView.extend({
+ someNonConcatenatedProperty: ['foo'],
+ classNames: ['foo'],
+ });
+
+ var fooBarView = App.FooBarView.create();
+ fooBarView.get('someNonConcatenatedProperty'); // ['foo']
+ fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo']
+ ```
+
+ This behavior extends to object creation as well. Continuing the
+ above example:
+
+ ```javascript
+ var view = App.FooBarView.create({
+ someNonConcatenatedProperty: ['baz'],
+ classNames: ['baz']
+ })
+ view.get('someNonConcatenatedProperty'); // ['baz']
+ view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
+ ```
+ Adding a single property that is not an array will just add it in the array:
+
+ ```javascript
+ var view = App.FooBarView.create({
+ classNames: 'baz'
+ })
+ view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
+ ```
+
+ Using the `concatenatedProperties` property, we can tell to Ember that mix
+ the content of the properties.
+
+ In `Ember.View` the `classNameBindings` and `attributeBindings` properties
+ are also concatenated, in addition to `classNames`.
+
+ This feature is available for you to use throughout the Ember object model,
+ although typical app developers are likely to use it infrequently.
+
+ @property concatenatedProperties
+ @type Array
+ @default null
+ */
+ concatenatedProperties: null,
+
+ /**
+ Destroyed object property flag.
+
+ if this property is `true` the observers and bindings were already
+ removed by the effect of calling the `destroy()` method.
+
+ @property isDestroyed
+ @default false
+ */
+ isDestroyed: false,
+
+ /**
+ Destruction scheduled flag. The `destroy()` method has been called.
+
+ The object stays intact until the end of the run loop at which point
+ the `isDestroyed` flag is set.
+
+ @property isDestroying
+ @default false
+ */
+ isDestroying: false,
+
+ /**
+ Destroys an object by setting the `isDestroyed` flag and removing its
+ metadata, which effectively destroys observers and bindings.
+
+ If you try to set a property on a destroyed object, an exception will be
+ raised.
+
+ Note that destruction is scheduled for the end of the run loop and does not
+ happen immediately.
+
+ @method destroy
+ @return {Ember.Object} receiver
+ */
+ destroy: function() {
+ if (this._didCallDestroy) { return; }
+
+ this.isDestroying = true;
+ this._didCallDestroy = true;
+
+ if (this.willDestroy) { this.willDestroy(); }
+
+ schedule('destroy', this, this._scheduledDestroy);
+ return this;
+ },
+
+ /**
+ @private
+
+ Invoked by the run loop to actually destroy the object. This is
+ scheduled for execution by the `destroy` method.
+
+ @method _scheduledDestroy
+ */
+ _scheduledDestroy: function() {
+ destroy(this);
+ set(this, 'isDestroyed', true);
+
+ if (this.didDestroy) { this.didDestroy(); }
+ },
+
+ bind: function(to, from) {
+ if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
+ from.to(to).connect(this);
+ return from;
+ },
+
+ /**
+ Returns a string representation which attempts to provide more information
+ than Javascript's `toString` typically does, in a generic way for all Ember
+ objects.
+
+ App.Person = Em.Object.extend()
+ person = App.Person.create()
+ person.toString() //=> ""
+
+ If the object's class is not defined on an Ember namespace, it will
+ indicate it is a subclass of the registered superclass:
+
+ Student = App.Person.extend()
+ student = Student.create()
+ student.toString() //=> "<(subclass of App.Person):ember1025>"
+
+ If the method `toStringExtension` is defined, its return value will be
+ included in the output.
+
+ App.Teacher = App.Person.extend({
+ toStringExtension: function(){
+ return this.get('fullName');
+ }
+ });
+ teacher = App.Teacher.create()
+ teacher.toString(); //=> ""
+
+ @method toString
+ @return {String} string representation
+ */
+ toString: function toString() {
+ var hasToStringExtension = typeof this.toStringExtension === 'function',
+ extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
+ var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
+ this.toString = makeToString(ret);
+ return ret;
+ }
+});
+
+CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
+
+function makeToString(ret) {
+ return function() { return ret; };
+}
+
+if (Ember.config.overridePrototypeMixin) {
+ Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
+}
+
+CoreObject.__super__ = null;
+
+var ClassMixin = Mixin.create({
+
+ ClassMixin: Ember.required(),
+
+ PrototypeMixin: Ember.required(),
+
+ isClass: true,
+
+ isMethod: false,
+
+ extend: function() {
+ var Class = makeCtor(), proto;
+ Class.ClassMixin = Mixin.create(this.ClassMixin);
+ Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
+
+ Class.ClassMixin.ownerConstructor = Class;
+ Class.PrototypeMixin.ownerConstructor = Class;
+
+ reopen.apply(Class.PrototypeMixin, arguments);
+
+ Class.superclass = this;
+ Class.__super__ = this.prototype;
+
+ proto = Class.prototype = o_create(this.prototype);
+ proto.constructor = Class;
+ generateGuid(proto, 'ember');
+ meta(proto).proto = proto; // this will disable observers on prototype
+
+ Class.ClassMixin.apply(Class);
+ return Class;
+ },
+
+ createWithMixins: function() {
+ var C = this;
+ if (arguments.length>0) { this._initMixins(arguments); }
+ return new C();
+ },
+
+ create: function() {
+ var C = this;
+ if (arguments.length>0) { this._initProperties(arguments); }
+ return new C();
+ },
+
+ reopen: function() {
+ this.willReopen();
+ reopen.apply(this.PrototypeMixin, arguments);
+ return this;
+ },
+
+ reopenClass: function() {
+ reopen.apply(this.ClassMixin, arguments);
+ applyMixin(this, arguments, false);
+ return this;
+ },
+
+ detect: function(obj) {
+ if ('function' !== typeof obj) { return false; }
+ while(obj) {
+ if (obj===this) { return true; }
+ obj = obj.superclass;
+ }
+ return false;
+ },
+
+ detectInstance: function(obj) {
+ return obj instanceof this;
+ },
+
+ /**
+ In some cases, you may want to annotate computed properties with additional
+ metadata about how they function or what values they operate on. For
+ example, computed property functions may close over variables that are then
+ no longer available for introspection.
+
+ You can pass a hash of these values to a computed property like this:
+
+ ```javascript
+ person: function() {
+ var personId = this.get('personId');
+ return App.Person.create({ id: personId });
+ }.property().meta({ type: App.Person })
+ ```
+
+ Once you've done this, you can retrieve the values saved to the computed
+ property from your class like this:
+
+ ```javascript
+ MyClass.metaForProperty('person');
+ ```
+
+ This will return the original hash that was passed to `meta()`.
+
+ @method metaForProperty
+ @param key {String} property name
+ */
+ metaForProperty: function(key) {
+ var desc = meta(this.proto(), false).descs[key];
+
+ Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
+ return desc._meta || {};
+ },
+
+ /**
+ Iterate over each computed property for the class, passing its name
+ and any associated metadata (see `metaForProperty`) to the callback.
+
+ @method eachComputedProperty
+ @param {Function} callback
+ @param {Object} binding
+ */
+ eachComputedProperty: function(callback, binding) {
+ var proto = this.proto(),
+ descs = meta(proto).descs,
+ empty = {},
+ property;
+
+ for (var name in descs) {
+ property = descs[name];
+
+ if (property instanceof Ember.ComputedProperty) {
+ callback.call(binding || this, name, property._meta || empty);
+ }
+ }
+ }
+
+});
+
+ClassMixin.ownerConstructor = CoreObject;
+
+if (Ember.config.overrideClassMixin) {
+ Ember.config.overrideClassMixin(ClassMixin);
+}
+
+CoreObject.ClassMixin = ClassMixin;
+ClassMixin.apply(CoreObject);
+
+/**
+ @class CoreObject
+ @namespace Ember
+*/
+Ember.CoreObject = CoreObject;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ `Ember.Object` is the main base class for all Ember objects. It is a subclass
+ of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
+ see the documentation for each of these.
+
+ @class Object
+ @namespace Ember
+ @extends Ember.CoreObject
+ @uses Ember.Observable
+*/
+Ember.Object = Ember.CoreObject.extend(Ember.Observable);
+Ember.Object.toString = function() { return "Ember.Object"; };
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf;
+
+/**
+ A Namespace is an object usually used to contain other objects or methods
+ such as an application or framework. Create a namespace anytime you want
+ to define one of these new containers.
+
+ # Example Usage
+
+ ```javascript
+ MyFramework = Ember.Namespace.create({
+ VERSION: '1.0.0'
+ });
+ ```
+
+ @class Namespace
+ @namespace Ember
+ @extends Ember.Object
+*/
+var Namespace = Ember.Namespace = Ember.Object.extend({
+ isNamespace: true,
+
+ init: function() {
+ Ember.Namespace.NAMESPACES.push(this);
+ Ember.Namespace.PROCESSED = false;
+ },
+
+ toString: function() {
+ var name = get(this, 'name');
+ if (name) { return name; }
+
+ findNamespaces();
+ return this[Ember.GUID_KEY+'_name'];
+ },
+
+ nameClasses: function() {
+ processNamespace([this.toString()], this, {});
+ },
+
+ destroy: function() {
+ var namespaces = Ember.Namespace.NAMESPACES;
+ Ember.lookup[this.toString()] = undefined;
+ namespaces.splice(indexOf.call(namespaces, this), 1);
+ this._super();
+ }
+});
+
+Namespace.reopenClass({
+ NAMESPACES: [Ember],
+ NAMESPACES_BY_ID: {},
+ PROCESSED: false,
+ processAll: processAllNamespaces,
+ byName: function(name) {
+ if (!Ember.BOOTED) {
+ processAllNamespaces();
+ }
+
+ return NAMESPACES_BY_ID[name];
+ }
+});
+
+var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID;
+
+var hasOwnProp = ({}).hasOwnProperty,
+ guidFor = Ember.guidFor;
+
+function processNamespace(paths, root, seen) {
+ var idx = paths.length;
+
+ NAMESPACES_BY_ID[paths.join('.')] = root;
+
+ // Loop over all of the keys in the namespace, looking for classes
+ for(var key in root) {
+ if (!hasOwnProp.call(root, key)) { continue; }
+ var obj = root[key];
+
+ // If we are processing the `Ember` namespace, for example, the
+ // `paths` will start with `["Ember"]`. Every iteration through
+ // the loop will update the **second** element of this list with
+ // the key, so processing `Ember.View` will make the Array
+ // `['Ember', 'View']`.
+ paths[idx] = key;
+
+ // If we have found an unprocessed class
+ if (obj && obj.toString === classToString) {
+ // Replace the class' `toString` with the dot-separated path
+ // and set its `NAME_KEY`
+ obj.toString = makeToString(paths.join('.'));
+ obj[NAME_KEY] = paths.join('.');
+
+ // Support nested namespaces
+ } else if (obj && obj.isNamespace) {
+ // Skip aliased namespaces
+ if (seen[guidFor(obj)]) { continue; }
+ seen[guidFor(obj)] = true;
+
+ // Process the child namespace
+ processNamespace(paths, obj, seen);
+ }
+ }
+
+ paths.length = idx; // cut out last item
+}
+
+function findNamespaces() {
+ var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace;
+
+ if (Namespace.PROCESSED) { return; }
+
+ for (var prop in lookup) {
+ // These don't raise exceptions but can cause warnings
+ if (prop === "parent" || prop === "top" || prop === "frameElement") { continue; }
+
+ // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox.
+ // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage
+ if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; }
+ // Unfortunately, some versions of IE don't support window.hasOwnProperty
+ if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; }
+
+ // At times we are not allowed to access certain properties for security reasons.
+ // There are also times where even if we can access them, we are not allowed to access their properties.
+ try {
+ obj = Ember.lookup[prop];
+ isNamespace = obj && obj.isNamespace;
+ } catch (e) {
+ continue;
+ }
+
+ if (isNamespace) {
+ Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop));
+ obj[NAME_KEY] = prop;
+ }
+ }
+}
+
+var NAME_KEY = Ember.NAME_KEY = Ember.GUID_KEY + '_name';
+
+function superClassString(mixin) {
+ var superclass = mixin.superclass;
+ if (superclass) {
+ if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
+ else { return superClassString(superclass); }
+ } else {
+ return;
+ }
+}
+
+function classToString() {
+ if (!Ember.BOOTED && !this[NAME_KEY]) {
+ processAllNamespaces();
+ }
+
+ var ret;
+
+ if (this[NAME_KEY]) {
+ ret = this[NAME_KEY];
+ } else {
+ var str = superClassString(this);
+ if (str) {
+ ret = "(subclass of " + str + ")";
+ } else {
+ ret = "(unknown mixin)";
+ }
+ this.toString = makeToString(ret);
+ }
+
+ return ret;
+}
+
+function processAllNamespaces() {
+ var unprocessedNamespaces = !Namespace.PROCESSED,
+ unprocessedMixins = Ember.anyUnprocessedMixins;
+
+ if (unprocessedNamespaces) {
+ findNamespaces();
+ Namespace.PROCESSED = true;
+ }
+
+ if (unprocessedNamespaces || unprocessedMixins) {
+ var namespaces = Namespace.NAMESPACES, namespace;
+ for (var i=0, l=namespaces.length; i get(this, 'content.length')) throw new Error(OUT_OF_RANGE_EXCEPTION);
+ this._replace(idx, 0, [object]);
+ return this;
+ },
+
+ insertAt: function(idx, object) {
+ if (get(this, 'arrangedContent') === get(this, 'content')) {
+ return this._insertAt(idx, object);
+ } else {
+ throw new Ember.Error("Using insertAt on an arranged ArrayProxy is not allowed.");
+ }
+ },
+
+ removeAt: function(start, len) {
+ if ('number' === typeof start) {
+ var content = get(this, 'content'),
+ arrangedContent = get(this, 'arrangedContent'),
+ indices = [], i;
+
+ if ((start < 0) || (start >= get(this, 'length'))) {
+ throw new Error(OUT_OF_RANGE_EXCEPTION);
+ }
+
+ if (len === undefined) len = 1;
+
+ // Get a list of indices in original content to remove
+ for (i=start; i=idx) {
+ var item = content.objectAt(loc);
+ if (item) {
+ Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
+ Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange');
+
+ // keep track of the index each item was found at so we can map
+ // it back when the obj changes.
+ guid = guidFor(item);
+ if (!objects[guid]) objects[guid] = [];
+ objects[guid].push(loc);
+ }
+ }
+}
+
+function removeObserverForContentKey(content, keyName, proxy, idx, loc) {
+ var objects = proxy._objects;
+ if (!objects) objects = proxy._objects = {};
+ var indicies, guid;
+
+ while(--loc>=idx) {
+ var item = content.objectAt(loc);
+ if (item) {
+ Ember.removeBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
+ Ember.removeObserver(item, keyName, proxy, 'contentKeyDidChange');
+
+ guid = guidFor(item);
+ indicies = objects[guid];
+ indicies[indicies.indexOf(loc)] = null;
+ }
+ }
+}
+
+/**
+ This is the object instance returned when you get the `@each` property on an
+ array. It uses the unknownProperty handler to automatically create
+ EachArray instances for property names.
+
+ @private
+ @class EachProxy
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.EachProxy = Ember.Object.extend({
+
+ init: function(content) {
+ this._super();
+ this._content = content;
+ content.addArrayObserver(this);
+
+ // in case someone is already observing some keys make sure they are
+ // added
+ forEach(Ember.watchedEvents(this), function(eventName) {
+ this.didAddListener(eventName);
+ }, this);
+ },
+
+ /**
+ You can directly access mapped properties by simply requesting them.
+ The `unknownProperty` handler will generate an EachArray of each item.
+
+ @method unknownProperty
+ @param keyName {String}
+ @param value {anything}
+ */
+ unknownProperty: function(keyName, value) {
+ var ret;
+ ret = new EachArray(this._content, keyName, this);
+ Ember.defineProperty(this, keyName, null, ret);
+ this.beginObservingContentKey(keyName);
+ return ret;
+ },
+
+ // ..........................................................
+ // ARRAY CHANGES
+ // Invokes whenever the content array itself changes.
+
+ arrayWillChange: function(content, idx, removedCnt, addedCnt) {
+ var keys = this._keys, key, lim;
+
+ lim = removedCnt>0 ? idx+removedCnt : -1;
+ Ember.beginPropertyChanges(this);
+
+ for(key in keys) {
+ if (!keys.hasOwnProperty(key)) { continue; }
+
+ if (lim>0) removeObserverForContentKey(content, key, this, idx, lim);
+
+ Ember.propertyWillChange(this, key);
+ }
+
+ Ember.propertyWillChange(this._content, '@each');
+ Ember.endPropertyChanges(this);
+ },
+
+ arrayDidChange: function(content, idx, removedCnt, addedCnt) {
+ var keys = this._keys, key, lim;
+
+ lim = addedCnt>0 ? idx+addedCnt : -1;
+ Ember.beginPropertyChanges(this);
+
+ for(key in keys) {
+ if (!keys.hasOwnProperty(key)) { continue; }
+
+ if (lim>0) addObserverForContentKey(content, key, this, idx, lim);
+
+ Ember.propertyDidChange(this, key);
+ }
+
+ Ember.propertyDidChange(this._content, '@each');
+ Ember.endPropertyChanges(this);
+ },
+
+ // ..........................................................
+ // LISTEN FOR NEW OBSERVERS AND OTHER EVENT LISTENERS
+ // Start monitoring keys based on who is listening...
+
+ didAddListener: function(eventName) {
+ if (IS_OBSERVER.test(eventName)) {
+ this.beginObservingContentKey(eventName.slice(0, -7));
+ }
+ },
+
+ didRemoveListener: function(eventName) {
+ if (IS_OBSERVER.test(eventName)) {
+ this.stopObservingContentKey(eventName.slice(0, -7));
+ }
+ },
+
+ // ..........................................................
+ // CONTENT KEY OBSERVING
+ // Actual watch keys on the source content.
+
+ beginObservingContentKey: function(keyName) {
+ var keys = this._keys;
+ if (!keys) keys = this._keys = {};
+ if (!keys[keyName]) {
+ keys[keyName] = 1;
+ var content = this._content,
+ len = get(content, 'length');
+ addObserverForContentKey(content, keyName, this, 0, len);
+ } else {
+ keys[keyName]++;
+ }
+ },
+
+ stopObservingContentKey: function(keyName) {
+ var keys = this._keys;
+ if (keys && (keys[keyName]>0) && (--keys[keyName]<=0)) {
+ var content = this._content,
+ len = get(content, 'length');
+ removeObserverForContentKey(content, keyName, this, 0, len);
+ }
+ },
+
+ contentKeyWillChange: function(obj, keyName) {
+ Ember.propertyWillChange(this, keyName);
+ },
+
+ contentKeyDidChange: function(obj, keyName) {
+ Ember.propertyDidChange(this, keyName);
+ }
+
+});
+
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+var get = Ember.get, set = Ember.set;
+
+// Add Ember.Array to Array.prototype. Remove methods with native
+// implementations and supply some more optimized versions of generic methods
+// because they are so common.
+var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember.Copyable, {
+
+ // because length is a built-in property we need to know to just get the
+ // original property.
+ get: function(key) {
+ if (key==='length') return this.length;
+ else if ('number' === typeof key) return this[key];
+ else return this._super(key);
+ },
+
+ objectAt: function(idx) {
+ return this[idx];
+ },
+
+ // primitive for array support.
+ replace: function(idx, amt, objects) {
+
+ if (this.isFrozen) throw Ember.FROZEN_ERROR ;
+
+ // if we replaced exactly the same number of items, then pass only the
+ // replaced range. Otherwise, pass the full remaining array length
+ // since everything has shifted
+ var len = objects ? get(objects, 'length') : 0;
+ this.arrayContentWillChange(idx, amt, len);
+
+ if (!objects || objects.length === 0) {
+ this.splice(idx, amt) ;
+ } else {
+ var args = [idx, amt].concat(objects) ;
+ this.splice.apply(this,args) ;
+ }
+
+ this.arrayContentDidChange(idx, amt, len);
+ return this ;
+ },
+
+ // If you ask for an unknown property, then try to collect the value
+ // from member items.
+ unknownProperty: function(key, value) {
+ var ret;// = this.reducedProperty(key, value) ;
+ if ((value !== undefined) && ret === undefined) {
+ ret = this[key] = value;
+ }
+ return ret ;
+ },
+
+ // If browser did not implement indexOf natively, then override with
+ // specialized version
+ indexOf: function(object, startAt) {
+ var idx, len = this.length;
+
+ if (startAt === undefined) startAt = 0;
+ else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
+ if (startAt < 0) startAt += len;
+
+ for(idx=startAt;idx=0;idx--) {
+ if (this[idx] === object) return idx ;
+ }
+ return -1;
+ },
+
+ copy: function(deep) {
+ if (deep) {
+ return this.map(function(item){ return Ember.copy(item, true); });
+ }
+
+ return this.slice();
+ }
+});
+
+// Remove any methods implemented natively so we don't override them
+var ignore = ['length'];
+Ember.EnumerableUtils.forEach(NativeArray.keys(), function(methodName) {
+ if (Array.prototype[methodName]) ignore.push(methodName);
+});
+
+if (ignore.length>0) {
+ NativeArray = NativeArray.without.apply(NativeArray, ignore);
+}
+
+/**
+ The NativeArray mixin contains the properties needed to to make the native
+ Array support Ember.MutableArray and all of its dependent APIs. Unless you
+ have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to
+ false, this will be applied automatically. Otherwise you can apply the mixin
+ at anytime by calling `Ember.NativeArray.activate`.
+
+ @class NativeArray
+ @namespace Ember
+ @extends Ember.Mixin
+ @uses Ember.MutableArray
+ @uses Ember.MutableEnumerable
+ @uses Ember.Copyable
+ @uses Ember.Freezable
+*/
+Ember.NativeArray = NativeArray;
+
+/**
+ Creates an `Ember.NativeArray` from an Array like object.
+ Does not modify the original object.
+
+ @method A
+ @for Ember
+ @return {Ember.NativeArray}
+*/
+Ember.A = function(arr){
+ if (arr === undefined) { arr = []; }
+ return Ember.Array.detect(arr) ? arr : Ember.NativeArray.apply(arr);
+};
+
+/**
+ Activates the mixin on the Array.prototype if not already applied. Calling
+ this method more than once is safe.
+
+ @method activate
+ @for Ember.NativeArray
+ @static
+ @return {void}
+*/
+Ember.NativeArray.activate = function() {
+ NativeArray.apply(Array.prototype);
+
+ Ember.A = function(arr) { return arr || []; };
+};
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
+ Ember.NativeArray.activate();
+}
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone, fmt = Ember.String.fmt;
+
+/**
+ An unordered collection of objects.
+
+ A Set works a bit like an array except that its items are not ordered. You
+ can create a set to efficiently test for membership for an object. You can
+ also iterate through a set just like an array, even accessing objects by
+ index, however there is no guarantee as to their order.
+
+ All Sets are observable via the Enumerable Observer API - which works
+ on any enumerable object including both Sets and Arrays.
+
+ ## Creating a Set
+
+ You can create a set like you would most objects using
+ `new Ember.Set()`. Most new sets you create will be empty, but you can
+ also initialize the set with some content by passing an array or other
+ enumerable of objects to the constructor.
+
+ Finally, you can pass in an existing set and the set will be copied. You
+ can also create a copy of a set by calling `Ember.Set#copy()`.
+
+ ```javascript
+ // creates a new empty set
+ var foundNames = new Ember.Set();
+
+ // creates a set with four names in it.
+ var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
+
+ // creates a copy of the names set.
+ var namesCopy = new Ember.Set(names);
+
+ // same as above.
+ var anotherNamesCopy = names.copy();
+ ```
+
+ ## Adding/Removing Objects
+
+ You generally add or remove objects from a set using `add()` or
+ `remove()`. You can add any type of object including primitives such as
+ numbers, strings, and booleans.
+
+ Unlike arrays, objects can only exist one time in a set. If you call `add()`
+ on a set with the same object multiple times, the object will only be added
+ once. Likewise, calling `remove()` with the same object multiple times will
+ remove the object the first time and have no effect on future calls until
+ you add the object to the set again.
+
+ NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do
+ so will be ignored.
+
+ In addition to add/remove you can also call `push()`/`pop()`. Push behaves
+ just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
+ object, remove it and return it. This is a good way to use a set as a job
+ queue when you don't care which order the jobs are executed in.
+
+ ## Testing for an Object
+
+ To test for an object's presence in a set you simply call
+ `Ember.Set#contains()`.
+
+ ## Observing changes
+
+ When using `Ember.Set`, you can observe the `"[]"` property to be
+ alerted whenever the content changes. You can also add an enumerable
+ observer to the set to be notified of specific objects that are added and
+ removed from the set. See `Ember.Enumerable` for more information on
+ enumerables.
+
+ This is often unhelpful. If you are filtering sets of objects, for instance,
+ it is very inefficient to re-filter all of the items each time the set
+ changes. It would be better if you could just adjust the filtered set based
+ on what was changed on the original set. The same issue applies to merging
+ sets, as well.
+
+ ## Other Methods
+
+ `Ember.Set` primary implements other mixin APIs. For a complete reference
+ on the methods you will use with `Ember.Set`, please consult these mixins.
+ The most useful ones will be `Ember.Enumerable` and
+ `Ember.MutableEnumerable` which implement most of the common iterator
+ methods you are used to on Array.
+
+ Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
+ APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
+ modified. The benefit of this is that when you call `frozenCopy()` on it,
+ Ember will avoid making copies of the set. This allows you to write
+ code that can know with certainty when the underlying set data will or
+ will not be modified.
+
+ @class Set
+ @namespace Ember
+ @extends Ember.CoreObject
+ @uses Ember.MutableEnumerable
+ @uses Ember.Copyable
+ @uses Ember.Freezable
+ @since Ember 0.9
+*/
+Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
+ /** @scope Ember.Set.prototype */ {
+
+ // ..........................................................
+ // IMPLEMENT ENUMERABLE APIS
+ //
+
+ /**
+ This property will change as the number of objects in the set changes.
+
+ @property length
+ @type number
+ @default 0
+ */
+ length: 0,
+
+ /**
+ Clears the set. This is useful if you want to reuse an existing set
+ without having to recreate it.
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.length; // 3
+ colors.clear();
+ colors.length; // 0
+ ```
+
+ @method clear
+ @return {Ember.Set} An empty Set
+ */
+ clear: function() {
+ if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); }
+
+ var len = get(this, 'length');
+ if (len === 0) { return this; }
+
+ var guid;
+
+ this.enumerableContentWillChange(len, 0);
+ Ember.propertyWillChange(this, 'firstObject');
+ Ember.propertyWillChange(this, 'lastObject');
+
+ for (var i=0; i < len; i++){
+ guid = guidFor(this[i]);
+ delete this[guid];
+ delete this[i];
+ }
+
+ set(this, 'length', 0);
+
+ Ember.propertyDidChange(this, 'firstObject');
+ Ember.propertyDidChange(this, 'lastObject');
+ this.enumerableContentDidChange(len, 0);
+
+ return this;
+ },
+
+ /**
+ Returns true if the passed object is also an enumerable that contains the
+ same objects as the receiver.
+
+ ```javascript
+ var colors = ["red", "green", "blue"],
+ same_colors = new Ember.Set(colors);
+
+ same_colors.isEqual(colors); // true
+ same_colors.isEqual(["purple", "brown"]); // false
+ ```
+
+ @method isEqual
+ @param {Ember.Set} obj the other object.
+ @return {Boolean}
+ */
+ isEqual: function(obj) {
+ // fail fast
+ if (!Ember.Enumerable.detect(obj)) return false;
+
+ var loc = get(this, 'length');
+ if (get(obj, 'length') !== loc) return false;
+
+ while(--loc >= 0) {
+ if (!obj.contains(this[loc])) return false;
+ }
+
+ return true;
+ },
+
+ /**
+ Adds an object to the set. Only non-`null` objects can be added to a set
+ and those can only be added once. If the object is already in the set or
+ the passed value is null this method will have no effect.
+
+ This is an alias for `Ember.MutableEnumerable.addObject()`.
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.add("blue"); // ["blue"]
+ colors.add("blue"); // ["blue"]
+ colors.add("red"); // ["blue", "red"]
+ colors.add(null); // ["blue", "red"]
+ colors.add(undefined); // ["blue", "red"]
+ ```
+
+ @method add
+ @param {Object} obj The object to add.
+ @return {Ember.Set} The set itself.
+ */
+ add: Ember.aliasMethod('addObject'),
+
+ /**
+ Removes the object from the set if it is found. If you pass a `null` value
+ or an object that is already not in the set, this method will have no
+ effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.remove("red"); // ["blue", "green"]
+ colors.remove("purple"); // ["blue", "green"]
+ colors.remove(null); // ["blue", "green"]
+ ```
+
+ @method remove
+ @param {Object} obj The object to remove
+ @return {Ember.Set} The set itself.
+ */
+ remove: Ember.aliasMethod('removeObject'),
+
+ /**
+ Removes the last element from the set and returns it, or `null` if it's empty.
+
+ ```javascript
+ var colors = new Ember.Set(["green", "blue"]);
+ colors.pop(); // "blue"
+ colors.pop(); // "green"
+ colors.pop(); // null
+ ```
+
+ @method pop
+ @return {Object} The removed object from the set or null.
+ */
+ pop: function() {
+ if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
+ var obj = this.length > 0 ? this[this.length-1] : null;
+ this.remove(obj);
+ return obj;
+ },
+
+ /**
+ Inserts the given object on to the end of the set. It returns
+ the set itself.
+
+ This is an alias for `Ember.MutableEnumerable.addObject()`.
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.push("red"); // ["red"]
+ colors.push("green"); // ["red", "green"]
+ colors.push("blue"); // ["red", "green", "blue"]
+ ```
+
+ @method push
+ @return {Ember.Set} The set itself.
+ */
+ push: Ember.aliasMethod('addObject'),
+
+ /**
+ Removes the last element from the set and returns it, or `null` if it's empty.
+
+ This is an alias for `Ember.Set.pop()`.
+
+ ```javascript
+ var colors = new Ember.Set(["green", "blue"]);
+ colors.shift(); // "blue"
+ colors.shift(); // "green"
+ colors.shift(); // null
+ ```
+
+ @method shift
+ @return {Object} The removed object from the set or null.
+ */
+ shift: Ember.aliasMethod('pop'),
+
+ /**
+ Inserts the given object on to the end of the set. It returns
+ the set itself.
+
+ This is an alias of `Ember.Set.push()`
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.unshift("red"); // ["red"]
+ colors.unshift("green"); // ["red", "green"]
+ colors.unshift("blue"); // ["red", "green", "blue"]
+ ```
+
+ @method unshift
+ @return {Ember.Set} The set itself.
+ */
+ unshift: Ember.aliasMethod('push'),
+
+ /**
+ Adds each object in the passed enumerable to the set.
+
+ This is an alias of `Ember.MutableEnumerable.addObjects()`
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"]
+ ```
+
+ @method addEach
+ @param {Ember.Enumerable} objects the objects to add.
+ @return {Ember.Set} The set itself.
+ */
+ addEach: Ember.aliasMethod('addObjects'),
+
+ /**
+ Removes each object in the passed enumerable to the set.
+
+ This is an alias of `Ember.MutableEnumerable.removeObjects()`
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.removeEach(["red", "blue"]); // ["green"]
+ ```
+
+ @method removeEach
+ @param {Ember.Enumerable} objects the objects to remove.
+ @return {Ember.Set} The set itself.
+ */
+ removeEach: Ember.aliasMethod('removeObjects'),
+
+ // ..........................................................
+ // PRIVATE ENUMERABLE SUPPORT
+ //
+
+ init: function(items) {
+ this._super();
+ if (items) this.addObjects(items);
+ },
+
+ // implement Ember.Enumerable
+ nextObject: function(idx) {
+ return this[idx];
+ },
+
+ // more optimized version
+ firstObject: Ember.computed(function() {
+ return this.length > 0 ? this[0] : undefined;
+ }),
+
+ // more optimized version
+ lastObject: Ember.computed(function() {
+ return this.length > 0 ? this[this.length-1] : undefined;
+ }),
+
+ // implements Ember.MutableEnumerable
+ addObject: function(obj) {
+ if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
+ if (none(obj)) return this; // nothing to do
+
+ var guid = guidFor(obj),
+ idx = this[guid],
+ len = get(this, 'length'),
+ added ;
+
+ if (idx>=0 && idx=0 && idx=0;
+ },
+
+ copy: function() {
+ var C = this.constructor, ret = new C(), loc = get(this, 'length');
+ set(ret, 'length', loc);
+ while(--loc>=0) {
+ ret[loc] = this[loc];
+ ret[guidFor(this[loc])] = loc;
+ }
+ return ret;
+ },
+
+ toString: function() {
+ var len = this.length, idx, array = [];
+ for(idx = 0; idx < len; idx++) {
+ array[idx] = this[idx];
+ }
+ return fmt("Ember.Set<%@>", [array.join(',')]);
+ }
+
+});
+
+})();
+
+
+
+(function() {
+var DeferredMixin = Ember.DeferredMixin, // mixins/deferred
+ get = Ember.get;
+
+var Deferred = Ember.Object.extend(DeferredMixin);
+
+Deferred.reopenClass({
+ promise: function(callback, binding) {
+ var deferred = Deferred.create();
+ callback.call(binding, deferred);
+ return get(deferred, 'promise');
+ }
+});
+
+Ember.Deferred = Deferred;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {};
+var loaded = {};
+
+/**
+@method onLoad
+@for Ember
+@param name {String} name of hook
+@param callback {Function} callback to be called
+*/
+Ember.onLoad = function(name, callback) {
+ var object;
+
+ loadHooks[name] = loadHooks[name] || Ember.A();
+ loadHooks[name].pushObject(callback);
+
+ if (object = loaded[name]) {
+ callback(object);
+ }
+};
+
+/**
+@method runLoadHooks
+@for Ember
+@param name {String} name of hook
+@param object {Object} object to pass to callbacks
+*/
+Ember.runLoadHooks = function(name, object) {
+ var hooks;
+
+ loaded[name] = object;
+
+ if (hooks = loadHooks[name]) {
+ loadHooks[name].forEach(function(callback) {
+ callback(object);
+ });
+ }
+};
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ `Ember.ControllerMixin` provides a standard interface for all classes that
+ compose Ember's controller layer: `Ember.Controller`,
+ `Ember.ArrayController`, and `Ember.ObjectController`.
+
+ Within an `Ember.Router`-managed application single shared instaces of every
+ Controller object in your application's namespace will be added to the
+ application's `Ember.Router` instance. See `Ember.Application#initialize`
+ for additional information.
+
+ ## Views
+
+ By default a controller instance will be the rendering context
+ for its associated `Ember.View.` This connection is made during calls to
+ `Ember.ControllerMixin#connectOutlet`.
+
+ Within the view's template, the `Ember.View` instance can be accessed
+ through the controller with `{{view}}`.
+
+ ## Target Forwarding
+
+ By default a controller will target your application's `Ember.Router`
+ instance. Calls to `{{action}}` within the template of a controller's view
+ are forwarded to the router. See `Ember.Handlebars.helpers.action` for
+ additional information.
+
+ @class ControllerMixin
+ @namespace Ember
+ @extends Ember.Mixin
+*/
+Ember.ControllerMixin = Ember.Mixin.create({
+ /* ducktype as a controller */
+ isController: true,
+
+ /**
+ The object to which events from the view should be sent.
+
+ For example, when a Handlebars template uses the `{{action}}` helper,
+ it will attempt to send the event to the view's controller's `target`.
+
+ By default, a controller's `target` is set to the router after it is
+ instantiated by `Ember.Application#initialize`.
+
+ @property target
+ @default null
+ */
+ target: null,
+
+ container: null,
+
+ store: null,
+
+ model: Ember.computed.alias('content'),
+
+ send: function(actionName) {
+ var args = [].slice.call(arguments, 1), target;
+
+ if (this[actionName]) {
+ Ember.assert("The controller " + this + " does not have the action " + actionName, typeof this[actionName] === 'function');
+ this[actionName].apply(this, args);
+ } else if(target = get(this, 'target')) {
+ Ember.assert("The target for controller " + this + " (" + target + ") did not define a `send` method", typeof target.send === 'function');
+ target.send.apply(target, arguments);
+ }
+ }
+});
+
+/**
+ @class Controller
+ @namespace Ember
+ @extends Ember.Object
+ @uses Ember.ControllerMixin
+*/
+Ember.Controller = Ember.Object.extend(Ember.ControllerMixin);
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
+
+/**
+ `Ember.SortableMixin` provides a standard interface for array proxies
+ to specify a sort order and maintain this sorting when objects are added,
+ removed, or updated without changing the implicit order of their underlying
+ content array:
+
+ ```javascript
+ songs = [
+ {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'},
+ {trackNumber: 2, title: 'Back in the U.S.S.R.'},
+ {trackNumber: 3, title: 'Glass Onion'},
+ ];
+
+ songsController = Ember.ArrayController.create({
+ content: songs,
+ sortProperties: ['trackNumber'],
+ sortAscending: true
+ });
+
+ songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
+
+ songsController.addObject({trackNumber: 1, title: 'Dear Prudence'});
+ songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'}
+ ```
+
+ @class SortableMixin
+ @namespace Ember
+ @extends Ember.Mixin
+ @uses Ember.MutableEnumerable
+*/
+Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
+
+ /**
+ Specifies which properties dictate the arrangedContent's sort order.
+
+ @property {Array} sortProperties
+ */
+ sortProperties: null,
+
+ /**
+ Specifies the arrangedContent's sort direction
+
+ @property {Boolean} sortAscending
+ */
+ sortAscending: true,
+
+ orderBy: function(item1, item2) {
+ var result = 0,
+ sortProperties = get(this, 'sortProperties'),
+ sortAscending = get(this, 'sortAscending');
+
+ Ember.assert("you need to define `sortProperties`", !!sortProperties);
+
+ forEach(sortProperties, function(propertyName) {
+ if (result === 0) {
+ result = Ember.compare(get(item1, propertyName), get(item2, propertyName));
+ if ((result !== 0) && !sortAscending) {
+ result = (-1) * result;
+ }
+ }
+ });
+
+ return result;
+ },
+
+ destroy: function() {
+ var content = get(this, 'content'),
+ sortProperties = get(this, 'sortProperties');
+
+ if (content && sortProperties) {
+ forEach(content, function(item) {
+ forEach(sortProperties, function(sortProperty) {
+ Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ return this._super();
+ },
+
+ isSorted: Ember.computed.bool('sortProperties'),
+
+ arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) {
+ var content = get(this, 'content'),
+ isSorted = get(this, 'isSorted'),
+ sortProperties = get(this, 'sortProperties'),
+ self = this;
+
+ if (content && isSorted) {
+ content = content.slice();
+ content.sort(function(item1, item2) {
+ return self.orderBy(item1, item2);
+ });
+ forEach(content, function(item) {
+ forEach(sortProperties, function(sortProperty) {
+ Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ return Ember.A(content);
+ }
+
+ return content;
+ }),
+
+ _contentWillChange: Ember.beforeObserver(function() {
+ var content = get(this, 'content'),
+ sortProperties = get(this, 'sortProperties');
+
+ if (content && sortProperties) {
+ forEach(content, function(item) {
+ forEach(sortProperties, function(sortProperty) {
+ Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ this._super();
+ }, 'content'),
+
+ sortAscendingWillChange: Ember.beforeObserver(function() {
+ this._lastSortAscending = get(this, 'sortAscending');
+ }, 'sortAscending'),
+
+ sortAscendingDidChange: Ember.observer(function() {
+ if (get(this, 'sortAscending') !== this._lastSortAscending) {
+ var arrangedContent = get(this, 'arrangedContent');
+ arrangedContent.reverseObjects();
+ }
+ }, 'sortAscending'),
+
+ contentArrayWillChange: function(array, idx, removedCount, addedCount) {
+ var isSorted = get(this, 'isSorted');
+
+ if (isSorted) {
+ var arrangedContent = get(this, 'arrangedContent');
+ var removedObjects = array.slice(idx, idx+removedCount);
+ var sortProperties = get(this, 'sortProperties');
+
+ forEach(removedObjects, function(item) {
+ arrangedContent.removeObject(item);
+
+ forEach(sortProperties, function(sortProperty) {
+ Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ return this._super(array, idx, removedCount, addedCount);
+ },
+
+ contentArrayDidChange: function(array, idx, removedCount, addedCount) {
+ var isSorted = get(this, 'isSorted'),
+ sortProperties = get(this, 'sortProperties');
+
+ if (isSorted) {
+ var addedObjects = array.slice(idx, idx+addedCount);
+
+ forEach(addedObjects, function(item) {
+ this.insertItemSorted(item);
+
+ forEach(sortProperties, function(sortProperty) {
+ Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ return this._super(array, idx, removedCount, addedCount);
+ },
+
+ insertItemSorted: function(item) {
+ var arrangedContent = get(this, 'arrangedContent');
+ var length = get(arrangedContent, 'length');
+
+ var idx = this._binarySearch(item, 0, length);
+ arrangedContent.insertAt(idx, item);
+ },
+
+ contentItemSortPropertyDidChange: function(item) {
+ var arrangedContent = get(this, 'arrangedContent'),
+ oldIndex = arrangedContent.indexOf(item),
+ leftItem = arrangedContent.objectAt(oldIndex - 1),
+ rightItem = arrangedContent.objectAt(oldIndex + 1),
+ leftResult = leftItem && this.orderBy(item, leftItem),
+ rightResult = rightItem && this.orderBy(item, rightItem);
+
+ if (leftResult < 0 || rightResult > 0) {
+ arrangedContent.removeObject(item);
+ this.insertItemSorted(item);
+ }
+ },
+
+ _binarySearch: function(item, low, high) {
+ var mid, midItem, res, arrangedContent;
+
+ if (low === high) {
+ return low;
+ }
+
+ arrangedContent = get(this, 'arrangedContent');
+
+ mid = low + Math.floor((high - low) / 2);
+ midItem = arrangedContent.objectAt(mid);
+
+ res = this.orderBy(midItem, item);
+
+ if (res < 0) {
+ return this._binarySearch(item, mid+1, high);
+ } else if (res > 0) {
+ return this._binarySearch(item, low, mid);
+ }
+
+ return mid;
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach,
+ replace = Ember.EnumerableUtils.replace;
+
+/**
+ `Ember.ArrayController` provides a way for you to publish a collection of
+ objects so that you can easily bind to the collection from a Handlebars
+ `#each` helper, an `Ember.CollectionView`, or other controllers.
+
+ The advantage of using an `ArrayController` is that you only have to set up
+ your view bindings once; to change what's displayed, simply swap out the
+ `content` property on the controller.
+
+ For example, imagine you wanted to display a list of items fetched via an XHR
+ request. Create an `Ember.ArrayController` and set its `content` property:
+
+ ```javascript
+ MyApp.listController = Ember.ArrayController.create();
+
+ $.get('people.json', function(data) {
+ MyApp.listController.set('content', data);
+ });
+ ```
+
+ Then, create a view that binds to your new controller:
+
+ ```handlebars
+ {{#each MyApp.listController}}
+ {{firstName}} {{lastName}}
+ {{/each}}
+ ```
+
+ Although you are binding to the controller, the behavior of this controller
+ is to pass through any methods or properties to the underlying array. This
+ capability comes from `Ember.ArrayProxy`, which this class inherits from.
+
+ Sometimes you want to display computed properties within the body of an
+ `#each` helper that depend on the underlying items in `content`, but are not
+ present on those items. To do this, set `itemController` to the name of a
+ controller (probably an `ObjectController`) that will wrap each individual item.
+
+ For example:
+
+ ```handlebars
+ {{#each post in controller}}
+ {{title}} ({{titleLength}} characters)
+ {{/each}}
+ ```
+
+ ```javascript
+ App.PostsController = Ember.ArrayController.extend({
+ itemController: 'post'
+ });
+
+ App.PostController = Ember.ObjectController.extend({
+ // the `title` property will be proxied to the underlying post.
+
+ titleLength: function() {
+ return this.get('title').length;
+ }.property('title')
+ });
+ ```
+
+ In some cases it is helpful to return a different `itemController` depending
+ on the particular item. Subclasses can do this by overriding
+ `lookupItemController`.
+
+ For example:
+
+ ```javascript
+ App.MyArrayController = Ember.ArrayController.extend({
+ lookupItemController: function( object ) {
+ if (object.get('isSpecial')) {
+ return "special"; // use App.SpecialController
+ } else {
+ return "regular"; // use App.RegularController
+ }
+ }
+ });
+ ```
+
+ @class ArrayController
+ @namespace Ember
+ @extends Ember.ArrayProxy
+ @uses Ember.SortableMixin
+ @uses Ember.ControllerMixin
+*/
+
+Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
+ Ember.SortableMixin, {
+
+ /**
+ The controller used to wrap items, if any.
+
+ @property itemController
+ @type String
+ @default null
+ */
+ itemController: null,
+
+ /**
+ Return the name of the controller to wrap items, or `null` if items should
+ be returned directly. The default implementation simply returns the
+ `itemController` property, but subclasses can override this method to return
+ different controllers for different objects.
+
+ For example:
+
+ ```javascript
+ App.MyArrayController = Ember.ArrayController.extend({
+ lookupItemController: function( object ) {
+ if (object.get('isSpecial')) {
+ return "special"; // use App.SpecialController
+ } else {
+ return "regular"; // use App.RegularController
+ }
+ }
+ });
+ ```
+
+ @method
+ @type String
+ @default null
+ */
+ lookupItemController: function(object) {
+ return get(this, 'itemController');
+ },
+
+ objectAtContent: function(idx) {
+ var length = get(this, 'length'),
+ arrangedContent = get(this,'arrangedContent'),
+ object = arrangedContent && arrangedContent.objectAt(idx);
+
+ if (idx >= 0 && idx < length) {
+ var controllerClass = this.lookupItemController(object);
+ if (controllerClass) {
+ return this.controllerAt(idx, object, controllerClass);
+ }
+ }
+
+ // When `controllerClass` is falsy, we have not opted in to using item
+ // controllers, so return the object directly.
+
+ // When the index is out of range, we want to return the "out of range"
+ // value, whatever that might be. Rather than make assumptions
+ // (e.g. guessing `null` or `undefined`) we defer this to `arrangedContent`.
+ return object;
+ },
+
+ arrangedContentDidChange: function() {
+ this._super();
+ this._resetSubControllers();
+ },
+
+ arrayContentDidChange: function(idx, removedCnt, addedCnt) {
+ var subControllers = get(this, '_subControllers'),
+ subControllersToRemove = subControllers.slice(idx, idx+removedCnt);
+
+ forEach(subControllersToRemove, function(subController) {
+ if (subController) { subController.destroy(); }
+ });
+
+ replace(subControllers, idx, removedCnt, new Array(addedCnt));
+
+ // The shadow array of subcontrollers must be updated before we trigger
+ // observers, otherwise observers will get the wrong subcontainer when
+ // calling `objectAt`
+ this._super(idx, removedCnt, addedCnt);
+ },
+
+ init: function() {
+ this._super();
+ if (!this.get('content')) { Ember.defineProperty(this, 'content', undefined, Ember.A()); }
+ this.set('_subControllers', Ember.A());
+ },
+
+ controllerAt: function(idx, object, controllerClass) {
+ var container = get(this, 'container'),
+ subControllers = get(this, '_subControllers'),
+ subController = subControllers[idx];
+
+ if (!subController) {
+ subController = container.lookup("controller:" + controllerClass, { singleton: false });
+ subControllers[idx] = subController;
+ }
+
+ if (!subController) {
+ throw new Error('Could not resolve itemController: "' + controllerClass + '"');
+ }
+
+ subController.set('target', this);
+ subController.set('content', object);
+
+ return subController;
+ },
+
+ _subControllers: null,
+
+ _resetSubControllers: function() {
+ var subControllers = get(this, '_subControllers');
+
+ forEach(subControllers, function(subController) {
+ if (subController) { subController.destroy(); }
+ });
+
+ this.set('_subControllers', Ember.A());
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ `Ember.ObjectController` is part of Ember's Controller layer. A single shared
+ instance of each `Ember.ObjectController` subclass in your application's
+ namespace will be created at application initialization and be stored on your
+ application's `Ember.Router` instance.
+
+ `Ember.ObjectController` derives its functionality from its superclass
+ `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin.
+
+ @class ObjectController
+ @namespace Ember
+ @extends Ember.ObjectProxy
+ @uses Ember.ControllerMixin
+**/
+Ember.ObjectController = Ember.ObjectProxy.extend(Ember.ControllerMixin);
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+Ember Runtime
+
+@module ember
+@submodule ember-runtime
+@requires ember-metal
+*/
+
+})();
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var jQuery = Ember.imports.jQuery;
+Ember.assert("Ember Views require jQuery 1.8, 1.9 or 2.0", jQuery && (jQuery().jquery.match(/^((1\.(8|9))|2.0)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
+
+/**
+ Alias for jQuery
+
+ @method $
+ @for Ember
+*/
+Ember.$ = jQuery;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+if (Ember.$) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
+ var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
+
+ // Copies the `dataTransfer` property from a browser event object onto the
+ // jQuery event object for the specified events
+ Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
+ Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
+ });
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+/*** BEGIN METAMORPH HELPERS ***/
+
+// Internet Explorer prior to 9 does not allow setting innerHTML if the first element
+// is a "zero-scope" element. This problem can be worked around by making
+// the first node an invisible text node. We, like Modernizr, use
+
+var needsShy = this.document && (function(){
+ var testEl = document.createElement('div');
+ testEl.innerHTML = "";
+ var slash = '/';
+ testEl.firstChild.innerHTML = "\n);
+ }
+ else {
+ print qq($_\n);
+ }
+}
+
+
+
+
+sub fetch {
+ my $file = shift;
+ open F, "<$file" or die "Could not open index: $file $!\n";
+ if($file =~ /\.js/) {
+ my $js = minify(input => *F);
+ # ember.js (and probably others) contain strings which contain
+ # which leads to rendering failures, those will be fixed here
+ $js =~ s/<\/script>/' + Slash + 'script>/g;
+ close F;
+ return "/* js from $file */\n" . $js;
+ }
+ else {
+ my $src = join "", ;
+ close F;
+ return "/* css from $file */\n" . css_compress($src);
+ }
+}