From 37970e5befb82fdc28ff09886c81ea679f7db8d5 Mon Sep 17 00:00:00 2001 From: Benjamin ter Kuile Date: Sat, 22 Mar 2014 11:57:09 +0100 Subject: [PATCH] Ember updates --- Gemfile.lock | 18 +- .../images/supplier/drop-target-active.svg | 14 + app/assets/images/supplier/drop-target.svg | 14 + .../supplier/app/application.js.coffee | 2 + .../controllers/section_controller.js.coffee | 1 + .../controllers/sections_controller.js.coffee | 3 +- .../app/helpers/drag_n_drop.js.coffee | 9 +- .../app/templates/section_tab_header.emblem | 1 + .../supplier/app/templates/sections.emblem | 4 + .../supplier/app/templates/sections.hbs | 6 - .../views/section_tab_header_view.js.coffee | 20 +- .../base1-shared/section-tab-header.css.sass | 26 + .../section_tables.css.sass | 2 +- .../supplier/basic1/application.css | 1 + .../supplier/wood1/application.css | 1 + vendor/assets/ember/development/ember-data.js | 20068 +++-- vendor/assets/ember/development/ember.js | 74602 +++++++++------- vendor/assets/ember/production/ember-data.js | 22 +- vendor/assets/ember/production/ember.js | 47 +- 19 files changed, 52257 insertions(+), 42604 deletions(-) create mode 100644 app/assets/images/supplier/drop-target-active.svg create mode 100644 app/assets/images/supplier/drop-target.svg create mode 100644 app/assets/javascripts/supplier/app/templates/section_tab_header.emblem create mode 100644 app/assets/javascripts/supplier/app/templates/sections.emblem delete mode 100644 app/assets/javascripts/supplier/app/templates/sections.hbs create mode 100644 app/assets/stylesheets/supplier/base1-shared/section-tab-header.css.sass rename app/assets/stylesheets/supplier/{basic1 => base1-shared}/section_tables.css.sass (98%) diff --git a/Gemfile.lock b/Gemfile.lock index 0d7a0f4b..c351d739 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -86,7 +86,7 @@ GEM tzinfo (~> 0.3.37) addressable (2.3.5) arel (4.0.1) - atomic (1.1.14) + atomic (1.1.16) barber (0.4.2) ember-source execjs @@ -173,8 +173,8 @@ GEM handlebars-source jquery-rails (>= 1.0.17) railties (>= 3.1) - ember-source (1.2.0.1) - handlebars-source (~> 1.1.2) + ember-source (1.4.0) + handlebars-source (~> 1.0) emblem-rails (0.2.1) barber-emblem (~> 0.1.1) ember-rails (>= 0.14.0) @@ -213,13 +213,13 @@ GEM activesupport (>= 4.0.1) haml (>= 3.1, < 5.0) railties (>= 4.0.1) - handlebars-source (1.1.2) + handlebars-source (1.3.0) hashie (2.0.5) hike (1.2.3) http_parser.rb (0.6.0) httpauth (0.2.0) i18n (0.6.9) - jquery-rails (3.0.4) + jquery-rails (3.1.0) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) jquery-ui-rails (4.1.1) @@ -246,7 +246,7 @@ GEM subexec (~> 0.2.1) mini_portile (0.5.2) minitest (4.7.5) - multi_json (1.9.0) + multi_json (1.9.2) multipart-post (1.2.0) nokogiri (1.6.1) mini_portile (~> 0.5.0) @@ -355,8 +355,8 @@ GEM eventmachine (>= 1.0.0) rack (>= 1.0.0) thor (0.18.1) - thread_safe (0.1.3) - atomic + thread_safe (0.3.1) + atomic (>= 1.1.7, < 2) tilt (1.4.1) tinymce-rails (4.0.11) railties (>= 3.1.1) @@ -366,7 +366,7 @@ GEM turnip (1.1.0) gherkin (>= 2.5) rspec (~> 2.0) - tzinfo (0.3.38) + tzinfo (0.3.39) uglifier (2.4.0) execjs (>= 0.3.0) json (>= 1.8.0) diff --git a/app/assets/images/supplier/drop-target-active.svg b/app/assets/images/supplier/drop-target-active.svg new file mode 100644 index 00000000..59d97d97 --- /dev/null +++ b/app/assets/images/supplier/drop-target-active.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/assets/images/supplier/drop-target.svg b/app/assets/images/supplier/drop-target.svg new file mode 100644 index 00000000..08cc6d58 --- /dev/null +++ b/app/assets/images/supplier/drop-target.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/assets/javascripts/supplier/app/application.js.coffee b/app/assets/javascripts/supplier/app/application.js.coffee index 86c56bd9..e9e25993 100644 --- a/app/assets/javascripts/supplier/app/application.js.coffee +++ b/app/assets/javascripts/supplier/app/application.js.coffee @@ -1,5 +1,7 @@ +#= require_self #= require ember #= require ember-data #= require_directory ./modifications #= require ./app #= require_tree . +@EmberENV = {FEATURES: {'query-params-new': true}} diff --git a/app/assets/javascripts/supplier/app/controllers/section_controller.js.coffee b/app/assets/javascripts/supplier/app/controllers/section_controller.js.coffee index 66620149..3776fc8f 100644 --- a/app/assets/javascripts/supplier/app/controllers/section_controller.js.coffee +++ b/app/assets/javascripts/supplier/app/controllers/section_controller.js.coffee @@ -1,4 +1,5 @@ Qsupplier.App.SectionController = Ember.ObjectController.extend + needs: ['application'] editmode: false makeEditable: -> @set('editmode', true) finishEditable: -> diff --git a/app/assets/javascripts/supplier/app/controllers/sections_controller.js.coffee b/app/assets/javascripts/supplier/app/controllers/sections_controller.js.coffee index b027479e..b10db036 100644 --- a/app/assets/javascripts/supplier/app/controllers/sections_controller.js.coffee +++ b/app/assets/javascripts/supplier/app/controllers/sections_controller.js.coffee @@ -1 +1,2 @@ -Qsupplier.App.SectionsController = Ember.ArrayController.extend {} +Qsupplier.App.SectionsController = Ember.ArrayController.extend + needs: ['application', 'section'] diff --git a/app/assets/javascripts/supplier/app/helpers/drag_n_drop.js.coffee b/app/assets/javascripts/supplier/app/helpers/drag_n_drop.js.coffee index d6c75ea5..bd8244a8 100644 --- a/app/assets/javascripts/supplier/app/helpers/drag_n_drop.js.coffee +++ b/app/assets/javascripts/supplier/app/helpers/drag_n_drop.js.coffee @@ -10,11 +10,15 @@ DragNDrop.Draggable = Ember.Mixin.create draggable: 'true' dragStart: (e)-> @set 'content.isDragging', true + @set 'controller.isDragging', true + @set 'controller.controllers.application.isDragging', true localStorage.setItem('draggingView', @get('elementId')) dataTransfer = e.originalEvent.dataTransfer dataTransfer.setData 'Text', @get('elementId') dragEnd: (e)-> @set 'content.isDragging', false + @set 'controller.isDragging', false + @set 'controller.controllers.application.isDragging', false localStorage.removeItem 'draggingView' if localStorage.getItem 'draggingView' DragNDrop.Droppable = Ember.Mixin.create @@ -27,12 +31,15 @@ DragNDrop.Droppable = Ember.Mixin.create else DragNDrop.cancel(e) dragOver: DragNDrop.cancel + dragLeave: -> + if @dragLeft + @dragLeft() drop: (e)-> e.preventDefault() viewId = e.originalEvent.dataTransfer.getData('Text') view = Ember.View.views[viewId] return unless view - # Calculate drop position relative to container + # Calculate the element's center drop position relative to container position = left: Math.max(e.originalEvent.pageX - view.$el.offsetParent().offset().left - (view.$el.outerWidth()/2), 0) top: Math.max(e.originalEvent.pageY - view.$el.offsetParent().offset().top - (view.$el.outerHeight()/2), 0) diff --git a/app/assets/javascripts/supplier/app/templates/section_tab_header.emblem b/app/assets/javascripts/supplier/app/templates/section_tab_header.emblem new file mode 100644 index 00000000..3c034d92 --- /dev/null +++ b/app/assets/javascripts/supplier/app/templates/section_tab_header.emblem @@ -0,0 +1 @@ += title diff --git a/app/assets/javascripts/supplier/app/templates/sections.emblem b/app/assets/javascripts/supplier/app/templates/sections.emblem new file mode 100644 index 00000000..cad582a2 --- /dev/null +++ b/app/assets/javascripts/supplier/app/templates/sections.emblem @@ -0,0 +1,4 @@ +.section-tabs-container + each section in controller + view Qsupplier.App.SectionTabHeaderView context=section += outlet diff --git a/app/assets/javascripts/supplier/app/templates/sections.hbs b/app/assets/javascripts/supplier/app/templates/sections.hbs deleted file mode 100644 index 4e513b1c..00000000 --- a/app/assets/javascripts/supplier/app/templates/sections.hbs +++ /dev/null @@ -1,6 +0,0 @@ - -{{outlet}} diff --git a/app/assets/javascripts/supplier/app/views/section_tab_header_view.js.coffee b/app/assets/javascripts/supplier/app/views/section_tab_header_view.js.coffee index b3f8fe3b..1054c601 100644 --- a/app/assets/javascripts/supplier/app/views/section_tab_header_view.js.coffee +++ b/app/assets/javascripts/supplier/app/views/section_tab_header_view.js.coffee @@ -1,16 +1,18 @@ Qsupplier.App.SectionTabHeaderView = Ember.View.extend DragNDrop.Droppable, - template: Ember.Handlebars.compile('{{view.content.title}}') - tagName: 'a' - attributeBindings: ['href'] - classNameBindings: ['section_header_class'] + templateName: 'section_tab_header' + classNames: ['section-tab-header'] + classNameBindings: ['section_header_class', 'controller.controllers.application.isDragging:table-dragging', 'section_active'] + click: (e)-> + @get('controller').transitionToRoute 'section', @get('context.id') - section_header_class: (-> "section-tab-header-#{@get('content.id')}").property() - href: (-> Routes.suppliers_section_path(@content.id)).property() + section_header_class: (-> "section-tab-header-#{@get('context.id')}").property() dragEntered: (view)-> + @$().addClass('table-hover') return false # Changing the route for now is too difficult. Just do a move #if view.constructor.toString().match(/SectionTableView$/) #@get('controller').transitionToRoute 'section', @get('content') + dragLeft: -> @$().removeClass('table-hover') dropped: (view)-> if view.constructor.toString().match(/SectionTableView$/) table = view.get('content') @@ -19,3 +21,9 @@ Qsupplier.App.SectionTabHeaderView = Ember.View.extend DragNDrop.Droppable, table.save() didInsertElement: -> @$el = $ @get('element') + + section_active: (-> + current_section = @get('controller.controllers.section.content.id') + view_section = @get('context.id') + if current_section == view_section then 'active' else '' + ).property('controller.controllers.section.content.id') diff --git a/app/assets/stylesheets/supplier/base1-shared/section-tab-header.css.sass b/app/assets/stylesheets/supplier/base1-shared/section-tab-header.css.sass new file mode 100644 index 00000000..7dad190d --- /dev/null +++ b/app/assets/stylesheets/supplier/base1-shared/section-tab-header.css.sass @@ -0,0 +1,26 @@ +.section-tabs-container + .section-tab-header + float: left + margin-right: 10px + $border: 1px solid black + border: $border + padding: 5px + cursor: pointer + font-weight: bold + background-color: rgba(200,200,200,0.4) + color: #333 + &.active + border-bottom-width: 0 + &.table-hover, &.table-dragging + background-size: 100px + background-position: center center + background-repeat: no-repeat + &.table-dragging + background-image: image-url('supplier/drop-target.svg') + &.table-hover + background-image: image-url('supplier/drop-target-active.svg') + + &:after + content: '' + display: block + clear: left diff --git a/app/assets/stylesheets/supplier/basic1/section_tables.css.sass b/app/assets/stylesheets/supplier/base1-shared/section_tables.css.sass similarity index 98% rename from app/assets/stylesheets/supplier/basic1/section_tables.css.sass rename to app/assets/stylesheets/supplier/base1-shared/section_tables.css.sass index 91809260..6c28e265 100644 --- a/app/assets/stylesheets/supplier/basic1/section_tables.css.sass +++ b/app/assets/stylesheets/supplier/base1-shared/section_tables.css.sass @@ -3,7 +3,7 @@ padding: 4px 0px .section-tables-container .section-manage-tables - margin: -26px 6px 4px 6px + margin: -32px 6px 4px 6px .section-table background-color: #ccc //TODO remove width and height for ember control diff --git a/app/assets/stylesheets/supplier/basic1/application.css b/app/assets/stylesheets/supplier/basic1/application.css index ba917973..d24917f3 100644 --- a/app/assets/stylesheets/supplier/basic1/application.css +++ b/app/assets/stylesheets/supplier/basic1/application.css @@ -5,6 +5,7 @@ *= require qtip *= require 'general' *= require user/active_list + *= require_directory ../base1-shared *= require_directory . *= require_self */ diff --git a/app/assets/stylesheets/supplier/wood1/application.css b/app/assets/stylesheets/supplier/wood1/application.css index ba917973..d24917f3 100644 --- a/app/assets/stylesheets/supplier/wood1/application.css +++ b/app/assets/stylesheets/supplier/wood1/application.css @@ -5,6 +5,7 @@ *= require qtip *= require 'general' *= require user/active_list + *= require_directory ../base1-shared *= require_directory . *= require_self */ diff --git a/vendor/assets/ember/development/ember-data.js b/vendor/assets/ember/development/ember-data.js index 6d9d3ffb..cf7d6aa1 100644 --- a/vendor/assets/ember/development/ember-data.js +++ b/vendor/assets/ember/development/ember-data.js @@ -1,17 +1,14 @@ -// Fetched from channel: release, with url http://builds.emberjs.com/beta/ember-data.js -// Fetched on: 2014-01-04T21:06:07Z +// Fetched from channel: canary, with url http://builds.emberjs.com/canary/ember-data.js +// Fetched on: 2014-03-22T07:36:41Z /*! * @overview Ember Data - * @copyright Copyright 2011-2013 Tilde Inc. and contributors. + * @copyright Copyright 2011-2014 Tilde Inc. and contributors. * Portions Copyright 2011 LivingSocial Inc. * @license Licensed under MIT license (see license.js) + * @version 1.0.0-beta.7+canary.f85a7438 */ - - - // Version: 1.0.0-beta.5+pre.69cb8b87 - -(function() { -var define, requireModule; +(function(global) { +var define, requireModule, require, requirejs; (function() { var registry = {}, seen = {}; @@ -20,3860 +17,6768 @@ var define, requireModule; registry[name] = { deps: deps, callback: callback }; }; - requireModule = function(name) { + requirejs = require = requireModule = function(name) { + requirejs._eak_seen = registry; + if (seen[name]) { return seen[name]; } seen[name] = {}; - var mod, deps, callback, reified , exports; - - mod = registry[name]; - - if (!mod) { - throw new Error("Module '" + name + "' not found."); + if (!registry[name]) { + throw new Error("Could not find module " + name); } - deps = mod.deps; - callback = mod.callback; - reified = []; - exports; + var mod = registry[name], + deps = mod.deps, + callback = mod.callback, + reified = [], + exports; for (var i=0, l=deps.length; i "famous_people" + ``` + + @method pathForType + @param {String} type + @returns String + */ + pathForType: function(type) { + var decamelized = decamelize(type); + var underscored = underscore(decamelized); + return pluralize(underscored); + }, + + /** + The ActiveModelAdapter overrides the `ajaxError` method + to return a DS.InvalidError for all 422 Unprocessable Entity + responses. + + A 422 HTTP response from the server generally implies that the request + was well formed but the API was unable to process it because the + content was not semantically correct or meaningful per the API. + + For more information on 422 HTTP Error code see 11.2 WebDAV RFC 4918 + https://tools.ietf.org/html/rfc4918#section-11.2 + + @method ajaxError + @param jqXHR + @returns error + */ + ajaxError: function(jqXHR) { + var error = this._super(jqXHR); + + if (jqXHR && jqXHR.status === 422) { + var response = Ember.$.parseJSON(jqXHR.responseText), + errors = {}; + + if (response.errors !== undefined) { + var jsonErrors = response.errors; + + forEach(Ember.keys(jsonErrors), function(key) { + errors[Ember.String.camelize(key)] = jsonErrors[key]; + }); + } + + return new InvalidError(errors); + } else { + return error; + } } }); - ``` - ## Customizing an App-Wide Serializer + __exports__["default"] = ActiveModelAdapter; + }); +define("activemodel-adapter/lib/system/active_model_serializer", + ["../../../ember-inflector/lib/main","../../../ember-data/lib/serializers/rest_serializer","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var singularize = __dependency1__.singularize; + var RESTSerializer = __dependency2__["default"]; + /** + @module ember-data + */ - If you want to define a serializer for your entire - application, you'll probably want to use `eachAttribute` - and `eachRelationship` on the record. + var get = Ember.get, + forEach = Ember.EnumerableUtils.forEach, + camelize = Ember.String.camelize, + capitalize = Ember.String.capitalize, + decamelize = Ember.String.decamelize, + underscore = Ember.String.underscore; - ```javascript - App.ApplicationSerializer = DS.JSONSerializer.extend({ - serialize: function(record, options) { - var json = {}; + var ActiveModelSerializer = RESTSerializer.extend({ + // SERIALIZE - record.eachAttribute(function(name) { - json[serverAttributeName(name)] = record.get(name); - }) + /** + Converts camelcased attributes to underscored when serializing. - record.eachRelationship(function(name, relationship) { - if (relationship.kind === 'hasMany') { - json[serverHasManyName(name)] = record.get(name).mapBy('id'); + @method keyForAttribute + @param {String} attribute + @returns String + */ + keyForAttribute: function(attr) { + return decamelize(attr); + }, + + /** + Underscores relationship names and appends "_id" or "_ids" when serializing + relationship keys. + + @method keyForRelationship + @param {String} key + @param {String} kind + @returns String + */ + keyForRelationship: function(key, kind) { + key = decamelize(key); + if (kind === "belongsTo") { + return key + "_id"; + } else if (kind === "hasMany") { + return singularize(key) + "_ids"; + } else { + return key; + } + }, + + /** + Does not serialize hasMany relationships by default. + */ + serializeHasMany: Ember.K, + + /** + Underscores the JSON root keys when serializing. + + @method serializeIntoHash + @param {Object} hash + @param {subclass of DS.Model} type + @param {DS.Model} record + @param {Object} options + */ + serializeIntoHash: function(data, type, record, options) { + var root = underscore(decamelize(type.typeKey)); + data[root] = this.serialize(record, options); + }, + + /** + Serializes a polymorphic type as a fully capitalized model name. + + @method serializePolymorphicType + @param {DS.Model} record + @param {Object} json + @param relationship + */ + serializePolymorphicType: function(record, json, relationship) { + var key = relationship.key, + belongsTo = get(record, key); + key = this.keyForAttribute(key); + json[key + "_type"] = capitalize(camelize(belongsTo.constructor.typeKey)); + }, + + // EXTRACT + + /** + Extracts the model typeKey from underscored root objects. + + @method typeForRoot + @param {String} root + @returns String the model's typeKey + */ + typeForRoot: function(root) { + var camelized = camelize(root); + return singularize(camelized); + }, + + /** + Add extra step to `DS.RESTSerializer.normalize` so links are + normalized. + + If your payload looks like this + + ```js + { + "post": { + "id": 1, + "title": "Rails is omakase", + "links": { "flagged_comments": "api/comments/flagged" } + } + } + ``` + The normalized version would look like this + + ```js + { + "post": { + "id": 1, + "title": "Rails is omakase", + "links": { "flaggedComments": "api/comments/flagged" } + } + } + ``` + + @method normalize + @param {subclass of DS.Model} type + @param {Object} hash + @param {String} prop + @returns Object + */ + + normalize: function(type, hash, prop) { + this.normalizeLinks(hash); + + return this._super(type, hash, prop); + }, + + /** + Convert `snake_cased` links to `camelCase` + + @method normalizeLinks + @param {Object} hash + */ + + normalizeLinks: function(data){ + if (data.links) { + var links = data.links; + + for (var link in links) { + var camelizedLink = camelize(link); + + if (camelizedLink !== link) { + links[camelizedLink] = links[link]; + delete links[link]; + } + } + } + }, + + /** + Normalize the polymorphic type from the JSON. + + Normalize: + ```js + { + id: "1" + minion: { type: "evil_minion", id: "12"} + } + ``` + + To: + ```js + { + id: "1" + minion: { type: "evilMinion", id: "12"} + } + ``` + + @method normalizeRelationships + @private + */ + normalizeRelationships: function(type, hash) { + var payloadKey, payload; + + if (this.keyForRelationship) { + type.eachRelationship(function(key, relationship) { + if (relationship.options.polymorphic) { + payloadKey = this.keyForAttribute(key); + payload = hash[payloadKey]; + if (payload && payload.type) { + payload.type = this.typeForRoot(payload.type); + } else if (payload && relationship.kind === "hasMany") { + var self = this; + forEach(payload, function(single) { + single.type = self.typeForRoot(single.type); + }); + } + } else { + payloadKey = this.keyForRelationship(key, relationship.kind); + payload = hash[payloadKey]; + } + + hash[key] = payload; + + if (key !== payloadKey) { + delete hash[payloadKey]; + } + }, this); + } + } + }); + + __exports__["default"] = ActiveModelSerializer; + }); +define("activemodel-adapter/lib/system/embedded_records_mixin", + ["../../../ember-inflector/lib/main","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var get = Ember.get; + var forEach = Ember.EnumerableUtils.forEach; + + var pluralize = __dependency1__.pluralize; + + /** + The EmbeddedRecordsMixin allows you to add embedded record support to your + serializers. + To set up embedded records, you include the mixin into the serializer and then + define your embedded relations. + + ```js + App.PostSerializer = DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, { + attrs: { + comments: {embedded: 'always'} + } + }) + ``` + + Currently only `{embedded: 'always'}` records are supported. + + @class EmbeddedRecordsMixin + @namespace DS + */ + var EmbeddedRecordsMixin = Ember.Mixin.create({ + + /** + Serialize has-many relationship when it is configured as embedded objects. + + @method serializeHasMany + */ + serializeHasMany: function(record, json, relationship) { + var key = relationship.key, + attrs = get(this, 'attrs'), + embed = attrs && attrs[key] && attrs[key].embedded === 'always'; + + if (embed) { + json[this.keyForAttribute(key)] = get(record, key).map(function(relation) { + var data = relation.serialize(), + primaryKey = get(this, 'primaryKey'); + + data[primaryKey] = get(relation, primaryKey); + + return data; + }, this); + } + }, + + /** + Extract embedded objects out of the payload for a single object + and add them as sideloaded objects instead. + + @method extractSingle + */ + extractSingle: function(store, primaryType, payload, recordId, requestType) { + var root = this.keyForAttribute(primaryType.typeKey), + partial = payload[root]; + + updatePayloadWithEmbedded(store, this, primaryType, partial, payload); + + return this._super(store, primaryType, payload, recordId, requestType); + }, + + /** + Extract embedded objects out of a standard payload + and add them as sideloaded objects instead. + + @method extractArray + */ + extractArray: function(store, type, payload) { + var root = this.keyForAttribute(type.typeKey), + partials = payload[pluralize(root)]; + + forEach(partials, function(partial) { + updatePayloadWithEmbedded(store, this, type, partial, payload); + }, this); + + return this._super(store, type, payload); + } + }); + + function updatePayloadWithEmbedded(store, serializer, type, partial, payload) { + var attrs = get(serializer, 'attrs'); + + if (!attrs) { + return; + } + + type.eachRelationship(function(key, relationship) { + var expandedKey, embeddedTypeKey, attribute, ids, + config = attrs[key], + serializer = store.serializerFor(relationship.type.typeKey), + primaryKey = get(serializer, "primaryKey"); + + if (relationship.kind !== "hasMany") { + return; + } + + if (config && (config.embedded === 'always' || config.embedded === 'load')) { + // underscore forces the embedded records to be side loaded. + // it is needed when main type === relationship.type + embeddedTypeKey = '_' + Ember.String.pluralize(relationship.type.typeKey); + expandedKey = this.keyForRelationship(key, relationship.kind); + attribute = this.keyForAttribute(key); + ids = []; + + if (!partial[attribute]) { + return; + } + + payload[embeddedTypeKey] = payload[embeddedTypeKey] || []; + + forEach(partial[attribute], function(data) { + var embeddedType = store.modelFor(relationship.type.typeKey); + updatePayloadWithEmbedded(store, serializer, embeddedType, data, payload); + ids.push(data[primaryKey]); + payload[embeddedTypeKey].push(data); + }); + + partial[expandedKey] = ids; + delete partial[attribute]; + } + }, serializer); + } + + __exports__["default"] = EmbeddedRecordsMixin; + }); +define("ember-data/lib/adapters", + ["./adapters/fixture_adapter","./adapters/rest_adapter","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + /** + @module ember-data + */ + + var FixtureAdapter = __dependency1__["default"]; + var RESTAdapter = __dependency2__["default"]; + + __exports__.RESTAdapter = RESTAdapter; + __exports__.FixtureAdapter = FixtureAdapter; + }); +define("ember-data/lib/adapters/fixture_adapter", + ["../system/adapter","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /** + @module ember-data + */ + + var get = Ember.get, fmt = Ember.String.fmt, + indexOf = Ember.EnumerableUtils.indexOf; + + var counter = 0; + + var Adapter = __dependency1__["default"]; + + /** + `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`. + + For information on how to use the `FixtureAdapter` in your + application please see the [FixtureAdapter + guide](/guides/models/the-fixture-adapter/). + + @class FixtureAdapter + @namespace DS + @extends DS.Adapter + */ + var FixtureAdapter = Adapter.extend({ + // by default, fixtures are already in normalized form + serializer: null, + + /** + If `simulateRemoteResponse` is `true` the `FixtureAdapter` will + wait a number of milliseconds before resolving promises with the + fixture values. The wait time can be configured via the `latency` + property. + + @property simulateRemoteResponse + @type {Boolean} + @default true + */ + simulateRemoteResponse: true, + + /** + By default the `FixtureAdapter` will simulate a wait of the + `latency` milliseconds before resolving promises with the fixture + values. This behavior can be turned off via the + `simulateRemoteResponse` property. + + @property latency + @type {Number} + @default 50 + */ + latency: 50, + + /** + Implement this method in order to provide data associated with a type + + @method fixturesForType + @param {Subclass of DS.Model} type + @return {Array} + */ + fixturesForType: function(type) { + if (type.FIXTURES) { + var fixtures = Ember.A(type.FIXTURES); + return fixtures.map(function(fixture){ + var fixtureIdType = typeof fixture.id; + if(fixtureIdType !== "number" && fixtureIdType !== "string"){ + throw new Error(fmt('the id property must be defined as a number or string for fixture %@', [fixture])); + } + fixture.id = fixture.id + ''; + return fixture; + }); + } + return null; + }, + + /** + Implement this method in order to query fixtures data + + @method queryFixtures + @param {Array} fixture + @param {Object} query + @param {Subclass of DS.Model} type + @return {Promise|Array} + */ + queryFixtures: function(fixtures, query, type) { + Ember.assert('Not implemented: You must override the DS.FixtureAdapter::queryFixtures method to support querying the fixture store.'); + }, + + /** + @method updateFixtures + @param {Subclass of DS.Model} type + @param {Array} fixture + */ + 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 json for CRUD methods + + @method mockJSON + @param {Subclass of DS.Model} type + @param {DS.Model} record + */ + mockJSON: function(store, type, record) { + return store.serializerFor(type).serialize(record, { includeId: true }); + }, + + /** + @method generateIdForRecord + @param {DS.Store} store + @param {DS.Model} record + @return {String} id + */ + generateIdForRecord: function(store) { + return "fixture-" + counter++; + }, + + /** + @method find + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {String} id + @return {Promise} promise + */ + find: function(store, type, id) { + var fixtures = this.fixturesForType(type), + fixture; + + Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures); + + if (fixtures) { + fixture = Ember.A(fixtures).findProperty('id', id); + } + + if (fixture) { + return this.simulateRemoteCall(function() { + return fixture; + }, this); + } + }, + + /** + @method findMany + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Array} ids + @return {Promise} promise + */ + 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 indexOf(ids, item.id) !== -1; + }); + } + + if (fixtures) { + return this.simulateRemoteCall(function() { + return fixtures; + }, this); + } + }, + + /** + @private + @method findAll + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {String} sinceToken + @return {Promise} promise + */ + findAll: function(store, type) { + var fixtures = this.fixturesForType(type); + + Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures); + + return this.simulateRemoteCall(function() { + return fixtures; + }, this); + }, + + /** + @private + @method findQuery + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} query + @param {DS.AdapterPopulatedRecordArray} recordArray + @return {Promise} promise + */ + findQuery: function(store, type, query, array) { + var fixtures = this.fixturesForType(type); + + Ember.assert("Unable to find fixtures for model type " + type.toString(), fixtures); + + fixtures = this.queryFixtures(fixtures, query, type); + + if (fixtures) { + return this.simulateRemoteCall(function() { + return fixtures; + }, this); + } + }, + + /** + @method createRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {DS.Model} record + @return {Promise} promise + */ + createRecord: function(store, type, record) { + var fixture = this.mockJSON(store, type, record); + + this.updateFixtures(type, fixture); + + return this.simulateRemoteCall(function() { + return fixture; + }, this); + }, + + /** + @method updateRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {DS.Model} record + @return {Promise} promise + */ + updateRecord: function(store, type, record) { + var fixture = this.mockJSON(store, type, record); + + this.updateFixtures(type, fixture); + + return this.simulateRemoteCall(function() { + return fixture; + }, this); + }, + + /** + @method deleteRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {DS.Model} record + @return {Promise} promise + */ + deleteRecord: function(store, type, record) { + var fixture = this.mockJSON(store, type, record); + + this.deleteLoadedFixture(type, fixture); + + return this.simulateRemoteCall(function() { + // no payload in a deletion + return null; + }); + }, + + /* + @method deleteLoadedFixture + @private + @param type + @param record + */ + deleteLoadedFixture: function(type, record) { + var existingFixture = this.findExistingFixture(type, record); + + if(existingFixture) { + var index = indexOf(type.FIXTURES, existingFixture); + type.FIXTURES.splice(index, 1); + return true; + } + }, + + /* + @method findExistingFixture + @private + @param type + @param record + */ + findExistingFixture: function(type, record) { + var fixtures = this.fixturesForType(type); + var id = get(record, 'id'); + + return this.findFixtureById(fixtures, id); + }, + + /* + @method findFixtureById + @private + @param fixtures + @param id + */ + findFixtureById: function(fixtures, id) { + return Ember.A(fixtures).find(function(r) { + if(''+get(r, 'id') === ''+id) { + return true; + } else { + return false; + } + }); + }, + + /* + @method simulateRemoteCall + @private + @param callback + @param context + */ + simulateRemoteCall: function(callback, context) { + var adapter = this; + + return new Ember.RSVP.Promise(function(resolve) { + if (get(adapter, 'simulateRemoteResponse')) { + // Schedule with setTimeout + Ember.run.later(function() { + resolve(callback.call(context)); + }, get(adapter, 'latency')); + } else { + // Asynchronous, but at the of the runloop with zero latency + Ember.run.schedule('actions', null, function() { + resolve(callback.call(context)); + }); + } + }, "DS: FixtureAdapter#simulateRemoteCall"); + } + }); + + __exports__["default"] = FixtureAdapter; + }); +define("ember-data/lib/adapters/rest_adapter", + ["../system/adapter","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /** + @module ember-data + */ + + var Adapter = __dependency1__["default"]; + var get = Ember.get, set = Ember.set; + var forEach = Ember.ArrayPolyfills.forEach; + + /** + 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 camelCased 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": { + "firstName": "Barack", + "lastName": "Obama", + "occupation": "President" + } + } + ``` + + ## Customization + + ### Endpoint path customization + + Endpoint paths can be prefixed with a `namespace` by setting the namespace + property on the adapter: + + ```js + DS.RESTAdapter.reopen({ + namespace: 'api/1' + }); + ``` + Requests for `App.Person` would now target `/api/1/people/1`. + + ### Host customization + + An adapter can target other hosts by setting the `host` property. + + ```js + DS.RESTAdapter.reopen({ + host: 'https://api.example.com' + }); + ``` + + ### Headers customization + + Some APIs require HTTP headers, e.g. to provide an API key. Arbitrary + headers can be set as key/value pairs on the `RESTAdapter`'s `headers` + object and Ember Data will send them along with each ajax request. + + + ```js + DS.RESTAdapter.reopen({ + headers: { + "API_KEY": "secret key", + "ANOTHER_HEADER": "Some header value" + } + }); + ``` + + `headers` can also be used as a computed property to support dynamic + headers. + + ```js + App.ApplicationAdapter = DS.RESTAdapter.extend({ + headers: function() { + return { + "API_KEY": this.get("session.authToken"), + "ANOTHER_HEADER": "Some header value" + }; + }.property("session.authToken") + }); + ``` + + @class RESTAdapter + @constructor + @namespace DS + @extends DS.Adapter + */ + var RESTAdapter = Adapter.extend({ + defaultSerializer: '-rest', + /** + Endpoint paths can be prefixed with a `namespace` by setting the namespace + property on the adapter: + + ```javascript + DS.RESTAdapter.reopen({ + namespace: 'api/1' + }); + ``` + + Requests for `App.Post` would now target `/api/1/post/`. + + @property namespace + @type {String} + */ + + /** + An adapter can target other hosts by setting the `host` property. + + ```javascript + DS.RESTAdapter.reopen({ + host: 'https://api.example.com' + }); + ``` + + Requests for `App.Post` would now target `https://api.example.com/post/`. + + @property host + @type {String} + */ + + /** + Some APIs require HTTP headers, e.g. to provide an API key. Arbitrary + headers can be set as key/value pairs on the `RESTAdapter`'s `headers` + object and Ember Data will send them along with each ajax request. + + ```javascript + DS.RESTAdapter.reopen({ + headers: { + "API_KEY": "secret key", + "ANOTHER_HEADER": "Some header value" + } + }); + ``` + + @property headers + @type {Object} + */ + + /** + Called by the store in order to fetch the JSON for a given + type and ID. + + The `find` method makes an Ajax request to a URL computed by `buildURL`, and returns a + promise for the resulting payload. + + This method performs an HTTP `GET` request with the id provided as part of the query string. + + @method find + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {String} id + @returns {Promise} promise + */ + find: function(store, type, id) { + return this.ajax(this.buildURL(type.typeKey, id), 'GET'); + }, + + /** + Called by the store in order to fetch a JSON array for all + of the records for a given type. + + The `findAll` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a + promise for the resulting payload. + + @private + @method findAll + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {String} sinceToken + @returns {Promise} promise + */ + findAll: function(store, type, sinceToken) { + var query; + + if (sinceToken) { + query = { since: sinceToken }; + } + + return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query }); + }, + + /** + Called by the store in order to fetch a JSON array for + the records that match a particular query. + + The `findQuery` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a + promise for the resulting payload. + + The `query` argument is a simple JavaScript object that will be passed directly + to the server as parameters. + + @private + @method findQuery + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} query + @returns {Promise} promise + */ + findQuery: function(store, type, query) { + return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query }); + }, + + /** + Called by the store in order to fetch a JSON array for + the unloaded records in a has-many relationship that were originally + specified as IDs. + + For example, if the original payload looks like: + + ```js + { + "id": 1, + "title": "Rails is omakase", + "comments": [ 1, 2, 3 ] + } + ``` + + The IDs will be passed as a URL-encoded Array of IDs, in this form: + + ``` + ids[]=1&ids[]=2&ids[]=3 + ``` + + Many servers, such as Rails and PHP, will automatically convert this URL-encoded array + into an Array for you on the server-side. If you want to encode the + IDs, differently, just override this (one-line) method. + + The `findMany` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a + promise for the resulting payload. + + @method findMany + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Array} ids + @returns {Promise} promise + */ + findMany: function(store, type, ids) { + return this.ajax(this.buildURL(type.typeKey), 'GET', { data: { ids: ids } }); + }, + + /** + Called by the store in order to fetch a JSON array for + the unloaded records in a has-many relationship that were originally + specified as a URL (inside of `links`). + + For example, if your original payload looks like this: + + ```js + { + "post": { + "id": 1, + "title": "Rails is omakase", + "links": { "comments": "/posts/1/comments" } + } + } + ``` + + This method will be called with the parent record and `/posts/1/comments`. + + The `findHasMany` method will make an Ajax (HTTP GET) request to the originally specified URL. + If the URL is host-relative (starting with a single slash), the + request will use the host specified on the adapter (if any). + + @method findHasMany + @param {DS.Store} store + @param {DS.Model} record + @param {String} url + @returns {Promise} promise + */ + findHasMany: function(store, record, url) { + var host = get(this, 'host'), + id = get(record, 'id'), + type = record.constructor.typeKey; + + if (host && url.charAt(0) === '/' && url.charAt(1) !== '/') { + url = host + url; + } + + return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET'); + }, + + /** + Called by the store in order to fetch a JSON array for + the unloaded records in a belongs-to relationship that were originally + specified as a URL (inside of `links`). + + For example, if your original payload looks like this: + + ```js + { + "person": { + "id": 1, + "name": "Tom Dale", + "links": { "group": "/people/1/group" } + } + } + ``` + + This method will be called with the parent record and `/people/1/group`. + + The `findBelongsTo` method will make an Ajax (HTTP GET) request to the originally specified URL. + + @method findBelongsTo + @param {DS.Store} store + @param {DS.Model} record + @param {String} url + @returns {Promise} promise + */ + findBelongsTo: function(store, record, url) { + var id = get(record, 'id'), + type = record.constructor.typeKey; + + return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET'); + }, + + /** + Called by the store when a newly created record is + saved via the `save` method on a model record instance. + + The `createRecord` method serializes the record and makes an Ajax (HTTP POST) request + to a URL computed by `buildURL`. + + See `serialize` for information on how to customize the serialized form + of a record. + + @method createRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {DS.Model} record + @returns {Promise} promise + */ + createRecord: function(store, type, record) { + var data = {}; + var serializer = store.serializerFor(type.typeKey); + + serializer.serializeIntoHash(data, type, record, { includeId: true }); + + return this.ajax(this.buildURL(type.typeKey), "POST", { data: data }); + }, + + /** + Called by the store when an existing record is saved + via the `save` method on a model record instance. + + The `updateRecord` method serializes the record and makes an Ajax (HTTP PUT) request + to a URL computed by `buildURL`. + + See `serialize` for information on how to customize the serialized form + of a record. + + @method updateRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {DS.Model} record + @returns {Promise} promise + */ + updateRecord: function(store, type, record) { + var data = {}; + var serializer = store.serializerFor(type.typeKey); + + serializer.serializeIntoHash(data, type, record); + + var id = get(record, 'id'); + + return this.ajax(this.buildURL(type.typeKey, id), "PUT", { data: data }); + }, + + /** + Called by the store when a record is deleted. + + The `deleteRecord` method makes an Ajax (HTTP DELETE) request to a URL computed by `buildURL`. + + @method deleteRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {DS.Model} record + @returns {Promise} promise + */ + deleteRecord: function(store, type, record) { + var id = get(record, 'id'); + + return this.ajax(this.buildURL(type.typeKey, id), "DELETE"); + }, + + /** + Builds a URL for a given type and optional ID. + + By default, it pluralizes the type's name (for example, 'post' + becomes 'posts' and 'person' becomes 'people'). To override the + pluralization see [pathForType](#method_pathForType). + + If an ID is specified, it adds the ID to the path generated + for the type, separated by a `/`. + + @method buildURL + @param {String} type + @param {String} id + @returns {String} url + */ + buildURL: function(type, id) { + var url = [], + host = get(this, 'host'), + prefix = this.urlPrefix(); + + if (type) { url.push(this.pathForType(type)); } + if (id) { url.push(id); } + + if (prefix) { url.unshift(prefix); } + + url = url.join('/'); + if (!host && url) { url = '/' + url; } + + return url; + }, + + /** + @method urlPrefix + @private + @param {String} path + @param {String} parentUrl + @return {String} urlPrefix + */ + urlPrefix: function(path, parentURL) { + var host = get(this, 'host'), + namespace = get(this, 'namespace'), + url = []; + + if (path) { + // Absolute path + if (path.charAt(0) === '/') { + if (host) { + path = path.slice(1); + url.push(host); + } + // Relative path + } else if (!/^http(s)?:\/\//.test(path)) { + url.push(parentURL); + } + } else { + if (host) { url.push(host); } + if (namespace) { url.push(namespace); } + } + + if (path) { + url.push(path); + } + + return url.join('/'); + }, + + /** + Determines the pathname for a given type. + + By default, it pluralizes the type's name (for example, + 'post' becomes 'posts' and 'person' becomes 'people'). + + ### Pathname customization + + For example if you have an object LineItem with an + endpoint of "/line_items/". + + ```js + DS.RESTAdapter.reopen({ + pathForType: function(type) { + var decamelized = Ember.String.decamelize(type); + return Ember.String.pluralize(decamelized); + }; + }); + ``` + + @method pathForType + @param {String} type + @returns {String} path + **/ + pathForType: function(type) { + var camelized = Ember.String.camelize(type); + return Ember.String.pluralize(camelized); + }, + + /** + Takes an ajax response, and returns a relevant error. + + Returning a `DS.InvalidError` from this method will cause the + record to transition into the `invalid` state and make the + `errors` object available on the record. + + ```javascript + App.ApplicationAdapter = DS.RESTAdapter.extend({ + ajaxError: function(jqXHR) { + var error = this._super(jqXHR); + + if (jqXHR && jqXHR.status === 422) { + var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"]; + + return new DS.InvalidError(jsonErrors); + } else { + return error; + } + } + }); + ``` + + Note: As a correctness optimization, the default implementation of + the `ajaxError` method strips out the `then` method from jquery's + ajax response (jqXHR). This is important because the jqXHR's + `then` method fulfills the promise with itself resulting in a + circular "thenable" chain which may cause problems for some + promise libraries. + + @method ajaxError + @param {Object} jqXHR + @return {Object} jqXHR + */ + ajaxError: function(jqXHR) { + if (jqXHR) { + jqXHR.then = null; + } + + return jqXHR; + }, + + /** + Takes a URL, an HTTP method and a hash of data, and makes an + HTTP request. + + When the server responds with a payload, Ember Data will call into `extractSingle` + or `extractArray` (depending on whether the original query was for one record or + many records). + + By default, `ajax` method has the following behavior: + + * It sets the response `dataType` to `"json"` + * If the HTTP method is not `"GET"`, it sets the `Content-Type` to be + `application/json; charset=utf-8` + * If the HTTP method is not `"GET"`, it stringifies the data passed in. The + data is the serialized record in the case of a save. + * Registers success and failure handlers. + + @method ajax + @private + @param {String} url + @param {String} type The request type GET, POST, PUT, DELETE etc. + @param {Object} hash + @return {Promise} promise + */ + ajax: function(url, type, hash) { + var adapter = this; + + return new Ember.RSVP.Promise(function(resolve, reject) { + hash = adapter.ajaxOptions(url, type, hash); + + hash.success = function(json) { + Ember.run(null, resolve, json); + }; + + hash.error = function(jqXHR, textStatus, errorThrown) { + Ember.run(null, reject, adapter.ajaxError(jqXHR)); + }; + + Ember.$.ajax(hash); + }, "DS: RestAdapter#ajax " + type + " to " + url); + }, + + /** + @method ajaxOptions + @private + @param {String} url + @param {String} type The request type GET, POST, PUT, DELETE etc. + @param {Object} hash + @return {Object} hash + */ + ajaxOptions: function(url, type, hash) { + hash = hash || {}; + hash.url = url; + hash.type = type; + hash.dataType = 'json'; + hash.context = this; + + if (hash.data && type !== 'GET') { + hash.contentType = 'application/json; charset=utf-8'; + hash.data = JSON.stringify(hash.data); + } + + var headers = get(this, 'headers'); + if (headers !== undefined) { + hash.beforeSend = function (xhr) { + forEach.call(Ember.keys(headers), function(key) { + xhr.setRequestHeader(key, headers[key]); + }); + }; + } + + + return hash; + } + + }); + + __exports__["default"] = RESTAdapter; + }); +define("ember-data/lib/core", + ["exports"], + function(__exports__) { + "use strict"; + /** + @module ember-data + */ + + /** + All Ember Data methods and functions are defined inside of this namespace. + + @class DS + @static + */ + var DS; + if ('undefined' === typeof DS) { + /** + @property VERSION + @type String + @default '1.0.0-beta.7+canary.f85a7438' + @static + */ + DS = Ember.Namespace.create({ + VERSION: '1.0.0-beta.7+canary.f85a7438' + }); + + if (Ember.libraries) { + Ember.libraries.registerCoreLibrary('Ember Data', DS.VERSION); + } + } + + __exports__["default"] = DS; + }); +define("ember-data/lib/ext/date", + [], + function() { + "use strict"; + /** + @module ember-data + */ + + /** + Date.parse with progressive enhancement for ISO 8601 + + © 2011 Colin Snover + + Released under MIT license. + + @class Date + @namespace Ember + @static + */ + Ember.Date = Ember.Date || {}; + + var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; + + /** + @method parse + @param date + */ + Ember.Date.parse = function (date) { + var timestamp, struct, minutesOffset = 0; + + // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string + // before falling back to any implementation-specific date parsing, so that’s what we do, even if native + // implementations could be faster + // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm + if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) { + // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC + for (var i = 0, k; (k = numericKeys[i]); ++i) { + struct[k] = +struct[k] || 0; + } + + // allow undefined days and months + struct[2] = (+struct[2] || 1) - 1; + struct[3] = +struct[3] || 1; + + if (struct[8] !== 'Z' && struct[9] !== undefined) { + minutesOffset = struct[10] * 60 + struct[11]; + + if (struct[9] === '+') { + minutesOffset = 0 - minutesOffset; + } + } + + timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); + } + else { + timestamp = origParse ? origParse(date) : NaN; + } + + return timestamp; + }; + + if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Date) { + Date.parse = Ember.Date.parse; + } + }); +define("ember-data/lib/initializers", + ["./system/store","./serializers","./adapters","./system/debug/debug_adapter","./system/container_proxy","./transforms"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__) { + "use strict"; + var Store = __dependency1__["default"]; + var JSONSerializer = __dependency2__.JSONSerializer; + var RESTSerializer = __dependency2__.RESTSerializer; + var RESTAdapter = __dependency3__.RESTAdapter; + var DebugAdapter = __dependency4__["default"]; + var ContainerProxy = __dependency5__["default"]; + var BooleanTransform = __dependency6__.BooleanTransform; + var DateTransform = __dependency6__.DateTransform; + var StringTransform = __dependency6__.StringTransform; + var NumberTransform = __dependency6__.NumberTransform; + + /** + @module ember-data + */ + + 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: 'custom' + }); + + 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) { + Application.initializer({ + name: "store", + + initialize: function(container, application) { + application.register('store:main', application.Store || Store); + + // allow older names to be looked up + + var proxy = new ContainerProxy(container); + proxy.registerDeprecations([ + {deprecated: 'serializer:_default', valid: 'serializer:-default'}, + {deprecated: 'serializer:_rest', valid: 'serializer:-rest'}, + {deprecated: 'adapter:_rest', valid: 'adapter:-rest'} + ]); + + // new go forward paths + application.register('serializer:-default', JSONSerializer); + application.register('serializer:-rest', RESTSerializer); + application.register('adapter:-rest', RESTAdapter); + + // Eagerly generate the store so defaultStore is populated. + // TODO: Do this in a finisher hook + container.lookup('store:main'); + } + }); + + Application.initializer({ + name: "transforms", + before: "store", + + initialize: function(container, application) { + application.register('transform:boolean', BooleanTransform); + application.register('transform:date', DateTransform); + application.register('transform:number', NumberTransform); + application.register('transform:string', StringTransform); + } + }); + + Application.initializer({ + name: "data-adapter", + before: "store", + + initialize: function(container, application) { + application.register('data-adapter:main', DebugAdapter); + } + }); + + Application.initializer({ + name: "injectStore", + before: "store", + + initialize: function(container, application) { + application.inject('controller', 'store', 'store:main'); + application.inject('route', 'store', 'store:main'); + application.inject('serializer', 'store', 'store:main'); + application.inject('data-adapter', 'store', 'store:main'); + } + }); + + }); + }); +define("ember-data/lib/main", + ["./core","./ext/date","./system/store","./system/model","./system/changes","./system/adapter","./system/debug","./system/record_arrays","./system/record_array_manager","./adapters","./serializers/json_serializer","./serializers/rest_serializer","../../ember-inflector/lib/main","../../activemodel-adapter/lib/main","./transforms","./system/relationships","./initializers","./system/container_proxy","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __exports__) { + "use strict"; + /** + Ember Data + + @module ember-data + @main ember-data + */ + + // support RSVP 2.x via resolve, but prefer RSVP 3.x's Promise.cast + Ember.RSVP.Promise.cast = Ember.RSVP.Promise.cast || Ember.RSVP.resolve; + + var DS = __dependency1__["default"]; + + var Store = __dependency3__.Store; + var PromiseArray = __dependency3__.PromiseArray; + var PromiseObject = __dependency3__.PromiseObject; + var Model = __dependency4__.Model; + var Errors = __dependency4__.Errors; + var RootState = __dependency4__.RootState; + var attr = __dependency4__.attr; + var AttributeChange = __dependency5__.AttributeChange; + var RelationshipChange = __dependency5__.RelationshipChange; + var RelationshipChangeAdd = __dependency5__.RelationshipChangeAdd; + var RelationshipChangeRemove = __dependency5__.RelationshipChangeRemove; + var OneToManyChange = __dependency5__.OneToManyChange; + var ManyToNoneChange = __dependency5__.ManyToNoneChange; + var OneToOneChange = __dependency5__.OneToOneChange; + var ManyToManyChange = __dependency5__.ManyToManyChange; + var InvalidError = __dependency6__.InvalidError; + var Adapter = __dependency6__.Adapter; + var DebugAdapter = __dependency7__["default"]; + var RecordArray = __dependency8__.RecordArray; + var FilteredRecordArray = __dependency8__.FilteredRecordArray; + var AdapterPopulatedRecordArray = __dependency8__.AdapterPopulatedRecordArray; + var ManyArray = __dependency8__.ManyArray; + var RecordArrayManager = __dependency9__["default"]; + var RESTAdapter = __dependency10__.RESTAdapter; + var FixtureAdapter = __dependency10__.FixtureAdapter; + var JSONSerializer = __dependency11__["default"]; + var RESTSerializer = __dependency12__["default"]; + var ActiveModelAdapter = __dependency14__.ActiveModelAdapter; + var ActiveModelSerializer = __dependency14__.ActiveModelSerializer; + var EmbeddedRecordsMixin = __dependency14__.EmbeddedRecordsMixin; + + var Transform = __dependency15__.Transform; + var DateTransform = __dependency15__.DateTransform; + var NumberTransform = __dependency15__.NumberTransform; + var StringTransform = __dependency15__.StringTransform; + var BooleanTransform = __dependency15__.BooleanTransform; + + var hasMany = __dependency16__.hasMany; + var belongsTo = __dependency16__.belongsTo; + + var ContainerProxy = __dependency18__["default"]; + + DS.Store = Store; + DS.PromiseArray = PromiseArray; + DS.PromiseObject = PromiseObject; + + DS.Model = Model; + DS.RootState = RootState; + DS.attr = attr; + DS.Errors = Errors; + + DS.AttributeChange = AttributeChange; + DS.RelationshipChange = RelationshipChange; + DS.RelationshipChangeAdd = RelationshipChangeAdd; + DS.OneToManyChange = OneToManyChange; + DS.ManyToNoneChange = OneToManyChange; + DS.OneToOneChange = OneToOneChange; + DS.ManyToManyChange = ManyToManyChange; + + DS.Adapter = Adapter; + DS.InvalidError = InvalidError; + + DS.DebugAdapter = DebugAdapter; + + DS.RecordArray = RecordArray; + DS.FilteredRecordArray = FilteredRecordArray; + DS.AdapterPopulatedRecordArray = AdapterPopulatedRecordArray; + DS.ManyArray = ManyArray; + + DS.RecordArrayManager = RecordArrayManager; + + DS.RESTAdapter = RESTAdapter; + DS.FixtureAdapter = FixtureAdapter; + + DS.RESTSerializer = RESTSerializer; + DS.JSONSerializer = JSONSerializer; + + DS.Transform = Transform; + DS.DateTransform = DateTransform; + DS.StringTransform = StringTransform; + DS.NumberTransform = NumberTransform; + DS.BooleanTransform = BooleanTransform; + + DS.ActiveModelAdapter = ActiveModelAdapter; + DS.ActiveModelSerializer = ActiveModelSerializer; + DS.EmbeddedRecordsMixin = EmbeddedRecordsMixin; + + DS.belongsTo = belongsTo; + DS.hasMany = hasMany; + + DS.ContainerProxy = ContainerProxy; + + Ember.lookup.DS = DS; + + __exports__["default"] = DS; + }); +define("ember-data/lib/serializers", + ["./serializers/json_serializer","./serializers/rest_serializer","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var JSONSerializer = __dependency1__["default"]; + var RESTSerializer = __dependency2__["default"]; + + __exports__.JSONSerializer = JSONSerializer; + __exports__.RESTSerializer = RESTSerializer; + }); +define("ember-data/lib/serializers/json_serializer", + ["../system/changes","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var RelationshipChange = __dependency1__.RelationshipChange; + var get = Ember.get, set = Ember.set, isNone = Ember.isNone; + + /** + In Ember Data a Serializer is used to serialize and deserialize + records when they are transferred in and out of an external source. + This process involves normalizing property names, transforming + attribute values and serializing relationships. + + For maximum performance Ember Data recommends you use the + [RESTSerializer](DS.RESTSerializer.html) or one of its subclasses. + + `JSONSerializer` is useful for simpler or legacy backends that may + not support the http://jsonapi.org/ spec. + + @class JSONSerializer + @namespace DS + */ + var JSONSerializer = Ember.Object.extend({ + /** + The primaryKey is used when serializing and deserializing + data. Ember Data always uses the `id` property to store the id of + the record. The external source may not always follow this + convention. In these cases it is useful to override the + primaryKey property to match the primaryKey of your external + store. + + Example + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + primaryKey: '_id' + }); + ``` + + @property primaryKey + @type {String} + @default 'id' + */ + primaryKey: 'id', + + /** + Given a subclass of `DS.Model` and a JSON object this method will + iterate through each attribute of the `DS.Model` and invoke the + `DS.Transform#deserialize` method on the matching property of the + JSON object. This method is typically called after the + serializer's `normalize` method. + + @method applyTransforms + @private + @param {subclass of DS.Model} type + @param {Object} data The data to transform + @return {Object} data The transformed data object + */ + applyTransforms: function(type, data) { + type.eachTransformedAttribute(function(key, type) { + var transform = this.transformFor(type); + data[key] = transform.deserialize(data[key]); + }, this); + + return data; + }, + + /** + Normalizes a part of the JSON payload returned by + the server. You should override this method, munge the hash + and call super if you have generic normalization to do. + + It takes the type of the record that is being normalized + (as a DS.Model class), the property where the hash was + originally found, and the hash to normalize. + + You can use this method, for example, to normalize underscored keys to camelized + or other general-purpose normalizations. + + Example + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + normalize: function(type, hash) { + var fields = Ember.get(type, 'fields'); + fields.forEach(function(field) { + var payloadField = Ember.String.underscore(field); + if (field === payloadField) { return; } + + hash[field] = hash[payloadField]; + delete hash[payloadField]; + }); + return this._super.apply(this, arguments); + } + }); + ``` + + @method normalize + @param {subclass of DS.Model} type + @param {Object} hash + @return {Object} + */ + normalize: function(type, hash) { + if (!hash) { return hash; } + + this.applyTransforms(type, hash); + return hash; + }, + + // SERIALIZE + /** + Called when a record is saved in order to convert the + record into JSON. + + By default, it creates a JSON object with a key for + each attribute and belongsTo relationship. + + For example, consider this model: + + ```javascript + App.Comment = DS.Model.extend({ + title: DS.attr(), + body: DS.attr(), + + author: DS.belongsTo('user') + }); + ``` + + The default serialization would create a JSON object like: + + ```javascript + { + "title": "Rails is unagi", + "body": "Rails? Omakase? O_O", + "author": 12 + } + ``` + + By default, attributes are passed through as-is, unless + you specified an attribute type (`DS.attr('date')`). If + you specify a transform, the JavaScript value will be + serialized when inserted into the JSON hash. + + By default, belongs-to relationships are converted into + IDs when inserted into the JSON hash. + + ## IDs + + `serialize` takes an options hash with a single option: + `includeId`. If this option is `true`, `serialize` will, + by default include the ID in the JSON object it builds. + + The adapter passes in `includeId: true` when serializing + a record for `createRecord`, but not for `updateRecord`. + + ## Customization + + Your server may expect a different JSON format than the + built-in serialization format. + + In that case, you can implement `serialize` yourself and + return a JSON hash of your choosing. + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serialize: function(post, options) { + var json = { + POST_TTL: post.get('title'), + POST_BDY: post.get('body'), + POST_CMS: post.get('comments').mapProperty('id') + } + + if (options.includeId) { + json.POST_ID_ = post.get('id'); + } + + return json; + } + }); + ``` + + ## Customizing an App-Wide Serializer + + If you want to define a serializer for your entire + application, you'll probably want to use `eachAttribute` + and `eachRelationship` on the record. + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + serialize: function(record, options) { + var json = {}; + + record.eachAttribute(function(name) { + json[serverAttributeName(name)] = record.get(name); + }) + + record.eachRelationship(function(name, relationship) { + if (relationship.kind === 'hasMany') { + json[serverHasManyName(name)] = record.get(name).mapBy('id'); + } + }); + + if (options.includeId) { + json.ID_ = record.get('id'); + } + + return json; } }); - if (options.includeId) { - json.ID_ = record.get('id'); + function serverAttributeName(attribute) { + return attribute.underscore().toUpperCase(); } - return json; - } - }); + function serverHasManyName(name) { + return serverAttributeName(name.singularize()) + "_IDS"; + } + ``` - function serverAttributeName(attribute) { - return attribute.underscore().toUpperCase(); - } + This serializer will generate JSON that looks like this: - function serverHasManyName(name) { - return serverAttributeName(name.singularize()) + "_IDS"; - } - ``` + ```javascript + { + "TITLE": "Rails is omakase", + "BODY": "Yep. Omakase.", + "COMMENT_IDS": [ 1, 2, 3 ] + } + ``` - This serializer will generate JSON that looks like this: + ## Tweaking the Default JSON - ```javascript - { - "TITLE": "Rails is omakase", - "BODY": "Yep. Omakase.", - "COMMENT_IDS": [ 1, 2, 3 ] - } - ``` + If you just want to do some small tweaks on the default JSON, + you can call super first and make the tweaks on the returned + JSON. - ## Tweaking the Default JSON + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serialize: function(record, options) { + var json = this._super.apply(this, arguments); - If you just want to do some small tweaks on the default JSON, - you can call super first and make the tweaks on the returned - JSON. + json.subject = json.title; + delete json.title; - ```javascript - App.PostSerializer = DS.JSONSerializer.extend({ + return json; + } + }); + ``` + + @method serialize + @param {subclass of DS.Model} record + @param {Object} options + @return {Object} json + */ serialize: function(record, options) { - var json = this._super.apply(this, arguments); + var json = {}; - json.subject = json.title; - delete json.title; + if (options && options.includeId) { + var id = get(record, 'id'); + + if (id) { + json[get(this, 'primaryKey')] = id; + } + } + + record.eachAttribute(function(key, attribute) { + this.serializeAttribute(record, json, key, attribute); + }, this); + + record.eachRelationship(function(key, relationship) { + if (relationship.kind === 'belongsTo') { + this.serializeBelongsTo(record, json, relationship); + } else if (relationship.kind === 'hasMany') { + this.serializeHasMany(record, json, relationship); + } + }, this); return json; + }, + + /** + `serializeAttribute` can be used to customize how `DS.attr` + properties are serialized + + For example if you wanted to ensure all you attributes were always + serialized as properties on an `attributes` object you could + write: + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + serializeAttribute: function(record, json, key, attributes) { + json.attributes = json.attributes || {}; + this._super(record, json.attributes, key, attributes); + } + }); + ``` + + @method serializeAttribute + @param {DS.Model} record + @param {Object} json + @param {String} key + @param {Object} attribute + */ + serializeAttribute: function(record, json, key, attribute) { + var attrs = get(this, 'attrs'); + var value = get(record, key), type = attribute.type; + + if (type) { + var transform = this.transformFor(type); + value = transform.serialize(value); + } + + // if provided, use the mapping provided by `attrs` in + // the serializer + key = attrs && attrs[key] || (this.keyForAttribute ? this.keyForAttribute(key) : key); + + json[key] = value; + }, + + /** + `serializeBelongsTo` can be used to customize how `DS.belongsTo` + properties are serialized. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serializeBelongsTo: function(record, json, relationship) { + var key = relationship.key; + + var belongsTo = get(record, key); + + key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key; + + json[key] = Ember.isNone(belongsTo) ? belongsTo : belongsTo.toJSON(); + } + }); + ``` + + @method serializeBelongsTo + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ + serializeBelongsTo: function(record, json, relationship) { + var key = relationship.key; + + var belongsTo = get(record, key); + + key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key; + + if (isNone(belongsTo)) { + json[key] = belongsTo; + } else { + json[key] = get(belongsTo, 'id'); + } + + if (relationship.options.polymorphic) { + this.serializePolymorphicType(record, json, relationship); + } + }, + + /** + `serializeHasMany` can be used to customize how `DS.hasMany` + properties are serialized. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serializeHasMany: function(record, json, relationship) { + var key = relationship.key; + if (key === 'comments') { + return; + } else { + this._super.apply(this, arguments); + } + } + }); + ``` + + @method serializeHasMany + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ + serializeHasMany: function(record, json, relationship) { + var key = relationship.key; + + var relationshipType = RelationshipChange.determineRelationshipType(record.constructor, relationship); + + if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') { + json[key] = get(record, key).mapBy('id'); + // TODO support for polymorphic manyToNone and manyToMany relationships + } + }, + + /** + You can use this method to customize how polymorphic objects are + serialized. Objects are considered to be polymorphic if + `{polymorphic: true}` is pass as the second argument to the + `DS.belongsTo` function. + + Example + + ```javascript + App.CommentSerializer = DS.JSONSerializer.extend({ + serializePolymorphicType: function(record, json, relationship) { + var key = relationship.key, + belongsTo = get(record, key); + key = this.keyForAttribute ? this.keyForAttribute(key) : key; + json[key + "_type"] = belongsTo.constructor.typeKey; + } + }); + ``` + + @method serializePolymorphicType + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ + serializePolymorphicType: Ember.K, + + // EXTRACT + + /** + The `extract` method is used to deserialize payload data from the + server. By default the `JSONSerializer` does not push the records + into the store. However records that subclass `JSONSerializer` + such as the `RESTSerializer` may push records into the store as + part of the extract call. + + This method delegates to a more specific extract method based on + the `requestType`. + + Example + + ```javascript + var get = Ember.get; + socket.on('message', function(message) { + var modelName = message.model; + var data = message.data; + var type = store.modelFor(modelName); + var serializer = store.serializerFor(type.typeKey); + var record = serializer.extract(store, type, data, get(data, 'id'), 'single'); + store.push(modelName, record); + }); + ``` + + @method extract + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @param {String or Number} id + @param {String} requestType + @return {Object} json The deserialized payload + */ + extract: function(store, type, payload, id, requestType) { + this.extractMeta(store, type, payload); + + var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1); + return this[specificExtract](store, type, payload, id, requestType); + }, + + /** + `extractFindAll` is a hook into the extract method used when a + call is made to `DS.Store#findAll`. By default this method is an + alias for [extractArray](#method_extractArray). + + @method extractFindAll + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindAll: function(store, type, payload){ + return this.extractArray(store, type, payload); + }, + /** + `extractFindQuery` is a hook into the extract method used when a + call is made to `DS.Store#findQuery`. By default this method is an + alias for [extractArray](#method_extractArray). + + @method extractFindQuery + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindQuery: function(store, type, payload){ + return this.extractArray(store, type, payload); + }, + /** + `extractFindMany` is a hook into the extract method used when a + call is made to `DS.Store#findMany`. By default this method is + alias for [extractArray](#method_extractArray). + + @method extractFindMany + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindMany: function(store, type, payload){ + return this.extractArray(store, type, payload); + }, + /** + `extractFindHasMany` is a hook into the extract method used when a + call is made to `DS.Store#findHasMany`. By default this method is + alias for [extractArray](#method_extractArray). + + @method extractFindHasMany + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindHasMany: function(store, type, payload){ + return this.extractArray(store, type, payload); + }, + + /** + `extractCreateRecord` is a hook into the extract method used when a + call is made to `DS.Store#createRecord`. By default this method is + alias for [extractSave](#method_extractSave). + + @method extractCreateRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractCreateRecord: function(store, type, payload) { + return this.extractSave(store, type, payload); + }, + /** + `extractUpdateRecord` is a hook into the extract method used when + a call is made to `DS.Store#update`. By default this method is alias + for [extractSave](#method_extractSave). + + @method extractUpdateRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractUpdateRecord: function(store, type, payload) { + return this.extractSave(store, type, payload); + }, + /** + `extractDeleteRecord` is a hook into the extract method used when + a call is made to `DS.Store#deleteRecord`. By default this method is + alias for [extractSave](#method_extractSave). + + @method extractDeleteRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractDeleteRecord: function(store, type, payload) { + return this.extractSave(store, type, payload); + }, + + /** + `extractFind` is a hook into the extract method used when + a call is made to `DS.Store#find`. By default this method is + alias for [extractSingle](#method_extractSingle). + + @method extractFind + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractFind: function(store, type, payload) { + return this.extractSingle(store, type, payload); + }, + /** + `extractFindBelongsTo` is a hook into the extract method used when + a call is made to `DS.Store#findBelongsTo`. By default this method is + alias for [extractSingle](#method_extractSingle). + + @method extractFindBelongsTo + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractFindBelongsTo: function(store, type, payload) { + return this.extractSingle(store, type, payload); + }, + /** + `extractSave` is a hook into the extract method used when a call + is made to `DS.Model#save`. By default this method is alias + for [extractSingle](#method_extractSingle). + + @method extractSave + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractSave: function(store, type, payload) { + return this.extractSingle(store, type, payload); + }, + + /** + `extractSingle` is used to deserialize a single record returned + from the adapter. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + extractSingle: function(store, type, payload) { + payload.comments = payload._embedded.comment; + delete payload._embedded; + + return this._super(store, type, payload); + }, + }); + ``` + + @method extractSingle + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractSingle: function(store, type, payload) { + return this.normalize(type, payload); + }, + + /** + `extractArray` is used to deserialize an array of records + returned from the adapter. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + extractArray: function(store, type, payload) { + return payload.map(function(json) { + return this.extractSingle(store, type, json); + }, this); + } + }); + ``` + + @method extractArray + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractArray: function(store, type, payload) { + return this.normalize(type, payload); + }, + + /** + `extractMeta` is used to deserialize any meta information in the + adapter payload. By default Ember Data expects meta information to + be located on the `meta` property of the payload object. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + extractMeta: function(store, type, payload) { + if (payload && payload._pagination) { + store.metaForType(type, payload._pagination); + delete payload._pagination; + } + } + }); + ``` + + @method extractMeta + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + */ + extractMeta: function(store, type, payload) { + if (payload && payload.meta) { + store.metaForType(type, payload.meta); + delete payload.meta; + } + }, + + /** + `keyForAttribute` can be used to define rules for how to convert an + attribute name in your model to a key in your JSON. + + Example + + ```javascript + App.ApplicationSerializer = DS.RESTSerializer.extend({ + keyForAttribute: function(attr) { + return Ember.String.underscore(attr).toUpperCase(); + } + }); + ``` + + @method keyForAttribute + @param {String} key + @return {String} normalized key + */ + + + /** + `keyForRelationship` can be used to define a custom key when + serializing relationship properties. By default `JSONSerializer` + does not provide an implementation of this method. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + keyForRelationship: function(key, relationship) { + return 'rel_' + Ember.String.underscore(key); + } + }); + ``` + + @method keyForRelationship + @param {String} key + @param {String} relationship type + @return {String} normalized key + */ + + // HELPERS + + /** + @method transformFor + @private + @param {String} attributeType + @param {Boolean} skipAssertion + @return {DS.Transform} transform + */ + transformFor: function(attributeType, skipAssertion) { + var transform = this.container.lookup('transform:' + attributeType); + Ember.assert("Unable to find transform for '" + attributeType + "'", skipAssertion || !!transform); + return transform; } }); - ``` - @method serialize - @param {subclass of DS.Model} record - @param {Object} options - @return {Object} json - */ - serialize: function(record, options) { - var json = {}; + __exports__["default"] = JSONSerializer; + }); +define("ember-data/lib/serializers/rest_serializer", + ["./json_serializer","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /** + @module ember-data + */ - if (options && options.includeId) { - var id = get(record, 'id'); + var JSONSerializer = __dependency1__["default"]; + var get = Ember.get, set = Ember.set; + var forEach = Ember.ArrayPolyfills.forEach; + var map = Ember.ArrayPolyfills.map; - if (id) { - json[get(this, 'primaryKey')] = get(record, 'id'); - } + function coerceId(id) { + return id == null ? null : id+''; } - record.eachAttribute(function(key, attribute) { - this.serializeAttribute(record, json, key, attribute); - }, this); + /** + Normally, applications will use the `RESTSerializer` by implementing + the `normalize` method and individual normalizations under + `normalizeHash`. - record.eachRelationship(function(key, relationship) { - if (relationship.kind === 'belongsTo') { - this.serializeBelongsTo(record, json, relationship); - } else if (relationship.kind === 'hasMany') { - this.serializeHasMany(record, json, relationship); - } - }, this); + This allows you to do whatever kind of munging you need, and is + especially useful if your server is inconsistent and you need to + do munging differently for many different kinds of responses. - return json; - }, + See the `normalize` documentation for more information. - /** - `serializeAttribute` can be used to customize how `DS.attr` - properties are serialized + ## Across the Board Normalization - For example if you wanted to ensure all you attributes were always - serialized as properties on an `attributes` object you could - write: + There are also a number of hooks that you might find useful to defined + across-the-board rules for your payload. These rules will be useful + if your server is consistent, or if you're building an adapter for + an infrastructure service, like Parse, and want to encode service + conventions. - ```javascript - App.ApplicationSerializer = DS.JSONSerializer.extend({ - serializeAttribute: function(record, json, key, attributes) { - json.attributes = json.attributes || {}; - this._super(record, json.attributes, key, attributes); - } - }); - ``` + For example, if all of your keys are underscored and all-caps, but + otherwise consistent with the names you use in your models, you + can implement across-the-board rules for how to convert an attribute + name in your model to a key in your JSON. - @method serializeAttribute - @param {DS.Model} record - @param {Object} json - @param {String} key - @param {Object} attribute - */ - serializeAttribute: function(record, json, key, attribute) { - var attrs = get(this, 'attrs'); - var value = get(record, key), type = attribute.type; + ```js + App.ApplicationSerializer = DS.RESTSerializer.extend({ + keyForAttribute: function(attr) { + return Ember.String.underscore(attr).toUpperCase(); + } + }); + ``` - if (type) { - var transform = this.transformFor(type); - value = transform.serialize(value); - } + You can also implement `keyForRelationship`, which takes the name + of the relationship as the first parameter, and the kind of + relationship (`hasMany` or `belongsTo`) as the second parameter. - // if provided, use the mapping provided by `attrs` in - // the serializer - key = attrs && attrs[key] || (this.keyForAttribute ? this.keyForAttribute(key) : key); + @class RESTSerializer + @namespace DS + @extends DS.JSONSerializer + */ + var RESTSerializer = JSONSerializer.extend({ + /** + If you want to do normalizations specific to some part of the payload, you + can specify those under `normalizeHash`. - json[key] = value; - }, + For example, given the following json where the the `IDs` under + `"comments"` are provided as `_id` instead of `id`. - /** - `serializeBelongsTo` can be used to customize how `DS.belongsTo` - properties are serialized. + ```javascript + { + "post": { + "id": 1, + "title": "Rails is omakase", + "comments": [ 1, 2 ] + }, + "comments": [{ + "_id": 1, + "body": "FIRST" + }, { + "_id": 2, + "body": "Rails is unagi" + }] + } + ``` - Example + You use `normalizeHash` to normalize just the comments: - ```javascript - App.PostSerializer = DS.JSONSerializer.extend({ - serializeBelongsTo: function(record, json, relationship) { - var key = relationship.key; + ```javascript + App.PostSerializer = DS.RESTSerializer.extend({ + normalizeHash: { + comments: function(hash) { + hash.id = hash._id; + delete hash._id; + return hash; + } + } + }); + ``` - var belongsTo = get(record, key); + The key under `normalizeHash` is usually just the original key + that was in the original payload. However, key names will be + impacted by any modifications done in the `normalizePayload` + method. The `DS.RESTSerializer`'s default implementation makes no + changes to the payload keys. - key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key; + @property normalizeHash + @type {Object} + @default undefined + */ - json[key] = Ember.isNone(belongsTo) ? belongsTo : belongsTo.toJSON(); - } - }); - ``` + /** + Normalizes a part of the JSON payload returned by + the server. You should override this method, munge the hash + and call super if you have generic normalization to do. - @method serializeBelongsTo - @param {DS.Model} record - @param {Object} json - @param {Object} relationship - */ - serializeBelongsTo: function(record, json, relationship) { - var key = relationship.key; + It takes the type of the record that is being normalized + (as a DS.Model class), the property where the hash was + originally found, and the hash to normalize. - var belongsTo = get(record, key); + For example, if you have a payload that looks like this: - key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key; + ```js + { + "post": { + "id": 1, + "title": "Rails is omakase", + "comments": [ 1, 2 ] + }, + "comments": [{ + "id": 1, + "body": "FIRST" + }, { + "id": 2, + "body": "Rails is unagi" + }] + } + ``` - if (isNone(belongsTo)) { - json[key] = belongsTo; - } else { - json[key] = get(belongsTo, 'id'); - } + The `normalize` method will be called three times: - if (relationship.options.polymorphic) { - this.serializePolymorphicType(record, json, relationship); - } - }, + * With `App.Post`, `"posts"` and `{ id: 1, title: "Rails is omakase", ... }` + * With `App.Comment`, `"comments"` and `{ id: 1, body: "FIRST" }` + * With `App.Comment`, `"comments"` and `{ id: 2, body: "Rails is unagi" }` - /** - `serializeHasMany` can be used to customize how `DS.hasMany` - properties are serialized. + You can use this method, for example, to normalize underscored keys to camelized + or other general-purpose normalizations. - Example + If you want to do normalizations specific to some part of the payload, you + can specify those under `normalizeHash`. - ```javascript - App.PostSerializer = DS.JSONSerializer.extend({ - serializeHasMany: function(record, json, relationship) { - var key = relationship.key; - if (key === 'comments') { - return; - } else { - this._super.apply(this, arguments); - } - } - }); - ``` + For example, if the `IDs` under `"comments"` are provided as `_id` instead of + `id`, you can specify how to normalize just the comments: - @method serializeHasMany - @param {DS.Model} record - @param {Object} json - @param {Object} relationship - */ - serializeHasMany: function(record, json, relationship) { - var key = relationship.key; + ```js + App.PostSerializer = DS.RESTSerializer.extend({ + normalizeHash: { + comments: function(hash) { + hash.id = hash._id; + delete hash._id; + return hash; + } + } + }); + ``` - var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship); + The key under `normalizeHash` is just the original key that was in the original + payload. - if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') { - json[key] = get(record, key).mapBy('id'); - // TODO support for polymorphic manyToNone and manyToMany relationships - } - }, + @method normalize + @param {subclass of DS.Model} type + @param {Object} hash + @param {String} prop + @returns {Object} + */ + normalize: function(type, hash, prop) { + this.normalizeId(hash); + this.normalizeAttributes(type, hash); + this.normalizeRelationships(type, hash); - /** - You can use this method to customize how polymorphic objects are - serialized. Objects are considered to be polymorphic if - `{polymorphic: true}` is pass as the second argument to the - `DS.belongsTo` function. + this.normalizeUsingDeclaredMapping(type, hash); - Example + if (this.normalizeHash && this.normalizeHash[prop]) { + this.normalizeHash[prop](hash); + } - ```javascript - App.CommentSerializer = DS.JSONSerializer.extend({ + return this._super(type, hash, prop); + }, + + /** + You can use this method to normalize all payloads, regardless of whether they + represent single records or an array. + + For example, you might want to remove some extraneous data from the payload: + + ```js + App.ApplicationSerializer = DS.RESTSerializer.extend({ + normalizePayload: function(type, payload) { + delete payload.version; + delete payload.status; + return payload; + } + }); + ``` + + @method normalizePayload + @param {subclass of DS.Model} type + @param {Object} payload + @returns {Object} the normalized payload + */ + normalizePayload: function(type, payload) { + return payload; + }, + + /** + @method normalizeId + @private + */ + normalizeId: function(hash) { + var primaryKey = get(this, 'primaryKey'); + + if (primaryKey === 'id') { return; } + + hash.id = hash[primaryKey]; + delete hash[primaryKey]; + }, + + /** + @method normalizeUsingDeclaredMapping + @private + */ + normalizeUsingDeclaredMapping: function(type, hash) { + var attrs = get(this, 'attrs'), payloadKey, key; + + if (attrs) { + for (key in attrs) { + payloadKey = attrs[key]; + if (payloadKey && payloadKey.key) { + payloadKey = payloadKey.key; + } + if (typeof payloadKey === 'string') { + hash[key] = hash[payloadKey]; + delete hash[payloadKey]; + } + } + } + }, + + /** + @method normalizeAttributes + @private + */ + normalizeAttributes: function(type, hash) { + var payloadKey, key; + + if (this.keyForAttribute) { + type.eachAttribute(function(key) { + payloadKey = this.keyForAttribute(key); + if (key === payloadKey) { return; } + + hash[key] = hash[payloadKey]; + delete hash[payloadKey]; + }, this); + } + }, + + /** + @method normalizeRelationships + @private + */ + normalizeRelationships: function(type, hash) { + var payloadKey, key; + + if (this.keyForRelationship) { + type.eachRelationship(function(key, relationship) { + payloadKey = this.keyForRelationship(key, relationship.kind); + if (key === payloadKey) { return; } + + hash[key] = hash[payloadKey]; + delete hash[payloadKey]; + }, this); + } + }, + + /** + Called when the server has returned a payload representing + a single record, such as in response to a `find` or `save`. + + It is your opportunity to clean up the server's response into the normalized + form expected by Ember Data. + + If you want, you can just restructure the top-level of your payload, and + do more fine-grained normalization in the `normalize` method. + + For example, if you have a payload like this in response to a request for + post 1: + + ```js + { + "id": 1, + "title": "Rails is omakase", + + "_embedded": { + "comment": [{ + "_id": 1, + "comment_title": "FIRST" + }, { + "_id": 2, + "comment_title": "Rails is unagi" + }] + } + } + ``` + + You could implement a serializer that looks like this to get your payload + into shape: + + ```js + App.PostSerializer = DS.RESTSerializer.extend({ + // First, restructure the top-level so it's organized by type + extractSingle: function(store, type, payload, id, requestType) { + var comments = payload._embedded.comment; + delete payload._embedded; + + payload = { comments: comments, post: payload }; + return this._super(store, type, payload, id, requestType); + }, + + normalizeHash: { + // Next, normalize individual comments, which (after `extract`) + // are now located under `comments` + comments: function(hash) { + hash.id = hash._id; + hash.title = hash.comment_title; + delete hash._id; + delete hash.comment_title; + return hash; + } + } + }) + ``` + + When you call super from your own implementation of `extractSingle`, the + built-in implementation will find the primary record in your normalized + payload and push the remaining records into the store. + + The primary record is the single hash found under `post` or the first + element of the `posts` array. + + The primary record has special meaning when the record is being created + for the first time or updated (`createRecord` or `updateRecord`). In + particular, it will update the properties of the record that was saved. + + @method extractSingle + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @param {String} id + @param {'find'|'createRecord'|'updateRecord'|'deleteRecord'} requestType + @returns {Object} the primary response to the original request + */ + extractSingle: function(store, primaryType, payload, recordId, requestType) { + payload = this.normalizePayload(primaryType, payload); + + var primaryTypeName = primaryType.typeKey, + primaryRecord; + + for (var prop in payload) { + var typeName = this.typeForRoot(prop), + type = store.modelFor(typeName), + isPrimary = type.typeKey === primaryTypeName; + + // legacy support for singular resources + if (isPrimary && Ember.typeOf(payload[prop]) !== "array" ) { + primaryRecord = this.normalize(primaryType, payload[prop], prop); + continue; + } + + /*jshint loopfunc:true*/ + forEach.call(payload[prop], function(hash) { + var typeName = this.typeForRoot(prop), + type = store.modelFor(typeName), + typeSerializer = store.serializerFor(type); + + hash = typeSerializer.normalize(type, hash, prop); + + var isFirstCreatedRecord = isPrimary && !recordId && !primaryRecord, + isUpdatedRecord = isPrimary && coerceId(hash.id) === recordId; + + // find the primary record. + // + // It's either: + // * the record with the same ID as the original request + // * in the case of a newly created record that didn't have an ID, the first + // record in the Array + if (isFirstCreatedRecord || isUpdatedRecord) { + primaryRecord = hash; + } else { + store.push(typeName, hash); + } + }, this); + } + + return primaryRecord; + }, + + /** + Called when the server has returned a payload representing + multiple records, such as in response to a `findAll` or `findQuery`. + + It is your opportunity to clean up the server's response into the normalized + form expected by Ember Data. + + If you want, you can just restructure the top-level of your payload, and + do more fine-grained normalization in the `normalize` method. + + For example, if you have a payload like this in response to a request for + all posts: + + ```js + { + "_embedded": { + "post": [{ + "id": 1, + "title": "Rails is omakase" + }, { + "id": 2, + "title": "The Parley Letter" + }], + "comment": [{ + "_id": 1, + "comment_title": "Rails is unagi" + "post_id": 1 + }, { + "_id": 2, + "comment_title": "Don't tread on me", + "post_id": 2 + }] + } + } + ``` + + You could implement a serializer that looks like this to get your payload + into shape: + + ```js + App.PostSerializer = DS.RESTSerializer.extend({ + // First, restructure the top-level so it's organized by type + // and the comments are listed under a post's `comments` key. + extractArray: function(store, type, payload, id, requestType) { + var posts = payload._embedded.post; + var comments = []; + var postCache = {}; + + posts.forEach(function(post) { + post.comments = []; + postCache[post.id] = post; + }); + + payload._embedded.comment.forEach(function(comment) { + comments.push(comment); + postCache[comment.post_id].comments.push(comment); + delete comment.post_id; + } + + payload = { comments: comments, posts: payload }; + + return this._super(store, type, payload, id, requestType); + }, + + normalizeHash: { + // Next, normalize individual comments, which (after `extract`) + // are now located under `comments` + comments: function(hash) { + hash.id = hash._id; + hash.title = hash.comment_title; + delete hash._id; + delete hash.comment_title; + return hash; + } + } + }) + ``` + + When you call super from your own implementation of `extractArray`, the + built-in implementation will find the primary array in your normalized + payload and push the remaining records into the store. + + The primary array is the array found under `posts`. + + The primary record has special meaning when responding to `findQuery` + or `findHasMany`. In particular, the primary array will become the + list of records in the record array that kicked off the request. + + If your primary array contains secondary (embedded) records of the same type, + you cannot place these into the primary array `posts`. Instead, place the + secondary items into an underscore prefixed property `_posts`, which will + push these items into the store and will not affect the resulting query. + + @method extractArray + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @param {'findAll'|'findMany'|'findHasMany'|'findQuery'} requestType + @returns {Array} The primary array that was returned in response + to the original query. + */ + extractArray: function(store, primaryType, payload) { + payload = this.normalizePayload(primaryType, payload); + + var primaryTypeName = primaryType.typeKey, + primaryArray; + + for (var prop in payload) { + var typeKey = prop, + forcedSecondary = false; + + if (prop.charAt(0) === '_') { + forcedSecondary = true; + typeKey = prop.substr(1); + } + + var typeName = this.typeForRoot(typeKey), + type = store.modelFor(typeName), + typeSerializer = store.serializerFor(type), + isPrimary = (!forcedSecondary && (type.typeKey === primaryTypeName)); + + /*jshint loopfunc:true*/ + var normalizedArray = map.call(payload[prop], function(hash) { + return typeSerializer.normalize(type, hash, prop); + }, this); + + if (isPrimary) { + primaryArray = normalizedArray; + } else { + store.pushMany(typeName, normalizedArray); + } + } + + return primaryArray; + }, + + /** + This method allows you to push a payload containing top-level + collections of records organized per type. + + ```js + { + "posts": [{ + "id": "1", + "title": "Rails is omakase", + "author", "1", + "comments": [ "1" ] + }], + "comments": [{ + "id": "1", + "body": "FIRST" + }], + "users": [{ + "id": "1", + "name": "@d2h" + }] + } + ``` + + It will first normalize the payload, so you can use this to push + in data streaming in from your server structured the same way + that fetches and saves are structured. + + @method pushPayload + @param {DS.Store} store + @param {Object} payload + */ + pushPayload: function(store, payload) { + payload = this.normalizePayload(null, payload); + + for (var prop in payload) { + var typeName = this.typeForRoot(prop), + type = store.modelFor(typeName), + typeSerializer = store.serializerFor(type); + + /*jshint loopfunc:true*/ + var normalizedArray = map.call(Ember.makeArray(payload[prop]), function(hash) { + return typeSerializer.normalize(type, hash, prop); + }, this); + + store.pushMany(typeName, normalizedArray); + } + }, + + /** + You can use this method to normalize the JSON root keys returned + into the model type expected by your store. + + For example, your server may return underscored root keys rather than + the expected camelcased versions. + + ```js + App.ApplicationSerializer = DS.RESTSerializer.extend({ + typeForRoot: function(root) { + var camelized = Ember.String.camelize(root); + return Ember.String.singularize(camelized); + } + }); + ``` + + @method typeForRoot + @param {String} root + @returns {String} the model's typeKey + */ + typeForRoot: function(root) { + return Ember.String.singularize(root); + }, + + // SERIALIZE + + /** + Called when a record is saved in order to convert the + record into JSON. + + By default, it creates a JSON object with a key for + each attribute and belongsTo relationship. + + For example, consider this model: + + ```js + App.Comment = DS.Model.extend({ + title: DS.attr(), + body: DS.attr(), + + author: DS.belongsTo('user') + }); + ``` + + The default serialization would create a JSON object like: + + ```js + { + "title": "Rails is unagi", + "body": "Rails? Omakase? O_O", + "author": 12 + } + ``` + + By default, attributes are passed through as-is, unless + you specified an attribute type (`DS.attr('date')`). If + you specify a transform, the JavaScript value will be + serialized when inserted into the JSON hash. + + By default, belongs-to relationships are converted into + IDs when inserted into the JSON hash. + + ## IDs + + `serialize` takes an options hash with a single option: + `includeId`. If this option is `true`, `serialize` will, + by default include the ID in the JSON object it builds. + + The adapter passes in `includeId: true` when serializing + a record for `createRecord`, but not for `updateRecord`. + + ## Customization + + Your server may expect a different JSON format than the + built-in serialization format. + + In that case, you can implement `serialize` yourself and + return a JSON hash of your choosing. + + ```js + App.PostSerializer = DS.RESTSerializer.extend({ + serialize: function(post, options) { + var json = { + POST_TTL: post.get('title'), + POST_BDY: post.get('body'), + POST_CMS: post.get('comments').mapProperty('id') + } + + if (options.includeId) { + json.POST_ID_ = post.get('id'); + } + + return json; + } + }); + ``` + + ## Customizing an App-Wide Serializer + + If you want to define a serializer for your entire + application, you'll probably want to use `eachAttribute` + and `eachRelationship` on the record. + + ```js + App.ApplicationSerializer = DS.RESTSerializer.extend({ + serialize: function(record, options) { + var json = {}; + + record.eachAttribute(function(name) { + json[serverAttributeName(name)] = record.get(name); + }) + + record.eachRelationship(function(name, relationship) { + if (relationship.kind === 'hasMany') { + json[serverHasManyName(name)] = record.get(name).mapBy('id'); + } + }); + + if (options.includeId) { + json.ID_ = record.get('id'); + } + + return json; + } + }); + + function serverAttributeName(attribute) { + return attribute.underscore().toUpperCase(); + } + + function serverHasManyName(name) { + return serverAttributeName(name.singularize()) + "_IDS"; + } + ``` + + This serializer will generate JSON that looks like this: + + ```js + { + "TITLE": "Rails is omakase", + "BODY": "Yep. Omakase.", + "COMMENT_IDS": [ 1, 2, 3 ] + } + ``` + + ## Tweaking the Default JSON + + If you just want to do some small tweaks on the default JSON, + you can call super first and make the tweaks on the returned + JSON. + + ```js + App.PostSerializer = DS.RESTSerializer.extend({ + serialize: function(record, options) { + var json = this._super(record, options); + + json.subject = json.title; + delete json.title; + + return json; + } + }); + ``` + + @method serialize + @param record + @param options + */ + serialize: function(record, options) { + return this._super.apply(this, arguments); + }, + + /** + You can use this method to customize the root keys serialized into the JSON. + By default the REST Serializer sends camelized root keys. + For example, your server may expect underscored root objects. + + ```js + App.ApplicationSerializer = DS.RESTSerializer.extend({ + serializeIntoHash: function(data, type, record, options) { + var root = Ember.String.decamelize(type.typeKey); + data[root] = this.serialize(record, options); + } + }); + ``` + + @method serializeIntoHash + @param {Object} hash + @param {subclass of DS.Model} type + @param {DS.Model} record + @param {Object} options + */ + serializeIntoHash: function(hash, type, record, options) { + var root = Ember.String.camelize(type.typeKey); + hash[root] = this.serialize(record, options); + }, + + /** + You can use this method to customize how polymorphic objects are serialized. + By default the JSON Serializer creates the key by appending `Type` to + the attribute and value from the model's camelcased model name. + + @method serializePolymorphicType + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ serializePolymorphicType: function(record, json, relationship) { var key = relationship.key, belongsTo = get(record, key); key = this.keyForAttribute ? this.keyForAttribute(key) : key; - json[key + "_type"] = belongsTo.constructor.typeKey; + json[key + "Type"] = Ember.String.camelize(belongsTo.constructor.typeKey); } }); - ``` - @method serializePolymorphicType - @param {DS.Model} record - @param {Object} json - @param {Object} relationship - */ - serializePolymorphicType: Ember.K, + __exports__["default"] = RESTSerializer; + }); +define("ember-data/lib/system/adapter", + ["exports"], + function(__exports__) { + "use strict"; + /** + @module ember-data + */ - // EXTRACT + var get = Ember.get, set = Ember.set; + var map = Ember.ArrayPolyfills.map; - /** - The `extract` method is used to deserialize payload data from the - server. By default the `JSONSerializer` does not push the records - into the store. However records that subclass `JSONSerializer` - such as the `RESTSerializer` may push records into the store as - part of the extract call. + var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - This method deletegates to a more specific extract method based on - the `requestType`. + /** + A `DS.InvalidError` is used by an adapter to signal the external API + was unable to process a request because the content was not + semantically correct or meaningful per the API. Usually this means a + record failed some form of server side validation. When a promise + from an adapter is rejected with a `DS.InvalidError` the record will + transition to the `invalid` state and the errors will be set to the + `errors` property on the record. - Example + Example - ```javascript - var get = Ember.get; - socket.on('message', function(message) { - var modelName = message.model; - var data = message.data; - var type = store.modelFor(modelName); - var serializer = store.serializerFor(type.typeKey); - var record = serializer.extract(store, type, data, get(data, 'id'), 'single'); - store.push(modelName, record); - }); - ``` + ```javascript + App.ApplicationAdapter = DS.RESTAdapter.extend({ + ajaxError: function(jqXHR) { + var error = this._super(jqXHR); - @method extract - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {Object} payload - @param {String or Number} id - @param {String} requestType - @return {Object} json The deserialized payload - */ - extract: function(store, type, payload, id, requestType) { - this.extractMeta(store, type, payload); + if (jqXHR && jqXHR.status === 422) { + var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"]; + return new DS.InvalidError(jsonErrors); + } else { + return error; + } + } + }); + ``` - var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1); - return this[specificExtract](store, type, payload, id, requestType); - }, + The `DS.InvalidError` must be constructed with a single object whose + keys are the invalid model properties, and whose values are the + corresponding error messages. For example: - /** - `extractFindAll` is a hook into the extract method used when a - call is made to `DS.Store#findAll`. By default this method is an - alias for [extractArray](#method_extractArray). + ```javascript + return new DS.InvalidError({ + length: 'Must be less than 15', + name: 'Must not be blank + }); + ``` - @method extractFindAll - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {Object} payload - @return {Array} array An array of deserialized objects - */ - extractFindAll: aliasMethod('extractArray'), - /** - `extractFindQuery` is a hook into the extract method used when a - call is made to `DS.Store#findQuery`. By default this method is an - alias for [extractArray](#method_extractArray). + @class InvalidError + @namespace DS + */ + var InvalidError = function(errors) { + var tmp = Error.prototype.constructor.call(this, "The backend rejected the commit because it was invalid: " + Ember.inspect(errors)); + this.errors = errors; - @method extractFindQuery - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {Object} payload - @return {Array} array An array of deserialized objects - */ - extractFindQuery: aliasMethod('extractArray'), - /** - `extractFindMany` is a hook into the extract method used when a - call is made to `DS.Store#findMany`. By default this method is - alias for [extractArray](#method_extractArray). + for (var i=0, l=errorProps.length; i self.attributeLimit) { return false; } - var desc = capitalize(underscore(name).replace('_', ' ')); - columns.push({ name: name, desc: desc }); - }); - return columns; - }, - - getRecords: function(type) { - return this.get('store').all(type); - }, - - getRecordColumnValues: function(record) { - var self = this, count = 0, - columnValues = { id: get(record, 'id') }; - - record.eachAttribute(function(key) { - if (count++ > self.attributeLimit) { - return false; - } - var value = get(record, key); - columnValues[key] = value; - }); - return columnValues; - }, - - getRecordKeywords: function(record) { - var keywords = [], keys = Ember.A(['id']); - record.eachAttribute(function(key) { - keys.push(key); - }); - keys.forEach(function(key) { - keywords.push(get(record, key)); - }); - return keywords; - }, - - getRecordFilterValues: function(record) { - return { - isNew: record.get('isNew'), - isModified: record.get('isDirty') && !record.get('isNew'), - isClean: !record.get('isDirty') }; - }, - getRecordColor: function(record) { - var color = 'black'; - if (record.get('isNew')) { - color = 'green'; - } else if (record.get('isDirty')) { - color = 'blue'; - } - return color; - }, + RelationshipChange.createChange = function(firstRecord, secondRecord, store, options){ + // Get the type of the child based on the child's client ID + var firstRecordType = firstRecord.constructor, changeType; + changeType = RelationshipChange.determineRelationshipType(firstRecordType, options); + if (changeType === "oneToMany"){ + return OneToManyChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "manyToOne"){ + return OneToManyChange.createChange(secondRecord, firstRecord, store, options); + } + else if (changeType === "oneToNone"){ + return OneToNoneChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "manyToNone"){ + return ManyToNoneChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "oneToOne"){ + return OneToOneChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "manyToMany"){ + return ManyToManyChange.createChange(firstRecord, secondRecord, store, options); + } + }; - observeRecord: function(record, recordUpdated) { - var releaseMethods = Ember.A(), self = this, - keysToObserve = Ember.A(['id', 'isNew', 'isDirty']); - - record.eachAttribute(function(key) { - keysToObserve.push(key); - }); - - keysToObserve.forEach(function(key) { - var handler = function() { - recordUpdated(self.wrapRecord(record)); - }; - Ember.addObserver(record, key, handler); - releaseMethods.push(function() { - Ember.removeObserver(record, key, handler); + OneToNoneChange.createChange = function(childRecord, parentRecord, store, options) { + var key = options.key; + var change = RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + store: store, + changeType: options.changeType, + firstRecordName: key, + firstRecordKind: "belongsTo" }); - }); - var release = function() { - releaseMethods.forEach(function(fn) { fn(); } ); + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + + return change; }; - return release; - } + ManyToNoneChange.createChange = function(childRecord, parentRecord, store, options) { + var key = options.key; + var change = RelationshipChange._createChange({ + parentRecord: childRecord, + childRecord: parentRecord, + secondRecord: childRecord, + store: store, + changeType: options.changeType, + secondRecordName: options.key, + secondRecordKind: "hasMany" + }); -}); - -})(); + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + return change; + }; + ManyToManyChange.createChange = function(childRecord, parentRecord, store, options) { + // 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. + var key = options.key; -(function() { -/** - The `DS.Transform` class is used to serialize and deserialize model - attributes when they are saved or loaded from an - adapter. Subclassing `DS.Transform` is useful for creating custom - attributes. All subclasses of `DS.Transform` must implement a - `serialize` and a `deserialize` method. + var change = RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + secondRecord: parentRecord, + firstRecordKind: "hasMany", + secondRecordKind: "hasMany", + store: store, + changeType: options.changeType, + firstRecordName: key + }); - Example + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); - ```javascript - App.RawTransform = DS.Transform.extend({ - deserialize: function(serialized) { - return serialized; - }, - serialize: function(deserialized) { - return deserialized; + + return change; + }; + + OneToOneChange.createChange = function(childRecord, parentRecord, store, options) { + var 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 = options.parentType.inverseFor(options.key).name; + } 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 = RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + secondRecord: parentRecord, + firstRecordKind: "belongsTo", + secondRecordKind: "belongsTo", + store: store, + changeType: options.changeType, + firstRecordName: key + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + + + return change; + }; + + OneToOneChange.maintainInvariant = function(options, store, childRecord, key){ + if (options.changeType === "add" && store.recordIsMaterialized(childRecord)) { + var oldParent = get(childRecord, key); + if (oldParent){ + var correspondingChange = OneToOneChange.createChange(childRecord, oldParent, store, { + parentType: options.parentType, + hasManyName: options.hasManyName, + changeType: "remove", + key: options.key + }); + store.addRelationshipChangeFor(childRecord, key, options.parentRecord , null, correspondingChange); + correspondingChange.sync(); + } + } + }; + + OneToManyChange.createChange = function(childRecord, parentRecord, store, options) { + var 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 = options.parentType.inverseFor(options.key).name; + OneToManyChange.maintainInvariant( options, store, childRecord, 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 = RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + secondRecord: parentRecord, + firstRecordKind: "belongsTo", + secondRecordKind: "hasMany", + store: store, + changeType: options.changeType, + firstRecordName: key + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, change.getSecondRecordName(), change); + + + return change; + }; + + + OneToManyChange.maintainInvariant = function(options, store, childRecord, key){ + if (options.changeType === "add" && childRecord) { + var oldParent = get(childRecord, key); + if (oldParent){ + var correspondingChange = OneToManyChange.createChange(childRecord, oldParent, store, { + parentType: options.parentType, + hasManyName: options.hasManyName, + changeType: "remove", + key: options.key + }); + store.addRelationshipChangeFor(childRecord, key, options.parentRecord, correspondingChange.getSecondRecordName(), correspondingChange); + correspondingChange.sync(); + } + } + }; + + /** + @class RelationshipChange + @namespace DS + */ + RelationshipChange.prototype = { + + getSecondRecordName: function() { + var name = this.secondRecordName, parent; + + if (!name) { + parent = this.secondRecord; + if (!parent) { return; } + + var childType = this.firstRecord.constructor; + var inverse = childType.inverseFor(this.firstRecordName); + this.secondRecordName = inverse.name; + } + + return this.secondRecordName; + }, + + /** + Get the name of the relationship on the belongsTo side. + + @method getFirstRecordName + @return {String} + */ + getFirstRecordName: function() { + var name = this.firstRecordName; + return name; + }, + + /** + @method destroy + @private + */ + destroy: function() { + var childRecord = this.childRecord, + belongsToName = this.getFirstRecordName(), + hasManyName = this.getSecondRecordName(), + store = this.store; + + store.removeRelationshipChangeFor(childRecord, belongsToName, this.parentRecord, hasManyName, this.changeType); + }, + + getSecondRecord: function(){ + return this.secondRecord; + }, + + /** + @method getFirstRecord + @private + */ + getFirstRecord: function() { + return this.firstRecord; + }, + + coalesce: function(){ + var relationshipPairs = this.store.relationshipChangePairsFor(this.firstRecord); + forEach(relationshipPairs, function(pair){ + var addedChange = pair["add"]; + var removedChange = pair["remove"]; + if(addedChange && removedChange) { + addedChange.destroy(); + removedChange.destroy(); + } + }); + } + }; + + RelationshipChangeAdd.prototype = Ember.create(RelationshipChange.create({})); + RelationshipChangeRemove.prototype = Ember.create(RelationshipChange.create({})); + + // the object is a value, and not a promise + function isValue(object) { + return typeof object === 'object' && (!object.then || typeof object.then !== 'function'); } + + RelationshipChangeAdd.prototype.changeType = "add"; + 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); + + if (secondRecord instanceof Model && firstRecord instanceof Model) { + if(this.secondRecordKind === "belongsTo"){ + secondRecord.suspendRelationshipObservers(function(){ + set(secondRecord, secondRecordName, firstRecord); + }); + + } + else if(this.secondRecordKind === "hasMany"){ + secondRecord.suspendRelationshipObservers(function(){ + var relationship = get(secondRecord, secondRecordName); + if (isValue(relationship)) { relationship.addObject(firstRecord); } + }); + } + } + + if (firstRecord instanceof Model && secondRecord instanceof Model && get(firstRecord, firstRecordName) !== secondRecord) { + if(this.firstRecordKind === "belongsTo"){ + firstRecord.suspendRelationshipObservers(function(){ + set(firstRecord, firstRecordName, secondRecord); + }); + } + else if(this.firstRecordKind === "hasMany"){ + firstRecord.suspendRelationshipObservers(function(){ + var relationship = get(firstRecord, firstRecordName); + if (isValue(relationship)) { relationship.addObject(secondRecord); } + }); + } + } + + this.coalesce(); + }; + + RelationshipChangeRemove.prototype.changeType = "remove"; + 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); + + if (secondRecord instanceof Model && firstRecord instanceof Model) { + if(this.secondRecordKind === "belongsTo"){ + secondRecord.suspendRelationshipObservers(function(){ + set(secondRecord, secondRecordName, null); + }); + } + else if(this.secondRecordKind === "hasMany"){ + secondRecord.suspendRelationshipObservers(function(){ + var relationship = get(secondRecord, secondRecordName); + if (isValue(relationship)) { relationship.removeObject(firstRecord); } + }); + } + } + + if (firstRecord instanceof Model && get(firstRecord, firstRecordName)) { + if(this.firstRecordKind === "belongsTo"){ + firstRecord.suspendRelationshipObservers(function(){ + set(firstRecord, firstRecordName, null); + }); + } + else if(this.firstRecordKind === "hasMany"){ + firstRecord.suspendRelationshipObservers(function(){ + var relationship = get(firstRecord, firstRecordName); + if (isValue(relationship)) { relationship.removeObject(secondRecord); } + }); + } + } + + this.coalesce(); + }; + + __exports__.RelationshipChange = RelationshipChange; + __exports__.RelationshipChangeAdd = RelationshipChangeAdd; + __exports__.RelationshipChangeRemove = RelationshipChangeRemove; + __exports__.OneToManyChange = OneToManyChange; + __exports__.ManyToNoneChange = ManyToNoneChange; + __exports__.OneToOneChange = OneToOneChange; + __exports__.ManyToManyChange = ManyToManyChange; }); - ``` +define("ember-data/lib/system/container_proxy", + ["exports"], + function(__exports__) { + "use strict"; + /** + This is used internally to enable deprecation of container paths and provide + a decent message to the user indicating how to fix the issue. - Usage + @class ContainerProxy + @namespace DS + @private + */ + var ContainerProxy = function (container){ + this.container = container; + }; - ```javascript - var attr = DS.attr; - App.Requirement = DS.Model.extend({ - name: attr('string'), - optionsArray: attr('raw') + ContainerProxy.prototype.aliasedFactory = function(path, preLookup) { + var _this = this; + + return {create: function(){ + if (preLookup) { preLookup(); } + + return _this.container.lookup(path); + }}; + }; + + ContainerProxy.prototype.registerAlias = function(source, dest, preLookup) { + var factory = this.aliasedFactory(dest, preLookup); + + return this.container.register(source, factory); + }; + + ContainerProxy.prototype.registerDeprecation = function(deprecated, valid) { + var preLookupCallback = function(){ + Ember.deprecate("You tried to look up '" + deprecated + "', " + + "but this has been deprecated in favor of '" + valid + "'.", false); + }; + + return this.registerAlias(deprecated, valid, preLookupCallback); + }; + + ContainerProxy.prototype.registerDeprecations = function(proxyPairs) { + for (var i = proxyPairs.length; i > 0; i--) { + var proxyPair = proxyPairs[i - 1], + deprecated = proxyPair['deprecated'], + valid = proxyPair['valid']; + + this.registerDeprecation(deprecated, valid); + } + }; + + __exports__["default"] = ContainerProxy; }); - ``` +define("ember-data/lib/system/debug", + ["./debug/debug_info","./debug/debug_adapter","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + /** + @module ember-data + */ - @class Transform - @namespace DS - */ -DS.Transform = Ember.Object.extend({ - /** - When given a deserialized value from a record attribute this - method must return the serialized value. + var DebugAdapter = __dependency2__["default"]; - Example + __exports__["default"] = DebugAdapter; + }); +define("ember-data/lib/system/debug/debug_adapter", + ["../model","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /** + @module ember-data + */ + var Model = __dependency1__.Model; + var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.String.underscore; - ```javascript - serialize: function(deserialized) { - return Ember.isEmpty(deserialized) ? null : Number(deserialized); + /** + Extend `Ember.DataAdapter` with ED specific code. + + @class DebugAdapter + @namespace DS + @extends Ember.DataAdapter + @private + */ + var DebugAdapter = Ember.DataAdapter.extend({ + getFilters: function() { + return [ + { name: 'isNew', desc: 'New' }, + { name: 'isModified', desc: 'Modified' }, + { name: 'isClean', desc: 'Clean' } + ]; + }, + + detect: function(klass) { + return klass !== Model && Model.detect(klass); + }, + + columnsForType: function(type) { + var columns = [{ name: 'id', desc: 'Id' }], count = 0, self = this; + get(type, 'attributes').forEach(function(name, meta) { + if (count++ > self.attributeLimit) { return false; } + var desc = capitalize(underscore(name).replace('_', ' ')); + columns.push({ name: name, desc: desc }); + }); + return columns; + }, + + getRecords: function(type) { + return this.get('store').all(type); + }, + + getRecordColumnValues: function(record) { + var self = this, count = 0, + columnValues = { id: get(record, 'id') }; + + record.eachAttribute(function(key) { + if (count++ > self.attributeLimit) { + return false; + } + var value = get(record, key); + columnValues[key] = value; + }); + return columnValues; + }, + + getRecordKeywords: function(record) { + var keywords = [], keys = Ember.A(['id']); + record.eachAttribute(function(key) { + keys.push(key); + }); + keys.forEach(function(key) { + keywords.push(get(record, key)); + }); + return keywords; + }, + + getRecordFilterValues: function(record) { + return { + isNew: record.get('isNew'), + isModified: record.get('isDirty') && !record.get('isNew'), + isClean: !record.get('isDirty') + }; + }, + + getRecordColor: function(record) { + var color = 'black'; + if (record.get('isNew')) { + color = 'green'; + } else if (record.get('isDirty')) { + color = 'blue'; + } + return color; + }, + + observeRecord: function(record, recordUpdated) { + var releaseMethods = Ember.A(), self = this, + keysToObserve = Ember.A(['id', 'isNew', 'isDirty']); + + record.eachAttribute(function(key) { + keysToObserve.push(key); + }); + + keysToObserve.forEach(function(key) { + var handler = function() { + recordUpdated(self.wrapRecord(record)); + }; + Ember.addObserver(record, key, handler); + releaseMethods.push(function() { + Ember.removeObserver(record, key, handler); + }); + }); + + var release = function() { + releaseMethods.forEach(function(fn) { fn(); } ); + }; + + return release; + } + + }); + + __exports__["default"] = DebugAdapter; + }); +define("ember-data/lib/system/debug/debug_info", + ["../model","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Model = __dependency1__.Model; + + Model.reopen({ + + /** + Provides info about the model for debugging purposes + by grouping the properties into more semantic groups. + + Meant to be used by debugging tools such as the Chrome Ember Extension. + + - Groups all attributes in "Attributes" group. + - Groups all belongsTo relationships in "Belongs To" group. + - Groups all hasMany relationships in "Has Many" group. + - Groups all flags in "Flags" group. + - Flags relationship CPs as expensive properties. + + @method _debugInfo + @for DS.Model + @private + */ + _debugInfo: function() { + var attributes = ['id'], + relationships = { belongsTo: [], hasMany: [] }, + expensiveProperties = []; + + this.eachAttribute(function(name, meta) { + attributes.push(name); + }, this); + + this.eachRelationship(function(name, relationship) { + relationships[relationship.kind].push(name); + expensiveProperties.push(name); + }); + + var groups = [ + { + name: 'Attributes', + properties: attributes, + expand: true + }, + { + name: 'Belongs To', + properties: relationships.belongsTo, + expand: true + }, + { + name: 'Has Many', + properties: relationships.hasMany, + expand: true + }, + { + name: 'Flags', + properties: ['isLoaded', 'isDirty', 'isSaving', 'isDeleted', 'isError', 'isNew', 'isValid'] + } + ]; + + return { + propertyInfo: { + // include all other mixins / properties (not just the grouped ones) + includeOtherProperties: true, + groups: groups, + // don't pre-calculate unless cached + expensiveProperties: expensiveProperties + } + }; + } + }); + + __exports__["default"] = Model; + }); +define("ember-data/lib/system/model", + ["./model/model","./model/attributes","./model/states","./model/errors","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + /** + @module ember-data + */ + + var Model = __dependency1__["default"]; + var attr = __dependency2__["default"]; + var RootState = __dependency3__["default"]; + var Errors = __dependency4__["default"]; + + __exports__.Model = Model; + __exports__.RootState = RootState; + __exports__.attr = attr; + __exports__.Errors = Errors; + }); +define("ember-data/lib/system/model/attributes", + ["./model","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Model = __dependency1__["default"]; + + /** + @module ember-data + */ + + var get = Ember.get; + + /** + @class Model + @namespace DS + */ + Model.reopenClass({ + /** + A map whose keys are the attributes of the model (properties + described by DS.attr) and whose values are the meta object for the + property. + + Example + + ```javascript + + App.Person = DS.Model.extend({ + firstName: attr('string'), + lastName: attr('string'), + birthday: attr('date') + }); + + var attributes = Ember.get(App.Person, 'attributes') + + attributes.forEach(function(name, meta) { + console.log(name, meta); + }); + + // prints: + // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"} + // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"} + // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"} + ``` + + @property attributes + @static + @type {Ember.Map} + @readOnly + */ + 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; + }), + + /** + A map whose keys are the attributes of the model (properties + described by DS.attr) and whose values are type of transformation + applied to each attribute. This map does not include any + attributes that do not have an transformation type. + + Example + + ```javascript + App.Person = DS.Model.extend({ + firstName: attr(), + lastName: attr('string'), + birthday: attr('date') + }); + + var transformedAttributes = Ember.get(App.Person, 'transformedAttributes') + + transformedAttributes.forEach(function(field, type) { + console.log(field, type); + }); + + // prints: + // lastName string + // birthday date + ``` + + @property transformedAttributes + @static + @type {Ember.Map} + @readOnly + */ + transformedAttributes: Ember.computed(function() { + var map = Ember.Map.create(); + + this.eachAttribute(function(key, meta) { + if (meta.type) { + map.set(key, meta.type); + } + }); + + return map; + }), + + /** + Iterates through the attributes of the model, calling the passed function on each + attribute. + + The callback method you provide should have the following signature (all + parameters are optional): + + ```javascript + function(name, meta); + ``` + + - `name` the name of the current property in the iteration + - `meta` the meta object for the attribute property in the iteration + + Note that in addition to a callback, you can also pass an optional target + object that will be set as `this` on the context. + + Example + + ```javascript + App.Person = DS.Model.extend({ + firstName: attr('string'), + lastName: attr('string'), + birthday: attr('date') + }); + + App.Person.eachAttribute(function(name, meta) { + console.log(name, meta); + }); + + // prints: + // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"} + // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"} + // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"} + ``` + + @method eachAttribute + @param {Function} callback The callback to execute + @param {Object} [target] The target object to use + @static + */ + eachAttribute: function(callback, binding) { + get(this, 'attributes').forEach(function(name, meta) { + callback.call(binding, name, meta); + }, binding); + }, + + /** + Iterates through the transformedAttributes of the model, calling + the passed function on each attribute. Note the callback will not be + called for any attributes that do not have an transformation type. + + The callback method you provide should have the following signature (all + parameters are optional): + + ```javascript + function(name, type); + ``` + + - `name` the name of the current property in the iteration + - `type` a string containing the name of the type of transformed + applied to the attribute + + Note that in addition to a callback, you can also pass an optional target + object that will be set as `this` on the context. + + Example + + ```javascript + App.Person = DS.Model.extend({ + firstName: attr(), + lastName: attr('string'), + birthday: attr('date') + }); + + App.Person.eachTransformedAttribute(function(name, type) { + console.log(name, type); + }); + + // prints: + // lastName string + // birthday date + ``` + + @method eachTransformedAttribute + @param {Function} callback The callback to execute + @param {Object} [target] The target object to use + @static + */ + eachTransformedAttribute: function(callback, binding) { + get(this, 'transformedAttributes').forEach(function(name, type) { + callback.call(binding, name, type); + }); + } + }); + + + Model.reopen({ + eachAttribute: function(callback, binding) { + this.constructor.eachAttribute(callback, binding); + } + }); + + function getDefaultValue(record, options, key) { + if (typeof options.defaultValue === "function") { + return options.defaultValue.apply(null, arguments); + } else { + return options.defaultValue; + } } - ``` - @method serialize - @param deserialized The deserialized value - @return The serialized value - */ - serialize: Ember.required(), - - /** - When given a serialize value from a JSON object this method must - return the deserialized value for the record attribute. - - Example - - ```javascript - deserialize: function(serialized) { - return empty(serialized) ? null : Number(serialized); + function hasValue(record, key) { + return record._attributes.hasOwnProperty(key) || + record._inFlightAttributes.hasOwnProperty(key) || + record._data.hasOwnProperty(key); } - ``` - @method deserialized - @param serialized The serialized value - @return The deserialized value - */ - deserialize: Ember.required() + function getValue(record, key) { + if (record._attributes.hasOwnProperty(key)) { + return record._attributes[key]; + } else if (record._inFlightAttributes.hasOwnProperty(key)) { + return record._inFlightAttributes[key]; + } else { + return record._data[key]; + } + } -}); + /** + `DS.attr` defines an attribute on a [DS.Model](/api/data/classes/DS.Model.html). + By default, attributes are passed through as-is, however you can specify an + optional type to have the value automatically transformed. + Ember Data ships with four basic transform types: `string`, `number`, + `boolean` and `date`. You can define your own transforms by subclassing + [DS.Transform](/api/data/classes/DS.Transform.html). -})(); + Note that you cannot use `attr` to define an attribute of `id`. + `DS.attr` takes an optional hash as a second parameter, currently + supported options are: + - `defaultValue`: Pass a string or a function to be called to set the attribute + to a default value if none is supplied. -(function() { + Example -/** - The `DS.BooleanTransform` class is used to serialize and deserialize - boolean attributes on Ember Data record objects. This transform is - used when `boolean` is passed as the type parameter to the - [DS.attr](../../data#method_attr) function. + ```javascript + var attr = DS.attr; - Usage + App.User = DS.Model.extend({ + username: attr('string'), + email: attr('string'), + verified: attr('boolean', {defaultValue: false}) + }); + ``` - ```javascript - var attr = DS.attr; - App.User = DS.Model.extend({ - isAdmin: attr('boolean'), - name: attr('string'), - email: attr('string') + @namespace + @method attr + @for DS + @param {String} type the attribute type + @param {Object} options a hash of options + @return {Attribute} + */ + + function attr(type, options) { + options = options || {}; + + var meta = { + type: type, + isAttribute: true, + options: options + }; + + return Ember.computed('data', function(key, value) { + 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'); + var oldValue = getValue(this, key); + + if (value !== oldValue) { + // Add the new value to the changed attributes hash; it will get deleted by + // the 'didSetProperty' handler if it is no different from the original value + this._attributes[key] = value; + + this.send('didSetProperty', { + name: key, + oldValue: oldValue, + originalValue: this._data[key], + value: value + }); + } + + return value; + } else if (hasValue(this, key)) { + return getValue(this, key); + } else { + return getDefaultValue(this, options, key); + } + + // `data` is never set directly. However, it may be + // invalidated from the state manager's setData + // event. + }).meta(meta); + } + + __exports__["default"] = attr; }); - ``` +define("ember-data/lib/system/model/errors", + ["exports"], + function(__exports__) { + "use strict"; + var get = Ember.get, isEmpty = Ember.isEmpty; - @class BooleanTransform - @extends DS.Transform - @namespace DS - */ -DS.BooleanTransform = DS.Transform.extend({ - deserialize: function(serialized) { - var type = typeof serialized; + /** + @module ember-data + */ + + /** + Holds validation errors for a given record organized by attribute names. + + @class Errors + @namespace DS + @extends Ember.Object + @uses Ember.Enumerable + @uses Ember.Evented + */ + var Errors = Ember.Object.extend(Ember.Enumerable, Ember.Evented, { + /** + Register with target handler + + @method registerHandlers + @param {Object} target + @param {Function} becameInvalid + @param {Function} becameValid + */ + registerHandlers: function(target, becameInvalid, becameValid) { + this.on('becameInvalid', target, becameInvalid); + this.on('becameValid', target, becameValid); + }, + + /** + @property errorsByAttributeName + @type {Ember.MapWithDefault} + @private + */ + errorsByAttributeName: Ember.reduceComputed("content", { + initialValue: function() { + return Ember.MapWithDefault.create({ + defaultValue: function() { + return Ember.A(); + } + }); + }, + + addedItem: function(errors, error) { + errors.get(error.attribute).pushObject(error); + + return errors; + }, + + removedItem: function(errors, error) { + errors.get(error.attribute).removeObject(error); + + return errors; + } + }), + + /** + Returns errors for a given attribute + + @method errorsFor + @param {String} attribute + @returns {Array} + */ + errorsFor: function(attribute) { + return get(this, 'errorsByAttributeName').get(attribute); + }, + + /** + */ + messages: Ember.computed.mapBy('content', 'message'), + + /** + @property content + @type {Array} + @private + */ + content: Ember.computed(function() { + return Ember.A(); + }), + + /** + @method unknownProperty + @private + */ + unknownProperty: function(attribute) { + var errors = this.errorsFor(attribute); + if (isEmpty(errors)) { return null; } + return errors; + }, + + /** + @method nextObject + @private + */ + nextObject: function(index, previousObject, context) { + return get(this, 'content').objectAt(index); + }, + + /** + Total number of errors. + + @property length + @type {Number} + @readOnly + */ + length: Ember.computed.oneWay('content.length').readOnly(), + + /** + @property isEmpty + @type {Boolean} + @readOnly + */ + isEmpty: Ember.computed.not('length').readOnly(), + + /** + Adds error messages to a given attribute and sends + `becameInvalid` event to the record. + + @method add + @param {String} attribute + @param {Array|String} messages + */ + add: function(attribute, messages) { + var wasEmpty = get(this, 'isEmpty'); + + messages = this._findOrCreateMessages(attribute, messages); + get(this, 'content').addObjects(messages); + + this.notifyPropertyChange(attribute); + this.enumerableContentDidChange(); + + if (wasEmpty && !get(this, 'isEmpty')) { + this.trigger('becameInvalid'); + } + }, + + /** + @method _findOrCreateMessages + @private + */ + _findOrCreateMessages: function(attribute, messages) { + var errors = this.errorsFor(attribute); + + return Ember.makeArray(messages).map(function(message) { + return errors.findBy('message', message) || { + attribute: attribute, + message: message + }; + }); + }, + + /** + Removes all error messages from the given attribute and sends + `becameValid` event to the record if there no more errors left. + + @method remove + @param {String} attribute + */ + remove: function(attribute) { + if (get(this, 'isEmpty')) { return; } + + var content = get(this, 'content').rejectBy('attribute', attribute); + get(this, 'content').setObjects(content); + + this.notifyPropertyChange(attribute); + this.enumerableContentDidChange(); + + if (get(this, 'isEmpty')) { + this.trigger('becameValid'); + } + }, + + /** + Removes all error messages and sends `becameValid` event + to the record. + + @method clear + */ + clear: function() { + if (get(this, 'isEmpty')) { return; } + + get(this, 'content').clear(); + this.enumerableContentDidChange(); + + this.trigger('becameValid'); + }, + + /** + Checks if there is error messages for the given attribute. + + @method has + @param {String} attribute + @returns {Boolean} true if there some errors on given attribute + */ + has: function(attribute) { + return !isEmpty(this.errorsFor(attribute)); + } + }); + + __exports__["default"] = Errors; + }); +define("ember-data/lib/system/model/model", + ["./states","./errors","../store","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var RootState = __dependency1__["default"]; + var Errors = __dependency2__["default"]; + var PromiseObject = __dependency3__.PromiseObject; + /** + @module ember-data + */ + + var get = Ember.get, set = Ember.set, + merge = Ember.merge, + Promise = Ember.RSVP.Promise; + + var JSONSerializer; + var retrieveFromCurrentState = Ember.computed('currentState', function(key, value) { + return get(get(this, 'currentState'), key); + }).readOnly(); + + /** + + The model class that all Ember Data records descend from. + + @class Model + @namespace DS + @extends Ember.Object + @uses Ember.Evented + */ + var Model = Ember.Object.extend(Ember.Evented, { + _recordArrays: undefined, + _relationships: undefined, + _loadingRecordArrays: undefined, + /** + If this property is `true` the record is in the `empty` + state. Empty is the first state all records enter after they have + been created. Most records created by the store will quickly + transition to the `loading` state if data needs to be fetched from + the server or the `created` state if the record is created on the + client. A record can also enter the empty state if the adapter is + unable to locate the record. + + @property isEmpty + @type {Boolean} + @readOnly + */ + isEmpty: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `loading` state. A + record enters this state when the store asks the adapter for its + data. It remains in this state until the adapter provides the + requested data. + + @property isLoading + @type {Boolean} + @readOnly + */ + isLoading: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `loaded` state. A + record enters this state when its data is populated. Most of a + record's lifecycle is spent inside substates of the `loaded` + state. + + Example + + ```javascript + var record = store.createRecord('model'); + record.get('isLoaded'); // true + + store.find('model', 1).then(function(model) { + model.get('isLoaded'); // true + }); + ``` + + @property isLoaded + @type {Boolean} + @readOnly + */ + isLoaded: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `dirty` state. 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. + + Example + + ```javascript + var record = store.createRecord('model'); + record.get('isDirty'); // true + + store.find('model', 1).then(function(model) { + model.get('isDirty'); // false + model.set('foo', 'some value'); + model.get('isDirty'); // true + }); + ``` + + @property isDirty + @type {Boolean} + @readOnly + */ + isDirty: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `saving` state. A + record enters the saving state when `save` is called, but the + adapter has not yet acknowledged that the changes have been + persisted to the backend. + + Example + + ```javascript + var record = store.createRecord('model'); + record.get('isSaving'); // false + var promise = record.save(); + record.get('isSaving'); // true + promise.then(function() { + record.get('isSaving'); // false + }); + ``` + + @property isSaving + @type {Boolean} + @readOnly + */ + isSaving: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `deleted` state + and has been 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. + + Example + + ```javascript + var record = store.createRecord('model'); + record.get('isDeleted'); // false + record.deleteRecord(); + + // Locally deleted + record.get('isDeleted'); // true + record.get('isDirty'); // true + record.get('isSaving'); // false + + // Persisting the deletion + var promise = record.save(); + record.get('isDeleted'); // true + record.get('isSaving'); // true + + // Deletion Persisted + promise.then(function() { + record.get('isDeleted'); // true + record.get('isSaving'); // false + record.get('isDirty'); // false + }); + ``` + + @property isDeleted + @type {Boolean} + @readOnly + */ + isDeleted: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `new` state. A + record will be in the `new` state when it has been created on the + client and the adapter has not yet report that it was successfully + saved. + + Example + + ```javascript + var record = store.createRecord('model'); + record.get('isNew'); // true + + record.save().then(function(model) { + model.get('isNew'); // false + }); + ``` + + @property isNew + @type {Boolean} + @readOnly + */ + isNew: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `valid` state. A + record will be in the `valid` state when no client-side + validations have failed and the adapter did not report any + server-side validation failures. + + @property isValid + @type {Boolean} + @readOnly + */ + isValid: retrieveFromCurrentState, + /** + If the record is in the dirty state this property will report what + kind of change has caused it to move into the dirty + state. Possible values are: + + - `created` The record has been created by the client and not yet saved to the adapter. + - `updated` The record has been updated by the client and not yet saved to the adapter. + - `deleted` The record has been deleted by the client and not yet saved to the adapter. + + Example + + ```javascript + var record = store.createRecord('model'); + record.get('dirtyType'); // 'created' + ``` + + @property dirtyType + @type {String} + @readOnly + */ + dirtyType: retrieveFromCurrentState, + + /** + If `true` 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. + + Example + + ```javascript + record.get('isError'); // false + record.set('foo', 'invalid value'); + record.save().then(null, function() { + record.get('isError'); // true + }); + ``` + + @property isError + @type {Boolean} + @readOnly + */ + isError: false, + /** + If `true` the store is attempting to reload the record form the adapter. + + Example + + ```javascript + record.get('isReloading'); // false + record.reload(); + record.get('isReloading'); // true + ``` + + @property isReloading + @type {Boolean} + @readOnly + */ + isReloading: false, + + /** + The `clientId` property is a transient numerical identifier + generated at runtime by the data store. It is important + primarily because newly created objects may not yet have an + externally generated id. + + @property clientId + @private + @type {Number|String} + */ + clientId: null, + /** + All ember models have an id property. This is an identifier + managed by an external source. These are always coerced to be + strings before being used internally. Note when declaring the + attributes for a model it is an error to declare an id + attribute. + + ```javascript + var record = store.createRecord('model'); + record.get('id'); // null + + store.find('model', 1).then(function(model) { + model.get('id'); // '1' + }); + ``` + + @property id + @type {String} + */ + id: null, + + /** + @property currentState + @private + @type {Object} + */ + currentState: RootState.empty, + + /** + When the record is in the `invalid` state this object will contain + any errors returned by the adapter. When present the errors hash + typically contains keys corresponding to the invalid property names + and values which are an array of error messages. + + ```javascript + record.get('errors.length'); // 0 + record.set('foo', 'invalid value'); + record.save().then(null, function() { + record.get('errors').get('foo'); // ['foo should be a number.'] + }); + ``` + + @property errors + @type {Object} + */ + errors: Ember.computed(function() { + var errors = Errors.create(); + + errors.registerHandlers(this, function() { + this.send('becameInvalid'); + }, function() { + this.send('becameValid'); + }); + + return errors; + }).readOnly(), + + /** + Create a JSON representation of the record, using the serialization + strategy of the store's adapter. + + `serialize` takes an optional hash as a parameter, currently + supported options are: + + - `includeId`: `true` if the record's ID should be included in the + JSON representation. + + @method serialize + @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); + }, + + /** + Use [DS.JSONSerializer](DS.JSONSerializer.html) to + get the JSON representation of a record. + + `toJSON` takes an optional hash as a parameter, currently + supported options are: + + - `includeId`: `true` if the record's ID should be included in the + JSON representation. + + @method toJSON + @param {Object} options + @returns {Object} A JSON representation of the object. + */ + toJSON: function(options) { + if (!JSONSerializer) { JSONSerializer = requireModule("ember-data/lib/serializers/json_serializer")["default"]; } + // container is for lazy transform lookups + var serializer = JSONSerializer.create({ container: this.container }); + return serializer.serialize(this, options); + }, + + /** + Fired when the record is loaded from the server. + + @event didLoad + */ + didLoad: Ember.K, + + /** + Fired when the record is updated. + + @event didUpdate + */ + didUpdate: Ember.K, + + /** + Fired when the record is created. + + @event didCreate + */ + didCreate: Ember.K, + + /** + Fired when the record is deleted. + + @event didDelete + */ + didDelete: Ember.K, + + /** + Fired when the record becomes invalid. + + @event becameInvalid + */ + becameInvalid: Ember.K, + + /** + Fired when the record enters the error state. + + @event becameError + */ + becameError: Ember.K, + + /** + @property data + @private + @type {Object} + */ + data: Ember.computed(function() { + this._data = this._data || {}; + return this._data; + }).readOnly(), + + _data: null, + + init: function() { + this._super(); + this._setup(); + }, + + _setup: function() { + this._changesToSync = {}; + this._deferredTriggers = []; + this._data = {}; + this._attributes = {}; + this._inFlightAttributes = {}; + this._relationships = {}; + }, + + /** + @method send + @private + @param {String} name + @param {Object} context + */ + send: function(name, context) { + var currentState = get(this, 'currentState'); + + if (!currentState[name]) { + this._unhandledEvent(currentState, name, context); + } + + return currentState[name](this, context); + }, + + /** + @method transitionTo + @private + @param {String} name + */ + transitionTo: function(name) { + // POSSIBLE TODO: Remove this code and replace with + // always having direct references to state objects + + var pivotName = name.split(".", 1), + currentState = get(this, 'currentState'), + state = currentState; + + do { + if (state.exit) { state.exit(this); } + state = state.parentState; + } while (!state.hasOwnProperty(pivotName)); + + var path = name.split("."); + + var setups = [], enters = [], i, l; + + for (i=0, l=path.length; i "root.created.uncommitted" + ``` + + The hierarchy of valid states that ship with ember data looks like + this: + + ```text + * root + * deleted + * saved + * uncommitted + * inFlight + * empty + * loaded + * created + * uncommitted + * inFlight + * saved + * updated + * uncommitted + * inFlight + * loading + ``` + + The `DS.Model` states are themselves stateless. What we mean is + that, 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 record + points to this global, immutable shared instance. How does a state + know which record it should be acting on? We pass the record + instance into the state's event handlers as the first argument. + + The record 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. + + ### Events and Flags + + A state may implement zero or more events and flags. + + #### Events + + Events are named functions that are invoked when sent to a record. The + record 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: + + ```javascript + aState: DS.State.create({ + myEvent: function(manager, param) { + console.log("Received myEvent with", param); + } + }) + ``` + + To trigger this event: + + ```javascript + 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 record'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 + * uncommitted <-- currentState + * inFlight + * updated + * inFlight + + If we are currently in the `uncommitted` 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: + + ```javascript + var statePath = record.get('stateManager.currentPath'); + if (statePath === 'created.inFlight') { + doSomething(); + } + ``` + + You can say: + + ```javascript + 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. + + + * [isEmpty](DS.Model.html#property_isEmpty) + * [isLoading](DS.Model.html#property_isLoading) + * [isLoaded](DS.Model.html#property_isLoaded) + * [isDirty](DS.Model.html#property_isDirty) + * [isSaving](DS.Model.html#property_isSaving) + * [isDeleted](DS.Model.html#property_isDeleted) + * [isNew](DS.Model.html#property_isNew) + * [isValid](DS.Model.html#property_isValid) + + @namespace DS + @class RootState + */ + + function hasDefinedProperties(object) { + // Ignore internal property defined by simulated `Ember.create`. + var names = Ember.keys(object); + var i, l, name; + for (i = 0, l = names.length; i < l; i++ ) { + name = names[i]; + if (object.hasOwnProperty(name) && object[name]) { return true; } + } - if (type === "boolean") { - return serialized; - } else if (type === "string") { - return serialized.match(/^true$|^t$|^1$/i) !== null; - } else if (type === "number") { - return serialized === 1; - } else { return false; } - }, - serialize: function(deserialized) { - return Boolean(deserialized); - } -}); - -})(); - - - -(function() { -/** - The `DS.DateTransform` class is used to serialize and deserialize - date attributes on Ember Data record objects. This transform is used - when `date` is passed as the type parameter to the - [DS.attr](../../data#method_attr) function. - - ```javascript - var attr = DS.attr; - App.Score = DS.Model.extend({ - value: attr('number'), - player: DS.belongsTo('player'), - date: attr('date') - }); - ``` - - @class DateTransform - @extends DS.Transform - @namespace DS - */ -DS.DateTransform = DS.Transform.extend({ - - deserialize: function(serialized) { - var type = typeof serialized; - - if (type === "string") { - return new Date(Ember.Date.parse(serialized)); - } else if (type === "number") { - return new Date(serialized); - } else if (serialized === null || serialized === undefined) { - // if the value is not present in the data, - // return undefined, not null. - return serialized; - } else { - return null; - } - }, - - serialize: function(date) { - if (date instanceof Date) { - var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - - var pad = function(num) { - return num < 10 ? "0"+num : ""+num; - }; - - var utcYear = date.getUTCFullYear(), - utcMonth = date.getUTCMonth(), - utcDayOfMonth = date.getUTCDate(), - utcDay = date.getUTCDay(), - utcHours = date.getUTCHours(), - utcMinutes = date.getUTCMinutes(), - utcSeconds = date.getUTCSeconds(); - - - var dayOfWeek = days[utcDay]; - var dayOfMonth = pad(utcDayOfMonth); - var month = months[utcMonth]; - - return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " + - pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT"; - } else { - return null; - } - } - -}); - -})(); - - - -(function() { -var empty = Ember.isEmpty; -/** - The `DS.NumberTransform` class is used to serialize and deserialize - numeric attributes on Ember Data record objects. This transform is - used when `number` is passed as the type parameter to the - [DS.attr](../../data#method_attr) function. - - Usage - - ```javascript - var attr = DS.attr; - App.Score = DS.Model.extend({ - value: attr('number'), - player: DS.belongsTo('player'), - date: attr('date') - }); - ``` - - @class NumberTransform - @extends DS.Transform - @namespace DS - */ -DS.NumberTransform = DS.Transform.extend({ - - deserialize: function(serialized) { - return empty(serialized) ? null : Number(serialized); - }, - - serialize: function(deserialized) { - return empty(deserialized) ? null : Number(deserialized); - } -}); - -})(); - - - -(function() { -var none = Ember.isNone; - -/** - The `DS.StringTransform` class is used to serialize and deserialize - string attributes on Ember Data record objects. This transform is - used when `string` is passed as the type parameter to the - [DS.attr](../../data#method_attr) function. - - Usage - - ```javascript - var attr = DS.attr; - App.User = DS.Model.extend({ - isAdmin: attr('boolean'), - name: attr('string'), - email: attr('string') - }); - ``` - - @class StringTransform - @extends DS.Transform - @namespace DS - */ -DS.StringTransform = DS.Transform.extend({ - - deserialize: function(serialized) { - return none(serialized) ? null : String(serialized); - }, - - serialize: function(deserialized) { - return none(deserialized) ? null : String(deserialized); - } - -}); - -})(); - - - -(function() { - -})(); - - - -(function() { -/** - @module ember-data -*/ - -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: 'custom' - }); - - 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) { - Application.initializer({ - name: "store", - - initialize: function(container, application) { - application.register('store:main', application.Store || DS.Store); - application.register('serializer:_default', DS.JSONSerializer); - application.register('serializer:_rest', DS.RESTSerializer); - application.register('adapter:_rest', DS.RESTAdapter); - - // Eagerly generate the store so defaultStore is populated. - // TODO: Do this in a finisher hook - container.lookup('store:main'); - } - }); - - Application.initializer({ - name: "transforms", - before: "store", - - initialize: function(container, application) { - application.register('transform:boolean', DS.BooleanTransform); - application.register('transform:date', DS.DateTransform); - application.register('transform:number', DS.NumberTransform); - application.register('transform:string', DS.StringTransform); - } - }); - - Application.initializer({ - name: "dataAdapter", - before: "store", - - initialize: function(container, application) { - application.register('dataAdapter:main', DS.DebugAdapter); - } - }); - - Application.initializer({ - name: "injectStore", - before: "store", - - initialize: function(container, application) { - application.inject('controller', 'store', 'store:main'); - application.inject('route', 'store', 'store:main'); - application.inject('serializer', 'store', 'store:main'); - application.inject('dataAdapter', 'store', 'store:main'); - } - }); - -}); - -})(); - - - -(function() { -/** - @module ember-data -*/ - -/** - Date.parse with progressive enhancement for ISO 8601 - - © 2011 Colin Snover - - Released under MIT license. - - @class Date - @namespace Ember - @static -*/ -Ember.Date = Ember.Date || {}; - -var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; - -/** - @method parse - @param date -*/ -Ember.Date.parse = function (date) { - var timestamp, struct, minutesOffset = 0; - - // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string - // before falling back to any implementation-specific date parsing, so that’s what we do, even if native - // implementations could be faster - // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm - if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) { - // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC - for (var i = 0, k; (k = numericKeys[i]); ++i) { - struct[k] = +struct[k] || 0; - } - - // allow undefined days and months - struct[2] = (+struct[2] || 1) - 1; - struct[3] = +struct[3] || 1; - - if (struct[8] !== 'Z' && struct[9] !== undefined) { - minutesOffset = struct[10] * 60 + struct[11]; - - if (struct[9] === '+') { - minutesOffset = 0 - minutesOffset; - } - } - - timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); - } - else { - timestamp = origParse ? origParse(date) : NaN; - } - - return timestamp; -}; - -if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Date) { - Date.parse = Ember.Date.parse; -} - -})(); - - - -(function() { - -})(); - - - -(function() { -/** - @module ember-data -*/ - -var get = Ember.get, set = Ember.set; - -/** - 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. - - @class RecordArray - @namespace DS - @extends Ember.ArrayProxy - @uses Ember.Evented -*/ - -DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { - /** - The model type contained by this record array. - - @property type - @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'); - - return content.objectAt(index); - }, - - update: function() { - if (get(this, 'isUpdating')) { return; } - - var store = get(this, 'store'), - type = get(this, 'type'); - - store.fetchAll(type, this); - }, - - addRecord: function(record) { - get(this, 'content').addObject(record); - }, - - removeRecord: function(record) { - get(this, 'content').removeObject(record); - }, - - save: function() { - var promiseLabel = "DS: RecordArray#save " + get(this, 'type'); - var promise = Ember.RSVP.all(this.invoke("save"), promiseLabel).then(function(array) { - return Ember.A(array); - }, null, "DS: RecordArray#save apply Ember.NativeArray"); - - return DS.PromiseArray.create({ promise: promise }); - } -}); - -})(); - - - -(function() { -/** - @module ember-data -*/ - -var get = Ember.get; - -/** - @class FilteredRecordArray - @namespace DS - @extends DS.RecordArray -*/ -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 manager = get(this, 'manager'); - manager.updateFilter(this, get(this, 'type'), get(this, 'filterFunction')); - }, 'filterFunction') -}); - -})(); - - - -(function() { -/** - @module ember-data -*/ - -var get = Ember.get, set = Ember.set; - -/** - @class AdapterPopulatedRecordArray - @namespace DS - @extends DS.RecordArray -*/ -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(data) { - var store = get(this, 'store'), - type = get(this, 'type'), - records = store.pushMany(type, data), - meta = store.metadataFor(type); - - this.setProperties({ - content: Ember.A(records), - isLoaded: true, - meta: meta - }); - - // TODO: does triggering didLoad event should be the last action of the runLoop? - Ember.run.once(this, 'trigger', 'didLoad'); - } -}); - -})(); - - - -(function() { -/** - @module ember-data -*/ - -var get = Ember.get, set = Ember.set; -var map = Ember.EnumerableUtils.map; - -/** - 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('comment') - }); - - App.Comment = DS.Model.extend({ - post: DS.belongsTo('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_. - - @class ManyArray - @namespace DS - @extends DS.RecordArray -*/ -DS.ManyArray = DS.RecordArray.extend({ - init: function() { - this._super.apply(this, arguments); - this._changesToSync = Ember.OrderedSet.create(); - }, - - /** - The property name of the relationship - - @property {String} - @private - */ - name: null, - - /** - The record to which this relationship belongs. - - @property {DS.Model} - @private - */ - owner: null, - - /** - `true` if the relationship is polymorphic, `false` otherwise. - - @property {Boolean} - @private - */ - isPolymorphic: false, - - // LOADING STATE - - isLoaded: false, - - /** - Used for async `hasMany` arrays - to keep track of when they will resolve. - - @property {Ember.RSVP.Promise} - @private - */ - promise: null, - - loadingRecordsCount: function(count) { - this.loadingRecordsCount = count; - }, - - loadedRecord: function() { - this.loadingRecordsCount--; - if (this.loadingRecordsCount === 0) { - set(this, 'isLoaded', true); - this.trigger('didLoad'); - } - }, - - fetch: function() { - var records = get(this, 'content'), - store = get(this, 'store'), - owner = get(this, 'owner'), - resolver = Ember.RSVP.defer("DS: ManyArray#fetch " + get(this, 'type')); - - var unloadedRecords = records.filterProperty('isEmpty', true); - store.fetchMany(unloadedRecords, owner, resolver); - }, - - // 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 = map(added, function(record) { - Ember.assert("You cannot add '" + record.constructor.typeKey + "' records to this relationship (only '" + this.type.typeKey + "' allowed)", !this.type || record instanceof this.type); - return record; - }, this); - - this._super(index, removed, added); - }, - - arrangedContentDidChange: function() { - Ember.run.once(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} records - @param {String} type - @param {Resolver} resolver - @return DS.ManyArray - */ - findMany: function(owner, records, type, resolver) { - type = this.modelFor(type); - - records = Ember.A(records); - - var unloadedRecords = records.filterProperty('isEmpty', true), - manyArray = this.recordArrayManager.createManyArray(type, records); - - forEach(unloadedRecords, function(record) { - record.loadingData(); - }); - - manyArray.loadingRecordsCount = unloadedRecords.length; - - if (unloadedRecords.length) { - forEach(unloadedRecords, function(record) { - this.recordArrayManager.registerWaitingRecordArray(record, manyArray); - }, this); - - this.fetchMany(unloadedRecords, owner, resolver); - } else { - if (resolver) { resolver.resolve(); } - manyArray.set('isLoaded', true); - Ember.run.once(manyArray, 'trigger', 'didLoad'); - } - - return manyArray; - }, - - /** - If a relationship was originally populated by the adapter as a link - (as opposed to a list of IDs), this method is called when the - relationship is fetched. - - The link (which is usually a URL) is passed through unchanged, so the - adapter can make whatever request it wants. - - The usual use-case is for the server to register a URL as a link, and - then use that URL in the future to make a request for the relationship. - - @method findHasMany - @private - @param {DS.Model} owner - @param {any} link - @param {String} type - @param {Resolver} resolver - @return DS.ManyArray - */ - findHasMany: function(owner, link, relationship, resolver) { - var adapter = this.adapterFor(owner.constructor); - - Ember.assert("You tried to load a hasMany relationship but you have no adapter (for " + owner.constructor + ")", adapter); - Ember.assert("You tried to load a hasMany relationship from a specified `link` in the original payload but your adapter does not implement `findHasMany`", adapter.findHasMany); - - var records = this.recordArrayManager.createManyArray(relationship.type, Ember.A([])); - resolver.resolve(_findHasMany(adapter, this, owner, link, relationship)); - return records; - }, - - findBelongsTo: function(owner, link, relationship, resolver) { - var adapter = this.adapterFor(owner.constructor); - - Ember.assert("You tried to load a belongsTo relationship but you have no adapter (for " + owner.constructor + ")", adapter); - Ember.assert("You tried to load a belongsTo relationship from a specified `link` in the original payload but your adapter does not implement `findBelongsTo`", adapter.findBelongsTo); - - resolver.resolve(_findBelongsTo(adapter, this, owner, link, relationship)); - }, - - /** - This method delegates a query to the adapter. This is the one place where - adapter-level semantics are exposed to the application. - - Exposing queries this way seems preferable to creating an abstract query - language for all server-side queries, and then require all adapters to - implement them. - - This method returns a promise, which is resolved with a `RecordArray` - once the server returns. - - @method findQuery - @private - @param {String} type - @param {any} query an opaque query to be used by the adapter - @return Promise - */ - findQuery: function(type, query) { - type = this.modelFor(type); - - var array = DS.AdapterPopulatedRecordArray.create({ - type: type, - query: query, - content: Ember.A(), - store: this - }); - - var adapter = this.adapterFor(type), - promiseLabel = "DS: Store#findQuery " + type, - resolver = Ember.RSVP.defer(promiseLabel); - - Ember.assert("You tried to load a query but you have no adapter (for " + type + ")", adapter); - Ember.assert("You tried to load a query but your adapter does not implement `findQuery`", adapter.findQuery); - - resolver.resolve(_findQuery(adapter, this, type, query, array)); - - return promiseArray(resolver.promise); - }, - - /** - This method returns an array of all records adapter can find. - It triggers the adapter's `findAll` method to give it an opportunity to populate - the array with records of that type. - - @method findAll - @private - @param {Class} type - @return {DS.AdapterPopulatedRecordArray} - */ - findAll: function(type) { - type = this.modelFor(type); - - return this.fetchAll(type, this.all(type)); - }, - - /** - @method fetchAll - @private - @param type - @param array - @returns Promise - */ - fetchAll: function(type, array) { - var adapter = this.adapterFor(type), - sinceToken = this.typeMapFor(type).metadata.since, - resolver = Ember.RSVP.defer("DS: Store#findAll " + type); - - set(array, 'isUpdating', true); - - Ember.assert("You tried to load all records but you have no adapter (for " + type + ")", adapter); - Ember.assert("You tried to load all records but your adapter does not implement `findAll`", adapter.findAll); - - resolver.resolve(_findAll(adapter, this, type, sinceToken)); - - return promiseArray(resolver.promise); - }, - - /** - @method didUpdateAll - @param type - */ - didUpdateAll: function(type) { - var findAllCache = this.typeMapFor(type).findAllCache; - set(findAllCache, 'isUpdating', false); - }, - - /** - This method returns a filtered array that contains all of the known records - for a given type. - - Note that because it's just a filter, it will have any locally - created records of the type. - - Also note that multiple calls to `all` for a given type will always - return the same RecordArray. - - @method all - @param {Class} type - @return {DS.RecordArray} - */ - all: function(type) { - type = this.modelFor(type); - - var typeMap = this.typeMapFor(type), - findAllCache = typeMap.findAllCache; - - if (findAllCache) { return findAllCache; } - - var array = DS.RecordArray.create({ - type: type, - content: Ember.A(), - store: this, - isLoaded: true - }); - - this.recordArrayManager.registerFilteredRecordArray(array, type); - - typeMap.findAllCache = array; - return array; - }, - - - /** - This method unloads all of the known records for a given type. - - @method unloadAll - @param {Class} type - */ - unloadAll: function(type) { - type = this.modelFor(type); - - var typeMap = this.typeMapFor(type), - records = typeMap.records, record; - - while(record = records.pop()) { - record.unloadRecord(); - } - - typeMap.findAllCache = null; - }, - - /** - Takes a type and filter function, and returns a live RecordArray that - remains up to date as new records are loaded into the store or created - locally. - - The callback function takes a materialized record, and returns true - if the record should be included in the filter and false if it should - not. - - The filter function is called once on all records for the type when - it is created, and then once on each newly loaded or created record. - - If any of a record's properties change, or if it changes state, the - filter function will be invoked again to determine whether it should - still be in the array. - - Optionally you can pass a query which will be triggered at first. The - results returned by the server could then appear in the filter if they - match the filter function. - - @method filter - @param {Class} type - @param {Object} query optional query - @param {Function} filter - @return {DS.FilteredRecordArray} - */ - filter: function(type, query, filter) { - var promise; - - // allow an optional server query - if (arguments.length === 3) { - promise = this.findQuery(type, query); - } else if (arguments.length === 2) { - filter = query; - } - - type = this.modelFor(type); - - var array = DS.FilteredRecordArray.create({ - type: type, - content: Ember.A(), - store: this, - manager: this.recordArrayManager, - filterFunction: filter - }); - - this.recordArrayManager.registerFilteredRecordArray(array, type, filter); - - if (promise) { - return promise.then(function() { return array; }, null, "DS: Store#filter of " + type); - } else { - return array; - } - }, - - /** - This method returns if a certain record is already loaded - in the store. Use this function to know beforehand if a find() - will result in a request or that it will be a cache hit. - - @method recordIsLoaded - @param type - @param {string} id - @return {boolean} - */ - recordIsLoaded: function(type, id) { - if (!this.hasRecordForId(type, id)) { return false; } - return !get(this.recordForId(type, id), 'isEmpty'); - }, - - /** - This method returns the metadata for a specific type. - - @method metadataFor - @param {string} type - @return {object} - */ - metadataFor: function(type) { - type = this.modelFor(type); - return this.typeMapFor(type).metadata; - }, - - // ............ - // . UPDATING . - // ............ - - /** - If the adapter updates attributes or acknowledges creation - or deletion, the record will notify the store to update its - membership in any filters. - To avoid thrashing, this method is invoked only once per - - run loop per record. - - @method dataWasUpdated - @private - @param {Class} type - @param {Number|String} clientId - @param {DS.Model} record - */ - dataWasUpdated: function(type, record) { - this.recordArrayManager.recordDidChange(record); - }, - - // .............. - // . PERSISTING . - // .............. - - /** - This method is called by `record.save`, and gets passed a - resolver for the promise that `record.save` returns. - - It schedules saving to happen at the end of the run loop. - - @method scheduleSave - @private - @param {DS.Model} record - @param {Resolver} resolver - */ - scheduleSave: function(record, resolver) { - record.adapterWillCommit(); - this._pendingSave.push([record, resolver]); - once(this, 'flushPendingSave'); - }, - - /** - This method is called at the end of the run loop, and - flushes any records passed into `scheduleSave` - - @method flushPendingSave - @private - */ - flushPendingSave: function() { - var pending = this._pendingSave.slice(); - this._pendingSave = []; - - forEach(pending, function(tuple) { - var record = tuple[0], resolver = tuple[1], - adapter = this.adapterFor(record.constructor), - operation; - - if (get(record, 'isNew')) { - operation = 'createRecord'; - } else if (get(record, 'isDeleted')) { - operation = 'deleteRecord'; - } else { - operation = 'updateRecord'; - } - - resolver.resolve(_commit(adapter, this, operation, record)); - }, this); - }, - - /** - This method is called once the promise returned by an - adapter's `createRecord`, `updateRecord` or `deleteRecord` - is resolved. - - If the data provides a server-generated ID, it will - update the record and the store's indexes. - - @method didSaveRecord - @private - @param {DS.Model} record the in-flight record - @param {Object} data optional data (see above) - */ - didSaveRecord: function(record, data) { - if (data) { - // normalize relationship IDs into records - data = normalizeRelationships(this, record.constructor, data, record); - - this.updateId(record, data); - } - - record.adapterDidCommit(data); - }, - - /** - This method is called once the promise returned by an - adapter's `createRecord`, `updateRecord` or `deleteRecord` - is rejected with a `DS.InvalidError`. - - @method recordWasInvalid - @private - @param {DS.Model} record - @param {Object} errors - */ - recordWasInvalid: function(record, errors) { - record.adapterDidInvalidate(errors); - }, - - /** - This method is called once the promise returned by an - adapter's `createRecord`, `updateRecord` or `deleteRecord` - is rejected (with anything other than a `DS.InvalidError`). - - @method recordWasError - @private - @param {DS.Model} record - */ - recordWasError: function(record) { - record.adapterDidError(); - }, - - /** - When an adapter's `createRecord`, `updateRecord` or `deleteRecord` - resolves with data, this method extracts the ID from the supplied - data. - - @method updateId - @private - @param {DS.Model} record - @param {Object} data - */ - updateId: function(record, data) { - var oldId = get(record, 'id'), - id = coerceId(data.id); - - Ember.assert("An adapter cannot assign a new id to a record that already has an id. " + record + " had id: " + oldId + " and you tried to update it with " + id + ". This likely happened because your server returned data in response to a find or update that had a different id than the one you sent.", oldId === null || id === oldId); - - this.typeMapFor(record.constructor).idToRecord[id] = record; - - set(record, 'id', id); - }, - - /** - Returns a map of IDs to client IDs for a given type. - - @method typeMapFor - @private - @param type - */ - typeMapFor: function(type) { - var typeMaps = get(this, 'typeMaps'), - guid = Ember.guidFor(type), - typeMap; - - typeMap = typeMaps[guid]; - - if (typeMap) { return typeMap; } - - typeMap = { - idToRecord: {}, - records: [], - metadata: {} - }; - - typeMaps[guid] = typeMap; - - return typeMap; - }, - - // ................ - // . LOADING DATA . - // ................ - - /** - This internal method is used by `push`. - - @method _load - @private - @param {DS.Model} type - @param {Object} data - @param {Boolean} partial the data should be merged into - the existing data, not replace it. - */ - _load: function(type, data, partial) { - var id = coerceId(data.id), - record = this.recordForId(type, id); - - record.setupData(data, partial); - this.recordArrayManager.recordDidChange(record); - - return record; - }, - - /** - Returns a model class for a particular key. Used by - methods that take a type key (like `find`, `createRecord`, - etc.) - - @method modelFor - @param {String or subclass of DS.Model} key - @returns {subclass of DS.Model} - */ - modelFor: function(key) { - var factory; - - if (typeof key === 'string') { - factory = this.container.lookupFactory('model:' + key); - if (!factory) { throw new Ember.Error("No model was found for '" + key + "'"); } - factory.typeKey = key; - } else { - // A factory already supplied. - factory = key; - } - - factory.store = this; - return factory; - }, - - /** - Push some data for a given type into the store. - - This method expects normalized data: - - * The ID is a key named `id` (an ID is mandatory) - * The names of attributes are the ones you used in - your model's `DS.attr`s. - * Your relationships must be: - * represented as IDs or Arrays of IDs - * represented as model instances - * represented as URLs, under the `links` key - - For this model: - - ```js - App.Person = DS.Model.extend({ - firstName: DS.attr(), - lastName: DS.attr(), - - children: DS.hasMany('person') - }); - ``` - - To represent the children as IDs: - - ```js - { - id: 1, - firstName: "Tom", - lastName: "Dale", - children: [1, 2, 3] - } - ``` - - To represent the children relationship as a URL: - - ```js - { - id: 1, - firstName: "Tom", - lastName: "Dale", - links: { - children: "/people/1/children" - } - } - ``` - - If you're streaming data or implementing an adapter, - make sure that you have converted the incoming data - into this form. - - This method can be used both to push in brand new - records, as well as to update existing records. - - @method push - @param {String} type - @param {Object} data - @returns DS.Model the record that was created or - updated. - */ - push: function(type, data, _partial) { - // _partial is an internal param used by `update`. - // If passed, it means that the data should be - // merged into the existing data, not replace it. - - Ember.assert("You must include an `id` in a hash passed to `push`", data.id != null); - - type = this.modelFor(type); - - // normalize relationship IDs into records - data = normalizeRelationships(this, type, data); - - this._load(type, data, _partial); - - return this.recordForId(type, data.id); - }, - - /** - Push some raw data into the store. - - The data will be automatically deserialized using the - serializer for the `type` param. - - This method can be used both to push in brand new - records, as well as to update existing records. - - You can push in more than one type of object at once. - All objects should be in the format expected by the - serializer. - - ```js - App.ApplicationSerializer = DS.ActiveModelSerializer; - - var pushData = { - posts: [ - {id: 1, post_title: "Great post", comment_ids: [2]} - ], - comments: [ - {id: 2, comment_body: "Insightful comment"} - ] - } - - store.pushPayload('post', pushData); - ``` - - @method pushPayload - @param {String} type - @param {Object} payload - */ - - pushPayload: function (type, payload) { - var serializer; - if (!payload) { - payload = type; - serializer = defaultSerializer(this.container); - Ember.assert("You cannot use `store#pushPayload` without a type unless your default serializer defines `pushPayload`", serializer.pushPayload); - } else { - serializer = this.serializerFor(type); - } - serializer.pushPayload(this, payload); - }, - - update: function(type, data) { - Ember.assert("You must include an `id` in a hash passed to `update`", data.id != null); - - return this.push(type, data, true); - }, - - /** - If you have an Array of normalized data to push, - you can call `pushMany` with the Array, and it will - call `push` repeatedly for you. - - @method pushMany - @param {String} type - @param {Array} datas - @return {Array} - */ - pushMany: function(type, datas) { - return map(datas, function(data) { - return this.push(type, data); - }, this); - }, - - /** - If you have some metadata to set for a type - you can call `metaForType`. - - @method metaForType - @param {String} type - @param {Object} metadata - */ - metaForType: function(type, metadata) { - type = this.modelFor(type); - - Ember.merge(this.typeMapFor(type).metadata, metadata); - }, - - /** - Build a brand new record for a given type, ID, and - initial data. - - @method buildRecord - @private - @param {subclass of DS.Model} type - @param {String} id - @param {Object} data - @returns DS.Model - */ - buildRecord: function(type, id, data) { - var typeMap = this.typeMapFor(type), - idToRecord = typeMap.idToRecord; - - Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToRecord[id]); - - // lookupFactory should really return an object that creates - // instances with the injections applied - var record = type._create({ - id: id, - store: this, - container: this.container - }); - - if (data) { - record.setupData(data); - } - - // if we're creating an item, this process will be done - // later, once the object has been persisted. - if (id) { - idToRecord[id] = record; - } - - typeMap.records.push(record); - - return record; - }, - - // ............... - // . DESTRUCTION . - // ............... - - /** - When a record is destroyed, this un-indexes it and - removes it from any record arrays so it can be GCed. - - @method dematerializeRecord - @private - @param {DS.Model} record - */ - dematerializeRecord: function(record) { - var type = record.constructor, - typeMap = this.typeMapFor(type), - id = get(record, 'id'); - - record.updateRecordArrays(); - - if (id) { - delete typeMap.idToRecord[id]; - } - - var loc = indexOf(typeMap.records, record); - typeMap.records.splice(loc, 1); - }, - - // ........................ - // . RELATIONSHIP CHANGES . - // ........................ - - addRelationshipChangeFor: function(childRecord, childKey, parentRecord, parentKey, change) { - var clientId = childRecord.clientId, - parentClientId = parentRecord ? parentRecord : parentRecord; - var key = childKey + parentKey; - var changes = this._relationshipChanges; - if (!(clientId in changes)) { - changes[clientId] = {}; - } - if (!(parentClientId in changes[clientId])) { - changes[clientId][parentClientId] = {}; - } - if (!(key in changes[clientId][parentClientId])) { - changes[clientId][parentClientId][key] = {}; - } - changes[clientId][parentClientId][key][change.changeType] = change; - }, - - removeRelationshipChangeFor: function(clientRecord, childKey, parentRecord, parentKey, type) { - var clientId = clientRecord.clientId, - parentClientId = parentRecord ? parentRecord.clientId : parentRecord; - var changes = this._relationshipChanges; - var key = childKey + parentKey; - if (!(clientId in changes) || !(parentClientId in changes[clientId]) || !(key in changes[clientId][parentClientId])){ - return; - } - delete changes[clientId][parentClientId][key][type]; - }, - - relationshipChangePairsFor: function(record){ - var toReturn = []; - - if( !record ) { return toReturn; } - - //TODO(Igor) What about the other side - var changesObject = this._relationshipChanges[record.clientId]; - for (var objKey in changesObject){ - if(changesObject.hasOwnProperty(objKey)){ - for (var changeKey in changesObject[objKey]){ - if(changesObject[objKey].hasOwnProperty(changeKey)){ - toReturn.push(changesObject[objKey][changeKey]); - } - } - } - } - return toReturn; - }, - - // ...................... - // . PER-TYPE ADAPTERS - // ...................... - - /** - Returns the adapter for a given type. - - @method adapterFor - @private - @param {subclass of DS.Model} type - @returns DS.Adapter - */ - adapterFor: function(type) { - var container = this.container, adapter; - - if (container) { - adapter = container.lookup('adapter:' + type.typeKey) || container.lookup('adapter:application'); - } - - return adapter || get(this, 'defaultAdapter'); - }, - - // .............................. - // . RECORD CHANGE NOTIFICATION . - // .............................. - - /** - Returns an instance of the serializer for a given type. For - example, `serializerFor('person')` will return an instance of - `App.PersonSerializer`. - - If no `App.PersonSerializer` is found, this method will look - for an `App.ApplicationSerializer` (the default serializer for - your entire application). - - If no `App.ApplicationSerializer` is found, it will fall back - to an instance of `DS.JSONSerializer`. - - @method serializerFor - @private - @param {String} type the record to serialize - */ - serializerFor: function(type) { - type = this.modelFor(type); - var adapter = this.adapterFor(type); - - return serializerFor(this.container, type.typeKey, adapter && adapter.defaultSerializer); - } -}); - -function normalizeRelationships(store, type, data, record) { - type.eachRelationship(function(key, relationship) { - // A link (usually a URL) was already provided in - // normalized form - if (data.links && data.links[key]) { - if (record && relationship.options.async) { record._relationships[key] = null; } - return; - } - - var kind = relationship.kind, - value = data[key]; - - if (value == null) { return; } - - if (kind === 'belongsTo') { - deserializeRecordId(store, data, key, relationship, value); - } else if (kind === 'hasMany') { - deserializeRecordIds(store, data, key, relationship, value); - addUnsavedRecords(record, key, value); - } - }); - - return data; -} - -function deserializeRecordId(store, data, key, relationship, id) { - if (isNone(id) || id instanceof DS.Model) { - return; - } - - var type; - - if (typeof id === 'number' || typeof id === 'string') { - type = typeFor(relationship, key, data); - data[key] = store.recordForId(type, id); - } else if (typeof id === 'object') { - // polymorphic - data[key] = store.recordForId(id.type, id.id); - } -} - -function typeFor(relationship, key, data) { - if (relationship.options.polymorphic) { - return data[key + "Type"]; - } else { - return relationship.type; - } -} - -function deserializeRecordIds(store, data, key, relationship, ids) { - for (var i=0, l=ids.length; i "root.created.uncommitted" - ``` - - The hierarchy of valid states that ship with ember data looks like - this: - - ```text - * root - * deleted - * saved - * uncommitted - * inFlight - * empty - * loaded - * created - * uncommitted - * inFlight - * saved - * updated - * uncommitted - * inFlight - * loading - ``` - - The `DS.Model` states are themselves stateless. What we mean is - that, 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 record - points to this global, immutable shared instance. How does a state - know which record it should be acting on? We pass the record - instance into the state's event handlers as the first argument. - - The record 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. - - ### Events and Flags - - A state may implement zero or more events and flags. - - #### Events - - Events are named functions that are invoked when sent to a record. The - record 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: - - ```javascript - aState: DS.State.create({ - myEvent: function(manager, param) { - console.log("Received myEvent with", param); - } - }) - ``` - - To trigger this event: - - ```javascript - 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 record'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 - * uncommitted <-- currentState - * inFlight - * updated - * inFlight - - If we are currently in the `uncommitted` 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: - - ```javascript - var statePath = record.get('stateManager.currentPath'); - if (statePath === 'created.inFlight') { - doSomething(); - } - ``` - - You can say: - - ```javascript - 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. - - - * [isEmpty](DS.Model.html#property_isEmpty) - * [isLoading](DS.Model.html#property_isLoading) - * [isLoaded](DS.Model.html#property_isLoaded) - * [isDirty](DS.Model.html#property_isDirty) - * [isSaving](DS.Model.html#property_isSaving) - * [isDeleted](DS.Model.html#property_isDeleted) - * [isNew](DS.Model.html#property_isNew) - * [isValid](DS.Model.html#property_isValid) - - @namespace DS - @class RootState -*/ - -var hasDefinedProperties = function(object) { - // Ignore internal property defined by simulated `Ember.create`. - var names = Ember.keys(object); - var i, l, name; - for (i = 0, l = names.length; i < l; i++ ) { - name = names[i]; - if (object.hasOwnProperty(name) && object[name]) { return true; } - } - - return false; -}; - -var didSetProperty = function(record, context) { - if (context.value === context.originalValue) { - delete record._attributes[context.name]; - record.send('propertyWasReset', context.name); - } else if (context.value !== context.oldValue) { - record.send('becomeDirty'); - } - - record.updateRecordArraysLater(); -}; - -// 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 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 = { - 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: { - // EVENTS - didSetProperty: didSetProperty, - - propertyWasReset: function(record, name) { - var stillDirty = false; - - for (var prop in record._attributes) { - stillDirty = true; - break; - } - - if (!stillDirty) { record.send('rolledBack'); } - }, - - pushedData: Ember.K, - - becomeDirty: Ember.K, - - willCommit: function(record) { - record.transitionTo('inFlight'); - }, - - reloadRecord: function(record, resolve) { - resolve(get(record, 'store').reloadRecord(record)); - }, - - rolledBack: function(record) { - record.transitionTo('loaded.saved'); - }, - - becameInvalid: function(record) { - record.transitionTo('invalid'); - }, - - rollback: function(record) { - 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: { - // FLAGS - isSaving: true, - - // EVENTS - didSetProperty: didSetProperty, - becomeDirty: Ember.K, - pushedData: Ember.K, - - // TODO: More robust semantics around save-while-in-flight - willCommit: Ember.K, - - didCommit: function(record) { - var dirtyType = get(this, 'dirtyType'); - - record.transitionTo('saved'); - record.send('invokeLifecycleCallbacks', dirtyType); - }, - - becameInvalid: function(record, errors) { - set(record, 'errors', errors); - - record.transitionTo('invalid'); - record.send('invokeLifecycleCallbacks'); - }, - - becameError: function(record) { - record.transitionTo('uncommitted'); - record.triggerLater('becameError', record); - } - }, - - // 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: { - // FLAGS - isValid: false, - - // EVENTS - deleteRecord: function(record) { - record.transitionTo('deleted.uncommitted'); - record.clearRelationships(); - }, - - didSetProperty: function(record, context) { - var errors = get(record, 'errors'), - key = context.name; - - set(errors, key, null); - - if (!hasDefinedProperties(errors)) { - record.send('becameValid'); - } - - didSetProperty(record, context); - }, - - becomeDirty: Ember.K, - - rollback: function(record) { - record.send('becameValid'); - record.send('rollback'); - }, - - becameValid: function(record) { - record.transitionTo('uncommitted'); - }, - - invokeLifecycleCallbacks: function(record) { - record.triggerLater('becameInvalid', record); - } - } -}; - -// The created and updated states are created outside the state -// chart so we can reopen their substates and add mixins as -// necessary. - -function deepClone(object) { - var clone = {}, value; - - for (var prop in object) { - value = object[prop]; - if (value && typeof value === 'object') { - clone[prop] = deepClone(value); - } else { - clone[prop] = value; - } - } - - return clone; -} - -function mixin(original, hash) { - for (var prop in hash) { - original[prop] = hash[prop]; - } - - return original; -} - -function dirtyState(options) { - var newState = deepClone(DirtyState); - return mixin(newState, options); -} - -var createdState = dirtyState({ - dirtyType: 'created', - - // FLAGS - isNew: true -}); - -createdState.uncommitted.rolledBack = function(record) { - record.transitionTo('deleted.saved'); -}; - -var updatedState = dirtyState({ - dirtyType: 'updated' -}); - -createdState.uncommitted.deleteRecord = function(record) { - record.clearRelationships(); - record.transitionTo('deleted.saved'); -}; - -createdState.uncommitted.rollback = function(record) { - DirtyState.uncommitted.rollback.apply(this, arguments); - record.transitionTo('deleted.saved'); -}; - -updatedState.uncommitted.deleteRecord = function(record) { - record.transitionTo('deleted.uncommitted'); - record.clearRelationships(); -}; - -var RootState = { - // FLAGS - isEmpty: false, - isLoading: false, - isLoaded: false, - isDirty: false, - isSaving: false, - isDeleted: false, - isNew: false, - isValid: true, - - // DEFAULT EVENTS - - // Trying to roll back if you're not in the dirty state - // doesn't change your state. For example, if you're in the - // in-flight state, rolling back the record doesn't move - // you out of the in-flight state. - rolledBack: Ember.K, - - propertyWasReset: Ember.K, - - // 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: { - isEmpty: true, - - // EVENTS - loadingData: function(record, promise) { - record._loadingPromise = promise; - record.transitionTo('loading'); - }, - - loadedData: function(record) { - record.transitionTo('loaded.created.uncommitted'); - - record.suspendRelationshipObservers(function() { - record.notifyPropertyChange('data'); - }); - }, - - pushedData: function(record) { - record.transitionTo('loaded.saved'); - record.triggerLater('didLoad'); - } - }, - - // 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: { - // FLAGS - isLoading: true, - - exit: function(record) { - record._loadingPromise = null; - }, - - // EVENTS - pushedData: function(record) { - record.transitionTo('loaded.saved'); - record.triggerLater('didLoad'); - set(record, 'isError', false); - }, - - becameError: function(record) { - record.triggerLater('becameError', record); - }, - - notFound: function(record) { - record.transitionTo('empty'); - } - }, - - // 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: { - initialState: 'saved', - - // FLAGS - isLoaded: true, - - // SUBSTATES - - // If there are no local changes to a record, it remains - // in the `saved` state. - saved: { - setup: function(record) { - var attrs = record._attributes, - isDirty = false; - - for (var prop in attrs) { - if (attrs.hasOwnProperty(prop)) { - isDirty = true; + // 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 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 = { + 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: { + // EVENTS + didSetProperty: didSetProperty, + + propertyWasReset: function(record, name) { + var stillDirty = false; + + for (var prop in record._attributes) { + stillDirty = true; break; } - } - if (isDirty) { - record.adapterDidDirty(); + if (!stillDirty) { record.send('rolledBack'); } + }, + + pushedData: Ember.K, + + becomeDirty: Ember.K, + + willCommit: function(record) { + record.transitionTo('inFlight'); + }, + + reloadRecord: function(record, resolve) { + resolve(get(record, 'store').reloadRecord(record)); + }, + + rolledBack: function(record) { + record.transitionTo('loaded.saved'); + }, + + becameInvalid: function(record) { + record.transitionTo('invalid'); + }, + + rollback: function(record) { + record.rollback(); } }, - // EVENTS - didSetProperty: didSetProperty, + // 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: { + // FLAGS + isSaving: true, - pushedData: Ember.K, + // EVENTS + didSetProperty: didSetProperty, + becomeDirty: Ember.K, + pushedData: Ember.K, - becomeDirty: function(record) { - record.transitionTo('updated.uncommitted'); + unloadRecord: function(record) { + Ember.assert("You can only unload a record which is not inFlight. `" + Ember.inspect(record) + " `", false); + }, + + // TODO: More robust semantics around save-while-in-flight + willCommit: Ember.K, + + didCommit: function(record) { + var dirtyType = get(this, 'dirtyType'); + + record.transitionTo('saved'); + record.send('invokeLifecycleCallbacks', dirtyType); + }, + + becameInvalid: function(record) { + record.transitionTo('invalid'); + record.send('invokeLifecycleCallbacks'); + }, + + becameError: function(record) { + record.transitionTo('uncommitted'); + record.triggerLater('becameError', record); + } }, - willCommit: function(record) { - record.transitionTo('updated.inFlight'); - }, + // 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: { + // FLAGS + isValid: false, - reloadRecord: function(record, resolve) { - resolve(get(record, 'store').reloadRecord(record)); - }, + // EVENTS + deleteRecord: function(record) { + record.transitionTo('deleted.uncommitted'); + record.clearRelationships(); + }, - deleteRecord: function(record) { - record.transitionTo('deleted.uncommitted'); - record.clearRelationships(); - }, + didSetProperty: function(record, context) { + get(record, 'errors').remove(context.name); + didSetProperty(record, context); + }, + + becomeDirty: Ember.K, + + rolledBack: function(record) { + get(record, 'errors').clear(); + }, + + becameValid: function(record) { + record.transitionTo('uncommitted'); + }, + + invokeLifecycleCallbacks: function(record) { + record.triggerLater('becameInvalid', record); + } + } + }; + + // The created and updated states are created outside the state + // chart so we can reopen their substates and add mixins as + // necessary. + + function deepClone(object) { + var clone = {}, value; + + for (var prop in object) { + value = object[prop]; + if (value && typeof value === 'object') { + clone[prop] = deepClone(value); + } else { + clone[prop] = value; + } + } + + return clone; + } + + function mixin(original, hash) { + for (var prop in hash) { + original[prop] = hash[prop]; + } + + return original; + } + + function dirtyState(options) { + var newState = deepClone(DirtyState); + return mixin(newState, options); + } + + var createdState = dirtyState({ + dirtyType: 'created', + // FLAGS + isNew: true + }); + + createdState.uncommitted.rolledBack = function(record) { + record.transitionTo('deleted.saved'); + }; + + var updatedState = dirtyState({ + dirtyType: 'updated' + }); + + createdState.uncommitted.deleteRecord = function(record) { + record.clearRelationships(); + record.transitionTo('deleted.saved'); + }; + + createdState.uncommitted.rollback = function(record) { + DirtyState.uncommitted.rollback.apply(this, arguments); + record.transitionTo('deleted.saved'); + }; + + createdState.uncommitted.propertyWasReset = Ember.K; + + function assertAgainstUnloadRecord(record) { + Ember.assert("You can only unload a record which is not inFlight. `" + Ember.inspect(record) + "`", false); + } + + updatedState.inFlight.unloadRecord = assertAgainstUnloadRecord; + + updatedState.uncommitted.deleteRecord = function(record) { + record.transitionTo('deleted.uncommitted'); + record.clearRelationships(); + }; + + var RootState = { + // FLAGS + isEmpty: false, + isLoading: false, + isLoaded: false, + isDirty: false, + isSaving: false, + isDeleted: false, + isNew: false, + isValid: true, + + // DEFAULT EVENTS + + // Trying to roll back if you're not in the dirty state + // doesn't change your state. For example, if you're in the + // in-flight state, rolling back the record doesn't move + // you out of the in-flight state. + rolledBack: Ember.K, unloadRecord: function(record) { // clear relationships before moving to deleted state // otherwise it fails @@ -3881,5748 +6786,4467 @@ var RootState = { record.transitionTo('deleted.saved'); }, - didCommit: function(record) { - record.send('invokeLifecycleCallbacks', get(record, 'lastDirtyType')); + + propertyWasReset: Ember.K, + + // 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: { + isEmpty: true, + + // EVENTS + loadingData: function(record, promise) { + record._loadingPromise = promise; + record.transitionTo('loading'); + }, + + loadedData: function(record) { + record.transitionTo('loaded.created.uncommitted'); + + record.suspendRelationshipObservers(function() { + record.notifyPropertyChange('data'); + }); + }, + + pushedData: function(record) { + record.transitionTo('loaded.saved'); + record.triggerLater('didLoad'); + } }, - // loaded.saved.notFound would be triggered by a failed - // `reload()` on an unchanged record - notFound: Ember.K + // A record enters this state when the store asks + // 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: { + // FLAGS + isLoading: true, - }, + exit: function(record) { + record._loadingPromise = null; + }, - // 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, + // EVENTS + pushedData: function(record) { + record.transitionTo('loaded.saved'); + record.triggerLater('didLoad'); + set(record, 'isError', false); + }, - // 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 - }, + becameError: function(record) { + record.triggerLater('becameError', record); + }, - // A record is in this state if it was deleted from the store. - deleted: { - initialState: 'uncommitted', - dirtyType: 'deleted', - - // FLAGS - isDeleted: true, - isLoaded: true, - isDirty: true, - - // TRANSITIONS - setup: function(record) { - record.updateRecordArrays(); - }, - - // SUBSTATES - - // When a record is deleted, it enters the `start` - // state. It will exit this state when the record - // starts to commit. - uncommitted: { - - // EVENTS - - willCommit: function(record) { - record.transitionTo('inFlight'); + notFound: function(record) { + record.transitionTo('empty'); + } }, - rollback: function(record) { - record.rollback(); + // 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: { + initialState: 'saved', + + // FLAGS + isLoaded: true, + + // SUBSTATES + + // If there are no local changes to a record, it remains + // in the `saved` state. + saved: { + setup: function(record) { + var attrs = record._attributes, + isDirty = false; + + for (var prop in attrs) { + if (attrs.hasOwnProperty(prop)) { + isDirty = true; + break; + } + } + + if (isDirty) { + record.adapterDidDirty(); + } + }, + + // EVENTS + didSetProperty: didSetProperty, + + pushedData: Ember.K, + + becomeDirty: function(record) { + record.transitionTo('updated.uncommitted'); + }, + + willCommit: function(record) { + record.transitionTo('updated.inFlight'); + }, + + reloadRecord: function(record, resolve) { + resolve(get(record, 'store').reloadRecord(record)); + }, + + deleteRecord: function(record) { + record.transitionTo('deleted.uncommitted'); + record.clearRelationships(); + }, + + unloadRecord: function(record) { + // clear relationships before moving to deleted state + // otherwise it fails + record.clearRelationships(); + record.transitionTo('deleted.saved'); + }, + + didCommit: function(record) { + record.send('invokeLifecycleCallbacks', get(record, 'lastDirtyType')); + }, + + // loaded.saved.notFound would be triggered by a failed + // `reload()` on an unchanged record + notFound: Ember.K + + }, + + // 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 }, - becomeDirty: Ember.K, - deleteRecord: Ember.K, + // A record is in this state if it was deleted from the store. + deleted: { + initialState: 'uncommitted', + dirtyType: 'deleted', - rolledBack: function(record) { - record.transitionTo('loaded.saved'); - } - }, + // FLAGS + isDeleted: true, + isLoaded: true, + isDirty: true, - // After a record starts committing, but - // before the adapter indicates that the deletion - // has saved to the server, a record is in the - // `inFlight` substate of `deleted`. - inFlight: { - // FLAGS - isSaving: true, + // TRANSITIONS + setup: function(record) { + record.updateRecordArrays(); + }, - // EVENTS + // SUBSTATES - // TODO: More robust semantics around save-while-in-flight - willCommit: Ember.K, - didCommit: function(record) { - record.transitionTo('saved'); + // When a record is deleted, it enters the `start` + // state. It will exit this state when the record + // starts to commit. + uncommitted: { - record.send('invokeLifecycleCallbacks'); + // EVENTS + + willCommit: function(record) { + record.transitionTo('inFlight'); + }, + + rollback: function(record) { + record.rollback(); + }, + + becomeDirty: Ember.K, + deleteRecord: Ember.K, + + rolledBack: function(record) { + record.transitionTo('loaded.saved'); + } + }, + + // After a record starts committing, but + // before the adapter indicates that the deletion + // has saved to the server, a record is in the + // `inFlight` substate of `deleted`. + inFlight: { + // FLAGS + isSaving: true, + + // EVENTS + + unloadRecord: assertAgainstUnloadRecord, + + // TODO: More robust semantics around save-while-in-flight + willCommit: Ember.K, + didCommit: function(record) { + record.transitionTo('saved'); + + record.send('invokeLifecycleCallbacks'); + }, + + becameError: function(record) { + record.transitionTo('uncommitted'); + record.triggerLater('becameError', record); + } + }, + + // Once the adapter indicates that the deletion has + // been saved, the record enters the `saved` substate + // of `deleted`. + saved: { + // FLAGS + isDirty: false, + + setup: function(record) { + var store = get(record, 'store'); + store.dematerializeRecord(record); + }, + + invokeLifecycleCallbacks: function(record) { + record.triggerLater('didDelete', record); + record.triggerLater('didCommit', record); + } + } }, - becameError: function(record) { - record.transitionTo('uncommitted'); - record.triggerLater('becameError', record); - } - }, + invokeLifecycleCallbacks: function(record, dirtyType) { + if (dirtyType === 'created') { + record.triggerLater('didCreate', record); + } else { + record.triggerLater('didUpdate', record); + } - // Once the adapter indicates that the deletion has - // been saved, the record enters the `saved` substate - // of `deleted`. - saved: { - // FLAGS - isDirty: false, - - setup: function(record) { - var store = get(record, 'store'); - store.dematerializeRecord(record); - }, - - invokeLifecycleCallbacks: function(record) { - record.triggerLater('didDelete', record); record.triggerLater('didCommit', record); } - } - }, - - invokeLifecycleCallbacks: function(record, dirtyType) { - if (dirtyType === 'created') { - record.triggerLater('didCreate', record); - } else { - record.triggerLater('didUpdate', record); - } - - record.triggerLater('didCommit', record); - } -}; - -function wireState(object, parent, name) { - /*jshint proto:true*/ - // TODO: Use Object.create and copy instead - object = mixin(parent ? Ember.create(parent) : {}, object); - object.parentState = parent; - object.stateName = name; - - for (var prop in object) { - if (!object.hasOwnProperty(prop) || prop === 'parentState' || prop === 'stateName') { continue; } - if (typeof object[prop] === 'object') { - object[prop] = wireState(object[prop], object, name + "." + prop); - } - } - - return object; -} - -RootState = wireState(RootState, null, "root"); - -DS.RootState = RootState; - -})(); - - - -(function() { -/** - @module ember-data -*/ - -var get = Ember.get, set = Ember.set, - merge = Ember.merge, once = Ember.run.once; - -var retrieveFromCurrentState = Ember.computed('currentState', function(key, value) { - return get(get(this, 'currentState'), key); -}).readOnly(); - -/** - - The model class that all Ember Data records descend from. - - @class Model - @namespace DS - @extends Ember.Object - @uses Ember.Evented -*/ -DS.Model = Ember.Object.extend(Ember.Evented, { - /** - If this property is `true` the record is in the `empty` - state. Empty is the first state all records enter after they have - been created. Most records created by the store will quickly - transition to the `loading` state if data needs to be fetched from - the server or the `created` state if the record is created on the - client. A record can also enter the empty state if the adapter is - unable to locate the record. - - @property isEmpty - @type {Boolean} - @readOnly - */ - isEmpty: retrieveFromCurrentState, - /** - If this property is `true` the record is in the `loading` state. 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. - - @property isLoading - @type {Boolean} - @readOnly - */ - isLoading: retrieveFromCurrentState, - /** - If this property is `true` the record is in the `loaded` state. A - record enters this state when its data is populated. Most of a - record's lifecycle is spent inside substates of the `loaded` - state. - - Example - - ```javascript - var record = store.createRecord(App.Model); - record.get('isLoaded'); // true - - store.find('model', 1).then(function(model) { - model.get('isLoaded'); // true - }); - ``` - - @property isLoaded - @type {Boolean} - @readOnly - */ - isLoaded: retrieveFromCurrentState, - /** - If this property is `true` the record is in the `dirty` state. 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. - - Example - - ```javascript - var record = store.createRecord(App.Model); - record.get('isDirty'); // true - - store.find('model', 1).then(function(model) { - model.get('isDirty'); // false - model.set('foo', 'some value'); - model.set('isDirty'); // true - }); - ``` - - @property isDirty - @type {Boolean} - @readOnly - */ - isDirty: retrieveFromCurrentState, - /** - If this property is `true` the record is in the `saving` state. A - record enters the saving state when `save` is called, but the - adapter has not yet acknowledged that the changes have been - persisted to the backend. - - Example - - ```javascript - var record = store.createRecord(App.Model); - record.get('isSaving'); // false - var promise = record.save(); - record.get('isSaving'); // true - promise.then(function() { - record.get('isSaving'); // false - }); - ``` - - @property isSaving - @type {Boolean} - @readOnly - */ - isSaving: retrieveFromCurrentState, - /** - If this property is `true` the record is in the `deleted` state - and has been 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. - - Example - - ```javascript - var record = store.createRecord(App.Model); - record.get('isDeleted'); // false - record.deleteRecord(); - record.get('isDeleted'); // true - ``` - - @property isDeleted - @type {Boolean} - @readOnly - */ - isDeleted: retrieveFromCurrentState, - /** - If this property is `true` the record is in the `new` state. A - record will be in the `new` state when it has been created on the - client and the adapter has not yet report that it was successfully - saved. - - Example - - ```javascript - var record = store.createRecord(App.Model); - record.get('isNew'); // true - - store.find('model', 1).then(function(model) { - model.get('isNew'); // false - }); - ``` - - @property isNew - @type {Boolean} - @readOnly - */ - isNew: retrieveFromCurrentState, - /** - If this property is `true` the record is in the `valid` state. A - record will be in the `valid` state when no client-side - validations have failed and the adapter did not report any - server-side validation failures. - - @property isValid - @type {Boolean} - @readOnly - */ - isValid: retrieveFromCurrentState, - /** - If the record is in the dirty state this property will report what - kind of change has caused it to move into the dirty - state. Possible values are: - - - `created` The record has been created by the client and not yet saved to the adapter. - - `updated` The record has been updated by the client and not yet saved to the adapter. - - `deleted` The record has been deleted by the client and not yet saved to the adapter. - - Example - - ```javascript - var record = store.createRecord(App.Model); - record.get('dirtyType'); // 'created' - ``` - - @property dirtyType - @type {String} - @readOnly - */ - dirtyType: retrieveFromCurrentState, - - /** - If `true` 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. - - Example - - ```javascript - record.get('isError'); // false - record.set('foo', 'invalid value'); - record.save().then(null, function() { - record.get('isError'); // true - }); - ``` - - @property isError - @type {Boolean} - @readOnly - */ - isError: false, - /** - If `true` the store is attempting to reload the record form the adapter. - - Example - - ```javascript - record.get('isReloading'); // false - record.reload(); - record.get('isReloading'); // true - ``` - - @property isReloading - @type {Boolean} - @readOnly - */ - isReloading: false, - - /** - The `clientId` property is a transient numerical identifier - generated at runtime by the data store. It is important - primarily because newly created objects may not yet have an - externally generated id. - - @property clientId - @private - @type {Number|String} - */ - clientId: null, - /** - All ember models have an id property. This is an identifier - managed by an external source. These are always coerced to be - strings before being used internally. Note when declaring the - attributes for a model it is an error to declare an id - attribute. - - ```javascript - var record = store.createRecord(App.Model); - record.get('id'); // null - - store.find('model', 1).then(function(model) { - model.get('id'); // '1' - }); - ``` - - @property id - @type {String} - */ - id: null, - transaction: null, - /** - @property currentState - @private - @type {Object} - */ - currentState: null, - /** - When the record is in the `invalid` state this object will contain - any errors returned by the adapter. When present the errors hash - typically contains keys coresponding to the invalid property names - and values which are an array of error messages. - - ```javascript - record.get('errors'); // null - record.set('foo', 'invalid value'); - record.save().then(null, function() { - record.get('errors'); // {foo: ['foo should be a number.']} - }); - ``` - - @property errors - @type {Object} - */ - errors: null, - - /** - Create a JSON representation of the record, using the serialization - strategy of the store's adapter. - - `serialize` takes an optional hash as a parameter, currently - supported options are: - - - `includeId`: `true` if the record's ID should be included in the - JSON representation. - - @method serialize - @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); - }, - - /** - Use [DS.JSONSerializer](DS.JSONSerializer.html) to - get the JSON representation of a record. - - `toJSON` takes an optional hash as a parameter, currently - supported options are: - - - `includeId`: `true` if the record's ID should be included in the - JSON representation. - - @method toJSON - @param {Object} options - @returns {Object} A JSON representation of the object. - */ - toJSON: function(options) { - // container is for lazy transform lookups - var serializer = DS.JSONSerializer.create({ container: this.container }); - return serializer.serialize(this, options); - }, - - /** - Fired when the record is loaded from the server. - - @event didLoad - */ - didLoad: Ember.K, - - /** - Fired when the record is updated. - - @event didUpdate - */ - didUpdate: Ember.K, - - /** - Fired when the record is created. - - @event didCreate - */ - didCreate: Ember.K, - - /** - Fired when the record is deleted. - - @event didDelete - */ - didDelete: Ember.K, - - /** - Fired when the record becomes invalid. - - @event becameInvalid - */ - becameInvalid: Ember.K, - - /** - Fired when the record enters the error state. - - @event becameError - */ - becameError: Ember.K, - - /** - @property data - @private - @type {Object} - */ - data: Ember.computed(function() { - this._data = this._data || {}; - return this._data; - }).property(), - - _data: null, - - init: function() { - set(this, 'currentState', DS.RootState.empty); - this._super(); - this._setup(); - }, - - _setup: function() { - this._changesToSync = {}; - this._deferredTriggers = []; - this._data = {}; - this._attributes = {}; - this._inFlightAttributes = {}; - this._relationships = {}; - }, - - /** - @method send - @private - @param {String} name - @param {Object} context - */ - send: function(name, context) { - var currentState = get(this, 'currentState'); - - if (!currentState[name]) { - this._unhandledEvent(currentState, name, context); - } - - return currentState[name](this, context); - }, - - /** - @method transitionTo - @private - @param {String} name - */ - transitionTo: function(name) { - // POSSIBLE TODO: Remove this code and replace with - // always having direct references to state objects - - var pivotName = name.split(".", 1), - currentState = get(this, 'currentState'), - state = currentState; - - do { - if (state.exit) { state.exit(this); } - state = state.parentState; - } while (!state.hasOwnProperty(pivotName)); - - var path = name.split("."); - - var setups = [], enters = [], i, l; - - for (i=0, l=path.length; i')` from " + this.toString(), name !== 'id'); - - meta.name = name; - map.set(name, meta); + if (options.async) { + return asyncBelongsTo(type, options, meta); } - }); - return map; - }), + return Ember.computed('data', function(key, value) { + var data = get(this, 'data'), + store = get(this, 'store'), belongsTo, typeClass; - /** - A map whose keys are the attributes of the model (properties - described by DS.attr) and whose values are type of transformation - applied to each attribute. This map does not include any - attributes that do not have an transformation type. + if (typeof type === 'string') { + typeClass = store.modelFor(type); + } else { + typeClass = type; + } - Example + if (arguments.length === 2) { + Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof typeClass); + return value === undefined ? null : value; + } - ```javascript - App.Person = DS.Model.extend({ - firstName: attr(), - lastName: attr('string'), - birthday: attr('date') - }); - - var transformedAttributes = Ember.get(App.Person, 'transformedAttributes') - - transformedAttributes.forEach(function(field, type) { - console.log(field, type); - }); - - // prints: - // lastName string - // birthday date - ``` - - @property transformedAttributes - @static - @type {Ember.Map} - @readOnly - */ - transformedAttributes: Ember.computed(function() { - var map = Ember.Map.create(); - - this.eachAttribute(function(key, meta) { - if (meta.type) { - map.set(key, meta.type); - } - }); - - return map; - }), - - /** - Iterates through the attributes of the model, calling the passed function on each - attribute. - - The callback method you provide should have the following signature (all - parameters are optional): - - ```javascript - function(name, meta); - ``` - - - `name` the name of the current property in the iteration - - `meta` the meta object for the attribute property in the iteration - - Note that in addition to a callback, you can also pass an optional target - object that will be set as `this` on the context. - - Example - - ```javascript - App.Person = DS.Model.extend({ - firstName: attr('string'), - lastName: attr('string'), - birthday: attr('date') - }); - - App.Person.eachAttribute(function(name, meta) { - console.log(name, meta); - }); - - // prints: - // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"} - // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"} - // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"} - ``` - - @method eachAttribute - @param {Function} callback The callback to execute - @param {Object} [target] The target object to use - @static - */ - eachAttribute: function(callback, binding) { - get(this, 'attributes').forEach(function(name, meta) { - callback.call(binding, name, meta); - }, binding); - }, - - /** - Iterates through the transformedAttributes of the model, calling - the passed function on each attribute. Note the callback will not be - called for any attributes that do not have an transformation type. - - The callback method you provide should have the following signature (all - parameters are optional): - - ```javascript - function(name, type); - ``` - - - `name` the name of the current property in the iteration - - `type` a tring contrining the name of the type of transformed - applied to the attribute - - Note that in addition to a callback, you can also pass an optional target - object that will be set as `this` on the context. - - Example - - ```javascript - App.Person = DS.Model.extend({ - firstName: attr(), - lastName: attr('string'), - birthday: attr('date') - }); - - App.Person.eachTransformedAttribute(function(name, type) { - console.log(name, type); - }); - - // prints: - // lastName string - // birthday date - ``` - - @method eachTransformedAttribute - @param {Function} callback The callback to execute - @param {Object} [target] The target object to use - @static - */ - eachTransformedAttribute: function(callback, binding) { - get(this, 'transformedAttributes').forEach(function(name, type) { - callback.call(binding, name, type); - }); - } -}); - - -DS.Model.reopen({ - eachAttribute: function(callback, binding) { - this.constructor.eachAttribute(callback, binding); - } -}); - -function getDefaultValue(record, options, key) { - if (typeof options.defaultValue === "function") { - return options.defaultValue(); - } else { - return options.defaultValue; - } -} - -function hasValue(record, key) { - return record._attributes.hasOwnProperty(key) || - record._inFlightAttributes.hasOwnProperty(key) || - record._data.hasOwnProperty(key); -} - -function getValue(record, key) { - if (record._attributes.hasOwnProperty(key)) { - return record._attributes[key]; - } else if (record._inFlightAttributes.hasOwnProperty(key)) { - return record._inFlightAttributes[key]; - } else { - return record._data[key]; - } -} - -/** - `DS.attr` defines an attribute on a [DS.Model](DS.Model.html). - By default, attributes are passed through as-is, however you can specify an - optional type to have the value automatically transformed. - Ember Data ships with four basic transform types: `string`, `number`, - `boolean` and `date`. You can define your own transforms by subclassing - [DS.Transform](DS.Transform.html). - - `DS.attr` takes an optional hash as a second parameter, currently - supported options are: - - - `defaultValue`: Pass a string or a function to be called to set the attribute - to a default value if none is supplied. - - Example - - ```javascript - var attr = DS.attr; - - App.User = DS.Model.extend({ - username: attr('string'), - email: attr('string'), - verified: attr('boolean', {defaultValue: false}) - }); - ``` - - @namespace - @method attr - @for DS - @param {String} type the attribute type - @param {Object} options a hash of options - @return {Attribute} -*/ - -DS.attr = function(type, options) { - options = options || {}; - - var meta = { - type: type, - isAttribute: true, - options: options - }; - - return Ember.computed(function(key, value) { - 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'); - var oldValue = this._attributes[key] || this._inFlightAttributes[key] || this._data[key]; - - this.send('didSetProperty', { - name: key, - oldValue: oldValue, - originalValue: this._data[key], - value: value - }); - - this._attributes[key] = value; - return value; - } else if (hasValue(this, key)) { - return getValue(this, key); - } else { - return getDefaultValue(this, options, key); - } - - // `data` is never set directly. However, it may be - // invalidated from the state manager's setData - // event. - }).property('data').meta(meta); -}; - - -})(); - - - -(function() { -/** - @module ember-data -*/ - -})(); - - - -(function() { -/** - @module ember-data -*/ - -/** - An AttributeChange object is created whenever a record's - attribute changes value. It is used to track changes to a - record between transaction commits. - - @class AttributeChange - @namespace DS - @private - @constructor -*/ -var AttributeChange = DS.AttributeChange = function(options) { - this.record = options.record; - this.store = options.store; - this.name = options.name; - this.value = options.value; - this.oldValue = options.oldValue; -}; - -AttributeChange.createChange = function(options) { - return new AttributeChange(options); -}; - -AttributeChange.prototype = { - sync: function() { - if (this.value !== this.oldValue) { - this.record.send('becomeDirty'); - this.record.updateRecordArraysLater(); - } - - // TODO: Use this object in the commit process - this.destroy(); - }, - - /** - If the AttributeChange is destroyed (either by being rolled back - or being committed), remove it from the list of pending changes - on the record. - - @method destroy - */ - destroy: function() { - delete this.record._changesToSync[this.name]; - } -}; - -})(); - - - -(function() { -/** - @module ember-data -*/ - -var get = Ember.get, set = Ember.set; -var forEach = Ember.EnumerableUtils.forEach; - -/** - @class RelationshipChange - @namespace DS - @private - @construtor -*/ -DS.RelationshipChange = function(options) { - this.parentRecord = options.parentRecord; - this.childRecord = options.childRecord; - this.firstRecord = options.firstRecord; - this.firstRecordKind = options.firstRecordKind; - this.firstRecordName = options.firstRecordName; - this.secondRecord = options.secondRecord; - this.secondRecordKind = options.secondRecordKind; - this.secondRecordName = options.secondRecordName; - this.changeType = options.changeType; - this.store = options.store; - - this.committed = {}; -}; - -/** - @class RelationshipChangeAdd - @namespace DS - @private - @construtor -*/ -DS.RelationshipChangeAdd = function(options){ - DS.RelationshipChange.call(this, options); -}; - -/** - @class RelationshipChangeRemove - @namespace DS - @private - @construtor -*/ -DS.RelationshipChangeRemove = function(options){ - DS.RelationshipChange.call(this, options); -}; - -DS.RelationshipChange.create = function(options) { - return new DS.RelationshipChange(options); -}; - -DS.RelationshipChangeAdd.create = function(options) { - return new DS.RelationshipChangeAdd(options); -}; - -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, otherKind; - var knownKind = knownSide.kind; - - var inverse = recordType.inverseFor(knownKey); - - if (inverse){ - key = inverse.name; - otherKind = inverse.kind; - } - - if (!inverse){ - return knownKind === "belongsTo" ? "oneToNone" : "manyToNone"; - } - else{ - if(otherKind === "belongsTo"){ - return knownKind === "belongsTo" ? "oneToOne" : "manyToOne"; - } - else{ - return knownKind === "belongsTo" ? "oneToMany" : "manyToMany"; - } - } - -}; - -DS.RelationshipChange.createChange = function(firstRecord, secondRecord, store, options){ - // Get the type of the child based on the child's client ID - var firstRecordType = firstRecord.constructor, changeType; - changeType = DS.RelationshipChange.determineRelationshipType(firstRecordType, options); - if (changeType === "oneToMany"){ - return DS.OneToManyChange.createChange(firstRecord, secondRecord, store, options); - } - else if (changeType === "manyToOne"){ - return DS.OneToManyChange.createChange(secondRecord, firstRecord, store, options); - } - else if (changeType === "oneToNone"){ - return DS.OneToNoneChange.createChange(firstRecord, secondRecord, store, options); - } - else if (changeType === "manyToNone"){ - return DS.ManyToNoneChange.createChange(firstRecord, secondRecord, store, options); - } - else if (changeType === "oneToOne"){ - return DS.OneToOneChange.createChange(firstRecord, secondRecord, store, options); - } - else if (changeType === "manyToMany"){ - return DS.ManyToManyChange.createChange(firstRecord, secondRecord, store, options); - } -}; - -DS.OneToNoneChange.createChange = function(childRecord, parentRecord, store, options) { - var key = options.key; - var change = DS.RelationshipChange._createChange({ - parentRecord: parentRecord, - childRecord: childRecord, - firstRecord: childRecord, - store: store, - changeType: options.changeType, - firstRecordName: key, - firstRecordKind: "belongsTo" - }); - - store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); - - return change; -}; - -DS.ManyToNoneChange.createChange = function(childRecord, parentRecord, store, options) { - var key = options.key; - var change = DS.RelationshipChange._createChange({ - parentRecord: childRecord, - childRecord: parentRecord, - secondRecord: childRecord, - store: store, - changeType: options.changeType, - secondRecordName: options.key, - secondRecordKind: "hasMany" - }); - - store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); - return change; -}; - - -DS.ManyToManyChange.createChange = function(childRecord, parentRecord, store, options) { - // 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. - var key = options.key; - - var change = DS.RelationshipChange._createChange({ - parentRecord: parentRecord, - childRecord: childRecord, - firstRecord: childRecord, - secondRecord: parentRecord, - firstRecordKind: "hasMany", - secondRecordKind: "hasMany", - store: store, - changeType: options.changeType, - firstRecordName: key - }); - - store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); - - - return change; -}; - -DS.OneToOneChange.createChange = function(childRecord, parentRecord, store, options) { - var 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 = options.parentType.inverseFor(options.key).name; - } 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({ - parentRecord: parentRecord, - childRecord: childRecord, - firstRecord: childRecord, - secondRecord: parentRecord, - firstRecordKind: "belongsTo", - secondRecordKind: "belongsTo", - store: store, - changeType: options.changeType, - firstRecordName: key - }); - - store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); - - - return change; -}; - -DS.OneToOneChange.maintainInvariant = function(options, store, childRecord, key){ - if (options.changeType === "add" && store.recordIsMaterialized(childRecord)) { - var oldParent = get(childRecord, key); - if (oldParent){ - var correspondingChange = DS.OneToOneChange.createChange(childRecord, oldParent, store, { - parentType: options.parentType, - hasManyName: options.hasManyName, - changeType: "remove", - key: options.key - }); - store.addRelationshipChangeFor(childRecord, key, options.parentRecord , null, correspondingChange); - correspondingChange.sync(); - } - } -}; - -DS.OneToManyChange.createChange = function(childRecord, parentRecord, store, options) { - var 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 = options.parentType.inverseFor(options.key).name; - DS.OneToManyChange.maintainInvariant( options, store, childRecord, 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({ - parentRecord: parentRecord, - childRecord: childRecord, - firstRecord: childRecord, - secondRecord: parentRecord, - firstRecordKind: "belongsTo", - secondRecordKind: "hasMany", - store: store, - changeType: options.changeType, - firstRecordName: key - }); - - store.addRelationshipChangeFor(childRecord, key, parentRecord, change.getSecondRecordName(), change); - - - return change; -}; - - -DS.OneToManyChange.maintainInvariant = function(options, store, childRecord, key){ - if (options.changeType === "add" && childRecord) { - var oldParent = get(childRecord, key); - if (oldParent){ - var correspondingChange = DS.OneToManyChange.createChange(childRecord, oldParent, store, { - parentType: options.parentType, - hasManyName: options.hasManyName, - changeType: "remove", - key: options.key - }); - store.addRelationshipChangeFor(childRecord, key, options.parentRecord, correspondingChange.getSecondRecordName(), correspondingChange); - correspondingChange.sync(); - } - } -}; - -/** - @class RelationshipChange - @namespace DS -*/ -DS.RelationshipChange.prototype = { - - getSecondRecordName: function() { - var name = this.secondRecordName, parent; - - if (!name) { - parent = this.secondRecord; - if (!parent) { return; } - - var childType = this.firstRecord.constructor; - var inverse = childType.inverseFor(this.firstRecordName); - this.secondRecordName = inverse.name; - } - - return this.secondRecordName; - }, - - /** - Get the name of the relationship on the belongsTo side. - - @method getFirstRecordName - @return {String} - */ - getFirstRecordName: function() { - var name = this.firstRecordName; - return name; - }, - - /** - @method destroy - @private - */ - destroy: function() { - var childRecord = this.childRecord, - belongsToName = this.getFirstRecordName(), - hasManyName = this.getSecondRecordName(), - store = this.store; - - store.removeRelationshipChangeFor(childRecord, belongsToName, this.parentRecord, hasManyName, this.changeType); - }, - - getSecondRecord: function(){ - return this.secondRecord; - }, - - /** - @method getFirstRecord - @private - */ - getFirstRecord: function() { - return this.firstRecord; - }, - - coalesce: function(){ - var relationshipPairs = this.store.relationshipChangePairsFor(this.firstRecord); - 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({})); - -// the object is a value, and not a promise -function isValue(object) { - return typeof object === 'object' && (!object.then || typeof object.then !== 'function'); -} - -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); - - if (secondRecord instanceof DS.Model && firstRecord instanceof DS.Model) { - if(this.secondRecordKind === "belongsTo"){ - secondRecord.suspendRelationshipObservers(function(){ - set(secondRecord, secondRecordName, firstRecord); - }); - - } - else if(this.secondRecordKind === "hasMany"){ - secondRecord.suspendRelationshipObservers(function(){ - var relationship = get(secondRecord, secondRecordName); - if (isValue(relationship)) { relationship.addObject(firstRecord); } - }); - } - } - - if (firstRecord instanceof DS.Model && secondRecord instanceof DS.Model && get(firstRecord, firstRecordName) !== secondRecord) { - if(this.firstRecordKind === "belongsTo"){ - firstRecord.suspendRelationshipObservers(function(){ - set(firstRecord, firstRecordName, secondRecord); - }); - } - else if(this.firstRecordKind === "hasMany"){ - firstRecord.suspendRelationshipObservers(function(){ - var relationship = get(firstRecord, firstRecordName); - if (isValue(relationship)) { relationship.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); - - if (secondRecord instanceof DS.Model && firstRecord instanceof DS.Model) { - if(this.secondRecordKind === "belongsTo"){ - secondRecord.suspendRelationshipObservers(function(){ - set(secondRecord, secondRecordName, null); - }); - } - else if(this.secondRecordKind === "hasMany"){ - secondRecord.suspendRelationshipObservers(function(){ - var relationship = get(secondRecord, secondRecordName); - if (isValue(relationship)) { relationship.removeObject(firstRecord); } - }); - } - } - - if (firstRecord instanceof DS.Model && get(firstRecord, firstRecordName)) { - if(this.firstRecordKind === "belongsTo"){ - firstRecord.suspendRelationshipObservers(function(){ - set(firstRecord, firstRecordName, null); - }); - } - else if(this.firstRecordKind === "hasMany"){ - firstRecord.suspendRelationshipObservers(function(){ - var relationship = get(firstRecord, firstRecordName); - if (isValue(relationship)) { relationship.removeObject(secondRecord); } - }); - } - } - - this.coalesce(); -}; - -})(); - - - -(function() { -/** - @module ember-data -*/ - -})(); - - - -(function() { -var get = Ember.get, set = Ember.set, - isNone = Ember.isNone; - -/** - @module ember-data -*/ - -function asyncBelongsTo(type, options, meta) { - return Ember.computed(function(key, value) { - var data = get(this, 'data'), - store = get(this, 'store'), - promiseLabel = "DS: Async belongsTo " + this + " : " + key; - - if (arguments.length === 2) { - Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof store.modelFor(type)); - return value === undefined ? null : DS.PromiseObject.create({ promise: Ember.RSVP.resolve(value, promiseLabel) }); - } - - var link = data.links && data.links[key], belongsTo = data[key]; - if(!isNone(belongsTo)) { - var promise = store.fetchRecord(belongsTo) || Ember.RSVP.resolve(belongsTo, promiseLabel); - return DS.PromiseObject.create({ promise: promise}); - } else if (link) { - var resolver = Ember.RSVP.defer("DS: Async belongsTo (link) " + this + " : " + key); - store.findBelongsTo(this, link, meta, resolver); - return DS.PromiseObject.create({ promise: resolver.promise }); - } else { - return null; - } - }).property('data').meta(meta); -} + if (isNone(belongsTo)) { return null; } -/** - `DS.belongsTo` is used to define One-To-One and One-To-Many - relationships on a [DS.Model](DS.Model.html). + store.fetchRecord(belongsTo); - - `DS.belongsTo` takes an optional hash as a second parameter, currently - supported options are: - - - `async`: A boolean value used to explicitly declare this to be an async relationship. - - `inverse`: A string used to identify the inverse property on a - related model in a One-To-Many relationship. See [Explicit Inverses](#toc_explicit-inverses) - - #### One-To-One - To declare a one-to-one relationship between two models, use - `DS.belongsTo`: - - ```javascript - App.User = DS.Model.extend({ - profile: DS.belongsTo('profile') - }); - - App.Profile = DS.Model.extend({ - user: DS.belongsTo('user') - }); - ``` - - #### One-To-Many - To declare a one-to-many relationship between two models, use - `DS.belongsTo` in combination with `DS.hasMany`, like this: - - ```javascript - App.Post = DS.Model.extend({ - comments: DS.hasMany('comment') - }); - - App.Comment = DS.Model.extend({ - post: DS.belongsTo('post') - }); - ``` - - @namespace - @method belongsTo - @for DS - @param {String or DS.Model} type the model type of the relationship - @param {Object} options a hash of options - @return {Ember.computed} relationship -*/ -DS.belongsTo = function(type, options) { - if (typeof type === 'object') { - options = type; - type = undefined; - } else { - 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' }; - - if (options.async) { - return asyncBelongsTo(type, options, meta); - } - - return Ember.computed(function(key, value) { - var data = get(this, 'data'), - store = get(this, 'store'), belongsTo, typeClass; - - if (typeof type === 'string') { - typeClass = store.modelFor(type); - } else { - typeClass = type; + return belongsTo; + }).meta(meta); } - if (arguments.length === 2) { - Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof typeClass); - return value === undefined ? null : value; - } + /** + These observers observe all `belongsTo` relationships on the record. See + `relationships/ext` to see how these observers get their dependencies. - belongsTo = data[key]; + @class Model + @namespace DS + */ + Model.reopen({ - if (isNone(belongsTo)) { return null; } + /** + @method belongsToWillChange + @private + @static + @param record + @param key + */ + belongsToWillChange: Ember.beforeObserver(function(record, key) { + if (get(record, 'isLoaded')) { + var oldParent = get(record, key); - store.fetchRecord(belongsTo); + if (oldParent) { + var store = get(record, 'store'), + change = RelationshipChange.createChange(record, oldParent, store, { key: key, kind: "belongsTo", changeType: "remove" }); - return belongsTo; - }).property('data').meta(meta); -}; - -/** - These observers observe all `belongsTo` relationships on the record. See - `relationships/ext` to see how these observers get their dependencies. - - @class Model - @namespace DS -*/ -DS.Model.reopen({ - - /** - @method belongsToWillChange - @private - @static - @param record - @param key - */ - belongsToWillChange: Ember.beforeObserver(function(record, key) { - if (get(record, 'isLoaded')) { - var oldParent = get(record, key); - - if (oldParent) { - var store = get(record, 'store'), - change = DS.RelationshipChange.createChange(record, oldParent, store, { key: key, kind: "belongsTo", changeType: "remove" }); - - change.sync(); - this._changesToSync[key] = change; - } - } - }), - - /** - @method belongsToDidChange - @private - @static - @param record - @param key - */ - belongsToDidChange: Ember.immediateObserver(function(record, key) { - if (get(record, 'isLoaded')) { - var newParent = get(record, key); - - if (newParent) { - var store = get(record, 'store'), - change = DS.RelationshipChange.createChange(record, newParent, store, { key: key, kind: "belongsTo", changeType: "add" }); - - change.sync(); - } - } - - delete this._changesToSync[key]; - }) -}); - -})(); - - - -(function() { -/** - @module ember-data -*/ - -var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties; - -function asyncHasMany(type, options, meta) { - return Ember.computed(function(key, value) { - var relationship = this._relationships[key], - promiseLabel = "DS: Async hasMany " + this + " : " + key; - - if (!relationship) { - var resolver = Ember.RSVP.defer(promiseLabel); - relationship = buildRelationship(this, key, options, function(store, data) { - var link = data.links && data.links[key]; - var rel; - if (link) { - rel = store.findHasMany(this, link, meta, resolver); - } else { - rel = store.findMany(this, data[key], meta.type, resolver); + change.sync(); + this._changesToSync[key] = change; + } } - // cache the promise so we can use it - // when we come back and don't need to rebuild - // the relationship. - set(rel, 'promise', resolver.promise); - return rel; - }); - } - - var promise = relationship.get('promise').then(function() { - return relationship; - }, null, "DS: Async hasMany records received"); - - return DS.PromiseArray.create({ promise: promise }); - }).property('data').meta(meta); -} - -function buildRelationship(record, key, options, callback) { - var rels = record._relationships; - - if (rels[key]) { return rels[key]; } - - var data = get(record, 'data'), - store = get(record, 'store'); - - var relationship = rels[key] = callback.call(record, store, data); - - return setProperties(relationship, { - owner: record, name: key, isPolymorphic: options.polymorphic - }); -} - -function hasRelationship(type, options) { - options = options || {}; - - var meta = { type: type, isRelationship: true, options: options, kind: 'hasMany' }; - - if (options.async) { - return asyncHasMany(type, options, meta); - } - - return Ember.computed(function(key, value) { - return buildRelationship(this, key, options, function(store, data) { - var records = data[key]; - Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", Ember.A(records).everyProperty('isEmpty', false)); - return store.findMany(this, data[key], meta.type); - }); - }).property('data').meta(meta); -} - -/** - `DS.hasMany` is used to define One-To-Many and Many-To-Many - relationships on a [DS.Model](DS.Model.html). - - `DS.hasMany` takes an optional hash as a second parameter, currently - supported options are: - - - `async`: A boolean value used to explicitly declare this to be an async relationship. - - `inverse`: A string used to identify the inverse property on a related model. - - #### One-To-Many - To declare a one-to-many relationship between two models, use - `DS.belongsTo` in combination with `DS.hasMany`, like this: - - ```javascript - App.Post = DS.Model.extend({ - comments: DS.hasMany('comment') - }); - - App.Comment = DS.Model.extend({ - post: DS.belongsTo('post') - }); - ``` - - #### Many-To-Many - To declare a many-to-many relationship between two models, use - `DS.hasMany`: - - ```javascript - App.Post = DS.Model.extend({ - tags: DS.hasMany('tag') - }); - - App.Tag = DS.Model.extend({ - posts: DS.hasMany('post') - }); - ``` - - #### Explicit Inverses - - Ember Data will do its best to discover which relationships map to - one another. In the one-to-many code above, for example, Ember Data - can figure out that changing the `comments` relationship should update - the `post` relationship on the inverse because post is the only - relationship to that model. - - However, sometimes you may have multiple `belongsTo`/`hasManys` for the - same type. You can specify which property on the related model is - the inverse using `DS.hasMany`'s `inverse` option: - - ```javascript - var belongsTo = DS.belongsTo, - hasMany = DS.hasMany; - - App.Comment = DS.Model.extend({ - onePost: belongsTo('post'), - twoPost: belongsTo('post'), - redPost: belongsTo('post'), - bluePost: belongsTo('post') - }); - - App.Post = DS.Model.extend({ - comments: hasMany('comment', { - inverse: 'redPost' - }) - }); - ``` - - You can also specify an inverse on a `belongsTo`, which works how - you'd expect. - - @namespace - @method hasMany - @for DS - @param {String or DS.Model} type the model type of the relationship - @param {Object} options a hash of options - @return {Ember.computed} relationship -*/ -DS.hasMany = function(type, options) { - if (typeof type === 'object') { - options = type; - type = undefined; - } - return hasRelationship(type, options); -}; - -})(); - - - -(function() { -var get = Ember.get, set = Ember.set; - -/** - @module ember-data -*/ - -/* - This file defines several extensions to the base `DS.Model` class that - add support for one-to-many relationships. -*/ - -/** - @class Model - @namespace DS -*/ -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: - - ```javascript - DS.Model.extend({ - parent: DS.belongsTo('user') - }); - ``` - - This hook would be called with "parent" as the key and the computed - property returned by `DS.belongsTo` as the value. - - @method didDefineProperty - @param proto - @param key - @param 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'); - } - - 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: - - ```javascript - App.Post = DS.Model.extend({ - comments: DS.hasMany('comment') - }); - ``` - - Calling `App.Post.typeForRelationship('comments')` will return `App.Comment`. - - @method typeForRelationship - @static - @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; - }, - - inverseFor: function(name) { - var inverseType = this.typeForRelationship(name); - - if (!inverseType) { return null; } - - var options = this.metaForProperty(name).options; - - if (options.inverse === null) { return null; } - - var inverseName, inverseKind; - - if (options.inverse) { - inverseName = options.inverse; - inverseKind = Ember.get(inverseType, 'relationshipsByName').get(inverseName).kind; - } else { - var possibleRelationships = findPossibleInverses(this, inverseType); - - if (possibleRelationships.length === 0) { return null; } - - Ember.assert("You defined the '" + name + "' relationship on " + this + ", but multiple possible inverse relationships of type " + this + " were found on " + inverseType + ".", possibleRelationships.length === 1); - - inverseName = possibleRelationships[0].name; - inverseKind = possibleRelationships[0].kind; - } - - function findPossibleInverses(type, inverseType, possibleRelationships) { - possibleRelationships = possibleRelationships || []; - - var relationshipMap = get(inverseType, 'relationships'); - if (!relationshipMap) { return; } - - var relationships = relationshipMap.get(type); - if (relationships) { - possibleRelationships.push.apply(possibleRelationships, relationshipMap.get(type)); - } - - if (type.superclass) { - findPossibleInverses(type.superclass, inverseType, possibleRelationships); - } - - return possibleRelationships; - } - - return { - type: inverseType, - name: inverseName, - kind: inverseKind - }; - }, - - /** - 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: - - ```javascript - App.Blog = DS.Model.extend({ - users: DS.hasMany('user'), - owner: DS.belongsTo('user'), - posts: DS.hasMany('post') - }); - ``` - - This computed property would return a map describing these - relationships, like this: - - ```javascript - var relationships = Ember.get(App.Blog, 'relationships'); - relationships.get(App.User); - //=> [ { name: 'users', kind: 'hasMany' }, - // { name: 'owner', kind: 'belongsTo' } ] - relationships.get(App.Post); - //=> [ { name: 'posts', kind: 'hasMany' } ] - ``` - - @property relationships - @static - @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 = this.store.modelFor(meta.type); + }), + + /** + @method belongsToDidChange + @private + @static + @param record + @param key + */ + belongsToDidChange: Ember.immediateObserver(function(record, key) { + if (get(record, 'isLoaded')) { + var newParent = get(record, key); + + if (newParent) { + var store = get(record, 'store'), + change = RelationshipChange.createChange(record, newParent, store, { key: key, kind: "belongsTo", changeType: "add" }); + + change.sync(); + } } - var relationshipsForType = map.get(meta.type); - - relationshipsForType.push({ name: name, kind: meta.kind }); - } + delete this._changesToSync[key]; + }) }); - return map; - }), - - /** - A hash containing lists of the model's relationships, grouped - by the relationship kind. For example, given a model with this - definition: - - ```javascript - App.Blog = DS.Model.extend({ - users: DS.hasMany('user'), - owner: DS.belongsTo('user'), - - posts: DS.hasMany('post') - }); - ``` - - This property would contain the following: - - ```javascript - var relationshipNames = Ember.get(App.Blog, 'relationshipNames'); - relationshipNames.hasMany; - //=> ['users', 'posts'] - relationshipNames.belongsTo; - //=> ['owner'] - ``` - - @property relationshipNames - @static - @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: - - ```javascript - App.Blog = DS.Model.extend({ - users: DS.hasMany('user'), - owner: DS.belongsTo('user'), - - posts: DS.hasMany('post') - }); - ``` - - This property would contain the following: - - ```javascript - var relatedTypes = Ember.get(App.Blog, 'relatedTypes'); - //=> [ App.User, App.Post ] - ``` - - @property relatedTypes - @static - @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) || this.store.modelFor(type); - } - - Ember.assert("You specified a hasMany (" + meta.type + ") on " + meta.parentType + " but " + meta.type + " was not found.", type); - - if (!types.contains(type)) { - Ember.assert("Trying to sideload " + name + " on " + this.toString() + " but the type doesn't exist.", !!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: - - ```javascript - App.Blog = DS.Model.extend({ - users: DS.hasMany('user'), - owner: DS.belongsTo('user'), - - posts: DS.hasMany('post') - }); - ``` - - This property would contain the following: - - ```javascript - 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 } - ``` - - @property relationshipsByName - @static - @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 (!type && meta.kind === 'hasMany') { - type = Ember.String.singularize(name); - } else if (!type) { - type = name; - } - - if (typeof type === 'string') { - meta.type = this.store.modelFor(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: - - ```javascript - - App.Blog = DS.Model.extend({ - users: DS.hasMany('user'), - owner: DS.belongsTo('user'), - - posts: DS.hasMany('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 - ``` - - @property fields - @static - @type Ember.Map - @readOnly - */ - fields: Ember.computed(function() { - var map = Ember.Map.create(); - - 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. - - @method eachRelationship - @static - @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. - - @method eachRelatedType - @static - @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. - - @method eachRelationship - @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); - } -}); - -})(); - - - -(function() { -/** - @module ember-data -*/ - -})(); - - - -(function() { -/** - @module ember-data -*/ - -var get = Ember.get, set = Ember.set; -var once = Ember.run.once; -var forEach = Ember.EnumerableUtils.forEach; - -/** - @class RecordArrayManager - @namespace DS - @private - @extends Ember.Object -*/ -DS.RecordArrayManager = Ember.Object.extend({ - init: function() { - this.filteredRecordArrays = Ember.MapWithDefault.create({ - defaultValue: function() { return []; } - }); - - this.changedRecords = []; - }, - - recordDidChange: function(record) { - this.changedRecords.push(record); - once(this, this.updateRecordArrays); - }, - - recordArraysForRecord: function(record) { - record._recordArrays = record._recordArrays || Ember.OrderedSet.create(); - return record._recordArrays; - }, - - /** - This method is invoked whenever data is loaded into the store by the - adapter or updated by the adapter, or when a record has changed. - - It updates all record arrays that a record belongs to. - - To avoid thrashing, it only runs at most once per run loop. - - @method updateRecordArrays - @param {Class} type - @param {Number|String} clientId - */ - updateRecordArrays: function() { - forEach(this.changedRecords, function(record) { - if (get(record, 'isDeleted')) { - this._recordWasDeleted(record); - } else { - this._recordWasChanged(record); - } - }, this); - - this.changedRecords = []; - }, - - _recordWasDeleted: function (record) { - var recordArrays = record._recordArrays; - - if (!recordArrays) { return; } - - forEach(recordArrays, function(array) { - array.removeRecord(record); - }); - }, - - _recordWasChanged: function (record) { - var type = record.constructor, - recordArrays = this.filteredRecordArrays.get(type), - filter; - - forEach(recordArrays, function(array) { - filter = get(array, 'filterFunction'); - this.updateRecordArray(array, filter, type, record); - }, this); - - // loop through all manyArrays containing an unloaded copy of this - // clientId and notify them that the record was loaded. - var manyArrays = record._loadingRecordArrays; - - if (manyArrays) { - for (var i=0, l=manyArrays.length; i [ { name: 'users', kind: 'hasMany' }, + // { name: 'owner', kind: 'belongsTo' } ] + relationships.get(App.Post); + //=> [ { name: 'posts', kind: 'hasMany' } ] + ``` + + @property relationships + @static + @type Ember.Map + @readOnly + */ + relationships: Ember.computed(function() { + var map = new Ember.MapWithDefault({ + defaultValue: function() { return []; } }); - payload._embedded.comment.forEach(function(comment) { - comments.push(comment); - postCache[comment.post_id].comments.push(comment); - delete comment.post_id; - } + // Loop through each computed property on the class + this.eachComputedProperty(function(name, meta) { - payload = { comments: comments, posts: payload }; + // If the computed property is a relationship, add + // it to the map. + if (meta.isRelationship) { + if (typeof meta.type === 'string') { + meta.type = this.store.modelFor(meta.type); + } - return this._super(store, type, payload, id, requestType); + 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: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post') + }); + ``` + + This property would contain the following: + + ```javascript + var relationshipNames = Ember.get(App.Blog, 'relationshipNames'); + relationshipNames.hasMany; + //=> ['users', 'posts'] + relationshipNames.belongsTo; + //=> ['owner'] + ``` + + @property relationshipNames + @static + @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: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post') + }); + ``` + + This property would contain the following: + + ```javascript + var relatedTypes = Ember.get(App.Blog, 'relatedTypes'); + //=> [ App.User, App.Post ] + ``` + + @property relatedTypes + @static + @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) || this.store.modelFor(type); + } + + Ember.assert("You specified a hasMany (" + meta.type + ") on " + meta.parentType + " but " + meta.type + " was not found.", type); + + if (!types.contains(type)) { + Ember.assert("Trying to sideload " + name + " on " + this.toString() + " but the type doesn't exist.", !!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: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post') + }); + ``` + + This property would contain the following: + + ```javascript + 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 } + ``` + + @property relationshipsByName + @static + @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 (!type && meta.kind === 'hasMany') { + type = singularize(name); + } else if (!type) { + type = name; + } + + if (typeof type === 'string') { + meta.type = this.store.modelFor(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: + + ```javascript + + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('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 + ``` + + @property fields + @static + @type Ember.Map + @readOnly + */ + fields: Ember.computed(function() { + var map = Ember.Map.create(); + + 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. + + @method eachRelationship + @static + @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); + }); }, - normalizeHash: { - // Next, normalize individual comments, which (after `extract`) - // are now located under `comments` - comments: function(hash) { - hash.id = hash._id; - hash.title = hash.comment_title; - delete hash._id; - delete hash.comment_title; - return hash; - } - } - }) - ``` + /** + 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. - When you call super from your own implementation of `extractArray`, the - built-in implementation will find the primary array in your normalized - payload and push the remaining records into the store. - - The primary array is the array found under `posts`. - - The primary record has special meaning when responding to `findQuery` - or `findHasMany`. In particular, the primary array will become the - list of records in the record array that kicked off the request. - - If your primary array contains secondary (embedded) records of the same type, - you cannot place these into the primary array `posts`. Instead, place the - secondary items into an underscore prefixed property `_posts`, which will - push these items into the store and will not affect the resulting query. - - @method extractArray - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {Object} payload - @param {'findAll'|'findMany'|'findHasMany'|'findQuery'} requestType - @returns {Array} The primary array that was returned in response - to the original query. - */ - extractArray: function(store, primaryType, payload) { - payload = this.normalizePayload(primaryType, payload); - - var primaryTypeName = primaryType.typeKey, - primaryArray; - - for (var prop in payload) { - var typeKey = prop, - forcedSecondary = false; - - if (prop.charAt(0) === '_') { - forcedSecondary = true; - typeKey = prop.substr(1); - } - - var typeName = this.typeForRoot(typeKey), - type = store.modelFor(typeName), - typeSerializer = store.serializerFor(type), - isPrimary = (!forcedSecondary && (typeName === primaryTypeName)); - - /*jshint loopfunc:true*/ - var normalizedArray = map.call(payload[prop], function(hash) { - return typeSerializer.normalize(type, hash, prop); - }, this); - - if (isPrimary) { - primaryArray = normalizedArray; - } else { - store.pushMany(typeName, normalizedArray); - } - } - - return primaryArray; - }, - - /** - This method allows you to push a payload containing top-level - collections of records organized per type. - - ```js - { - "posts": [{ - "id": "1", - "title": "Rails is omakase", - "author", "1", - "comments": [ "1" ] - }], - "comments": [{ - "id": "1", - "body": "FIRST" - }], - "users": [{ - "id": "1", - "name": "@d2h" - }] - } - ``` - - It will first normalize the payload, so you can use this to push - in data streaming in from your server structured the same way - that fetches and saves are structured. - - @method pushPayload - @param {DS.Store} store - @param {Object} payload - */ - pushPayload: function(store, payload) { - payload = this.normalizePayload(null, payload); - - for (var prop in payload) { - var typeName = this.typeForRoot(prop), - type = store.modelFor(typeName); - - /*jshint loopfunc:true*/ - var normalizedArray = map.call(payload[prop], function(hash) { - return this.normalize(type, hash, prop); - }, this); - - store.pushMany(typeName, normalizedArray); - } - }, - - /** - You can use this method to normalize the JSON root keys returned - into the model type expected by your store. - - For example, your server may return underscored root keys rather than - the expected camelcased versions. - - ```js - App.ApplicationSerializer = DS.RESTSerializer.extend({ - typeForRoot: function(root) { - var camelized = Ember.String.camelize(root); - return Ember.String.singularize(camelized); + @method eachRelatedType + @static + @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); + }); } }); - ``` - @method typeForRoot - @param {String} root - @returns {String} the model's typeKey - */ - typeForRoot: function(root) { - return Ember.String.singularize(root); - }, + 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. - // SERIALIZE - - /** - Called when a record is saved in order to convert the - record into JSON. - - By default, it creates a JSON object with a key for - each attribute and belongsTo relationship. - - For example, consider this model: - - ```js - App.Comment = DS.Model.extend({ - title: DS.attr(), - body: DS.attr(), - - author: DS.belongsTo('user') - }); - ``` - - The default serialization would create a JSON object like: - - ```js - { - "title": "Rails is unagi", - "body": "Rails? Omakase? O_O", - "author": 12 - } - ``` - - By default, attributes are passed through as-is, unless - you specified an attribute type (`DS.attr('date')`). If - you specify a transform, the JavaScript value will be - serialized when inserted into the JSON hash. - - By default, belongs-to relationships are converted into - IDs when inserted into the JSON hash. - - ## IDs - - `serialize` takes an options hash with a single option: - `includeId`. If this option is `true`, `serialize` will, - by default include the ID in the JSON object it builds. - - The adapter passes in `includeId: true` when serializing - a record for `createRecord`, but not for `updateRecord`. - - ## Customization - - Your server may expect a different JSON format than the - built-in serialization format. - - In that case, you can implement `serialize` yourself and - return a JSON hash of your choosing. - - ```js - App.PostSerializer = DS.RESTSerializer.extend({ - serialize: function(post, options) { - var json = { - POST_TTL: post.get('title'), - POST_BDY: post.get('body'), - POST_CMS: post.get('comments').mapProperty('id') - } - - if (options.includeId) { - json.POST_ID_ = post.get('id'); - } - - return json; + @method eachRelationship + @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); } }); - ``` + }); +define("ember-data/lib/system/relationships/has_many", + ["../store","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /** + @module ember-data + */ - ## Customizing an App-Wide Serializer + var PromiseArray = __dependency1__.PromiseArray; + var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties; - If you want to define a serializer for your entire - application, you'll probably want to use `eachAttribute` - and `eachRelationship` on the record. + function asyncHasMany(type, options, meta) { + return Ember.computed('data', function(key) { + var relationship = this._relationships[key], + promiseLabel = "DS: Async hasMany " + this + " : " + key; - ```js - App.ApplicationSerializer = DS.RESTSerializer.extend({ - serialize: function(record, options) { - var json = {}; + if (!relationship) { + var resolver = Ember.RSVP.defer(promiseLabel); + relationship = buildRelationship(this, key, options, function(store, data) { + var link = data.links && data.links[key]; + var rel; + if (link) { + rel = store.findHasMany(this, link, meta, resolver); + } else { + rel = store.findMany(this, data[key], meta.type, resolver); + } + // cache the promise so we can use it + // when we come back and don't need to rebuild + // the relationship. + set(rel, 'promise', resolver.promise); + return rel; + }); + } - record.eachAttribute(function(name) { - json[serverAttributeName(name)] = record.get(name); + var promise = relationship.get('promise').then(function() { + return relationship; + }, null, "DS: Async hasMany records received"); + + return PromiseArray.create({ + promise: promise + }); + }).meta(meta).readOnly(); + } + + function buildRelationship(record, key, options, callback) { + var rels = record._relationships; + + if (rels[key]) { return rels[key]; } + + var data = get(record, 'data'), + store = get(record, 'store'); + + var relationship = rels[key] = callback.call(record, store, data); + + return setProperties(relationship, { + owner: record, + name: key, + isPolymorphic: options.polymorphic + }); + } + + function hasRelationship(type, options) { + options = options || {}; + + var meta = { + type: type, + isRelationship: true, + options: options, + kind: 'hasMany' + }; + + if (options.async) { + return asyncHasMany(type, options, meta); + } + + return Ember.computed('data', function(key) { + return buildRelationship(this, key, options, function(store, data) { + var records = data[key]; + Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", Ember.A(records).everyProperty('isEmpty', false)); + return store.findMany(this, data[key], meta.type); + }); + }).meta(meta).readOnly(); + } + + /** + `DS.hasMany` is used to define One-To-Many and Many-To-Many + relationships on a [DS.Model](/api/data/classes/DS.Model.html). + + `DS.hasMany` takes an optional hash as a second parameter, currently + supported options are: + + - `async`: A boolean value used to explicitly declare this to be an async relationship. + - `inverse`: A string used to identify the inverse property on a related model. + + #### One-To-Many + To declare a one-to-many relationship between two models, use + `DS.belongsTo` in combination with `DS.hasMany`, like this: + + ```javascript + App.Post = DS.Model.extend({ + comments: DS.hasMany('comment') + }); + + App.Comment = DS.Model.extend({ + post: DS.belongsTo('post') + }); + ``` + + #### Many-To-Many + To declare a many-to-many relationship between two models, use + `DS.hasMany`: + + ```javascript + App.Post = DS.Model.extend({ + tags: DS.hasMany('tag') + }); + + App.Tag = DS.Model.extend({ + posts: DS.hasMany('post') + }); + ``` + + #### Explicit Inverses + + Ember Data will do its best to discover which relationships map to + one another. In the one-to-many code above, for example, Ember Data + can figure out that changing the `comments` relationship should update + the `post` relationship on the inverse because post is the only + relationship to that model. + + However, sometimes you may have multiple `belongsTo`/`hasManys` for the + same type. You can specify which property on the related model is + the inverse using `DS.hasMany`'s `inverse` option: + + ```javascript + var belongsTo = DS.belongsTo, + hasMany = DS.hasMany; + + App.Comment = DS.Model.extend({ + onePost: belongsTo('post'), + twoPost: belongsTo('post'), + redPost: belongsTo('post'), + bluePost: belongsTo('post') + }); + + App.Post = DS.Model.extend({ + comments: hasMany('comment', { + inverse: 'redPost' }) + }); + ``` - record.eachRelationship(function(name, relationship) { - if (relationship.kind === 'hasMany') { - json[serverHasManyName(name)] = record.get(name).mapBy('id'); - } - }); + You can also specify an inverse on a `belongsTo`, which works how + you'd expect. - if (options.includeId) { - json.ID_ = record.get('id'); - } - - return json; + @namespace + @method hasMany + @for DS + @param {String or DS.Model} type the model type of the relationship + @param {Object} options a hash of options + @return {Ember.computed} relationship + */ + function hasMany(type, options) { + if (typeof type === 'object') { + options = type; + type = undefined; } - }); - - function serverAttributeName(attribute) { - return attribute.underscore().toUpperCase(); + return hasRelationship(type, options); } - function serverHasManyName(name) { - return serverAttributeName(name.singularize()) + "_IDS"; + __exports__["default"] = hasMany; + }); +define("ember-data/lib/system/store", + ["./adapter","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /*globals Ember*/ + /*jshint eqnull:true*/ + + /** + @module ember-data + */ + + var Adapter = __dependency1__["default"]; + var get = Ember.get, set = Ember.set; + var once = Ember.run.once; + var isNone = Ember.isNone; + var forEach = Ember.EnumerableUtils.forEach; + var indexOf = Ember.EnumerableUtils.indexOf; + var map = Ember.EnumerableUtils.map; + var Promise = Ember.RSVP.Promise; + var copy = Ember.copy; + var Store, PromiseObject, PromiseArray, RecordArrayManager, Model; + + // Implementors Note: + // + // The variables in this file are consistently named according to the following + // scheme: + // + // * +id+ means an identifier managed by an external source, provided inside + // the data provided by that source. These are always coerced to be strings + // before being used internally. + // * +clientId+ means a transient numerical identifier generated at runtime by + // the data store. It is important primarily because newly created objects may + // not yet have an externally generated id. + // * +reference+ means a record reference object, which holds metadata about a + // record, even if it has not yet been fully materialized. + // * +type+ means a subclass of DS.Model. + + // Used by the store to normalize IDs entering the store. Despite the fact + // that developers may provide IDs as numbers (e.g., `store.find(Person, 1)`), + // it is important that internally we use strings, since IDs may be serialized + // and lose type information. For example, Ember's router may put a record's + // ID into the URL, and if we later try to deserialize that URL and find the + // corresponding record, we will not know if it is a string or a number. + function coerceId(id) { + return id == null ? null : id+''; } - ``` - This serializer will generate JSON that looks like this: + /** + The store contains all of the data for records loaded from the server. + It is also responsible for creating instances of `DS.Model` that wrap + the individual data for a record, so that they can be bound to in your + Handlebars templates. - ```js - { - "TITLE": "Rails is omakase", - "BODY": "Yep. Omakase.", - "COMMENT_IDS": [ 1, 2, 3 ] - } - ``` + Define your application's store like this: - ## Tweaking the Default JSON + ```javascript + MyApp.Store = DS.Store.extend(); + ``` - If you just want to do some small tweaks on the default JSON, - you can call super first and make the tweaks on the returned - JSON. + Most Ember.js applications will only have a single `DS.Store` that is + automatically created by their `Ember.Application`. - ```js - App.PostSerializer = DS.RESTSerializer.extend({ + You can retrieve models from the store in several ways. To retrieve a record + for a specific id, use `DS.Store`'s `find()` method: + + ```javascript + var person = store.find('person', 123); + ``` + + If your application has multiple `DS.Store` instances (an unusual case), you can + specify which store should be used: + + ```javascript + var person = store.find('person', 123); + ``` + + By default, the store will talk to your backend using a standard + REST mechanism. You can customize how the store talks to your + backend by specifying a custom adapter: + + ```javascript + MyApp.store = DS.Store.create({ + adapter: 'MyApp.CustomAdapter' + }); + ``` + + You can learn more about writing a custom adapter by reading the `DS.Adapter` + documentation. + + ### Store createRecord() vs. push() vs. pushPayload() vs. update() + + The store provides multiple ways to create new records object. They have + some subtle differences in their use which are detailed below: + + [createRecord](#method_createRecord) is used for creating new + records on the client side. This will return a new record in the + `created.uncommitted` state. In order to persist this record to the + backend you will need to call `record.save()`. + + [push](#method_push) is used to notify Ember Data's store of new or + updated records that exist in the backend. This will return a record + in the `loaded.saved` state. The primary use-case for `store#push` is + to notify Ember Data about record updates that happen + outside of the normal adapter methods (for example + [SSE](http://dev.w3.org/html5/eventsource/) or [Web + Sockets](http://www.w3.org/TR/2009/WD-websockets-20091222/)). + + [pushPayload](#method_pushPayload) is a convenience wrapper for + `store#push` that will deserialize payloads if the model's + Serializer implements a `pushPayload` method. + + [update](#method_update) works like `push`, except it can handle + partial attributes without overwriting the existing record + properties. + + Note: When creating a new record using any of the above methods + Ember Data will update `DS.RecordArray`s such as those returned by + `store#all()`, `store#findAll()` or `store#filter()`. This means any + data bindings or computed properties that depend on the RecordArray + will automatically be synced to include the new or updated record + values. + + @class Store + @namespace DS + @extends Ember.Object + */ + Store = Ember.Object.extend({ + + /** + @method init + @private + */ + init: function() { + // internal bookkeeping; not observable + if (!RecordArrayManager) { RecordArrayManager = requireModule("ember-data/lib/system/record_array_manager")["default"]; } + this.typeMaps = {}; + this.recordArrayManager = RecordArrayManager.create({ + store: this + }); + this._relationshipChanges = {}; + this._pendingSave = []; + }, + + /** + The adapter to use to communicate to a backend server or other persistence layer. + + This can be specified as an instance, class, or string. + + If you want to specify `App.CustomAdapter` as a string, do: + + ```js + adapter: 'custom' + ``` + + @property adapter + @default DS.RESTAdapter + @type {DS.Adapter|String} + */ + adapter: '-rest', + + /** + Returns a JSON representation of the record using a custom + type-specific serializer, if one exists. + + The available options are: + + * `includeId`: `true` if the record's ID should be included in + the JSON representation + + @method serialize + @private + @param {DS.Model} record the record to serialize + @param {Object} options an options hash + */ serialize: function(record, options) { - var json = this._super(record, options); + return this.serializerFor(record.constructor.typeKey).serialize(record, options); + }, - json.subject = json.title; - delete json.title; + /** + This property returns the adapter, after resolving a possible + string key. - return json; - } - }); - ``` + If the supplied `adapter` was a class, or a String property + path resolved to a class, this property will instantiate the + class. - @method serialize - @param record - @param options - */ - serialize: function(record, options) { - return this._super.apply(this, arguments); - }, + This property is cacheable, so the same instance of a specified + adapter class should be used for the lifetime of the store. - /** - You can use this method to customize the root keys serialized into the JSON. - By default the REST Serializer sends camelized root keys. - For example, your server may expect underscored root objects. + @property defaultAdapter + @private + @returns DS.Adapter + */ + defaultAdapter: Ember.computed('adapter', function() { + var adapter = get(this, 'adapter'); - ```js - App.ApplicationSerializer = DS.RESTSerializer.extend({ - serializeIntoHash: function(data, type, record, options) { - var root = Ember.String.decamelize(type.typeKey); - data[root] = this.serialize(record, options); - } - }); - ``` + Ember.assert('You tried to set `adapter` property to an instance of `DS.Adapter`, where it should be a name or a factory', !(adapter instanceof Adapter)); - @method serializeIntoHash - @param {Object} hash - @param {subclass of DS.Model} type - @param {DS.Model} record - @param {Object} options - */ - serializeIntoHash: function(hash, type, record, options) { - hash[type.typeKey] = this.serialize(record, options); - }, - - /** - You can use this method to customize how polymorphic objects are serialized. - By default the JSON Serializer creates the key by appending `Type` to - the attribute and value from the model's camelcased model name. - - @method serializePolymorphicType - @param {DS.Model} record - @param {Object} json - @param {Object} relationship - */ - serializePolymorphicType: function(record, json, relationship) { - var key = relationship.key, - belongsTo = get(record, key); - key = this.keyForAttribute ? this.keyForAttribute(key) : key; - json[key + "Type"] = belongsTo.constructor.typeKey; - } -}); - -})(); - - - -(function() { -/** - @module ember-data -*/ - -var get = Ember.get, set = Ember.set; -var forEach = Ember.ArrayPolyfills.forEach; - -/** - 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 camelCased 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": { - "firstName": "Barack", - "lastName": "Obama", - "occupation": "President" - } - } - ``` - - ## Customization - - ### Endpoint path customization - - Endpoint paths can be prefixed with a `namespace` by setting the namespace - property on the adapter: - - ```js - DS.RESTAdapter.reopen({ - namespace: 'api/1' - }); - ``` - Requests for `App.Person` would now target `/api/1/people/1`. - - ### Host customization - - An adapter can target other hosts by setting the `host` property. - - ```js - DS.RESTAdapter.reopen({ - host: 'https://api.example.com' - }); - ``` - - ### Headers customization - - Some APIs require HTTP headers, e.g. to provide an API key. An array of - headers can be added to the adapter which are passed with every request: - - ```js - DS.RESTAdapter.reopen({ - headers: { - "API_KEY": "secret key", - "ANOTHER_HEADER": "Some header value" - } - }); - ``` - - @class RESTAdapter - @constructor - @namespace DS - @extends DS.Adapter -*/ -DS.RESTAdapter = DS.Adapter.extend({ - defaultSerializer: '_rest', - - /** - Called by the store in order to fetch the JSON for a given - type and ID. - - The `find` method makes an Ajax request to a URL computed by `buildURL`, and returns a - promise for the resulting payload. - - This method performs an HTTP `GET` request with the id provided as part of the querystring. - - @method find - @see RESTAdapter/buildURL - @see RESTAdapter/ajax - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {String} id - @returns Promise - */ - find: function(store, type, id) { - return this.ajax(this.buildURL(type.typeKey, id), 'GET'); - }, - - /** - Called by the store in order to fetch a JSON array for all - of the records for a given type. - - The `findAll` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a - promise for the resulting payload. - - @private - @method findAll - @see RESTAdapter/buildURL - @see RESTAdapter/ajax - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {String} sinceToken - @returns Promise - */ - findAll: function(store, type, sinceToken) { - var query; - - if (sinceToken) { - query = { since: sinceToken }; - } - - return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query }); - }, - - /** - Called by the store in order to fetch a JSON array for - the records that match a particular query. - - The `findQuery` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a - promise for the resulting payload. - - The `query` argument is a simple JavaScript object that will be passed directly - to the server as parameters. - - @private - @method findQuery - @see RESTAdapter/buildURL - @see RESTAdapter/ajax - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {Object} query - @returns Promise - */ - findQuery: function(store, type, query) { - return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query }); - }, - - /** - Called by the store in order to fetch a JSON array for - the unloaded records in a has-many relationship that were originally - specified as IDs. - - For example, if the original payload looks like: - - ```js - { - "id": 1, - "title": "Rails is omakase", - "comments": [ 1, 2, 3 ] - } - ``` - - The IDs will be passed as a URL-encoded Array of IDs, in this form: - - ``` - ids[]=1&ids[]=2&ids[]=3 - ``` - - Many servers, such as Rails and PHP, will automatically convert this URL-encoded array - into an Array for you on the server-side. If you want to encode the - IDs, differently, just override this (one-line) method. - - The `findMany` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a - promise for the resulting payload. - - @method findMany - @see RESTAdapter/buildURL - @see RESTAdapter/ajax - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {Array} ids - @returns Promise - */ - findMany: function(store, type, ids, owner) { - return this.ajax(this.buildURL(type.typeKey), 'GET', { data: { ids: ids } }); - }, - - /** - Called by the store in order to fetch a JSON array for - the unloaded records in a has-many relationship that were originally - specified as a URL (inside of `links`). - - For example, if your original payload looks like this: - - ```js - { - "post": { - "id": 1, - "title": "Rails is omakase", - "links": { "comments": "/posts/1/comments" } - } - } - ``` - - This method will be called with the parent record and `/posts/1/comments`. - - The `findHasMany` method will make an Ajax (HTTP GET) request to the originally specified URL. - If the URL is host-relative (starting with a single slash), the - request will use the host specified on the adapter (if any). - - @method findHasMany - @see RESTAdapter/buildURL - @see RESTAdapter/ajax - @param {DS.Store} store - @param {DS.Model} record - @param {String} url - @returns Promise - */ - findHasMany: function(store, record, url) { - var host = get(this, 'host'), - id = get(record, 'id'), - type = record.constructor.typeKey; - - if (host && url.charAt(0) === '/' && url.charAt(1) !== '/') { - url = host + url; - } - - return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET'); - }, - - /** - Called by the store in order to fetch a JSON array for - the unloaded records in a belongs-to relationship that were originally - specified as a URL (inside of `links`). - - For example, if your original payload looks like this: - - ```js - { - "person": { - "id": 1, - "name": "Tom Dale", - "links": { "group": "/people/1/group" } - } - } - ``` - - This method will be called with the parent record and `/people/1/group`. - - The `findBelongsTo` method will make an Ajax (HTTP GET) request to the originally specified URL. - - @method findBelongsTo - @see RESTAdapter/buildURL - @see RESTAdapter/ajax - @param {DS.Store} store - @param {DS.Model} record - @param {String} url - @returns Promise - */ - findBelongsTo: function(store, record, url) { - var id = get(record, 'id'), - type = record.constructor.typeKey; - - return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET'); - }, - - /** - Called by the store when a newly created record is - saved via the `save` method on a model record instance. - - The `createRecord` method serializes the record and makes an Ajax (HTTP POST) request - to a URL computed by `buildURL`. - - See `serialize` for information on how to customize the serialized form - of a record. - - @method createRecord - @see RESTAdapter/buildURL - @see RESTAdapter/ajax - @see RESTAdapter/serialize - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {DS.Model} record - @returns Promise - */ - createRecord: function(store, type, record) { - var data = {}; - var serializer = store.serializerFor(type.typeKey); - - serializer.serializeIntoHash(data, type, record, { includeId: true }); - - return this.ajax(this.buildURL(type.typeKey), "POST", { data: data }); - }, - - /** - Called by the store when an existing record is saved - via the `save` method on a model record instance. - - The `updateRecord` method serializes the record and makes an Ajax (HTTP PUT) request - to a URL computed by `buildURL`. - - See `serialize` for information on how to customize the serialized form - of a record. - - @method updateRecord - @see RESTAdapter/buildURL - @see RESTAdapter/ajax - @see RESTAdapter/serialize - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {DS.Model} record - @returns Promise - */ - updateRecord: function(store, type, record) { - var data = {}; - var serializer = store.serializerFor(type.typeKey); - - serializer.serializeIntoHash(data, type, record); - - var id = get(record, 'id'); - - return this.ajax(this.buildURL(type.typeKey, id), "PUT", { data: data }); - }, - - /** - Called by the store when a record is deleted. - - The `deleteRecord` method makes an Ajax (HTTP DELETE) request to a URL computed by `buildURL`. - - @method deleteRecord - @see RESTAdapter/buildURL - @see RESTAdapter/ajax - @see RESTAdapter/serialize - @param {DS.Store} store - @param {subclass of DS.Model} type - @param {DS.Model} record - @returns Promise - */ - deleteRecord: function(store, type, record) { - var id = get(record, 'id'); - - return this.ajax(this.buildURL(type.typeKey, id), "DELETE"); - }, - - /** - Builds a URL for a given type and optional ID. - - By default, it pluralizes the type's name (for example, - 'post' becomes 'posts' and 'person' becomes 'people'). - - If an ID is specified, it adds the ID to the path generated - for the type, separated by a `/`. - - @method buildURL - @param {String} type - @param {String} id - @returns String - */ - buildURL: function(type, id) { - var url = [], - host = get(this, 'host'), - prefix = this.urlPrefix(); - - if (type) { url.push(this.pathForType(type)); } - if (id) { url.push(id); } - - if (prefix) { url.unshift(prefix); } - - url = url.join('/'); - if (!host && url) { url = '/' + url; } - - return url; - }, - - urlPrefix: function(path, parentURL) { - var host = get(this, 'host'), - namespace = get(this, 'namespace'), - url = []; - - if (path) { - // Absolute path - if (path.charAt(0) === '/') { - if (host) { - path = path.slice(1); - url.push(host); + if (typeof adapter === 'string') { + adapter = this.container.lookup('adapter:' + adapter) || this.container.lookup('adapter:application') || this.container.lookup('adapter:-rest'); } - // Relative path - } else if (!/^http(s)?:\/\//.test(path)) { - url.push(parentURL); - } - } else { - if (host) { url.push(host); } - if (namespace) { url.push(namespace); } - } - if (path) { - url.push(path); - } + if (DS.Adapter.detect(adapter)) { + adapter = adapter.create({ + container: this.container + }); + } - return url.join('/'); - }, + return adapter; + }), - /** - Determines the pathname for a given type. + // ..................... + // . CREATE NEW RECORD . + // ..................... - By default, it pluralizes the type's name (for example, - 'post' becomes 'posts' and 'person' becomes 'people'). + /** + Create a new record in the current store. The properties passed + to this method are set on the newly created record. - ### Pathname customization + To create a new instance of `App.Post`: - For example if you have an object LineItem with an - endpoint of "/line_items/". - - ```js - DS.RESTAdapter.reopen({ - pathForType: function(type) { - var decamelized = Ember.String.decamelize(type); - return Ember.String.pluralize(decamelized); - }; - }); - ``` - - @method pathForType - @param {String} type - @returns String - **/ - pathForType: function(type) { - return Ember.String.pluralize(type); - }, - - /** - Takes an ajax response, and returns a relavant error. - - By default, the `ajaxError` method has the following behavior: - - * It simply returns the ajax response (jqXHR). - - @method ajaxError - @param jqXHR - */ - ajaxError: function(jqXHR) { - if (jqXHR) { - jqXHR.then = null; - } - - return jqXHR; - }, - - /** - Takes a URL, an HTTP method and a hash of data, and makes an - HTTP request. - - When the server responds with a payload, Ember Data will call into `extractSingle` - or `extractArray` (depending on whether the original query was for one record or - many records). - - By default, `ajax` method has the following behavior: - - * It sets the response `dataType` to `"json"` - * If the HTTP method is not `"GET"`, it sets the `Content-Type` to be - `application/json; charset=utf-8` - * If the HTTP method is not `"GET"`, it stringifies the data passed in. The - data is the serialized record in the case of a save. - * Registers success and failure handlers. - - @method ajax - @private - @param url - @param type - @param hash - */ - ajax: function(url, type, hash) { - var adapter = this; - - return new Ember.RSVP.Promise(function(resolve, reject) { - hash = adapter.ajaxOptions(url, type, hash); - - hash.success = function(json) { - Ember.run(null, resolve, json); - }; - - hash.error = function(jqXHR, textStatus, errorThrown) { - Ember.run(null, reject, adapter.ajaxError(jqXHR)); - }; - - Ember.$.ajax(hash); - }, "DS: RestAdapter#ajax " + type + " to " + url); - }, - - ajaxOptions: function(url, type, hash) { - hash = hash || {}; - hash.url = url; - hash.type = type; - hash.dataType = 'json'; - hash.context = this; - - if (hash.data && type !== 'GET') { - hash.contentType = 'application/json; charset=utf-8'; - hash.data = JSON.stringify(hash.data); - } - - if (this.headers !== undefined) { - var headers = this.headers; - hash.beforeSend = function (xhr) { - forEach.call(Ember.keys(headers), function(key) { - xhr.setRequestHeader(key, headers[key]); + ```js + store.createRecord('post', { + title: "Rails is omakase" }); - }; - } + ``` + + @method createRecord + @param {String} type + @param {Object} properties a hash of properties to set on the + newly created record. + @returns {DS.Model} record + */ + createRecord: function(type, properties) { + type = this.modelFor(type); + + properties = copy(properties) || {}; + + // If the passed properties do not include a primary key, + // give the adapter an opportunity to generate one. Typically, + // client-side ID generators will use something like uuid.js + // to avoid conflicts. + + if (isNone(properties.id)) { + properties.id = this._generateId(type); + } + + // Coerce ID to a string + properties.id = coerceId(properties.id); + + var record = this.buildRecord(type, properties.id); + + // Move the record out of its initial `empty` state into + // the `loaded` state. + record.loadedData(); + + // Set the properties specified on the record. + record.setProperties(properties); + + return record; + }, + + /** + If possible, this method asks the adapter to generate an ID for + a newly created record. + + @method _generateId + @private + @param {String} type + @returns {String} if the adapter can generate one, an ID + */ + _generateId: function(type) { + var adapter = this.adapterFor(type); + + if (adapter && adapter.generateIdForRecord) { + return adapter.generateIdForRecord(this); + } + + return null; + }, + + // ................. + // . DELETE RECORD . + // ................. + + /** + For symmetry, a record can be deleted via the store. + + Example + + ```javascript + var post = store.createRecord('post', { + title: "Rails is omakase" + }); + + store.deleteRecord(post); + ``` + + @method deleteRecord + @param {DS.Model} record + */ + deleteRecord: function(record) { + record.deleteRecord(); + }, + + /** + For symmetry, a record can be unloaded via the store. Only + non-dirty records can be unloaded. + + Example + + ```javascript + store.find('post', 1).then(function(post) { + store.unloadRecord(post); + }); + ``` + + @method unloadRecord + @param {DS.Model} record + */ + unloadRecord: function(record) { + record.unloadRecord(); + }, + + // ................ + // . FIND RECORDS . + // ................ + + /** + This is the main entry point into finding records. The first parameter to + this method is the model's name as a string. + + --- + + To find a record by ID, pass the `id` as the second parameter: + + ```javascript + store.find('person', 1); + ``` + + The `find` method will always return a **promise** that will be resolved + with the record. If the record was already in the store, the promise will + be resolved immediately. Otherwise, the store will ask the adapter's `find` + method to find the necessary data. + + The `find` method will always resolve its promise with the same object for + a given type and `id`. + + --- + + To find all records for a type, call `find` with no additional parameters: + + ```javascript + store.find('person'); + ``` + + This will ask the adapter's `findAll` method to find the records for the + given type, and return a promise that will be resolved once the server + returns the values. + + --- + + To find a record by a query, call `find` with a hash as the second + parameter: + + ```javascript + store.find('person', { page: 1 }); + ``` + + This will ask the adapter's `findQuery` method to find the records for + the query, and return a promise that will be resolved once the server + responds. + + @method find + @param {String or subclass of DS.Model} type + @param {Object|String|Integer|null} id + @return {Promise} promise + */ + find: function(type, id) { + Ember.assert("You need to pass a type to the store's find method", arguments.length >= 1); + Ember.assert("You may not pass `" + id + "` as id to the store's find method", arguments.length === 1 || !Ember.isNone(id)); + + if (arguments.length === 1) { + return this.findAll(type); + } + + // We are passed a query instead of an id. + if (Ember.typeOf(id) === 'object') { + return this.findQuery(type, id); + } + + return this.findById(type, coerceId(id)); + }, + + /** + This method returns a record for a given type and id combination. + + @method findById + @private + @param {String or subclass of DS.Model} type + @param {String|Integer} id + @return {Promise} promise + */ + findById: function(type, id) { + type = this.modelFor(type); + + var record = this.recordForId(type, id); + var fetchedRecord = this.fetchRecord(record); + + return promiseObject(fetchedRecord || record, "DS: Store#findById " + type + " with id: " + id); + }, + + /** + This method makes a series of requests to the adapter's `find` method + and returns a promise that resolves once they are all loaded. + + @private + @method findByIds + @param {String} type + @param {Array} ids + @returns {Promise} promise + */ + findByIds: function(type, ids) { + var store = this; + var promiseLabel = "DS: Store#findByIds " + type; + return promiseArray(Ember.RSVP.all(map(ids, function(id) { + return store.findById(type, id); + })).then(Ember.A, null, "DS: Store#findByIds of " + type + " complete")); + }, + + /** + This method is called by `findById` if it discovers that a particular + type/id pair hasn't been loaded yet to kick off a request to the + adapter. + + @method fetchRecord + @private + @param {DS.Model} record + @returns {Promise} promise + */ + fetchRecord: function(record) { + if (isNone(record)) { return null; } + if (record._loadingPromise) { return record._loadingPromise; } + if (!get(record, 'isEmpty')) { return null; } + + var type = record.constructor, + id = get(record, 'id'); + + var adapter = this.adapterFor(type); + + Ember.assert("You tried to find a record but you have no adapter (for " + type + ")", adapter); + Ember.assert("You tried to find a record but your adapter (for " + type + ") does not implement 'find'", adapter.find); + + var promise = _find(adapter, this, type, id); + record.loadingData(promise); + return promise; + }, + + /** + Get a record by a given type and ID without triggering a fetch. + + This method will synchronously return the record if it's available. + Otherwise, it will return null. + + ```js + var post = store.getById('post', 1); + ``` + + @method getById + @param {String or subclass of DS.Model} type + @param {String|Integer} id + @param {DS.Model} record + */ + getById: function(type, id) { + if (this.hasRecordForId(type, id)) { + return this.recordForId(type, id); + } else { + return null; + } + }, + + /** + This method is called by the record's `reload` method. + + This method calls the adapter's `find` method, which returns a promise. When + **that** promise resolves, `reloadRecord` will resolve the promise returned + by the record's `reload`. + + @method reloadRecord + @private + @param {DS.Model} record + @return {Promise} promise + */ + reloadRecord: function(record) { + var type = record.constructor, + adapter = this.adapterFor(type), + id = get(record, 'id'); + + Ember.assert("You cannot reload a record without an ID", id); + Ember.assert("You tried to reload a record but you have no adapter (for " + type + ")", adapter); + Ember.assert("You tried to reload a record but your adapter does not implement `find`", adapter.find); + + return _find(adapter, this, type, id); + }, + + /** + This method takes a list of records, groups the records by type, + converts the records into IDs, and then invokes the adapter's `findMany` + method. + + The records are grouped by type to invoke `findMany` on adapters + for each unique type in records. + + It is used both by a brand new relationship (via the `findMany` + method) or when the data underlying an existing relationship + changes. + + @method fetchMany + @private + @param {Array} records + @param {DS.Model} owner + @return {Promise} promise + */ + fetchMany: function(records, owner) { + if (!records.length) { return; } + + // Group By Type + var recordsByTypeMap = Ember.MapWithDefault.create({ + defaultValue: function() { return Ember.A(); } + }); + + forEach(records, function(record) { + recordsByTypeMap.get(record.constructor).push(record); + }); + + var promises = []; + + forEach(recordsByTypeMap, function(type, records) { + var ids = records.mapProperty('id'), + adapter = this.adapterFor(type); + + Ember.assert("You tried to load many records but you have no adapter (for " + type + ")", adapter); + Ember.assert("You tried to load many records but your adapter does not implement `findMany`", adapter.findMany); + + promises.push(_findMany(adapter, this, type, ids, owner)); + }, this); + + return Ember.RSVP.all(promises); + }, + + /** + Returns true if a record for a given type and ID is already loaded. + + @method hasRecordForId + @param {String or subclass of DS.Model} type + @param {String|Integer} id + @returns {Boolean} + */ + hasRecordForId: function(type, id) { + id = coerceId(id); + type = this.modelFor(type); + return !!this.typeMapFor(type).idToRecord[id]; + }, + + /** + Returns id record for a given type and ID. If one isn't already loaded, + it builds a new record and leaves it in the `empty` state. + + @method recordForId + @private + @param {String or subclass of DS.Model} type + @param {String|Integer} id + @returns {DS.Model} record + */ + recordForId: function(type, id) { + type = this.modelFor(type); + + id = coerceId(id); + + var record = this.typeMapFor(type).idToRecord[id]; + + if (!record) { + record = this.buildRecord(type, id); + } + + return record; + }, + + /** + @method findMany + @private + @param {DS.Model} owner + @param {Array} records + @param {String or subclass of DS.Model} type + @param {Resolver} resolver + @return {DS.ManyArray} records + */ + findMany: function(owner, records, type, resolver) { + type = this.modelFor(type); + + records = Ember.A(records); + + var unloadedRecords = records.filterProperty('isEmpty', true), + manyArray = this.recordArrayManager.createManyArray(type, records); + + forEach(unloadedRecords, function(record) { + record.loadingData(); + }); + + manyArray.loadingRecordsCount = unloadedRecords.length; + + if (unloadedRecords.length) { + forEach(unloadedRecords, function(record) { + this.recordArrayManager.registerWaitingRecordArray(record, manyArray); + }, this); + + resolver.resolve(this.fetchMany(unloadedRecords, owner)); + } else { + if (resolver) { resolver.resolve(); } + manyArray.set('isLoaded', true); + once(manyArray, 'trigger', 'didLoad'); + } + + return manyArray; + }, + + /** + If a relationship was originally populated by the adapter as a link + (as opposed to a list of IDs), this method is called when the + relationship is fetched. + + The link (which is usually a URL) is passed through unchanged, so the + adapter can make whatever request it wants. + + The usual use-case is for the server to register a URL as a link, and + then use that URL in the future to make a request for the relationship. + + @method findHasMany + @private + @param {DS.Model} owner + @param {any} link + @param {String or subclass of DS.Model} type + @return {Promise} promise + */ + findHasMany: function(owner, link, relationship, resolver) { + var adapter = this.adapterFor(owner.constructor); + + Ember.assert("You tried to load a hasMany relationship but you have no adapter (for " + owner.constructor + ")", adapter); + Ember.assert("You tried to load a hasMany relationship from a specified `link` in the original payload but your adapter does not implement `findHasMany`", adapter.findHasMany); + + var records = this.recordArrayManager.createManyArray(relationship.type, Ember.A([])); + resolver.resolve(_findHasMany(adapter, this, owner, link, relationship)); + return records; + }, + + /** + @method findBelongsTo + @private + @param {DS.Model} owner + @param {any} link + @param {Relationship} relationship + @return {Promise} promise + */ + findBelongsTo: function(owner, link, relationship) { + var adapter = this.adapterFor(owner.constructor); + + Ember.assert("You tried to load a belongsTo relationship but you have no adapter (for " + owner.constructor + ")", adapter); + Ember.assert("You tried to load a belongsTo relationship from a specified `link` in the original payload but your adapter does not implement `findBelongsTo`", adapter.findBelongsTo); + + return _findBelongsTo(adapter, this, owner, link, relationship); + }, + + /** + This method delegates a query to the adapter. This is the one place where + adapter-level semantics are exposed to the application. + + Exposing queries this way seems preferable to creating an abstract query + language for all server-side queries, and then require all adapters to + implement them. + + This method returns a promise, which is resolved with a `RecordArray` + once the server returns. + + @method findQuery + @private + @param {String or subclass of DS.Model} type + @param {any} query an opaque query to be used by the adapter + @return {Promise} promise + */ + findQuery: function(type, query) { + type = this.modelFor(type); + + var array = this.recordArrayManager + .createAdapterPopulatedRecordArray(type, query); + + var adapter = this.adapterFor(type); + + Ember.assert("You tried to load a query but you have no adapter (for " + type + ")", adapter); + Ember.assert("You tried to load a query but your adapter does not implement `findQuery`", adapter.findQuery); + + return promiseArray(_findQuery(adapter, this, type, query, array)); + }, + + /** + This method returns an array of all records adapter can find. + It triggers the adapter's `findAll` method to give it an opportunity to populate + the array with records of that type. + + @method findAll + @private + @param {String or subclass of DS.Model} type + @return {DS.AdapterPopulatedRecordArray} + */ + findAll: function(type) { + type = this.modelFor(type); + + return this.fetchAll(type, this.all(type)); + }, + + /** + @method fetchAll + @private + @param {DS.Model} type + @param {DS.RecordArray} array + @returns {Promise} promise + */ + fetchAll: function(type, array) { + var adapter = this.adapterFor(type), + sinceToken = this.typeMapFor(type).metadata.since; + + set(array, 'isUpdating', true); + + Ember.assert("You tried to load all records but you have no adapter (for " + type + ")", adapter); + Ember.assert("You tried to load all records but your adapter does not implement `findAll`", adapter.findAll); + + return promiseArray(_findAll(adapter, this, type, sinceToken)); + }, + + /** + @method didUpdateAll + @param {DS.Model} type + */ + didUpdateAll: function(type) { + var findAllCache = this.typeMapFor(type).findAllCache; + set(findAllCache, 'isUpdating', false); + }, + + /** + This method returns a filtered array that contains all of the known records + for a given type. + + Note that because it's just a filter, it will have any locally + created records of the type. + + Also note that multiple calls to `all` for a given type will always + return the same RecordArray. + + Example + + ```javascript + var localPosts = store.all('post'); + ``` + + @method all + @param {String or subclass of DS.Model} type + @return {DS.RecordArray} + */ + all: function(type) { + type = this.modelFor(type); + + var typeMap = this.typeMapFor(type), + findAllCache = typeMap.findAllCache; + + if (findAllCache) { return findAllCache; } + + var array = this.recordArrayManager.createRecordArray(type); + + typeMap.findAllCache = array; + return array; + }, - return hash; - } + /** + This method unloads all of the known records for a given type. -}); + ```javascript + store.unloadAll('post'); + ``` -})(); + @method unloadAll + @param {String or subclass of DS.Model} type + */ + unloadAll: function(type) { + var modelType = this.modelFor(type); + var typeMap = this.typeMapFor(modelType); + var records = typeMap.records.slice(); + var record; + + for (var i = 0; i < records.length; i++) { + record = records[i]; + record.unloadRecord(); + record.destroy(); // maybe within unloadRecord + } + + typeMap.findAllCache = null; + }, + + /** + Takes a type and filter function, and returns a live RecordArray that + remains up to date as new records are loaded into the store or created + locally. + + The callback function takes a materialized record, and returns true + if the record should be included in the filter and false if it should + not. + + The filter function is called once on all records for the type when + it is created, and then once on each newly loaded or created record. + + If any of a record's properties change, or if it changes state, the + filter function will be invoked again to determine whether it should + still be in the array. + + Optionally you can pass a query which will be triggered at first. The + results returned by the server could then appear in the filter if they + match the filter function. + + Example + + ```javascript + store.filter('post', {unread: true}, function(post) { + return post.get('unread'); + }).then(function(unreadPosts) { + unreadPosts.get('length'); // 5 + var unreadPost = unreadPosts.objectAt(0); + unreadPost.set('unread', false); + unreadPosts.get('length'); // 4 + }); + ``` + + @method filter + @param {String or subclass of DS.Model} type + @param {Object} query optional query + @param {Function} filter + @return {DS.PromiseArray} + */ + filter: function(type, query, filter) { + var promise; + + // allow an optional server query + if (arguments.length === 3) { + promise = this.findQuery(type, query); + } else if (arguments.length === 2) { + filter = query; + } + + type = this.modelFor(type); + + var array = this.recordArrayManager + .createFilteredRecordArray(type, filter); + promise = promise || Promise.cast(array); + + return promiseArray(promise.then(function() { + return array; + }, null, "DS: Store#filter of " + type)); + }, + + /** + This method returns if a certain record is already loaded + in the store. Use this function to know beforehand if a find() + will result in a request or that it will be a cache hit. + + Example + + ```javascript + store.recordIsLoaded('post', 1); // false + store.find('post', 1).then(function() { + store.recordIsLoaded('post', 1); // true + }); + ``` + + @method recordIsLoaded + @param {String or subclass of DS.Model} type + @param {string} id + @return {boolean} + */ + recordIsLoaded: function(type, id) { + if (!this.hasRecordForId(type, id)) { return false; } + return !get(this.recordForId(type, id), 'isEmpty'); + }, + + /** + This method returns the metadata for a specific type. + + @method metadataFor + @param {String or subclass of DS.Model} type + @return {object} + */ + metadataFor: function(type) { + type = this.modelFor(type); + return this.typeMapFor(type).metadata; + }, + + // ............ + // . UPDATING . + // ............ + + /** + If the adapter updates attributes or acknowledges creation + or deletion, the record will notify the store to update its + membership in any filters. + To avoid thrashing, this method is invoked only once per + + run loop per record. + + @method dataWasUpdated + @private + @param {Class} type + @param {DS.Model} record + */ + dataWasUpdated: function(type, record) { + this.recordArrayManager.recordDidChange(record); + }, + + // .............. + // . PERSISTING . + // .............. + + /** + This method is called by `record.save`, and gets passed a + resolver for the promise that `record.save` returns. + + It schedules saving to happen at the end of the run loop. + + @method scheduleSave + @private + @param {DS.Model} record + @param {Resolver} resolver + */ + scheduleSave: function(record, resolver) { + record.adapterWillCommit(); + this._pendingSave.push([record, resolver]); + once(this, 'flushPendingSave'); + }, + + /** + This method is called at the end of the run loop, and + flushes any records passed into `scheduleSave` + + @method flushPendingSave + @private + */ + flushPendingSave: function() { + var pending = this._pendingSave.slice(); + this._pendingSave = []; + + forEach(pending, function(tuple) { + var record = tuple[0], resolver = tuple[1], + adapter = this.adapterFor(record.constructor), + operation; + + if (get(record, 'isNew')) { + operation = 'createRecord'; + } else if (get(record, 'isDeleted')) { + operation = 'deleteRecord'; + } else { + operation = 'updateRecord'; + } + + resolver.resolve(_commit(adapter, this, operation, record)); + }, this); + }, + + /** + This method is called once the promise returned by an + adapter's `createRecord`, `updateRecord` or `deleteRecord` + is resolved. + + If the data provides a server-generated ID, it will + update the record and the store's indexes. + + @method didSaveRecord + @private + @param {DS.Model} record the in-flight record + @param {Object} data optional data (see above) + */ + didSaveRecord: function(record, data) { + if (data) { + // normalize relationship IDs into records + data = normalizeRelationships(this, record.constructor, data, record); + + this.updateId(record, data); + } + + record.adapterDidCommit(data); + }, + + /** + This method is called once the promise returned by an + adapter's `createRecord`, `updateRecord` or `deleteRecord` + is rejected with a `DS.InvalidError`. + + @method recordWasInvalid + @private + @param {DS.Model} record + @param {Object} errors + */ + recordWasInvalid: function(record, errors) { + record.adapterDidInvalidate(errors); + }, + + /** + This method is called once the promise returned by an + adapter's `createRecord`, `updateRecord` or `deleteRecord` + is rejected (with anything other than a `DS.InvalidError`). + + @method recordWasError + @private + @param {DS.Model} record + */ + recordWasError: function(record) { + record.adapterDidError(); + }, + + /** + When an adapter's `createRecord`, `updateRecord` or `deleteRecord` + resolves with data, this method extracts the ID from the supplied + data. + + @method updateId + @private + @param {DS.Model} record + @param {Object} data + */ + updateId: function(record, data) { + var oldId = get(record, 'id'), + id = coerceId(data.id); + + Ember.assert("An adapter cannot assign a new id to a record that already has an id. " + record + " had id: " + oldId + " and you tried to update it with " + id + ". This likely happened because your server returned data in response to a find or update that had a different id than the one you sent.", oldId === null || id === oldId); + + this.typeMapFor(record.constructor).idToRecord[id] = record; + + set(record, 'id', id); + }, + + /** + Returns a map of IDs to client IDs for a given type. + + @method typeMapFor + @private + @param type + @return {Object} typeMap + */ + typeMapFor: function(type) { + var typeMaps = get(this, 'typeMaps'), + guid = Ember.guidFor(type), + typeMap; + + typeMap = typeMaps[guid]; + + if (typeMap) { return typeMap; } + + typeMap = { + idToRecord: {}, + records: [], + metadata: {}, + type: type + }; + + typeMaps[guid] = typeMap; + + return typeMap; + }, + + // ................ + // . LOADING DATA . + // ................ + + /** + This internal method is used by `push`. + + @method _load + @private + @param {String or subclass of DS.Model} type + @param {Object} data + @param {Boolean} partial the data should be merged into + the existing data, not replace it. + */ + _load: function(type, data, partial) { + var id = coerceId(data.id), + record = this.recordForId(type, id); + + record.setupData(data, partial); + this.recordArrayManager.recordDidChange(record); + + return record; + }, + + /** + Returns a model class for a particular key. Used by + methods that take a type key (like `find`, `createRecord`, + etc.) + + @method modelFor + @param {String or subclass of DS.Model} key + @returns {subclass of DS.Model} + */ + modelFor: function(key) { + var factory; + if (typeof key === 'string') { + var normalizedKey = this.container.normalize('model:' + key); -(function() { -/** - @module ember-data -*/ + factory = this.container.lookupFactory(normalizedKey); + if (!factory) { throw new Ember.Error("No model was found for '" + key + "'"); } + factory.typeKey = normalizedKey.split(':', 2)[1]; + } else { + // A factory already supplied. + factory = key; + } -})(); + factory.store = this; + return factory; + }, + /** + Push some data for a given type into the store. + This method expects normalized data: -(function() { -DS.Model.reopen({ + * The ID is a key named `id` (an ID is mandatory) + * The names of attributes are the ones you used in + your model's `DS.attr`s. + * Your relationships must be: + * represented as IDs or Arrays of IDs + * represented as model instances + * represented as URLs, under the `links` key - /** - Provides info about the model for debugging purposes - by grouping the properties into more semantic groups. + For this model: - Meant to be used by debugging tools such as the Chrome Ember Extension. + ```js + App.Person = DS.Model.extend({ + firstName: DS.attr(), + lastName: DS.attr(), - - Groups all attributes in "Attributes" group. - - Groups all belongsTo relationships in "Belongs To" group. - - Groups all hasMany relationships in "Has Many" group. - - Groups all flags in "Flags" group. - - Flags relationship CPs as expensive properties. + children: DS.hasMany('person') + }); + ``` - @method _debugInfo - @for DS.Model - @private - */ - _debugInfo: function() { - var attributes = ['id'], - relationships = { belongsTo: [], hasMany: [] }, - expensiveProperties = []; + To represent the children as IDs: - this.eachAttribute(function(name, meta) { - attributes.push(name); - }, this); + ```js + { + id: 1, + firstName: "Tom", + lastName: "Dale", + children: [1, 2, 3] + } + ``` - this.eachRelationship(function(name, relationship) { - relationships[relationship.kind].push(name); - expensiveProperties.push(name); + To represent the children relationship as a URL: + + ```js + { + id: 1, + firstName: "Tom", + lastName: "Dale", + links: { + children: "/people/1/children" + } + } + ``` + + If you're streaming data or implementing an adapter, + make sure that you have converted the incoming data + into this form. + + This method can be used both to push in brand new + records, as well as to update existing records. + + @method push + @param {String or subclass of DS.Model} type + @param {Object} data + @returns {DS.Model} the record that was created or + updated. + */ + push: function(type, data, _partial) { + // _partial is an internal param used by `update`. + // If passed, it means that the data should be + // merged into the existing data, not replace it. + + Ember.assert("You must include an `id` in a hash passed to `push`", data.id != null); + + type = this.modelFor(type); + + // normalize relationship IDs into records + data = normalizeRelationships(this, type, data); + + this._load(type, data, _partial); + + return this.recordForId(type, data.id); + }, + + /** + Push some raw data into the store. + + The data will be automatically deserialized using the + serializer for the `type` param. + + This method can be used both to push in brand new + records, as well as to update existing records. + + You can push in more than one type of object at once. + All objects should be in the format expected by the + serializer. + + ```js + App.ApplicationSerializer = DS.ActiveModelSerializer; + + var pushData = { + posts: [ + {id: 1, post_title: "Great post", comment_ids: [2]} + ], + comments: [ + {id: 2, comment_body: "Insightful comment"} + ] + } + + store.pushPayload('post', pushData); + ``` + + @method pushPayload + @param {String} type + @param {Object} payload + */ + pushPayload: function (type, payload) { + var serializer; + if (!payload) { + payload = type; + serializer = defaultSerializer(this.container); + Ember.assert("You cannot use `store#pushPayload` without a type unless your default serializer defines `pushPayload`", serializer.pushPayload); + } else { + serializer = this.serializerFor(type); + } + serializer.pushPayload(this, payload); + }, + + /** + Update existing records in the store. Unlike [push](#method_push), + update will merge the new data properties with the existing + properties. This makes it safe to use with a subset of record + attributes. This method expects normalized data. + + `update` is useful if you app broadcasts partial updates to + records. + + ```js + App.Person = DS.Model.extend({ + firstName: DS.attr('string'), + lastName: DS.attr('string') + }); + + store.get('person', 1).then(function(tom) { + tom.get('firstName'); // Tom + tom.get('lastName'); // Dale + + var updateEvent = {id: 1, firstName: "TomHuda"}; + store.update('person', updateEvent); + + tom.get('firstName'); // TomHuda + tom.get('lastName'); // Dale + }); + ``` + + @method update + @param {String} type + @param {Object} data + @return {DS.Model} the record that was updated. + */ + update: function(type, data) { + Ember.assert("You must include an `id` in a hash passed to `update`", data.id != null); + + return this.push(type, data, true); + }, + + /** + If you have an Array of normalized data to push, + you can call `pushMany` with the Array, and it will + call `push` repeatedly for you. + + @method pushMany + @param {String or subclass of DS.Model} type + @param {Array} datas + @return {Array} + */ + pushMany: function(type, datas) { + return map(datas, function(data) { + return this.push(type, data); + }, this); + }, + + /** + If you have some metadata to set for a type + you can call `metaForType`. + + @method metaForType + @param {String or subclass of DS.Model} type + @param {Object} metadata + */ + metaForType: function(type, metadata) { + type = this.modelFor(type); + + Ember.merge(this.typeMapFor(type).metadata, metadata); + }, + + /** + Build a brand new record for a given type, ID, and + initial data. + + @method buildRecord + @private + @param {subclass of DS.Model} type + @param {String} id + @param {Object} data + @returns {DS.Model} record + */ + buildRecord: function(type, id, data) { + var typeMap = this.typeMapFor(type), + idToRecord = typeMap.idToRecord; + + Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToRecord[id]); + Ember.assert("`" + Ember.inspect(type)+ "` does not appear to be an ember-data model", (typeof type._create === 'function') ); + + // lookupFactory should really return an object that creates + // instances with the injections applied + var record = type._create({ + id: id, + store: this, + container: this.container + }); + + if (data) { + record.setupData(data); + } + + // if we're creating an item, this process will be done + // later, once the object has been persisted. + if (id) { + idToRecord[id] = record; + } + + typeMap.records.push(record); + + return record; + }, + + // ............... + // . DESTRUCTION . + // ............... + + /** + When a record is destroyed, this un-indexes it and + removes it from any record arrays so it can be GCed. + + @method dematerializeRecord + @private + @param {DS.Model} record + */ + dematerializeRecord: function(record) { + var type = record.constructor, + typeMap = this.typeMapFor(type), + id = get(record, 'id'); + + record.updateRecordArrays(); + + if (id) { + delete typeMap.idToRecord[id]; + } + + var loc = indexOf(typeMap.records, record); + typeMap.records.splice(loc, 1); + }, + + // ........................ + // . RELATIONSHIP CHANGES . + // ........................ + + addRelationshipChangeFor: function(childRecord, childKey, parentRecord, parentKey, change) { + var clientId = childRecord.clientId, + parentClientId = parentRecord ? parentRecord : parentRecord; + var key = childKey + parentKey; + var changes = this._relationshipChanges; + if (!(clientId in changes)) { + changes[clientId] = {}; + } + if (!(parentClientId in changes[clientId])) { + changes[clientId][parentClientId] = {}; + } + if (!(key in changes[clientId][parentClientId])) { + changes[clientId][parentClientId][key] = {}; + } + changes[clientId][parentClientId][key][change.changeType] = change; + }, + + removeRelationshipChangeFor: function(clientRecord, childKey, parentRecord, parentKey, type) { + var clientId = clientRecord.clientId, + parentClientId = parentRecord ? parentRecord.clientId : parentRecord; + var changes = this._relationshipChanges; + var key = childKey + parentKey; + if (!(clientId in changes) || !(parentClientId in changes[clientId]) || !(key in changes[clientId][parentClientId])){ + return; + } + delete changes[clientId][parentClientId][key][type]; + }, + + relationshipChangePairsFor: function(record){ + var toReturn = []; + + if( !record ) { return toReturn; } + + //TODO(Igor) What about the other side + var changesObject = this._relationshipChanges[record.clientId]; + for (var objKey in changesObject){ + if(changesObject.hasOwnProperty(objKey)){ + for (var changeKey in changesObject[objKey]){ + if(changesObject[objKey].hasOwnProperty(changeKey)){ + toReturn.push(changesObject[objKey][changeKey]); + } + } + } + } + return toReturn; + }, + + // ...................... + // . PER-TYPE ADAPTERS + // ...................... + + /** + Returns the adapter for a given type. + + @method adapterFor + @private + @param {subclass of DS.Model} type + @returns DS.Adapter + */ + adapterFor: function(type) { + var container = this.container, adapter; + + if (container) { + adapter = container.lookup('adapter:' + type.typeKey) || container.lookup('adapter:application'); + } + + return adapter || get(this, 'defaultAdapter'); + }, + + // .............................. + // . RECORD CHANGE NOTIFICATION . + // .............................. + + /** + Returns an instance of the serializer for a given type. For + example, `serializerFor('person')` will return an instance of + `App.PersonSerializer`. + + If no `App.PersonSerializer` is found, this method will look + for an `App.ApplicationSerializer` (the default serializer for + your entire application). + + If no `App.ApplicationSerializer` is found, it will fall back + to an instance of `DS.JSONSerializer`. + + @method serializerFor + @private + @param {String} type the record to serialize + @return {DS.Serializer} + */ + serializerFor: function(type) { + type = this.modelFor(type); + var adapter = this.adapterFor(type); + + return serializerFor(this.container, type.typeKey, adapter && adapter.defaultSerializer); + }, + + willDestroy: function() { + var map = this.typeMaps; + var keys = Ember.keys(map); + var store = this; + var types = keys.map(byType); + + this.recordArrayManager.destroy(); + + types.forEach(this.unloadAll, this); + + function byType(entry) { + return map[entry].type; + } + } }); - var groups = [ - { - name: 'Attributes', - properties: attributes, - expand: true - }, - { - name: 'Belongs To', - properties: relationships.belongsTo, - expand: true - }, - { - name: 'Has Many', - properties: relationships.hasMany, - expand: true - }, - { - name: 'Flags', - properties: ['isLoaded', 'isDirty', 'isSaving', 'isDeleted', 'isError', 'isNew', 'isValid'] - } - ]; - - return { - propertyInfo: { - // include all other mixins / properties (not just the grouped ones) - includeOtherProperties: true, - groups: groups, - // don't pre-calculate unless cached - expensiveProperties: expensiveProperties - } - }; - } - -}); - -})(); - - - -(function() { -/** - @module ember-data -*/ - -})(); - - - -(function() { -/** - Ember Data - - @module ember-data - @main ember-data -*/ - -})(); - -(function() { -Ember.String.pluralize = function(word) { - return Ember.Inflector.inflector.pluralize(word); -}; - -Ember.String.singularize = function(word) { - return Ember.Inflector.inflector.singularize(word); -}; - -})(); - - - -(function() { -var BLANK_REGEX = /^\s*$/; - -function loadUncountable(rules, uncountable) { - for (var i = 0, length = uncountable.length; i < length; i++) { - rules.uncountable[uncountable[i].toLowerCase()] = true; - } -} - -function loadIrregular(rules, irregularPairs) { - var pair; - - for (var i = 0, length = irregularPairs.length; i < length; i++) { - pair = irregularPairs[i]; - - rules.irregular[pair[0].toLowerCase()] = pair[1]; - rules.irregularInverse[pair[1].toLowerCase()] = pair[0]; - } -} - -/** - Inflector.Ember provides a mechanism for supplying inflection rules for your - application. Ember includes a default set of inflection rules, and provides an - API for providing additional rules. - - Examples: - - Creating an inflector with no rules. - - ```js - var inflector = new Ember.Inflector(); - ``` - - Creating an inflector with the default ember ruleset. - - ```js - var inflector = new Ember.Inflector(Ember.Inflector.defaultRules); - - inflector.pluralize('cow') //=> 'kine' - inflector.singularize('kine') //=> 'cow' - ``` - - Creating an inflector and adding rules later. - - ```javascript - var inflector = Ember.Inflector.inflector; - - inflector.pluralize('advice') // => 'advices' - inflector.uncountable('advice'); - inflector.pluralize('advice') // => 'advice' - - inflector.pluralize('formula') // => 'formulas' - inflector.irregular('formula', 'formulae'); - inflector.pluralize('formula') // => 'formulae' - - // you would not need to add these as they are the default rules - inflector.plural(/$/, 's'); - inflector.singular(/s$/i, ''); - ``` - - Creating an inflector with a nondefault ruleset. - - ```javascript - var rules = { - plurals: [ /$/, 's' ], - singular: [ /\s$/, '' ], - irregularPairs: [ - [ 'cow', 'kine' ] - ], - uncountable: [ 'fish' ] - }; - - var inflector = new Ember.Inflector(rules); - ``` - - @class Inflector - @namespace Ember -*/ -function Inflector(ruleSet) { - ruleSet = ruleSet || {}; - ruleSet.uncountable = ruleSet.uncountable || {}; - ruleSet.irregularPairs = ruleSet.irregularPairs || {}; - - var rules = this.rules = { - plurals: ruleSet.plurals || [], - singular: ruleSet.singular || [], - irregular: {}, - irregularInverse: {}, - uncountable: {} - }; - - loadUncountable(rules, ruleSet.uncountable); - loadIrregular(rules, ruleSet.irregularPairs); -} - -Inflector.prototype = { - /** - @method plural - @param {RegExp} regex - @param {String} string - */ - plural: function(regex, string) { - this.rules.plurals.push([regex, string.toLowerCase()]); - }, - - /** - @method singular - @param {RegExp} regex - @param {String} string - */ - singular: function(regex, string) { - this.rules.singular.push([regex, string.toLowerCase()]); - }, - - /** - @method uncountable - @param {String} regex - */ - uncountable: function(string) { - loadUncountable(this.rules, [string.toLowerCase()]); - }, - - /** - @method irregular - @param {String} singular - @param {String} plural - */ - irregular: function (singular, plural) { - loadIrregular(this.rules, [[singular, plural]]); - }, - - /** - @method pluralize - @param {String} word - */ - pluralize: function(word) { - return this.inflect(word, this.rules.plurals, this.rules.irregular); - }, - - /** - @method singularize - @param {String} word - */ - singularize: function(word) { - return this.inflect(word, this.rules.singular, this.rules.irregularInverse); - }, - - /** - @protected - - @method inflect - @param {String} word - @param {Object} typeRules - @param {Object} irregular - */ - inflect: function(word, typeRules, irregular) { - var inflection, substitution, result, lowercase, isBlank, - isUncountable, isIrregular, isIrregularInverse, rule; - - isBlank = BLANK_REGEX.test(word); - - if (isBlank) { - return word; - } - - lowercase = word.toLowerCase(); - - isUncountable = this.rules.uncountable[lowercase]; - - if (isUncountable) { - return word; - } - - isIrregular = irregular && irregular[lowercase]; - - if (isIrregular) { - return isIrregular; - } - - for (var i = typeRules.length, min = 0; i > min; i--) { - inflection = typeRules[i-1]; - rule = inflection[0]; - - if (rule.test(word)) { - break; - } - } - - inflection = inflection || []; - - rule = inflection[0]; - substitution = inflection[1]; - - result = word.replace(rule, substitution); - - return result; - } -}; - -Ember.Inflector = Inflector; - -})(); - - - -(function() { -Ember.Inflector.defaultRules = { - plurals: [ - [/$/, 's'], - [/s$/i, 's'], - [/^(ax|test)is$/i, '$1es'], - [/(octop|vir)us$/i, '$1i'], - [/(octop|vir)i$/i, '$1i'], - [/(alias|status)$/i, '$1es'], - [/(bu)s$/i, '$1ses'], - [/(buffal|tomat)o$/i, '$1oes'], - [/([ti])um$/i, '$1a'], - [/([ti])a$/i, '$1a'], - [/sis$/i, 'ses'], - [/(?:([^f])fe|([lr])f)$/i, '$1$2ves'], - [/(hive)$/i, '$1s'], - [/([^aeiouy]|qu)y$/i, '$1ies'], - [/(x|ch|ss|sh)$/i, '$1es'], - [/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'], - [/^(m|l)ouse$/i, '$1ice'], - [/^(m|l)ice$/i, '$1ice'], - [/^(ox)$/i, '$1en'], - [/^(oxen)$/i, '$1'], - [/(quiz)$/i, '$1zes'] - ], - - singular: [ - [/s$/i, ''], - [/(ss)$/i, '$1'], - [/(n)ews$/i, '$1ews'], - [/([ti])a$/i, '$1um'], - [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '$1sis'], - [/(^analy)(sis|ses)$/i, '$1sis'], - [/([^f])ves$/i, '$1fe'], - [/(hive)s$/i, '$1'], - [/(tive)s$/i, '$1'], - [/([lr])ves$/i, '$1f'], - [/([^aeiouy]|qu)ies$/i, '$1y'], - [/(s)eries$/i, '$1eries'], - [/(m)ovies$/i, '$1ovie'], - [/(x|ch|ss|sh)es$/i, '$1'], - [/^(m|l)ice$/i, '$1ouse'], - [/(bus)(es)?$/i, '$1'], - [/(o)es$/i, '$1'], - [/(shoe)s$/i, '$1'], - [/(cris|test)(is|es)$/i, '$1is'], - [/^(a)x[ie]s$/i, '$1xis'], - [/(octop|vir)(us|i)$/i, '$1us'], - [/(alias|status)(es)?$/i, '$1'], - [/^(ox)en/i, '$1'], - [/(vert|ind)ices$/i, '$1ex'], - [/(matr)ices$/i, '$1ix'], - [/(quiz)zes$/i, '$1'], - [/(database)s$/i, '$1'] - ], - - irregularPairs: [ - ['person', 'people'], - ['man', 'men'], - ['child', 'children'], - ['sex', 'sexes'], - ['move', 'moves'], - ['cow', 'kine'], - ['zombie', 'zombies'] - ], - - uncountable: [ - 'equipment', - 'information', - 'rice', - 'money', - 'species', - 'series', - 'fish', - 'sheep', - 'jeans', - 'police' - ] -}; - -})(); - - - -(function() { -if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { - /** - See {{#crossLink "Ember.String/pluralize"}}{{/crossLink}} - - @method pluralize - @for String - */ - String.prototype.pluralize = function() { - return Ember.String.pluralize(this); - }; - - /** - See {{#crossLink "Ember.String/singularize"}}{{/crossLink}} - - @method singularize - @for String - */ - String.prototype.singularize = function() { - return Ember.String.singularize(this); - }; -} - -})(); - - - -(function() { -Ember.Inflector.inflector = new Ember.Inflector(Ember.Inflector.defaultRules); - -})(); - - - -(function() { - -})(); - -(function() { -/** - @module ember-data -*/ - -var get = Ember.get; -var forEach = Ember.EnumerableUtils.forEach; - -DS.ActiveModelSerializer = DS.RESTSerializer.extend({ - // SERIALIZE - - /** - Converts camelcased attributes to underscored when serializing. - - @method keyForAttribute - @param {String} attribute - @returns String - */ - keyForAttribute: function(attr) { - return Ember.String.decamelize(attr); - }, - - /** - Underscores relationship names and appends "_id" or "_ids" when serializing - relationship keys. - - @method keyForRelationship - @param {String} key - @param {String} kind - @returns String - */ - keyForRelationship: function(key, kind) { - key = Ember.String.decamelize(key); - if (kind === "belongsTo") { - return key + "_id"; - } else if (kind === "hasMany") { - return Ember.String.singularize(key) + "_ids"; - } else { - return key; - } - }, - - /** - Does not serialize hasMany relationships by default. - */ - serializeHasMany: Ember.K, - - /** - Underscores the JSON root keys when serializing. - - @method serializeIntoHash - @param {Object} hash - @param {subclass of DS.Model} type - @param {DS.Model} record - @param {Object} options - */ - serializeIntoHash: function(data, type, record, options) { - var root = Ember.String.decamelize(type.typeKey); - data[root] = this.serialize(record, options); - }, - - /** - Serializes a polymorphic type as a fully capitalized model name. - - @method serializePolymorphicType - @param {DS.Model} record - @param {Object} json - @param relationship - */ - serializePolymorphicType: function(record, json, relationship) { - var key = relationship.key, - belongsTo = get(record, key); - key = this.keyForAttribute(key); - json[key + "_type"] = Ember.String.capitalize(belongsTo.constructor.typeKey); - }, - - // EXTRACT - - /** - Extracts the model typeKey from underscored root objects. - - @method typeForRoot - @param {String} root - @returns String the model's typeKey - */ - typeForRoot: function(root) { - var camelized = Ember.String.camelize(root); - return Ember.String.singularize(camelized); - }, - - /** - Add extra step to `DS.RESTSerializer.normalize` so links are - normalized. - - If your payload looks like this - - ```js - { - "post": { - "id": 1, - "title": "Rails is omakase", - "links": { "flagged_comments": "api/comments/flagged" } - } - } - ``` - The normalized version would look like this - - ```js - { - "post": { - "id": 1, - "title": "Rails is omakase", - "links": { "flaggedComments": "api/comments/flagged" } - } - } - ``` - - @method normalize - @param {subclass of DS.Model} type - @param {Object} hash - @param {String} prop - @returns Object - */ - - normalize: function(type, hash, prop) { - this.normalizeLinks(hash); - - return this._super(type, hash, prop); - }, - - /** - Convert `snake_cased` links to `camelCase` - - @method normalizeLinks - @param {Object} hash - */ - - normalizeLinks: function(data){ - if (data.links) { - var links = data.links; - - for (var link in links) { - var camelizedLink = Ember.String.camelize(link); - - if (camelizedLink !== link) { - links[camelizedLink] = links[link]; - delete links[link]; - } - } - } - }, - - /** - Normalize the polymorphic type from the JSON. - - Normalize: - ```js - { - id: "1" - minion: { type: "evil_minion", id: "12"} - } - ``` - - To: - ```js - { - id: "1" - minion: { type: "evilMinion", id: "12"} - } - ``` - - @method normalizeRelationships - @private - */ - normalizeRelationships: function(type, hash) { - var payloadKey, payload; - - if (this.keyForRelationship) { + function normalizeRelationships(store, type, data, record) { type.eachRelationship(function(key, relationship) { - if (relationship.options.polymorphic) { - payloadKey = this.keyForAttribute(key); - payload = hash[payloadKey]; - if (payload && payload.type) { - payload.type = this.typeForRoot(payload.type); - } else if (payload && relationship.kind === "hasMany") { - var self = this; - forEach(payload, function(single) { - single.type = self.typeForRoot(single.type); - }); - } - } else { - payloadKey = this.keyForRelationship(key, relationship.kind); - payload = hash[payloadKey]; + // A link (usually a URL) was already provided in + // normalized form + if (data.links && data.links[key]) { + if (record && relationship.options.async) { record._relationships[key] = null; } + return; } - hash[key] = payload; + var kind = relationship.kind, + value = data[key]; - if (key !== payloadKey) { - delete hash[payloadKey]; + if (value == null) { return; } + + if (kind === 'belongsTo') { + deserializeRecordId(store, data, key, relationship, value); + } else if (kind === 'hasMany') { + deserializeRecordIds(store, data, key, relationship, value); + addUnsavedRecords(record, key, value); } - }, this); - } - } -}); + }); -})(); - - - -(function() { -var get = Ember.get; -var forEach = Ember.EnumerableUtils.forEach; - -/** - The EmbeddedRecordsMixin allows you to add embedded record support to your - serializers. - To set up embedded records, you include the mixin into the serializer and then - define your embedded relations. - - ```js - App.PostSerializer = DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, { - attrs: { - comments: {embedded: 'always'} - } - }) - ``` - - Currently only `{embedded: 'always'}` records are supported. - - @class EmbeddedRecordsMixin - @namespace DS -*/ -DS.EmbeddedRecordsMixin = Ember.Mixin.create({ - - /** - Serialize has-may relationship when it is configured as embedded objects. - - @method serializeHasMany - */ - serializeHasMany: function(record, json, relationship) { - var key = relationship.key, - attrs = get(this, 'attrs'), - embed = attrs && attrs[key] && attrs[key].embedded === 'always'; - - if (embed) { - json[this.keyForAttribute(key)] = get(record, key).map(function(relation) { - var data = relation.serialize(), - primaryKey = get(this, 'primaryKey'); - - data[primaryKey] = get(relation, primaryKey); - - return data; - }, this); - } - }, - - /** - Extract embedded objects out of the payload for a single object - and add them as sideloaded objects instead. - - @method extractSingle - */ - extractSingle: function(store, primaryType, payload, recordId, requestType) { - var root = this.keyForAttribute(primaryType.typeKey), - partial = payload[root]; - - updatePayloadWithEmbedded(store, this, primaryType, partial, payload); - - return this._super(store, primaryType, payload, recordId, requestType); - }, - - /** - Extract embedded objects out of a standard payload - and add them as sideloaded objects instead. - - @method extractArray - */ - extractArray: function(store, type, payload) { - var root = this.keyForAttribute(type.typeKey), - partials = payload[Ember.String.pluralize(root)]; - - forEach(partials, function(partial) { - updatePayloadWithEmbedded(store, this, type, partial, payload); - }, this); - - return this._super(store, type, payload); - } -}); - -function updatePayloadWithEmbedded(store, serializer, type, partial, payload) { - var attrs = get(serializer, 'attrs'); - - if (!attrs) { - return; - } - - type.eachRelationship(function(key, relationship) { - var expandedKey, embeddedTypeKey, attribute, ids, - config = attrs[key], - serializer = store.serializerFor(relationship.type.typeKey), - primaryKey = get(serializer, "primaryKey"); - - if (relationship.kind !== "hasMany") { - return; + return data; } - if (config && (config.embedded === 'always' || config.embedded === 'load')) { - // underscore forces the embedded records to be side loaded. - // it is needed when main type === relationship.type - embeddedTypeKey = '_' + Ember.String.pluralize(relationship.type.typeKey); - expandedKey = this.keyForRelationship(key, relationship.kind); - attribute = this.keyForAttribute(key); - ids = []; - - if (!partial[attribute]) { + function deserializeRecordId(store, data, key, relationship, id) { + if (!Model) { Model = requireModule("ember-data/lib/system/model")["Model"]; } + if (isNone(id) || id instanceof Model) { return; } - payload[embeddedTypeKey] = payload[embeddedTypeKey] || []; + var type; - forEach(partial[attribute], function(data) { - var embeddedType = store.modelFor(relationship.type.typeKey); - updatePayloadWithEmbedded(store, serializer, embeddedType, data, payload); - ids.push(data[primaryKey]); - payload[embeddedTypeKey].push(data); + if (typeof id === 'number' || typeof id === 'string') { + type = typeFor(relationship, key, data); + data[key] = store.recordForId(type, id); + } else if (typeof id === 'object') { + // polymorphic + data[key] = store.recordForId(id.type, id.id); + } + } + + function typeFor(relationship, key, data) { + if (relationship.options.polymorphic) { + return data[key + "Type"]; + } else { + return relationship.type; + } + } + + function deserializeRecordIds(store, data, key, relationship, ids) { + for (var i=0, l=ids.length; i "famous_people" - ``` - - @method pathForType - @param {String} type - @returns String - */ - pathForType: function(type) { - var decamelized = Ember.String.decamelize(type); - return Ember.String.pluralize(decamelized); - }, - - /** - The ActiveModelAdapter overrides the `ajaxError` method - to return a DS.InvalidError for all 422 Unprocessable Entity - responses. - - A 422 HTTP response from the server generally implies that the request - was well formed but the API was unable to process it because the - content was not semantically correct or meaningful per the API. - - For more information on 422 HTTP Error code see 11.2 WebDAV RFC 4918 - https://tools.ietf.org/html/rfc4918#section-11.2 - - @method ajaxError - @param jqXHR - @returns error - */ - ajaxError: function(jqXHR) { - var error = this._super(jqXHR); - - if (jqXHR && jqXHR.status === 422) { - var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"], - errors = {}; - - forEach(Ember.keys(jsonErrors), function(key) { - errors[Ember.String.camelize(key)] = jsonErrors[key]; + ```javascript + var promiseObject = DS.PromiseObject.create({ + promise: $.getJSON('/some/remote/data.json') }); - return new DS.InvalidError(errors); - } else { - return error; + promiseObject.get('name'); // null + + promiseObject.then(function() { + promiseObject.get('name'); // 'Tomster' + }); + ``` + + @class PromiseObject + @namespace DS + @extends Ember.ObjectProxy + @uses Ember.PromiseProxyMixin + */ + PromiseObject = Ember.ObjectProxy.extend(Ember.PromiseProxyMixin); + + function promiseObject(promise, label) { + return PromiseObject.create({ + promise: Promise.cast(promise, label) + }); } - } -}); -})(); + function promiseArray(promise, label) { + return PromiseArray.create({ + promise: Promise.cast(promise, label) + }); + } + + function isThenable(object) { + return object && typeof object.then === 'function'; + } + + function serializerFor(container, type, defaultSerializer) { + return container.lookup('serializer:'+type) || + container.lookup('serializer:application') || + container.lookup('serializer:' + defaultSerializer) || + container.lookup('serializer:-default'); + } + + function defaultSerializer(container) { + return container.lookup('serializer:application') || + container.lookup('serializer:-default'); + } + + function serializerForAdapter(adapter, type) { + var serializer = adapter.serializer, + defaultSerializer = adapter.defaultSerializer, + container = adapter.container; + + if (container && serializer === undefined) { + serializer = serializerFor(container, type.typeKey, defaultSerializer); + } + + if (serializer === null || serializer === undefined) { + serializer = { + extract: function(store, type, payload) { return payload; } + }; + } + + return serializer; + } + + function _find(adapter, store, type, id) { + var promise = adapter.find(store, type, id), + serializer = serializerForAdapter(adapter, type), + label = "DS: Handle Adapter#find of " + type + " with id: " + id; + + return Promise.cast(promise, label).then(function(adapterPayload) { + Ember.assert("You made a request for a " + type.typeKey + " with id " + id + ", but the adapter's response did not have any data", adapterPayload); + var payload = serializer.extract(store, type, adapterPayload, id, 'find'); + + return store.push(type, payload); + }, function(error) { + var record = store.getById(type, id); + record.notFound(); + throw error; + }, "DS: Extract payload of '" + type + "'"); + } + + function _findMany(adapter, store, type, ids, owner) { + var promise = adapter.findMany(store, type, ids, owner), + serializer = serializerForAdapter(adapter, type), + label = "DS: Handle Adapter#findMany of " + type; + + return Promise.cast(promise, label).then(function(adapterPayload) { + var payload = serializer.extract(store, type, adapterPayload, null, 'findMany'); + + Ember.assert("The response from a findMany must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array'); + + store.pushMany(type, payload); + }, null, "DS: Extract payload of " + type); + } + + function _findHasMany(adapter, store, record, link, relationship) { + var promise = adapter.findHasMany(store, record, link, relationship), + serializer = serializerForAdapter(adapter, relationship.type), + label = "DS: Handle Adapter#findHasMany of " + record + " : " + relationship.type; + + return Promise.cast(promise, label).then(function(adapterPayload) { + var payload = serializer.extract(store, relationship.type, adapterPayload, null, 'findHasMany'); + + Ember.assert("The response from a findHasMany must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array'); + + var records = store.pushMany(relationship.type, payload); + record.updateHasMany(relationship.key, records); + }, null, "DS: Extract payload of " + record + " : hasMany " + relationship.type); + } + + function _findBelongsTo(adapter, store, record, link, relationship) { + var promise = adapter.findBelongsTo(store, record, link, relationship), + serializer = serializerForAdapter(adapter, relationship.type), + label = "DS: Handle Adapter#findBelongsTo of " + record + " : " + relationship.type; + + return Promise.cast(promise, label).then(function(adapterPayload) { + var payload = serializer.extract(store, relationship.type, adapterPayload, null, 'findBelongsTo'); + var record = store.push(relationship.type, payload); + + record.updateBelongsTo(relationship.key, record); + return record; + }, null, "DS: Extract payload of " + record + " : " + relationship.type); + } + + function _findAll(adapter, store, type, sinceToken) { + var promise = adapter.findAll(store, type, sinceToken), + serializer = serializerForAdapter(adapter, type), + label = "DS: Handle Adapter#findAll of " + type; + + return Promise.cast(promise, label).then(function(adapterPayload) { + var payload = serializer.extract(store, type, adapterPayload, null, 'findAll'); + + Ember.assert("The response from a findAll must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array'); + + store.pushMany(type, payload); + store.didUpdateAll(type); + return store.all(type); + }, null, "DS: Extract payload of findAll " + type); + } + + function _findQuery(adapter, store, type, query, recordArray) { + var promise = adapter.findQuery(store, type, query, recordArray), + serializer = serializerForAdapter(adapter, type), + label = "DS: Handle Adapter#findQuery of " + type; + + return Promise.cast(promise, label).then(function(adapterPayload) { + var payload = serializer.extract(store, type, adapterPayload, null, 'findQuery'); + + Ember.assert("The response from a findQuery must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array'); + + recordArray.load(payload); + return recordArray; + }, null, "DS: Extract payload of findQuery " + type); + } + + function _commit(adapter, store, operation, record) { + var type = record.constructor, + promise = adapter[operation](store, type, record), + serializer = serializerForAdapter(adapter, type), + label = "DS: Extract and notify about " + operation + " completion of " + record; + + Ember.assert("Your adapter's '" + operation + "' method must return a promise, but it returned " + promise, isThenable(promise)); + + return promise.then(function(adapterPayload) { + var payload; + + if (adapterPayload) { + payload = serializer.extract(store, type, adapterPayload, get(record, 'id'), operation); + } else { + payload = adapterPayload; + } + + store.didSaveRecord(record, payload); + return record; + }, function(reason) { + if (reason instanceof DS.InvalidError) { + store.recordWasInvalid(record, reason.errors); + } else { + store.recordWasError(record, reason); + } + + throw reason; + }, label); + } + + __exports__.Store = Store; + __exports__.PromiseArray = PromiseArray; + __exports__.PromiseObject = PromiseObject; + __exports__["default"] = Store; + }); +define("ember-data/lib/transforms", + ["./transforms/base","./transforms/number","./transforms/date","./transforms/string","./transforms/boolean","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { + "use strict"; + var Transform = __dependency1__["default"]; + var NumberTransform = __dependency2__["default"]; + var DateTransform = __dependency3__["default"]; + var StringTransform = __dependency4__["default"]; + var BooleanTransform = __dependency5__["default"]; + + __exports__.Transform = Transform; + __exports__.NumberTransform = NumberTransform; + __exports__.DateTransform = DateTransform; + __exports__.StringTransform = StringTransform; + __exports__.BooleanTransform = BooleanTransform; + }); +define("ember-data/lib/transforms/base", + ["exports"], + function(__exports__) { + "use strict"; + /** + The `DS.Transform` class is used to serialize and deserialize model + attributes when they are saved or loaded from an + adapter. Subclassing `DS.Transform` is useful for creating custom + attributes. All subclasses of `DS.Transform` must implement a + `serialize` and a `deserialize` method. + + Example + + ```javascript + App.RawTransform = DS.Transform.extend({ + deserialize: function(serialized) { + return serialized; + }, + serialize: function(deserialized) { + return deserialized; + } + }); + ``` + + Usage + + ```javascript + var attr = DS.attr; + App.Requirement = DS.Model.extend({ + name: attr('string'), + optionsArray: attr('raw') + }); + ``` + + @class Transform + @namespace DS + */ + var Transform = Ember.Object.extend({ + /** + When given a deserialized value from a record attribute this + method must return the serialized value. + + Example + + ```javascript + serialize: function(deserialized) { + return Ember.isEmpty(deserialized) ? null : Number(deserialized); + } + ``` + + @method serialize + @param deserialized The deserialized value + @return The serialized value + */ + serialize: Ember.required(), + + /** + When given a serialize value from a JSON object this method must + return the deserialized value for the record attribute. + + Example + + ```javascript + deserialize: function(serialized) { + return empty(serialized) ? null : Number(serialized); + } + ``` + + @method deserialize + @param serialized The serialized value + @return The deserialized value + */ + deserialize: Ember.required() + + }); + + __exports__["default"] = Transform; + }); +define("ember-data/lib/transforms/boolean", + ["./base","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Transform = __dependency1__["default"]; + + /** + The `DS.BooleanTransform` class is used to serialize and deserialize + boolean attributes on Ember Data record objects. This transform is + used when `boolean` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + Usage + + ```javascript + var attr = DS.attr; + App.User = DS.Model.extend({ + isAdmin: attr('boolean'), + name: attr('string'), + email: attr('string') + }); + ``` + + @class BooleanTransform + @extends DS.Transform + @namespace DS + */ + var BooleanTransform = Transform.extend({ + deserialize: function(serialized) { + var type = typeof serialized; + + if (type === "boolean") { + return serialized; + } else if (type === "string") { + return serialized.match(/^true$|^t$|^1$/i) !== null; + } else if (type === "number") { + return serialized === 1; + } else { + return false; + } + }, + + serialize: function(deserialized) { + return Boolean(deserialized); + } + }); + __exports__["default"] = BooleanTransform; + }); +define("ember-data/lib/transforms/date", + ["./base","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /** + The `DS.DateTransform` class is used to serialize and deserialize + date attributes on Ember Data record objects. This transform is used + when `date` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + ```javascript + var attr = DS.attr; + App.Score = DS.Model.extend({ + value: attr('number'), + player: DS.belongsTo('player'), + date: attr('date') + }); + ``` + + @class DateTransform + @extends DS.Transform + @namespace DS + */ + var Transform = __dependency1__["default"]; + var DateTransform = Transform.extend({ + + deserialize: function(serialized) { + var type = typeof serialized; + + if (type === "string") { + return new Date(Ember.Date.parse(serialized)); + } else if (type === "number") { + return new Date(serialized); + } else if (serialized === null || serialized === undefined) { + // if the value is not present in the data, + // return undefined, not null. + return serialized; + } else { + return null; + } + }, + + serialize: function(date) { + if (date instanceof Date) { + var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + + var pad = function(num) { + return num < 10 ? "0"+num : ""+num; + }; + + var utcYear = date.getUTCFullYear(), + utcMonth = date.getUTCMonth(), + utcDayOfMonth = date.getUTCDate(), + utcDay = date.getUTCDay(), + utcHours = date.getUTCHours(), + utcMinutes = date.getUTCMinutes(), + utcSeconds = date.getUTCSeconds(); + var dayOfWeek = days[utcDay]; + var dayOfMonth = pad(utcDayOfMonth); + var month = months[utcMonth]; -(function() { + return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " + + pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT"; + } else { + return null; + } + } -})(); + }); + __exports__["default"] = DateTransform; + }); +define("ember-data/lib/transforms/number", + ["./base","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Transform = __dependency1__["default"]; + var empty = Ember.isEmpty; -(function() { -Ember.onLoad('Ember.Application', function(Application) { - Application.initializer({ - name: "activeModelAdapter", + /** + The `DS.NumberTransform` class is used to serialize and deserialize + numeric attributes on Ember Data record objects. This transform is + used when `number` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. - initialize: function(container, application) { - application.register('serializer:_ams', DS.ActiveModelSerializer); - application.register('adapter:_ams', DS.ActiveModelAdapter); + Usage + + ```javascript + var attr = DS.attr; + App.Score = DS.Model.extend({ + value: attr('number'), + player: DS.belongsTo('player'), + date: attr('date') + }); + ``` + + @class NumberTransform + @extends DS.Transform + @namespace DS + */ + var NumberTransform = Transform.extend({ + + deserialize: function(serialized) { + return empty(serialized) ? null : Number(serialized); + }, + + serialize: function(deserialized) { + return empty(deserialized) ? null : Number(deserialized); + } + }); + + __exports__["default"] = NumberTransform; + }); +define("ember-data/lib/transforms/string", + ["./base","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Transform = __dependency1__["default"]; + var none = Ember.isNone; + + /** + The `DS.StringTransform` class is used to serialize and deserialize + string attributes on Ember Data record objects. This transform is + used when `string` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + Usage + + ```javascript + var attr = DS.attr; + App.User = DS.Model.extend({ + isAdmin: attr('boolean'), + name: attr('string'), + email: attr('string') + }); + ``` + + @class StringTransform + @extends DS.Transform + @namespace DS + */ + var StringTransform = Transform.extend({ + + deserialize: function(serialized) { + return none(serialized) ? null : String(serialized); + }, + + serialize: function(deserialized) { + return none(deserialized) ? null : String(deserialized); + } + + }); + + __exports__["default"] = StringTransform; + }); +define("ember-inflector/lib/ext/string", + ["../system/string"], + function(__dependency1__) { + "use strict"; + var pluralize = __dependency1__.pluralize; + var singularize = __dependency1__.singularize; + + if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { + /** + See {{#crossLink "Ember.String/pluralize"}}{{/crossLink}} + + @method pluralize + @for String + */ + String.prototype.pluralize = function() { + return pluralize(this); + }; + + /** + See {{#crossLink "Ember.String/singularize"}}{{/crossLink}} + + @method singularize + @for String + */ + String.prototype.singularize = function() { + return singularize(this); + }; } }); -}); +define("ember-inflector/lib/main", + ["./system","./ext/string","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var Inflector = __dependency1__.Inflector; + var inflections = __dependency1__.inflections; + var pluralize = __dependency1__.pluralize; + var singularize = __dependency1__.singularize; -})(); + Inflector.defaultRules = inflections; + Ember.Inflector = Inflector; + + Ember.String.pluralize = pluralize; + Ember.String.singularize = singularize; + __exports__["default"] = Inflector; -(function() { + __exports__.pluralize = pluralize; + __exports__.singularize = singularize; + }); +define("ember-inflector/lib/system", + ["./system/inflector","./system/string","./system/inflections","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var Inflector = __dependency1__["default"]; -})(); + var pluralize = __dependency2__.pluralize; + var singularize = __dependency2__.singularize; + var defaultRules = __dependency3__["default"]; -})(); + + Inflector.inflector = new Inflector(defaultRules); + + __exports__.Inflector = Inflector; + __exports__.singularize = singularize; + __exports__.pluralize = pluralize; + __exports__.defaultRules = defaultRules; + }); +define("ember-inflector/lib/system/inflections", + ["exports"], + function(__exports__) { + "use strict"; + var defaultRules = { + plurals: [ + [/$/, 's'], + [/s$/i, 's'], + [/^(ax|test)is$/i, '$1es'], + [/(octop|vir)us$/i, '$1i'], + [/(octop|vir)i$/i, '$1i'], + [/(alias|status)$/i, '$1es'], + [/(bu)s$/i, '$1ses'], + [/(buffal|tomat)o$/i, '$1oes'], + [/([ti])um$/i, '$1a'], + [/([ti])a$/i, '$1a'], + [/sis$/i, 'ses'], + [/(?:([^f])fe|([lr])f)$/i, '$1$2ves'], + [/(hive)$/i, '$1s'], + [/([^aeiouy]|qu)y$/i, '$1ies'], + [/(x|ch|ss|sh)$/i, '$1es'], + [/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'], + [/^(m|l)ouse$/i, '$1ice'], + [/^(m|l)ice$/i, '$1ice'], + [/^(ox)$/i, '$1en'], + [/^(oxen)$/i, '$1'], + [/(quiz)$/i, '$1zes'] + ], + + singular: [ + [/s$/i, ''], + [/(ss)$/i, '$1'], + [/(n)ews$/i, '$1ews'], + [/([ti])a$/i, '$1um'], + [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '$1sis'], + [/(^analy)(sis|ses)$/i, '$1sis'], + [/([^f])ves$/i, '$1fe'], + [/(hive)s$/i, '$1'], + [/(tive)s$/i, '$1'], + [/([lr])ves$/i, '$1f'], + [/([^aeiouy]|qu)ies$/i, '$1y'], + [/(s)eries$/i, '$1eries'], + [/(m)ovies$/i, '$1ovie'], + [/(x|ch|ss|sh)es$/i, '$1'], + [/^(m|l)ice$/i, '$1ouse'], + [/(bus)(es)?$/i, '$1'], + [/(o)es$/i, '$1'], + [/(shoe)s$/i, '$1'], + [/(cris|test)(is|es)$/i, '$1is'], + [/^(a)x[ie]s$/i, '$1xis'], + [/(octop|vir)(us|i)$/i, '$1us'], + [/(alias|status)(es)?$/i, '$1'], + [/^(ox)en/i, '$1'], + [/(vert|ind)ices$/i, '$1ex'], + [/(matr)ices$/i, '$1ix'], + [/(quiz)zes$/i, '$1'], + [/(database)s$/i, '$1'] + ], + + irregularPairs: [ + ['person', 'people'], + ['man', 'men'], + ['child', 'children'], + ['sex', 'sexes'], + ['move', 'moves'], + ['cow', 'kine'], + ['zombie', 'zombies'] + ], + + uncountable: [ + 'equipment', + 'information', + 'rice', + 'money', + 'species', + 'series', + 'fish', + 'sheep', + 'jeans', + 'police' + ] + }; + + __exports__["default"] = defaultRules; + }); +define("ember-inflector/lib/system/inflector", + ["exports"], + function(__exports__) { + "use strict"; + var BLANK_REGEX = /^\s*$/; + + function loadUncountable(rules, uncountable) { + for (var i = 0, length = uncountable.length; i < length; i++) { + rules.uncountable[uncountable[i].toLowerCase()] = true; + } + } + + function loadIrregular(rules, irregularPairs) { + var pair; + + for (var i = 0, length = irregularPairs.length; i < length; i++) { + pair = irregularPairs[i]; + + rules.irregular[pair[0].toLowerCase()] = pair[1]; + rules.irregularInverse[pair[1].toLowerCase()] = pair[0]; + } + } + + /** + Inflector.Ember provides a mechanism for supplying inflection rules for your + application. Ember includes a default set of inflection rules, and provides an + API for providing additional rules. + + Examples: + + Creating an inflector with no rules. + + ```js + var inflector = new Ember.Inflector(); + ``` + + Creating an inflector with the default ember ruleset. + + ```js + var inflector = new Ember.Inflector(Ember.Inflector.defaultRules); + + inflector.pluralize('cow') //=> 'kine' + inflector.singularize('kine') //=> 'cow' + ``` + + Creating an inflector and adding rules later. + + ```javascript + var inflector = Ember.Inflector.inflector; + + inflector.pluralize('advice') // => 'advices' + inflector.uncountable('advice'); + inflector.pluralize('advice') // => 'advice' + + inflector.pluralize('formula') // => 'formulas' + inflector.irregular('formula', 'formulae'); + inflector.pluralize('formula') // => 'formulae' + + // you would not need to add these as they are the default rules + inflector.plural(/$/, 's'); + inflector.singular(/s$/i, ''); + ``` + + Creating an inflector with a nondefault ruleset. + + ```javascript + var rules = { + plurals: [ /$/, 's' ], + singular: [ /\s$/, '' ], + irregularPairs: [ + [ 'cow', 'kine' ] + ], + uncountable: [ 'fish' ] + }; + + var inflector = new Ember.Inflector(rules); + ``` + + @class Inflector + @namespace Ember + */ + function Inflector(ruleSet) { + ruleSet = ruleSet || {}; + ruleSet.uncountable = ruleSet.uncountable || {}; + ruleSet.irregularPairs = ruleSet.irregularPairs || {}; + + var rules = this.rules = { + plurals: ruleSet.plurals || [], + singular: ruleSet.singular || [], + irregular: {}, + irregularInverse: {}, + uncountable: {} + }; + + loadUncountable(rules, ruleSet.uncountable); + loadIrregular(rules, ruleSet.irregularPairs); + } + + Inflector.prototype = { + /** + @method plural + @param {RegExp} regex + @param {String} string + */ + plural: function(regex, string) { + this.rules.plurals.push([regex, string.toLowerCase()]); + }, + + /** + @method singular + @param {RegExp} regex + @param {String} string + */ + singular: function(regex, string) { + this.rules.singular.push([regex, string.toLowerCase()]); + }, + + /** + @method uncountable + @param {String} regex + */ + uncountable: function(string) { + loadUncountable(this.rules, [string.toLowerCase()]); + }, + + /** + @method irregular + @param {String} singular + @param {String} plural + */ + irregular: function (singular, plural) { + loadIrregular(this.rules, [[singular, plural]]); + }, + + /** + @method pluralize + @param {String} word + */ + pluralize: function(word) { + return this.inflect(word, this.rules.plurals, this.rules.irregular); + }, + + /** + @method singularize + @param {String} word + */ + singularize: function(word) { + return this.inflect(word, this.rules.singular, this.rules.irregularInverse); + }, + + /** + @protected + + @method inflect + @param {String} word + @param {Object} typeRules + @param {Object} irregular + */ + inflect: function(word, typeRules, irregular) { + var inflection, substitution, result, lowercase, isBlank, + isUncountable, isIrregular, isIrregularInverse, rule; + + isBlank = BLANK_REGEX.test(word); + + if (isBlank) { + return word; + } + + lowercase = word.toLowerCase(); + + isUncountable = this.rules.uncountable[lowercase]; + + if (isUncountable) { + return word; + } + + isIrregular = irregular && irregular[lowercase]; + + if (isIrregular) { + return isIrregular; + } + + for (var i = typeRules.length, min = 0; i > min; i--) { + inflection = typeRules[i-1]; + rule = inflection[0]; + + if (rule.test(word)) { + break; + } + } + + inflection = inflection || []; + + rule = inflection[0]; + substitution = inflection[1]; + + result = word.replace(rule, substitution); + + return result; + } + }; + + __exports__["default"] = Inflector; + }); +define("ember-inflector/lib/system/string", + ["./inflector","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Inflector = __dependency1__["default"]; + var pluralize = function(word) { + return Inflector.inflector.pluralize(word); + }; + + var singularize = function(word) { + return Inflector.inflector.singularize(word); + }; + + __exports__.pluralize = pluralize; + __exports__.singularize = singularize; + }); +global.DS = requireModule('ember-data/lib/main')['default']; +}(Ember.lookup)); diff --git a/vendor/assets/ember/development/ember.js b/vendor/assets/ember/development/ember.js index 2d4e1392..cece6e9e 100644 --- a/vendor/assets/ember/development/ember.js +++ b/vendor/assets/ember/development/ember.js @@ -1,2353 +1,3362 @@ -// Fetched from channel: release, with url http://builds.emberjs.com/release/ember.js -// Fetched on: 2014-01-04T21:05:01Z -// ========================================================================== -// Project: Ember - JavaScript Application Framework -// Copyright: Copyright 2011-2013 Tilde Inc. and contributors -// Portions Copyright 2006-2011 Strobe Inc. -// Portions Copyright 2008-2011 Apple Inc. All rights reserved. -// License: Licensed under MIT license -// See https://raw.github.com/emberjs/ember.js/master/LICENSE -// ========================================================================== - - - // Version: 1.2.1+pre.ce3a6b7c - -(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) { - Ember.Logger.assert(test, desc); - } - - if (Ember.testing && !test) { - // when testing, ensure test failures when assertions fail - throw new Ember.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.TESTING_DEPRECATION) { return; } - - if (arguments.length === 1) { test = false; } - if (test) { return; } - - if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new Ember.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); -}; - - - -/** - Alias an old, deprecated method with its new counterpart. - - Display a deprecation warning with the provided message and a stack trace - (Chrome and Firefox only) when the assigned method is called. - - Ember build tools will not remove calls to `Ember.deprecateFunc()`, though - no warnings will be shown in production. - - ```javascript - Ember.oldMethod = Ember.deprecateFunc("Please use the new, updated method", Ember.newMethod); - ``` - - @method deprecateFunc - @param {String} message A description of the deprecation. - @param {Function} func The new function called to replace its deprecated counterpart. - @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); - }; -}; - - -// Inform the developer about the Ember Inspector if not installed. -if (!Ember.testing) { - if (typeof window !== 'undefined' && window.chrome && window.addEventListener) { - window.addEventListener("load", function() { - if (document.body && document.body.dataset && !document.body.dataset.emberExtension) { - Ember.debug('For more advanced debugging, install the Ember Inspector from https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi'); - } - }, false); - } -} - -})(); - -// ========================================================================== -// Project: Ember - JavaScript Application Framework -// Copyright: Copyright 2011-2013 Tilde Inc. and contributors -// Portions Copyright 2006-2011 Strobe Inc. -// Portions Copyright 2008-2011 Apple Inc. All rights reserved. -// License: Licensed under MIT license -// See https://raw.github.com/emberjs/ember.js/master/LICENSE -// ========================================================================== - - - // Version: 1.2.1+pre.ce3a6b7c - -(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, deps, callback, reified, exports; - - mod = registry[name]; - - if (!mod) { - throw new Error("Module '" + name + "' not found."); - } - - deps = mod.deps; - callback = mod.callback; - reified = []; - - for (var i=0, l=deps.length; i -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; -}; - -/** - Array polyfills to support ES5 features in older browsers. - - @namespace Ember - @property ArrayPolyfills -*/ -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() { -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.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); - -// .......................................................... -// ERROR HANDLING -// - -/** - A function may be assigned to `Ember.onerror` to be called when Ember - internals encounter an error. This is useful for specialized error handling - and reporting code. - - ```javascript - Ember.onerror = function(error) { - Em.$.ajax('/report-error', 'POST', { - stack: error.stack, - otherInformation: 'whatever app state you want to provide' - }); - }; - ``` - - @event onerror - @for Ember - @param {Exception} error the error object -*/ -Ember.onerror = null; - -/** - @private - - Wrap code block in a try/catch if `Ember.onerror` is set. - - @method handleErrors - @for Ember - @param {Function} func - @param [context] -*/ -Ember.handleErrors = function(func, context) { - // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error, - // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch - if ('function' === typeof Ember.onerror) { - try { - return func.call(context || this); - } catch (error) { - Ember.onerror(error); - } - } else { - return func.call(context || this); - } -}; - -})(); - - - -(function() { -/** -@module ember-metal -*/ - -/** - @private - - Prefix used for guids through out Ember. - -*/ -Ember.GUID_PREFIX = 'ember'; - - -var o_defineProperty = Ember.platform.defineProperty, - o_create = Ember.create, - // Used for guid generation... - GUID_KEY = '__ember'+ (+ new Date()), - uuid = 0, - numberCache = [], - stringCache = {}; - -var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; - -/** - @private - - A unique key used to assign guids and other private metadata to objects. - If you inspect an object in your browser debugger you will often see these. - They can be safely ignored. - - On browsers that support it, these properties are added with enumeration - disabled so they won't show up when you iterate over your properties. - - @property GUID_KEY - @for Ember - @type String - @final -*/ -Ember.GUID_KEY = GUID_KEY; - -var GUID_DESC = { - writable: false, - configurable: false, - enumerable: false, - value: null -}; - -/** - @private - - Generates a new guid, optionally saving the guid to the object that you - pass in. You will rarely need to use this method. Instead you should - call `Ember.guidFor(obj)`, which return an existing guid if available. - - @method generateGuid - @for Ember - @param {Object} [obj] Object the guid will be used for. If passed in, the guid will - be saved on the object and reused whenever you pass the same object - again. - - If no object is passed, just generate a new guid. - @param {String} [prefix] Prefix to place in front of the guid. Useful when you want to - separate the guid into separate namespaces. - @return {String} the guid -*/ -Ember.generateGuid = function generateGuid(obj, prefix) { - if (!prefix) prefix = Ember.GUID_PREFIX; - var ret = (prefix + (uuid++)); - if (obj) { - GUID_DESC.value = ret; - o_defineProperty(obj, GUID_KEY, GUID_DESC); - } - return ret; -}; - -/** - @private - - Returns a unique id for the object. If the object does not yet have a guid, - one will be assigned to it. You can call this on any object, - `Ember.Object`-based or not, but be aware that it will add a `_guid` - property. - - You can also use this method on DOM Element objects. - - @method guidFor - @for Ember - @param {Object} obj any object, string, number, Element, or primitive - @return {String} the unique guid for this instance. -*/ -Ember.guidFor = function guidFor(obj) { - - // special cases where we don't want to add a key to object - if (obj === undefined) return "(undefined)"; - if (obj === null) return "(null)"; - - var ret; - var type = typeof obj; - - // Don't allow prototype changes to String etc. to change the guidFor - switch(type) { - case 'number': - ret = numberCache[obj]; - if (!ret) ret = numberCache[obj] = 'nu'+obj; - return ret; - - case 'string': - ret = stringCache[obj]; - if (!ret) ret = stringCache[obj] = 'st'+(uuid++); - return ret; - - case 'boolean': - return obj ? '(true)' : '(false)'; - - default: - if (obj[GUID_KEY]) return obj[GUID_KEY]; - if (obj === Object) return '(Object)'; - if (obj === Array) return '(Array)'; - ret = 'ember'+(uuid++); - GUID_DESC.value = ret; - o_defineProperty(obj, GUID_KEY, GUID_DESC); - return ret; - } -}; - -// .......................................................... -// META -// - -var META_DESC = { - writable: true, - configurable: false, - enumerable: false, - value: null -}; - -var META_KEY = Ember.GUID_KEY+'_meta'; - -/** - The key used to store meta information on object for property observing. - - @property META_KEY - @for Ember - @private - @final - @type String -*/ -Ember.META_KEY = META_KEY; - -// Placeholder for non-writable metas. -var EMPTY_META = { - descs: {}, - watching: {} -}; - -if (MANDATORY_SETTER) { EMPTY_META.values = {}; } - -Ember.EMPTY_META = EMPTY_META; - -if (Object.freeze) Object.freeze(EMPTY_META); - -var isDefinePropertySimulated = Ember.platform.defineProperty.isSimulated; - -function Meta(obj) { - this.descs = {}; - this.watching = {}; - this.cache = {}; - this.source = obj; -} - -if (isDefinePropertySimulated) { - // on platforms that don't support enumerable false - // make meta fail jQuery.isPlainObject() to hide from - // jQuery.extend() by having a property that fails - // hasOwnProperty check. - Meta.prototype.__preventPlainObject__ = true; - - // Without non-enumerable properties, meta objects will be output in JSON - // unless explicitly suppressed - Meta.prototype.toJSON = function () { }; -} - -/** - Retrieves the meta hash for an object. If `writable` is true ensures the - hash is writable for this object as well. - - The meta object contains information about computed property descriptors as - well as any watched properties and other information. You generally will - not access this information directly but instead work with higher level - methods that manipulate this hash indirectly. - - @method meta - @for Ember - @private - - @param {Object} obj The object to retrieve meta for - @param {Boolean} [writable=true] Pass `false` if you do not intend to modify - the meta hash, allowing the method to avoid making an unnecessary copy. - @return {Object} the meta hash for an object -*/ -Ember.meta = function meta(obj, writable) { - - var ret = obj[META_KEY]; - if (writable===false) return ret || EMPTY_META; - - if (!ret) { - if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC); - - ret = new Meta(obj); - - if (MANDATORY_SETTER) { ret.values = {}; } - - obj[META_KEY] = ret; - - // make sure we don't accidentally try to create constructor like desc - ret.descs.constructor = null; - - } else if (ret.source !== obj) { - if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC); - - ret = o_create(ret); - ret.descs = o_create(ret.descs); - ret.watching = o_create(ret.watching); - ret.cache = {}; - ret.source = obj; - - if (MANDATORY_SETTER) { ret.values = o_create(ret.values); } - - obj[META_KEY] = ret; - } - return ret; -}; - -Ember.getMeta = function getMeta(obj, property) { - var meta = Ember.meta(obj, false); - return meta[property]; -}; - -Ember.setMeta = function setMeta(obj, property, value) { - var meta = Ember.meta(obj, true); - meta[property] = value; - return value; -}; - -/** - @deprecated - @private - - In order to store defaults for a class, a prototype may need to create - a default meta object, which will be inherited by any objects instantiated - from the class's constructor. - - However, the properties of that meta object are only shallow-cloned, - so if a property is a hash (like the event system's `listeners` hash), - it will by default be shared across all instances of that class. - - This method allows extensions to deeply clone a series of nested hashes or - other complex objects. For instance, the event system might pass - `['listeners', 'foo:change', 'ember157']` to `prepareMetaPath`, which will - walk down the keys provided. - - For each key, if the key does not exist, it is created. If it already - exists and it was inherited from its constructor, the constructor's - key is cloned. - - You can also pass false for `writable`, which will simply return - undefined if `prepareMetaPath` discovers any part of the path that - shared or undefined. - - @method metaPath - @for Ember - @param {Object} obj The object whose meta we are examining - @param {Array} path An array of keys to walk down - @param {Boolean} writable whether or not to create a new meta - (or meta property) if one does not already exist or if it's - shared with its constructor -*/ -Ember.metaPath = function metaPath(obj, path, writable) { - Ember.deprecate("Ember.metaPath is deprecated and will be removed from future releases."); - var meta = Ember.meta(obj, writable), keyName, value; - - for (var i=0, l=path.length; i size ? size : ends; - if (count <= 0) { count = 0; } - - chunk = args.splice(0, size); - chunk = [start, count].concat(chunk); - - start += size; - ends -= count; - - ret = ret.concat(splice.apply(array, chunk)); - } - return ret; - }, - - replace: function(array, idx, amt, objects) { - if (array.replace) { - return array.replace(idx, amt, objects); - } else { - return utils._replace(array, idx, amt, objects); - } - }, - - intersection: function(array1, array2) { - var intersection = []; - - utils.forEach(array1, function(element) { - if (utils.indexOf(array2, element) >= 0) { - intersection.push(element); - } - }); - - return intersection; - } -}; - -})(); - - - -(function() { -/** -@module ember-metal -*/ - -var META_KEY = Ember.META_KEY, get; - -var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; - -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; - } - - Ember.assert("Cannot call get with "+ keyName +" key.", !!keyName); - Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined); - - if (obj === null || keyName.indexOf('.') !== -1) { - return getPath(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; - } -}; - -// Currently used only by Ember Data tests -if (Ember.config.overrideAccessors) { - Ember.get = get; - Ember.config.overrideAccessors(); - get = Ember.get; -} - -/** - @private - - Normalizes a target/path pair to reflect that actual target/path that should - be observed, etc. This takes into account passing in global property - paths (i.e. a path beginning with a captial letter not defined on the - target) and * separators. - - @method normalizeTuple - @for Ember - @param {Object} target The current target. May be `null`. - @param {String} path A path on the target or a global property path. - @return {Array} a temporary array with the normalized target/path pair. -*/ -var normalizeTuple = Ember.normalizeTuple = function(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 = path.match(FIRST_KEY)[0]; - 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 Ember.Error('Invalid Path'); - - return [ target, path ]; -}; - -var getPath = Ember._getPath = function(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 != null && idx < len; idx++) { - root = get(root, parts[idx], true); - if (root && root.isDestroyed) { return undefined; } - } - return root; -}; - -Ember.getWithDefault = function(root, key, defaultValue) { - var value = get(root, key); - - if (value === undefined) { return defaultValue; } - return value; -}; - - -Ember.get = get; - -})(); - - - -(function() { -/** -@module ember-metal -*/ - -var o_create = Ember.create, - metaFor = Ember.meta, - META_KEY = Ember.META_KEY, - a_slice = [].slice, - /* listener flags */ - ONCE = 1, SUSPENDED = 2; - -/* - 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, flags - ] + var mod = registry[name], + deps = mod.deps, + callback = mod.callback, + reified = [], + exports; + + for (var i=0, l=deps.length; i= 0; i -= 3) { - var target = actions[i], - method = actions[i+1], - flags = actions[i+2], - actionIndex = indexOf(otherActions, target, method); - - if (actionIndex === -1) { - otherActions.push(target, method, flags); - } - } -} - -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 - 3; i >= 0; i -= 3) { - var target = actions[i], - method = actions[i+1], - flags = actions[i+2], - actionIndex = indexOf(otherActions, target, method); - - if (actionIndex !== -1) { continue; } - - otherActions.push(target, method, flags); - diffActions.push(target, method, flags); - } - - 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), - flags = 0; - - if (once) flags |= ONCE; - - if (actionIndex !== -1) { return; } - - actions.push(target, method, flags); - - if ('function' === typeof obj.didAddListener) { - obj.didAddListener(eventName, target, method); - } -} - -/** - Remove an event listener - - Arguments should match those passed to `Ember.addListener`. - - @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) { - var actions = actionsFor(obj, eventName), - actionIndex = indexOf(actions, target, method); - - // action doesn't exist, give up silently - if (actionIndex === -1) { return; } - - actions.splice(actionIndex, 3); - - if ('function' === typeof obj.didRemoveListener) { - obj.didRemoveListener(eventName, target, method); - } - } - - if (method) { - _removeListener(target, method); + Ember.__loader = {define: define, require: require, registry: registry}; } else { - var meta = obj[META_KEY], - actions = meta && meta.listeners && meta.listeners[eventName]; + define = Ember.__loader.define; + requirejs = require = requireModule = Ember.__loader.require; + } +})(); +(function() { +define("ember-debug", + ["ember-metal/core","ember-metal/error","ember-metal/logger"], + function(__dependency1__, __dependency2__, __dependency3__) { + "use strict"; + /*global __fail__*/ - if (!actions) { return; } - for (var i = actions.length - 3; i >= 0; i -= 3) { - _removeListener(actions[i], actions[i+1]); + var Ember = __dependency1__["default"]; + var EmberError = __dependency2__["default"]; + var Logger = __dependency3__["default"]; + + /** + Ember Debug + + @module ember + @submodule ember-debug + */ + + /** + @class Ember + */ + + /** + 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 EmberError("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) { + Logger.warn("WARNING: "+message); + if ('trace' in Logger) 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) { + 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 (test) { return; } + + if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new EmberError(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; + } + + Logger.warn("DEPRECATION: "+message); + }; + + + + /** + Alias an old, deprecated method with its new counterpart. + + Display a deprecation warning with the provided message and a stack trace + (Chrome and Firefox only) when the assigned method is called. + + Ember build tools will not remove calls to `Ember.deprecateFunc()`, though + no warnings will be shown in production. + + ```javascript + Ember.oldMethod = Ember.deprecateFunc("Please use the new, updated method", Ember.newMethod); + ``` + + @method deprecateFunc + @param {String} message A description of the deprecation. + @param {Function} func The new function called to replace its deprecated counterpart. + @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); + }; + }; + + + /** + Run a function meant for debugging. Ember build tools will remove any calls to + `Ember.runInDebug()` when doing a production build. + + ```javascript + Ember.runInDebug( function() { + Ember.Handlebars.EachView.reopen({ + didInsertElement: function() { + console.log("I'm happy"); + } + }); + }); + ``` + + @method runInDebug + @param {Function} func The function to be executed. + */ + Ember.runInDebug = function(func) { + func() + }; + + // Inform the developer about the Ember Inspector if not installed. + if (!Ember.testing) { + var isFirefox = typeof InstallTrigger !== 'undefined'; + var isChrome = !!window.chrome && !window.opera; + + if (typeof window !== 'undefined' && (isFirefox || isChrome) && window.addEventListener) { + window.addEventListener("load", function() { + if (document.documentElement && document.documentElement.dataset && !document.documentElement.dataset.emberExtension) { + var downloadURL; + + if(isChrome) { + downloadURL = 'https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi'; + } else if(isFirefox) { + downloadURL = 'https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/'; + } + + Ember.debug('For more advanced debugging, install the Ember Inspector from ' + downloadURL); + } + }, false); + } } - } -} + }); +})(); -/** - @private +(function() { +define("ember-metal/array", + ["exports"], + function(__exports__) { + "use strict"; + /*jshint newcap:false*/ + /** + @module ember-metal + */ - Suspend listener during callback. + var ArrayPrototype = Array.prototype; - 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. + // 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 - @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; - } + // 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; + }; - var actions = actionsFor(obj, eventName), - actionIndex = indexOf(actions, target, method); + // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map + var map = isNativeFunc(ArrayPrototype.map) ? ArrayPrototype.map : function(fun /*, thisp */) { + //"use strict"; - if (actionIndex !== -1) { - actions[actionIndex+2] |= SUSPENDED; // mark the action as suspended - } + if (this === void 0 || this === null) { + throw new TypeError(); + } - function tryable() { return callback.call(target); } - function finalizer() { if (actionIndex !== -1) { actions[actionIndex+2] &= ~SUSPENDED; } } + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun !== "function") { + throw new TypeError(); + } - return Ember.tryFinally(tryable, finalizer); -} + 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); + } + } -/** - @private + return res; + }; - Suspends multiple listeners during a callback. + // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach + var forEach = isNativeFunc(ArrayPrototype.forEach) ? ArrayPrototype.forEach : function(fun /*, thisp */) { + //"use strict"; - - @method suspendListeners - @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; - } + if (this === void 0 || this === null) { + throw new TypeError(); + } - var suspendedActions = [], - actionsList = [], - eventName, actions, i, l; + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun !== "function") { + throw new TypeError(); + } - for (i=0, l=eventNames.length; i(" + 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; + trySet(obj, toPath, getWithGlobals(obj, fromPath)); + + // add an observer on the object to be notified when the binding should be updated + 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) { 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. + removeObserver(obj, this._from, this, this.fromDidChange); + + // if the binding is two-way, remove the observer from the target as well + if (twoWay) { 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) { + 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) { + trySet(obj, toPath, fromValue); + } else { + _suspendObserver(obj, toPath, this, this.toDidChange, function () { + 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); + } + _suspendObserver(obj, fromPath, this, this.fromDidChange, function () { + trySet(isGlobalPath(fromPath) ? Ember.lookup : obj, fromPath, toValue); + }); + } + } + + }; + + function mixinProperties(to, from) { + for (var key in from) { + if (from.hasOwnProperty(key)) { + to[key] = from[key]; + } + } } - } - return ret; -} -/** - Send an event. The execution of suspended listeners - is skipped, and once listeners are removed. A listener without - a target is executed on the passed object. If an array of actions - is not passed, the actions stored on the passed object are invoked. - - @method sendEvent - @for Ember - @param obj - @param {String} eventName - @param {Array} params Optional parameters for each listener. - @param {Array} actions Optional array of actions (listeners). - @return true -*/ -function sendEvent(obj, eventName, params, actions) { - // first give object a chance to handle it - if (obj !== Ember && 'function' === typeof obj.sendEvent) { - obj.sendEvent(eventName, params); - } + mixinProperties(Binding, { - if (!actions) { - var meta = obj[META_KEY]; - actions = meta && meta.listeners && meta.listeners[eventName]; - } + /* + See `Ember.Binding.from`. - if (!actions) { return; } + @method from + @static + */ + from: function() { + var C = this, binding = new C(); + return binding.from.apply(binding, arguments); + }, - for (var i = actions.length - 3; i >= 0; i -= 3) { // looping in reverse for once listeners - var target = actions[i], method = actions[i+1], flags = actions[i+2]; - if (!method) { continue; } - if (flags & SUSPENDED) { continue; } - if (flags & ONCE) { removeListener(obj, eventName, target, method); } - if (!target) { target = obj; } - if ('string' === typeof method) { method = target[method]; } - if (params) { - method.apply(target, params); + /* + See `Ember.Binding.to`. + + @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 `Binding.oneWay`. + + @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. + @return {Ember.Binding} `this` + */ + 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 "One Way 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; ES6TODO: where to put this? + + + /** + 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 + */ + function bind(obj, to, from) { + return new 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 + */ + function oneWay(obj, to, from) { + return new Binding(to, from).oneWay().connect(obj); + }; + + __exports__.Binding = Binding; + __exports__.bind = bind; + __exports__.oneWay = oneWay; + __exports__.isGlobalPath = isGlobalPath; + }); +define("ember-metal/chains", + ["ember-metal/core","ember-metal/property_get","ember-metal/utils","ember-metal/array","ember-metal/watch_key","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // warn, assert, etc; + var get = __dependency2__.get; + var normalizeTuple = __dependency2__.normalizeTuple; + var meta = __dependency3__.meta; + var META_KEY = __dependency3__.META_KEY; + var forEach = __dependency4__.forEach; + var watchKey = __dependency5__.watchKey; + var unwatchKey = __dependency5__.unwatchKey; + + var metaFor = meta, + warn = Ember.warn, + FIRST_KEY = /^([^\.\*]+)/; + + function firstKey(path) { + return path.match(FIRST_KEY)[0]; + } + + 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]); }); + + 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 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); + watchKey(obj, keyName, m); + } + + function removeChainWatcher(obj, keyName, node) { + if (!obj || 'object' !== typeof obj) { return; } // nothing to do + + var m = obj[META_KEY]; + if (m && !m.hasOwnProperty('chainWatchers')) { return; } // nothing to do + + var nodes = m && m.chainWatchers; + + if (nodes && nodes[keyName]) { + nodes = nodes[keyName]; + for (var i = 0, l = nodes.length; i < l; i++) { + if (nodes[i] === node) { nodes.splice(i, 1); } + } + } + unwatchKey(obj, keyName, m); + }; + + // 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. + function ChainNode(parent, key, value) { + 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; + + function lazyGet(obj, key) { + if (!obj) return undefined; + + var meta = obj[META_KEY]; + // check if object meant only to be a prototype + if (meta && meta.proto === obj) return undefined; + + if (key === "@each") return get(obj, key); + + // if a CP only return cached value + var desc = meta && meta.descs[key]; + if (desc && desc._cacheable) { + if (key in meta.cache) { + return meta.cache[key]; + } else { + return undefined; + } + } + + return get(obj, key); + } + + ChainNodePrototype.value = function() { + if (this._value === undefined && this._watching) { + var obj = this._parent.value(); + this._value = lazyGet(obj, this._key); + } + 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(events) { + var chains = this._chains; + if (chains) { + for(var key in chains) { + if (!chains.hasOwnProperty(key)) { continue; } + chains[key].willChange(events); + } + } + + if (this._parent) { this._parent.chainWillChange(this, this._key, 1, events); } + }; + + ChainNodePrototype.chainWillChange = function(chain, path, depth, events) { + if (this._key) { path = this._key + '.' + path; } + + if (this._parent) { + this._parent.chainWillChange(this, path, depth+1, events); + } else { + if (depth > 1) { + events.push(this.value(), path); + } + path = 'this.' + path; + if (this._paths[path] > 0) { + events.push(this.value(), path); + } + } + }; + + ChainNodePrototype.chainDidChange = function(chain, path, depth, events) { + if (this._key) { path = this._key + '.' + path; } + if (this._parent) { + this._parent.chainDidChange(this, path, depth+1, events); + } else { + if (depth > 1) { + events.push(this.value(), path); + } + path = 'this.' + path; + if (this._paths[path] > 0) { + events.push(this.value(), path); + } + } + }; + + ChainNodePrototype.didChange = function(events) { + // 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(events); + } + } + + // if no events are passed in then we only care about the above wiring update + if (events === null) { return; } + + // and finally tell parent about my path changing... + if (this._parent) { this._parent.chainDidChange(this, this._key, 1, events); } + }; + + function finishChains(obj) { + // We only create meta if we really have to + var m = obj[META_KEY], chains = m && m.chains; + if (chains) { + if (chains.value() !== obj) { + metaFor(obj).chains = chains = chains.copy(obj); + } else { + chains.didChange(null); + } + } + }; + + __exports__.flushPendingChains = flushPendingChains; + __exports__.removeChainWatcher = removeChainWatcher; + __exports__.ChainNode = ChainNode; + __exports__.finishChains = finishChains; + }); +define("ember-metal/computed", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/enumerable_utils","ember-metal/platform","ember-metal/watching","ember-metal/expand_properties","ember-metal/error","ember-metal/properties","ember-metal/property_events","ember-metal/is_empty","ember-metal/is_none","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var get = __dependency2__.get; + var set = __dependency3__.set; + var meta = __dependency4__.meta; + var META_KEY = __dependency4__.META_KEY; + var guidFor = __dependency4__.guidFor; + var typeOf = __dependency4__.typeOf; + var inspect = __dependency4__.inspect; + var EnumerableUtils = __dependency5__["default"]; + var create = __dependency6__.create; + var watch = __dependency7__.watch; + var unwatch = __dependency7__.unwatch; + var expandProperties = __dependency8__["default"]; + var EmberError = __dependency9__["default"]; + var Descriptor = __dependency10__.Descriptor; + var defineProperty = __dependency10__.defineProperty; + var propertyWillChange = __dependency11__.propertyWillChange; + var propertyDidChange = __dependency11__.propertyDidChange; + var isEmpty = __dependency12__["default"]; + var isNone = __dependency13__.isNone; + + /** + @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 metaFor = meta, + a_slice = [].slice, + o_create = create; + + function UNDEFINED() { } + + + var lengthPattern = /\.(length|\[\])$/; + + + // .......................................................... + // 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(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(meta) { + return keysForDep(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(meta); + + for(idx = 0, len = depKeys.length; idx < len; idx++) { + depKey = depKeys[idx]; + // Lookup keys meta for depKey + keys = keysForDep(depsMeta, depKey); + // Increment the number of times depKey depends on keyName. + keys[keyName] = (keys[keyName] || 0) + 1; + // Watch the depKey + watch(obj, depKey, meta); + } + } + + 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(meta); + + for(idx = 0, len = depKeys.length; idx < len; idx++) { + depKey = depKeys[idx]; + // Lookup keys meta for depKey + keys = keysForDep(depsMeta, depKey); + // Increment the number of times depKey depends on keyName. + keys[keyName] = (keys[keyName] || 0) - 1; + // Watch the depKey + unwatch(obj, depKey, meta); + } + } + + // .......................................................... + // COMPUTED PROPERTY + // + + /** + A computed property transforms an objects function into a property. + + By default the function backing the computed property will only be called + once and the result will be cached. You can specify various properties + that your computed property is dependent on. This will force the cached + result to be recomputed if the dependencies are modified. + + In the following example we declare a computed property (by calling + `.property()` on the fullName function) and setup the properties + dependencies (depending on firstName and lastName). The fullName function + will be called once (regardless of how many times it is accessed) as long + as it's dependencies have not been changed. Once firstName or lastName are updated + any future calls (or anything bound) to fullName will incorporate the new + values. + + ```javascript + Person = Ember.Object.extend({ + // these will be supplied by `create` + firstName: null, + lastName: null, + + fullName: function() { + var firstName = this.get('firstName'); + var lastName = this.get('lastName'); + + return firstName + ' ' + lastName; + }.property('firstName', 'lastName') + }); + + var tom = Person.create({ + firstName: "Tom", + lastName: "Dale" + }); + + tom.get('fullName') // "Tom Dale" + ``` + + You can also define what Ember should do when setting a computed property. + If you try to set a computed property, it will be invoked with the key and + value you want to set it to. You can also accept the previous value as the + third parameter. + + ```javascript + + Person = Ember.Object.extend({ + // these will be supplied by `create` + firstName: null, + lastName: null, + + fullName: function(key, value, oldValue) { + // getter + if (arguments.length === 1) { + var firstName = this.get('firstName'); + var lastName = this.get('lastName'); + + return firstName + ' ' + lastName; + + // setter + } else { + var name = value.split(" "); + + this.set('firstName', name[0]); + this.set('lastName', name[1]); + + return value; + } + }.property('firstName', 'lastName') + }); + + var person = Person.create(); + person.set('fullName', "Peter Wagenet"); + person.get('firstName') // Peter + person.get('lastName') // Wagenet + ``` + + @class ComputedProperty + @namespace Ember + @extends Ember.Descriptor + @constructor + */ + function ComputedProperty(func, opts) { + func.__ember_arity__ = func.length; + 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) || false; + } + + ComputedProperty.prototype = new Descriptor(); + + var ComputedPropertyPrototype = ComputedProperty.prototype; + ComputedPropertyPrototype._dependentKeys = undefined; + ComputedPropertyPrototype._suspended = undefined; + ComputedPropertyPrototype._meta = undefined; + + /** + Properties are cacheable by default. Computed property will automatically + cache the return value of your function until one of the dependent keys changes. + + Call `volatile()` to set it into non-cached mode. When in this mode + the computed property will not automatically cache the return value. + + However, if a property is properly observable, there is no reason to disable + caching. + + @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.extend({ + value: function() { + return OutsideService.getValue(); + }.property().volatile() + }).create(); + ``` + + @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.extend({ + guid: function() { + return 'guid-guid-guid'; + }.property().readOnly() + }); + + MyApp.person = MyApp.Person.create(); + + 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.extend({ + fullName: computed(function() { + return this.get('firstName') + ' ' + this.get('lastName'); + + // Tell Ember that this computed property depends on firstName + // and lastName + }).property('firstName', 'lastName') + }); + + MyApp.president = MyApp.President.create({ + firstName: 'Barack', + lastName: 'Obama', + }); + MyApp.president.get('fullName'); // Barack Obama + ``` + + @method property + @param {String} path* zero or more property paths + @return {Ember.ComputedProperty} this + @chainable + */ + ComputedPropertyPrototype.property = function() { + var args; + + var addArg = function (property) { + args.push(property); + }; + + args = []; + for (var i = 0, l = arguments.length; i < l; i++) { + expandProperties(arguments[i], addArg); + } + + 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.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 (meta.cache !== undefined) { + meta.cache[keyName] = undefined; + removeDependentKeys(this, obj, keyName, meta); + } + } + }; + + function finishChains(chainNodes) + { + for (var i=0, l=chainNodes.length; i 1) { + args = a_slice.call(arguments, 0, -1); + func = a_slice.call(arguments, -1)[0]; + } + + if (typeof func !== "function") { + throw new EmberError("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 {Object} the cached value + */ + function cacheFor(obj, key) { + var meta = obj[META_KEY], + cache = meta && meta.cache, + ret = cache && cache[key]; + + if (ret === UNDEFINED) { return undefined; } + return ret; + }; + + cacheFor.set = function(cache, key, value) { + if (value === undefined) { + cache[key] = UNDEFINED; + } else { + cache[key] = value; + } + }; + + cacheFor.get = function(cache, key) { + var ret = cache[key]; + if (ret === UNDEFINED) { return undefined; } + return ret; + }; + + cacheFor.remove = function(cache, key) { + cache[key] = undefined; + }; + + function getProperties(self, propertyNames) { + var ret = {}; + for(var i = 0; i < propertyNames.length; i++) { + ret[propertyNames[i]] = get(self, propertyNames[i]); + } + return ret; + } + + function registerComputed(name, macro) { + computed[name] = function(dependentKey) { + var args = a_slice.call(arguments); + return computed(dependentKey, function() { + return macro.apply(this, args); + }); + }; + }; + + function registerComputedWithProperties(name, macro) { + computed[name] = function() { + var properties = a_slice.call(arguments); + + var computedFunc = computed(function() { + return macro.apply(this, [getProperties(this, properties)]); + }); + + return computedFunc.property.apply(computedFunc, properties); + }; + }; + + + /** + A computed property that returns true if the value of the dependent + property is null, an empty string, empty array, or empty function. + + Example + + ```javascript + var ToDoList = Ember.Object.extend({ + done: Ember.computed.empty('todos') + }); + var todoList = ToDoList.create({todos: ['Unit Test', 'Documentation', 'Release']}); + todoList.get('done'); // false + todoList.get('todos').clear(); + todoList.get('done'); // true + ``` + + @method computed.empty + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computed property which negate + the original value for property + */ + computed.empty = function (dependentKey) { + return computed(dependentKey + '.length', function () { + return isEmpty(get(this, dependentKey)); + }); + }; + + /** + A computed property that returns true if the value of the dependent + property is NOT null, an empty string, empty array, or empty function. + + Note: When using `computed.notEmpty` to watch an array make sure to + use the `array.[]` syntax so the computed can subscribe to transitions + from empty to non-empty states. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + hasStuff: computed.notEmpty('backpack.[]') + }); + var hamster = Hamster.create({backpack: ['Food', 'Sleeping Bag', 'Tent']}); + hamster.get('hasStuff'); // true + hamster.get('backpack').clear(); // [] + hamster.get('hasStuff'); // false + ``` + + @method computed.notEmpty + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computed property which returns true if + original value for property is not empty. + */ + registerComputed('notEmpty', function(dependentKey) { + return !isEmpty(get(this, dependentKey)); + }); + + /** + A computed property that returns true if the value of the dependent + property is null or undefined. This avoids errors from JSLint complaining + about use of ==, which can be technically confusing. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + isHungry: computed.none('food') + }); + var hamster = Hamster.create(); + hamster.get('isHungry'); // true + hamster.set('food', 'Banana'); + hamster.get('isHungry'); // false + hamster.set('food', null); + hamster.get('isHungry'); // true + ``` + + @method computed.none + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computed property which + returns true if original value for property is null or undefined. + */ + registerComputed('none', function(dependentKey) { + return isNone(get(this, dependentKey)); + }); + + /** + A computed property that returns the inverse boolean value + of the original value for the dependent property. + + Example + + ```javascript + var User = Ember.Object.extend({ + isAnonymous: computed.not('loggedIn') + }); + var user = User.create({loggedIn: false}); + user.get('isAnonymous'); // true + user.set('loggedIn', true); + user.get('isAnonymous'); // false + ``` + + @method computed.not + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computed property which returns + inverse of the original value for property + */ + registerComputed('not', function(dependentKey) { + return !get(this, dependentKey); + }); + + /** + A computed property that converts the provided dependent property + into a boolean value. + + ```javascript + var Hamster = Ember.Object.extend({ + hasBananas: computed.bool('numBananas') + }); + var hamster = Hamster.create(); + hamster.get('hasBananas'); // false + hamster.set('numBananas', 0); + hamster.get('hasBananas'); // false + hamster.set('numBananas', 1); + hamster.get('hasBananas'); // true + hamster.set('numBananas', null); + hamster.get('hasBananas'); // false + ``` + + @method computed.bool + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computed property which converts + to boolean the original value for property + */ + registerComputed('bool', function(dependentKey) { + return !!get(this, dependentKey); + }); + + /** + A computed property which matches the original value for the + dependent property against a given RegExp, returning `true` + if they values matches the RegExp and `false` if it does not. + + Example + + ```javascript + var User = Ember.Object.extend({ + hasValidEmail: computed.match('email', /^.+@.+\..+$/) + }); + var user = User.create({loggedIn: false}); + user.get('hasValidEmail'); // false + user.set('email', ''); + user.get('hasValidEmail'); // false + user.set('email', 'ember_hamster@example.com'); + user.get('hasValidEmail'); // true + ``` + + @method computed.match + @for Ember + @param {String} dependentKey + @param {RegExp} regexp + @return {Ember.ComputedProperty} computed property which match + the original value for property against a given RegExp + */ + registerComputed('match', function(dependentKey, regexp) { + var value = get(this, dependentKey); + return typeof value === 'string' ? regexp.test(value) : false; + }); + + /** + A computed property that returns true if the provided dependent property + is equal to the given value. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + napTime: computed.equal('state', 'sleepy') + }); + var hamster = Hamster.create(); + hamster.get('napTime'); // false + hamster.set('state', 'sleepy'); + hamster.get('napTime'); // true + hamster.set('state', 'hungry'); + hamster.get('napTime'); // false + ``` + + @method computed.equal + @for Ember + @param {String} dependentKey + @param {String|Number|Object} value + @return {Ember.ComputedProperty} computed property which returns true if + the original value for property is equal to the given value. + */ + registerComputed('equal', function(dependentKey, value) { + return get(this, dependentKey) === value; + }); + + /** + A computed property that returns true if the provied dependent property + is greater than the provided value. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + hasTooManyBananas: computed.gt('numBananas', 10) + }); + var hamster = Hamster.create(); + hamster.get('hasTooManyBananas'); // false + hamster.set('numBananas', 3); + hamster.get('hasTooManyBananas'); // false + hamster.set('numBananas', 11); + hamster.get('hasTooManyBananas'); // true + ``` + + @method computed.gt + @for Ember + @param {String} dependentKey + @param {Number} value + @return {Ember.ComputedProperty} computed property which returns true if + the original value for property is greater then given value. + */ + registerComputed('gt', function(dependentKey, value) { + return get(this, dependentKey) > value; + }); + + /** + A computed property that returns true if the provided dependent property + is greater than or equal to the provided value. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + hasTooManyBananas: computed.gte('numBananas', 10) + }); + var hamster = Hamster.create(); + hamster.get('hasTooManyBananas'); // false + hamster.set('numBananas', 3); + hamster.get('hasTooManyBananas'); // false + hamster.set('numBananas', 10); + hamster.get('hasTooManyBananas'); // true + ``` + + @method computed.gte + @for Ember + @param {String} dependentKey + @param {Number} value + @return {Ember.ComputedProperty} computed property which returns true if + the original value for property is greater or equal then given value. + */ + registerComputed('gte', function(dependentKey, value) { + return get(this, dependentKey) >= value; + }); + + /** + A computed property that returns true if the provided dependent property + is less than the provided value. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + needsMoreBananas: computed.lt('numBananas', 3) + }); + var hamster = Hamster.create(); + hamster.get('needsMoreBananas'); // true + hamster.set('numBananas', 3); + hamster.get('needsMoreBananas'); // false + hamster.set('numBananas', 2); + hamster.get('needsMoreBananas'); // true + ``` + + @method computed.lt + @for Ember + @param {String} dependentKey + @param {Number} value + @return {Ember.ComputedProperty} computed property which returns true if + the original value for property is less then given value. + */ + registerComputed('lt', function(dependentKey, value) { + return get(this, dependentKey) < value; + }); + + /** + A computed property that returns true if the provided dependent property + is less than or equal to the provided value. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + needsMoreBananas: computed.lte('numBananas', 3) + }); + var hamster = Hamster.create(); + hamster.get('needsMoreBananas'); // true + hamster.set('numBananas', 5); + hamster.get('needsMoreBananas'); // false + hamster.set('numBananas', 3); + hamster.get('needsMoreBananas'); // true + ``` + + @method computed.lte + @for Ember + @param {String} dependentKey + @param {Number} value + @return {Ember.ComputedProperty} computed property which returns true if + the original value for property is less or equal then given value. + */ + registerComputed('lte', function(dependentKey, value) { + return get(this, dependentKey) <= value; + }); + + /** + A computed property that performs a logical `and` on the + original values for the provided dependent properties. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + readyForCamp: computed.and('hasTent', 'hasBackpack') + }); + var hamster = Hamster.create(); + hamster.get('readyForCamp'); // false + hamster.set('hasTent', true); + hamster.get('readyForCamp'); // false + hamster.set('hasBackpack', true); + hamster.get('readyForCamp'); // true + ``` + + @method computed.and + @for Ember + @param {String} dependentKey* + @return {Ember.ComputedProperty} computed property which performs + a logical `and` on the values of all the original values for properties. + */ + registerComputedWithProperties('and', function(properties) { + for (var key in properties) { + if (properties.hasOwnProperty(key) && !properties[key]) { + return false; + } + } + return true; + }); + + /** + A computed property which performs a logical `or` on the + original values for the provided dependent properties. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + readyForRain: computed.or('hasJacket', 'hasUmbrella') + }); + var hamster = Hamster.create(); + hamster.get('readyForRain'); // false + hamster.set('hasJacket', true); + hamster.get('readyForRain'); // true + ``` + + @method computed.or + @for Ember + @param {String} dependentKey* + @return {Ember.ComputedProperty} computed property which performs + a logical `or` on the values of all the original values for properties. + */ + registerComputedWithProperties('or', function(properties) { + for (var key in properties) { + if (properties.hasOwnProperty(key) && properties[key]) { + return true; + } + } + return false; + }); + + /** + A computed property that returns the first truthy value + from a list of dependent properties. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + hasClothes: computed.any('hat', 'shirt') + }); + var hamster = Hamster.create(); + hamster.get('hasClothes'); // null + hamster.set('shirt', 'Hawaiian Shirt'); + hamster.get('hasClothes'); // 'Hawaiian Shirt' + ``` + + @method computed.any + @for Ember + @param {String} dependentKey* + @return {Ember.ComputedProperty} computed property which returns + the first truthy value of given list of properties. + */ + registerComputedWithProperties('any', function(properties) { + for (var key in properties) { + if (properties.hasOwnProperty(key) && properties[key]) { + return properties[key]; + } + } + return null; + }); + + /** + A computed property that returns the array of values + for the provided dependent properties. + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + clothes: computed.collect('hat', 'shirt') + }); + var hamster = Hamster.create(); + hamster.get('clothes'); // [null, null] + hamster.set('hat', 'Camp Hat'); + hamster.set('shirt', 'Camp Shirt'); + hamster.get('clothes'); // ['Camp Hat', 'Camp Shirt'] + ``` + + @method computed.collect + @for Ember + @param {String} dependentKey* + @return {Ember.ComputedProperty} computed property which maps + values of all passed properties in to an array. + */ + registerComputedWithProperties('collect', function(properties) { + var res = []; + for (var key in properties) { + if (properties.hasOwnProperty(key)) { + if (isNone(properties[key])) { + res.push(null); + } else { + res.push(properties[key]); + } + } + } + return res; + }); + + /** + Creates a new property that is an alias for another property + on an object. Calls to `get` or `set` this property behave as + though they were called on the original property. + + ```javascript + Person = Ember.Object.extend({ + name: 'Alex Matchneer', + nomen: computed.alias('name') + }); + + alex = Person.create(); + alex.get('nomen'); // 'Alex Matchneer' + alex.get('name'); // 'Alex Matchneer' + + alex.set('nomen', '@machty'); + alex.get('name'); // '@machty' + ``` + @method computed.alias + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computed property which creates an + alias to the original value for property. + */ + computed.alias = function(dependentKey) { + return computed(dependentKey, function(key, value) { + if (arguments.length > 1) { + set(this, dependentKey, value); + return value; + } else { + return get(this, dependentKey); + } + }); + }; + + /** + Where `computed.alias` aliases `get` and `set`, and allows for bidirectional + data flow, `computed.oneWay` only provides an aliased `get`. The `set` will + not mutate the upstream property, rather causes the current property to + become the value set. This causes the downstream property to permentantly + diverge from the upstream property. + + Example + + ```javascript + User = Ember.Object.extend({ + firstName: null, + lastName: null, + nickName: computed.oneWay('firstName') + }); + + user = User.create({ + firstName: 'Teddy', + lastName: 'Zeenny' + }); + + user.get('nickName'); + # 'Teddy' + + user.set('nickName', 'TeddyBear'); + # 'TeddyBear' + + user.get('firstName'); + # 'Teddy' + ``` + + @method computed.oneWay + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computed property which creates a + one way computed property to the original value for property. + */ + computed.oneWay = function(dependentKey) { + return computed(dependentKey, function() { + return get(this, dependentKey); + }); + }; + + if (Ember.FEATURES.isEnabled('query-params-new')) { + /** + This is a more semantically meaningful alias of `computed.oneWay`, + whose name is somewhat ambiguous as to which direction the data flows. + + @method computed.reads + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computed property which creates a + one way computed property to the original value for property. + */ + computed.reads = computed.oneWay; + } + + + /** + Where `computed.oneWay` provides oneWay bindings, `computed.readOnly` provides + a readOnly one way binding. Very often when using `computed.oneWay` one does + not also want changes to propogate back up, as they will replace the value. + + This prevents the reverse flow, and also throws an exception when it occurs. + + Example + + ```javascript + User = Ember.Object.extend({ + firstName: null, + lastName: null, + nickName: computed.readOnly('firstName') + }); + + user = User.create({ + firstName: 'Teddy', + lastName: 'Zeenny' + }); + + user.get('nickName'); + # 'Teddy' + + user.set('nickName', 'TeddyBear'); + # throws Exception + # throw new Ember.Error('Cannot Set: nickName on: ' );` + + user.get('firstName'); + # 'Teddy' + ``` + + @method computed.readOnly + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computed property which creates a + one way computed property to the original value for property. + */ + computed.readOnly = function(dependentKey) { + return computed(dependentKey, function() { + return get(this, dependentKey); + }).readOnly(); + }; + + /** + A computed property that acts like a standard getter and setter, + but returns the value at the provided `defaultPath` if the + property itself has not been set to a value + + Example + + ```javascript + var Hamster = Ember.Object.extend({ + wishList: computed.defaultTo('favoriteFood') + }); + var hamster = Hamster.create({favoriteFood: 'Banana'}); + hamster.get('wishList'); // 'Banana' + hamster.set('wishList', 'More Unit Tests'); + hamster.get('wishList'); // 'More Unit Tests' + hamster.get('favoriteFood'); // 'Banana' + ``` + + @method computed.defaultTo + @for Ember + @param {String} defaultPath + @return {Ember.ComputedProperty} computed property which acts like + a standard getter and setter, but defaults to the value from `defaultPath`. + */ + // ES6TODO: computed should have its own export path so you can do import {defaultTo} from computed + computed.defaultTo = function(defaultPath) { + return computed(function(key, newValue, cachedValue) { + if (arguments.length === 1) { + return get(this, defaultPath); + } + return newValue != null ? newValue : get(this, defaultPath); + }); + }; + + __exports__.ComputedProperty = ComputedProperty; + __exports__.computed = computed; + __exports__.cacheFor = cacheFor; + }); +define("ember-metal/core", + ["exports"], + function(__exports__) { + "use strict"; + /*globals Em:true ENV EmberENV MetamorphENV:true */ + + /** + @module ember + @submodule ember-metal + */ + + /** + All Ember methods and functions are defined inside of this namespace. You + generally should not add new properties to this namespace as it may be + overwritten by future versions of Ember. + + You can also use the shorthand `Em` instead of `Ember`. + + Ember-Runtime is a framework that provides core functions for Ember including + cross-platform functions, support for property observing and objects. Its + focus is on small size and performance. You can use this in place of or + along-side other cross-platform libraries such as jQuery. + + The core Runtime framework is based on the jQuery API with a number of + performance optimizations. + + @class Ember + @static + @version 1.6.0-beta.1+canary.1efbf7a1 + */ + + if ('undefined' === typeof Ember) { + // Create core object. Make it act like an instance of Ember.Namespace so that + // objects assigned to it are given a sane string representation. + Ember = {}; + } + + // Default imports, exports and lookup to the global object; + var imports = Ember.imports = Ember.imports || this; + var exports = Ember.exports = Ember.exports || this; + var lookup = Ember.lookup = Ember.lookup || this; + + // aliases needed to keep minifiers from removing the global context + exports.Em = exports.Ember = Ember; + + // Make sure these are set whether Ember was already defined or not + + Ember.isNamespace = true; + + Ember.toString = function() { return "Ember"; }; + + + /** + @property VERSION + @type String + @default '1.6.0-beta.1+canary.1efbf7a1' + @static + */ + Ember.VERSION = '1.6.0-beta.1+canary.1efbf7a1'; + + /** + Standard environmental variables. You can define these in a global `EmberENV` + variable before loading Ember to control various configuration settings. + + For backwards compatibility with earlier versions of Ember the global `ENV` + variable will be used if `EmberENV` is not defined. + + @property ENV + @type Hash + */ + + if (Ember.ENV) { + // do nothing if Ember.ENV is already setup + } else if ('undefined' !== typeof EmberENV) { + Ember.ENV = EmberENV; + } else if('undefined' !== typeof ENV) { + Ember.ENV = ENV; } else { - method.call(target); + Ember.ENV = {}; } - } - 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]; + Ember.config = Ember.config || {}; - return !!(actions && actions.length); -} + // We disable the RANGE API by default for performance reasons + if ('undefined' === typeof Ember.ENV.DISABLE_RANGE_API) { + Ember.ENV.DISABLE_RANGE_API = true; + } -/** - @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 ("undefined" === typeof MetamorphENV) { + exports.MetamorphENV = {}; + } - if (!actions) { return ret; } + MetamorphENV.DISABLE_RANGE_API = Ember.ENV.DISABLE_RANGE_API; - for (var i = 0, l = actions.length; i < l; i += 3) { - var target = actions[i], - method = actions[i+1]; - ret.push([target, method]); - } + /** + Hash of enabled Canary features. Add to before creating your application. - return ret; -} + You can also define `ENV.FEATURES` if you need to enable features flagged at runtime. -/** - Define a property as a function that should be executed when - a specified event or events are triggered. + @property FEATURES + @type Hash + */ + Ember.FEATURES = Ember.ENV.FEATURES || {}; + + /** + Test that a feature is enabled. Parsed by Ember's build tools to leave + experimental features out of beta/stable builds. + + You can define the following configuration options: + + * `ENV.ENABLE_ALL_FEATURES` - force all features to be enabled. + * `ENV.ENABLE_OPTIONAL_FEATURES` - enable any features that have not been explicitly + enabled/disabled. + + @method isEnabled + @param {string} feature + */ + + Ember.FEATURES.isEnabled = function(feature) { + var featureValue = Ember.FEATURES[feature]; + + if (Ember.ENV.ENABLE_ALL_FEATURES) { + return true; + } else if (featureValue === true || featureValue === false || featureValue === undefined) { + return featureValue; + } else if (Ember.ENV.ENABLE_OPTIONAL_FEATURES) { + return true; + } else { + return false; + } + }; + + // .......................................................... + // BOOTSTRAP + // + + /** + Determines whether Ember should enhances some built-in object prototypes to + provide a more friendly API. If enabled, a few methods will be added to + `Function`, `String`, and `Array`. `Object.prototype` will not be enhanced, + which is the one that causes most trouble for people. + + In general we recommend leaving this option set to true since it rarely + conflicts with other code. If you need to turn it off however, you can + define an `ENV.EXTEND_PROTOTYPES` config to disable it. + + @property EXTEND_PROTOTYPES + @type Boolean + @default true + */ + Ember.EXTEND_PROTOTYPES = Ember.ENV.EXTEND_PROTOTYPES; + + if (typeof Ember.EXTEND_PROTOTYPES === 'undefined') { + Ember.EXTEND_PROTOTYPES = true; + } + + /** + Determines whether Ember logs a full stack trace during deprecation warnings + + @property LOG_STACKTRACE_ON_DEPRECATION + @type Boolean + @default true + */ + Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !== false); + + /** + Determines whether Ember should add ECMAScript 5 shims to older browsers. + + @property SHIM_ES5 + @type Boolean + @default Ember.EXTEND_PROTOTYPES + */ + Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES; + + /** + Determines whether Ember logs info about version of used libraries + + @property LOG_VERSION + @type Boolean + @default true + */ + Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true; + + /** + Empty function. Useful for some operations. Always returns `this`. + + @method K + @private + @return {Object} + */ + Ember.K = function() { return this; }; + + + // Stub out the methods defined by the ember-debug package in case it's not loaded + + if ('undefined' === typeof Ember.assert) { Ember.assert = Ember.K; } + if ('undefined' === typeof Ember.warn) { Ember.warn = Ember.K; } + if ('undefined' === typeof Ember.debug) { Ember.debug = Ember.K; } + if ('undefined' === typeof Ember.runInDebug) { Ember.runInDebug = Ember.K; } + if ('undefined' === typeof Ember.deprecate) { Ember.deprecate = Ember.K; } + if ('undefined' === typeof Ember.deprecateFunc) { + Ember.deprecateFunc = function(_, func) { return func; }; + } + + /** + Previously we used `Ember.$.uuid`, however `$.uuid` has been removed from + jQuery master. We'll just bootstrap our own uuid now. + + @property uuid + @type Number + @private + */ + Ember.uuid = 0; + + __exports__["default"] = Ember; + }); +define("ember-metal/enumerable_utils", + ["ember-metal/array","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var map, forEach, indexOf, splice, filter; + + var map = __dependency1__.map; + var forEach = __dependency1__.forEach; + var indexOf = __dependency1__.indexOf; + var filter = __dependency1__.filter; + + // ES6TODO: doesn't array polyfills already do this? + map = Array.prototype.map || map; + forEach = Array.prototype.forEach || forEach; + indexOf = Array.prototype.indexOf || indexOf; + filter = Array.prototype.filter || filter; + splice = Array.prototype.splice; + + /** + * Defines some convenience methods for working with Enumerables. + * `Ember.EnumerableUtils` uses `Ember.ArrayPolyfills` when necessary. + * + * @class EnumerableUtils + * @namespace Ember + * @static + * */ + var utils = { + /** + * Calls the map function on the passed object with a specified callback. This + * uses `Ember.ArrayPolyfill`'s-map method when necessary. + * + * @method map + * @param {Object} obj The object that should be mapped + * @param {Function} callback The callback to execute + * @param {Object} thisArg Value to use as this when executing *callback* + * + * @return {Array} An array of mapped values. + */ + map: function(obj, callback, thisArg) { + return obj.map ? obj.map.call(obj, callback, thisArg) : map.call(obj, callback, thisArg); + }, + + /** + * Calls the forEach function on the passed object with a specified callback. This + * uses `Ember.ArrayPolyfill`'s-forEach method when necessary. + * + * @method forEach + * @param {Object} obj The object to call forEach on + * @param {Function} callback The callback to execute + * @param {Object} thisArg Value to use as this when executing *callback* + * + */ + forEach: function(obj, callback, thisArg) { + return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : forEach.call(obj, callback, thisArg); + }, + + /** + * Calls the filter function on the passed object with a specified callback. This + * uses `Ember.ArrayPolyfill`'s-filter method when necessary. + * + * @method filter + * @param {Object} obj The object to call filter on + * @param {Function} callback The callback to execute + * @param {Object} thisArg Value to use as this when executing *callback* + * + * @return {Array} An array containing the filtered values + */ + filter: function(obj, callback, thisArg) { + return obj.filter ? obj.filter.call(obj, callback, thisArg) : filter.call(obj, callback, thisArg); + }, + + /** + * Calls the indexOf function on the passed object with a specified callback. This + * uses `Ember.ArrayPolyfill`'s-indexOf method when necessary. + * + * @method indexOf + * @param {Object} obj The object to call indexOn on + * @param {Function} callback The callback to execute + * @param {Object} index The index to start searching from + * + */ + indexOf: function(obj, element, index) { + return obj.indexOf ? obj.indexOf.call(obj, element, index) : indexOf.call(obj, element, index); + }, + + /** + * Returns an array of indexes of the first occurrences of the passed elements + * on the passed object. + * + * ```javascript + * var array = [1, 2, 3, 4, 5]; + * Ember.EnumerableUtils.indexesOf(array, [2, 5]); // [1, 4] + * + * var fubar = "Fubarr"; + * Ember.EnumerableUtils.indexesOf(fubar, ['b', 'r']); // [2, 4] + * ``` + * + * @method indexesOf + * @param {Object} obj The object to check for element indexes + * @param {Array} elements The elements to search for on *obj* + * + * @return {Array} An array of indexes. + * + */ + indexesOf: function(obj, elements) { + return elements === undefined ? [] : utils.map(elements, function(item) { + return utils.indexOf(obj, item); + }); + }, + + /** + * Adds an object to an array. If the array already includes the object this + * method has no effect. + * + * @method addObject + * @param {Array} array The array the passed item should be added to + * @param {Object} item The item to add to the passed array + * + * @return 'undefined' + */ + addObject: function(array, item) { + var index = utils.indexOf(array, item); + if (index === -1) { array.push(item); } + }, + + /** + * Removes an object from an array. If the array does not contain the passed + * object this method has no effect. + * + * @method removeObject + * @param {Array} array The array to remove the item from. + * @param {Object} item The item to remove from the passed array. + * + * @return 'undefined' + */ + removeObject: function(array, item) { + var index = utils.indexOf(array, item); + if (index !== -1) { array.splice(index, 1); } + }, + + _replace: function(array, idx, amt, objects) { + var args = [].concat(objects), chunk, ret = [], + // https://code.google.com/p/chromium/issues/detail?id=56588 + size = 60000, start = idx, ends = amt, count; + + while (args.length) { + count = ends > size ? size : ends; + if (count <= 0) { count = 0; } + + chunk = args.splice(0, size); + chunk = [start, count].concat(chunk); + + start += size; + ends -= count; + + ret = ret.concat(splice.apply(array, chunk)); + } + return ret; + }, + + /** + * Replaces objects in an array with the passed objects. + * + * ```javascript + * var array = [1,2,3]; + * Ember.EnumerableUtils.replace(array, 1, 2, [4, 5]); // [1, 4, 5] + * + * var array = [1,2,3]; + * Ember.EnumerableUtils.replace(array, 1, 1, [4, 5]); // [1, 4, 5, 3] + * + * var array = [1,2,3]; + * Ember.EnumerableUtils.replace(array, 10, 1, [4, 5]); // [1, 2, 3, 4, 5] + * ``` + * + * @method replace + * @param {Array} array The array the objects should be inserted into. + * @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 remove 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* + * + * @return {Array} The changed array. + */ + replace: function(array, idx, amt, objects) { + if (array.replace) { + return array.replace(idx, amt, objects); + } else { + return utils._replace(array, idx, amt, objects); + } + }, + + /** + * Calculates the intersection of two arrays. This method returns a new array + * filled with the records that the two passed arrays share with each other. + * If there is no intersection, an empty array will be returned. + * + * ```javascript + * var array1 = [1, 2, 3, 4, 5]; + * var array2 = [1, 3, 5, 6, 7]; + * + * Ember.EnumerableUtils.intersection(array1, array2); // [1, 3, 5] + * + * var array1 = [1, 2, 3]; + * var array2 = [4, 5, 6]; + * + * Ember.EnumerableUtils.intersection(array1, array2); // [] + * ``` + * + * @method intersection + * @param {Array} array1 The first array + * @param {Array} array2 The second array + * + * @return {Array} The intersection of the two passed arrays. + */ + intersection: function(array1, array2) { + var intersection = []; + + utils.forEach(array1, function(element) { + if (utils.indexOf(array2, element) >= 0) { + intersection.push(element); + } + }); + + return intersection; + } + }; + + __exports__["default"] = utils; + }); +define("ember-metal/error", + ["ember-metal/platform","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var create = __dependency1__.create; + + 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 + */ + var EmberError = function() { + var tmp = Error.apply(this, arguments); + + // Adds a `stack` property to the given error object that will yield the + // stack trace at the time captureStackTrace was called. + // When collecting the stack trace all frames above the topmost call + // to this function, including that call, will be left out of the + // stack trace. + // This is useful because we can hide Ember implementation details + // that are not very helpful for the user. + if (Error.captureStackTrace) { + Error.captureStackTrace(this, Ember.Error); + } + // 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]]; + } + }; + + EmberError.prototype = create(Error.prototype); + + __exports__["default"] = EmberError; + }); +define("ember-metal/events", + ["ember-metal/core","ember-metal/utils","ember-metal/platform","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + /** + @module ember-metal + */ + var Ember = __dependency1__["default"]; + var meta = __dependency2__.meta; + var META_KEY = __dependency2__.META_KEY; + var tryFinally = __dependency2__.tryFinally; + var apply = __dependency2__.apply; + var applyStr = __dependency2__.applyStr; + var create = __dependency3__.create; + + var a_slice = [].slice, + metaFor = meta, + /* listener flags */ + ONCE = 1, SUSPENDED = 2; + + + /* + 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, flags + ] + } + } + + */ + + function indexOf(array, target, method) { + var index = -1; + // hashes are added to the end of the event array + // so it makes sense to start searching at the end + // of the array and search in reverse + for (var i = array.length - 3 ; i >=0; i -= 3) { + if (target === array[i] && 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 = 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 listenersUnion(obj, eventName, otherActions) { + var meta = obj[META_KEY], + actions = meta && meta.listeners && meta.listeners[eventName]; + + if (!actions) { return; } + for (var i = actions.length - 3; i >= 0; i -= 3) { + var target = actions[i], + method = actions[i+1], + flags = actions[i+2], + actionIndex = indexOf(otherActions, target, method); + + if (actionIndex === -1) { + otherActions.push(target, method, flags); + } + } + } + + function listenersDiff(obj, eventName, otherActions) { + var meta = obj[META_KEY], + actions = meta && meta.listeners && meta.listeners[eventName], + diffActions = []; + + if (!actions) { return; } + for (var i = actions.length - 3; i >= 0; i -= 3) { + var target = actions[i], + method = actions[i+1], + flags = actions[i+2], + actionIndex = indexOf(otherActions, target, method); + + if (actionIndex !== -1) { continue; } + + otherActions.push(target, method, flags); + diffActions.push(target, method, flags); + } + + 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), + flags = 0; + + if (once) flags |= ONCE; + + if (actionIndex !== -1) { return; } + + actions.push(target, method, flags); + + if ('function' === typeof obj.didAddListener) { + obj.didAddListener(eventName, target, method); + } + } + + /** + Remove an event listener + + Arguments should match those passed to `Ember.addListener`. + + @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) { + var actions = actionsFor(obj, eventName), + actionIndex = indexOf(actions, target, method); + + // action doesn't exist, give up silently + if (actionIndex === -1) { return; } + + actions.splice(actionIndex, 3); + + 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 - 3; i >= 0; i -= 3) { + _removeListener(actions[i], actions[i+1]); + } + } + } + + /** + 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 + + @private + @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); + + if (actionIndex !== -1) { + actions[actionIndex+2] |= SUSPENDED; // mark the action as suspended + } + + function tryable() { return callback.call(target); } + function finalizer() { if (actionIndex !== -1) { actions[actionIndex+2] &= ~SUSPENDED; } } + + return tryFinally(tryable, finalizer); + } + + /** + Suspends multiple listeners during a callback. + + @method suspendListeners + @for Ember + + @private + @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 = [], + actionsList = [], + eventName, actions, i, l; + + for (i=0, l=eventNames.length; i= 0; i -= 3) { // looping in reverse for once listeners + var target = actions[i], method = actions[i+1], flags = actions[i+2]; + if (!method) { continue; } + if (flags & SUSPENDED) { continue; } + if (flags & ONCE) { removeListener(obj, eventName, target, method); } + if (!target) { target = obj; } + if ('string' === typeof method) { + if (params) { + applyStr(target, method, params); + } else { + target[method](); + } + } else { + if (params) { + apply(target, method, 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 += 3) { + var target = actions[i], + method = actions[i+1]; + ret.push([target, method]); + } + + return ret; + } + + /** + Define a property as a function that should be executed when + a specified event or events are triggered. + + + ``` javascript var Job = Ember.Object.extend({ logCompleted: Ember.on('completed', function(){ console.log('Job completed!'); @@ -2355,3037 +3364,4637 @@ function listenersFor(obj, eventName) { }); var job = Job.create(); Ember.sendEvent(job, 'completed'); // Logs "Job completed!" + ``` - @method on - @for Ember - @param {String} eventNames* - @param {Function} func - @return func -*/ -Ember.on = function(){ - var func = a_slice.call(arguments, -1)[0], - events = a_slice.call(arguments, 0, -1); - func.__ember_listens__ = events; - return func; -}; + @method on + @for Ember + @param {String} eventNames* + @param {Function} func + @return func + */ + function on(){ + var func = a_slice.call(arguments, -1)[0], + events = a_slice.call(arguments, 0, -1); + func.__ember_listens__ = events; + return func; + }; -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() { -var guidFor = Ember.guidFor, - sendEvent = Ember.sendEvent; - -/* - this.observerSet = { - [senderGuid]: { // variable name: `keySet` - [keyName]: listIndex - } - }, - this.observers = [ - { - sender: obj, - keyName: keyName, - eventName: eventName, - listeners: [ - [target, method, flags] - ] - }, - ... - ] -*/ -var ObserverSet = Ember._ObserverSet = function() { - this.clear(); -}; - -ObserverSet.prototype.add = function(sender, keyName, eventName) { - var observerSet = this.observerSet, - observers = this.observers, - senderGuid = 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; } - sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners); - } -}; - -ObserverSet.prototype.clear = function() { - this.observerSet = {}; - this.observers = []; -}; -})(); - - - -(function() { -var metaFor = Ember.meta, - guidFor = Ember.guidFor, - tryFinally = Ember.tryFinally, - sendEvent = Ember.sendEvent, - listenersUnion = Ember.listenersUnion, - listenersDiff = Ember.listenersDiff, - ObserverSet = Ember._ObserverSet, - beforeObserverSet = new ObserverSet(), - observerSet = new ObserverSet(), - deferred = 0; - -// .......................................................... -// 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); - 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, false); - notifyObservers(obj, keyName); -} -Ember.propertyDidChange = propertyDidChange; - -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; } -} - -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); - } - } -} - -function chainsWillChange(obj, keyName, m) { - if (!(m.hasOwnProperty('chainWatchers') && - m.chainWatchers[keyName])) { - return; - } - - var nodes = m.chainWatchers[keyName], - events = [], - i, l; - - for(i = 0, l = nodes.length; i < l; i++) { - nodes[i].willChange(events); - } - - for (i = 0, l = events.length; i < l; i += 2) { - propertyWillChange(events[i], events[i+1]); - } -} - -function chainsDidChange(obj, keyName, m, suppressEvents) { - if (!(m.hasOwnProperty('chainWatchers') && - m.chainWatchers[keyName])) { - return; - } - - var nodes = m.chainWatchers[keyName], - events = suppressEvents ? null : [], - i, l; - - for(i = 0, l = nodes.length; i < l; i++) { - nodes[i].didChange(events); - } - - if (suppressEvents) { - return; - } - - for (i = 0, l = events.length; i < l; i += 2) { - propertyDidChange(events[i], events[i+1]); - } -} - -Ember.overrideChains = function(obj, keyName, m) { - chainsDidChange(obj, keyName, m, true); -}; - -/** - @method beginPropertyChanges - @chainable - @private -*/ -function beginPropertyChanges() { - deferred++; -} - -Ember.beginPropertyChanges = beginPropertyChanges; - -/** - @method endPropertyChanges - @private -*/ -function endPropertyChanges() { - deferred--; - if (deferred<=0) { - beforeObserverSet.clear(); - observerSet.flush(); - } -} - -Ember.endPropertyChanges = endPropertyChanges; - -/** - Make a series of property changes together in an - exception-safe way. - - ```javascript - Ember.changeProperties(function() { - obj1.set('foo', mayBlowUpWhenSet); - obj2.set('bar', baz); + __exports__.on = on; + __exports__.addListener = addListener; + __exports__.removeListener = removeListener; + __exports__.suspendListener = suspendListener; + __exports__.suspendListeners = suspendListeners; + __exports__.sendEvent = sendEvent; + __exports__.hasListeners = hasListeners; + __exports__.watchedEvents = watchedEvents; + __exports__.listenersFor = listenersFor; + __exports__.listenersDiff = listenersDiff; + __exports__.listenersUnion = listenersUnion; }); - ``` +define("ember-metal/expand_properties", + ["ember-metal/enumerable_utils","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var EnumerableUtils = __dependency1__["default"]; - @method changeProperties - @param {Function} callback - @param [binding] -*/ -Ember.changeProperties = function(cb, binding) { - beginPropertyChanges(); - tryFinally(cb, endPropertyChanges, binding); -}; + /** + @module ember-metal + */ -function notifyBeforeObservers(obj, keyName) { - if (obj.isDestroying) { return; } + var forEach = EnumerableUtils.forEach, + BRACE_EXPANSION = /^((?:[^\.]*\.)*)\{(.*)\}$/; - var eventName = keyName + ':before', listeners, diff; - if (deferred) { - listeners = beforeObserverSet.add(obj, keyName, eventName); - diff = listenersDiff(obj, eventName, listeners); - sendEvent(obj, eventName, [obj, keyName], diff); - } else { - sendEvent(obj, eventName, [obj, keyName]); - } -} + /** + Expands `pattern`, invoking `callback` for each expansion. -function notifyObservers(obj, keyName) { - if (obj.isDestroying) { return; } + The only pattern supported is brace-expansion, anything else will be passed + once to `callback` directly. Brace expansion can only appear at the end of a + pattern, for example as the last item in a chain. - var eventName = keyName + ':change', listeners; - if (deferred) { - listeners = observerSet.add(obj, keyName, eventName); - listenersUnion(obj, eventName, listeners); - } else { - sendEvent(obj, eventName, [obj, keyName]); - } -} + Example + ```js + function echo(arg){ console.log(arg); } -})(); + Ember.expandProperties('foo.bar', echo); //=> 'foo.bar' + Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar' + Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz' + Ember.expandProperties('{foo,bar}.baz', echo); //=> '{foo,bar}.baz' + ``` + @method + @private + @param {string} pattern The property pattern to expand. + @param {function} callback The callback to invoke. It is invoked once per + expansion, and is passed the expansion. + */ + function expandProperties(pattern, callback) { + var match, prefix, list; + if (match = BRACE_EXPANSION.exec(pattern)) { + prefix = match[1]; + list = match[2]; -(function() { -// META_KEY -// _getPath -// propertyWillChange, propertyDidChange - -var META_KEY = Ember.META_KEY, - MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER, - IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/, - getPath = Ember._getPath; - -/** - 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 `setUnknownProperty` - 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 `setUnknownProperty` 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. -*/ -var 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; - } - - Ember.assert("Cannot call set with "+ keyName +" key.", !!keyName); - - 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]; + forEach(list.split(','), function (suffix) { + callback(prefix + suffix); + }); } else { - currentValue = obj[keyName]; + callback(pattern); } - // 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 + }; + + __exports__["default"] = expandProperties; + }); +define("ember-metal/get_properties", + ["ember-metal/property_get","ember-metal/utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var get = __dependency1__.get; + var typeOf = __dependency2__.typeOf; + + /** + To get multiple properties at once, call `Ember.getProperties` + with an object followed by a list of strings or an array: + + ```javascript + Ember.getProperties(record, 'firstName', 'lastName', 'zipCode'); + // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } + ``` + + is equivalent to: + + ```javascript + Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']); + // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } + ``` + + @method getProperties + @param obj + @param {String...|Array} list of keys to get + @return {Hash} + */ + function getProperties(obj) { + var ret = {}, + propertyNames = arguments, + i = 1; + + if (arguments.length === 2 && typeOf(arguments[1]) === 'array') { + i = 0; + propertyNames = arguments[1]; + } + for(var len = propertyNames.length; i < len; i++) { + ret[propertyNames[i]] = get(obj, propertyNames[i]); + } + return ret; + }; + + __exports__["default"] = getProperties; + }); +define("ember-metal/instrumentation", + ["ember-metal/core","ember-metal/utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var tryCatchFinally = __dependency2__.tryCatchFinally; + + /** + The purpose of the Ember Instrumentation module is + to provide efficient, general-purpose instrumentation + for Ember. + + Subscribe to a listener by using `Ember.subscribe`: + + ```javascript + Ember.subscribe("render", { + before: function(name, timestamp, payload) { + + }, + + after: function(name, timestamp, payload) { + + } + }); + ``` + + If you return a value from the `before` callback, that same + value will be passed as a fourth parameter to the `after` + callback. + + Instrument a block of code by using `Ember.instrument`: + + ```javascript + Ember.instrument("render.handlebars", payload, function() { + // rendering logic + }, binding); + ``` + + Event names passed to `Ember.instrument` are namespaced + by periods, from more general to more specific. Subscribers + can listen for events by whatever level of granularity they + are interested in. + + In the above example, the event is `render.handlebars`, + and the subscriber listened for all events beginning with + `render`. It would receive callbacks for events named + `render`, `render.handlebars`, `render.container`, or + even `render.handlebars.layout`. + + @class Instrumentation + @namespace Ember + @static + */ + var subscribers = [], cache = {}; + + var populateListeners = function(name) { + var listeners = [], subscriber; + + for (var i=0, l=subscribers.length; i -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.toArray(); + + 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.toArray(); + + 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 = OrderedSet.create(); + this.values = {}; + }; + + /** + @method create + @static + */ + Map.create = function() { + return new Map(); + }; + + Map.prototype = { + /** + This property will change as the number of objects in the map changes. + + @property length + @type number + @default 0 + */ + length: 0, + + + /** + Retrieve the value associated with a given key. + + @method get + @param {*} key + @return {*} 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 {*} key + @param {*} value + */ + set: function(key, value) { + var keys = this.keys, + values = this.values, + guid = guidFor(key); + + keys.add(key); + values[guid] = value; + set(this, 'length', keys.list.length); + }, + + /** + Removes a value from the map for an associated key. + + @method remove + @param {*} 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); + + if (values.hasOwnProperty(guid)) { + keys.remove(key); + delete values[guid]; + set(this, 'length', keys.list.length); + return true; + } else { + return false; + } + }, + + /** + Check whether a key is present. + + @method has + @param {*} 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 {*} 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 {*} [options.defaultValue] + */ + function MapWithDefault(options) { + Map.call(this); + this.defaultValue = options.defaultValue; + }; + + /** + @method create + @static + @param [options] + @param {*} [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 = create(Map.prototype); + + /** + Retrieve the value associated with a given key. + + @method get + @param {*} key + @return {*} 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 + })); + }; + + __exports__.OrderedSet = OrderedSet; + __exports__.Map = Map; + __exports__.MapWithDefault = MapWithDefault; + }); +define("ember-metal/merge", + ["exports"], + function(__exports__) { + "use strict"; + /** + Merge the contents of two objects together into the first object. + + ```javascript + Ember.merge({first: 'Tom'}, {last: 'Dale'}); // {first: 'Tom', last: 'Dale'} + var a = {first: 'Yehuda'}, b = {last: 'Katz'}; + Ember.merge(a, b); // a == {first: 'Yehuda', last: 'Katz'}, b == {last: 'Katz'} + ``` + + @method merge + @for Ember + @param {Object} original The object to merge into + @param {Object} updates The object to copy properties from + @return {Object} + */ + function merge(original, updates) { + for (var prop in updates) { + if (!updates.hasOwnProperty(prop)) { continue; } + original[prop] = updates[prop]; + } + return original; + }; + + __exports__["default"] = merge; + }); +define("ember-metal/mixin", + ["ember-metal/core","ember-metal/merge","ember-metal/array","ember-metal/platform","ember-metal/utils","ember-metal/expand_properties","ember-metal/properties","ember-metal/computed","ember-metal/binding","ember-metal/observer","ember-metal/events","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-metal + */ + + var Ember = __dependency1__["default"]; + // warn, assert, wrap, et; + var merge = __dependency2__["default"]; + var map = __dependency3__.map; + var indexOf = __dependency3__.indexOf; + var forEach = __dependency3__.forEach; + var create = __dependency4__.create; + var guidFor = __dependency5__.guidFor; + var meta = __dependency5__.meta; + var META_KEY = __dependency5__.META_KEY; + var wrap = __dependency5__.wrap; + var makeArray = __dependency5__.makeArray; + var apply = __dependency5__.apply; + var expandProperties = __dependency6__["default"]; + var Descriptor = __dependency7__.Descriptor; + var defineProperty = __dependency7__.defineProperty; + var ComputedProperty = __dependency8__.ComputedProperty; + var Binding = __dependency9__.Binding; + var addObserver = __dependency10__.addObserver; + var removeObserver = __dependency10__.removeObserver; + var addBeforeObserver = __dependency10__.addBeforeObserver; + var removeBeforeObserver = __dependency10__.removeBeforeObserver; + var addListener = __dependency11__.addListener; + var removeListener = __dependency11__.removeListener; + + var REQUIRED, Alias, + a_map = map, + a_indexOf = indexOf, + a_forEach = forEach, + a_slice = [].slice, + o_create = create, + defineProperty = defineProperty, + metaFor = meta; + + function superFunction(){ + var ret, func = this.__nextSuper; + if (func) { + this.__nextSuper = null; + ret = apply(this, func, arguments); + this.__nextSuper = func; + } + return ret; + } + + function mixinsMeta(obj) { + var m = metaFor(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 concatenatedMixinProperties(concatProp, props, values, base) { + var concats; + + // reset before adding each new mixin to pickup concats from previous + concats = values[concatProp] || base[concatProp]; + if (props[concatProp]) { + concats = concats ? concats.concat(props[concatProp]) : props[concatProp]; + } + + 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 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 = 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 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 makeArray(baseValue).concat(value); + } + } else { + return makeArray(value); + } + } + + function applyMergedProperties(obj, key, value, values) { + var baseValue = values[key] || obj[key]; + + if (!baseValue) { return value; } + + var newBase = merge({}, baseValue), + hasFunction = false; + + for (var prop in value) { + if (!value.hasOwnProperty(prop)) { continue; } + + var propValue = value[prop]; + if (isMethod(propValue)) { + // TODO: support for Computed Properties, etc? + hasFunction = true; + newBase[prop] = giveMethodSuper(obj, prop, propValue, baseValue, {}); + } else { + newBase[prop] = propValue; + } + } + + if (hasFunction) { + newBase._super = superFunction; + } + + return newBase; + } + + function addNormalizedProperty(base, key, value, meta, descs, values, concats, mergings) { + if (value instanceof Descriptor) { + if (value === REQUIRED && descs[key]) { return CONTINUE; } + + // Wrap descriptor function to implement + // __nextSuper() if needed + if (value.func) { + value = giveDescriptorSuper(meta, key, value, values, descs); + } + + descs[key] = value; + values[key] = undefined; + } else { + if ((concats && a_indexOf.call(concats, key) >= 0) || + key === 'concatenatedProperties' || + key === 'mergedProperties') { + value = applyConcatenatedProperties(base, key, value, values); + } else if ((mergings && a_indexOf.call(mergings, key) >= 0)) { + value = applyMergedProperties(base, key, value, values); + } else if (isMethod(value)) { + value = giveMethodSuper(base, key, value, values, descs); + } + + descs[key] = undefined; + values[key] = value; + } + } + + function mergeMixins(mixins, m, descs, values, base, keys) { + var mixin, props, key, concats, mergings, 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 m = obj[META_KEY], + mixins = m && m.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 m = obj[META_KEY], + mixins = m && m.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 Descriptor(); + REQUIRED.toString = function() { return '(Required Property)'; }; + + /** + Denotes a required property for a mixin + + @method required + @for Ember + */ + function required() { + return REQUIRED; + }; + + Alias = function(methodName) { + this.methodName = methodName; + }; + Alias.prototype = new Descriptor(); + + /** + 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} + */ + function aliasMethod(methodName) { + return new Alias(methodName); + }; + + // .......................................................... + // OBSERVER HELPER + // + + /** + Specify a method that observes property changes. + + ```javascript + Ember.Object.extend({ + valueObserver: Ember.observer('value', function() { + // Executes whenever the "value" property changes + }) + }); + ``` + + In the future this method may become asynchronous. If you want to ensure + synchronous behavior, use `immediateObserver`. + + Also available as `Function.prototype.observes` if prototype extensions are + enabled. + + @method observer + @for Ember + @param {String} propertyNames* + @param {Function} func + @return func + */ + function observer() { + var func = a_slice.call(arguments, -1)[0]; + var paths; + + var addWatchedProperty = function (path) { paths.push(path); }; + var _paths = a_slice.call(arguments, 0, -1); + + if (typeof func !== "function") { + // revert to old, soft-deprecated argument ordering + + func = arguments[0]; + _paths = a_slice.call(arguments, 1); + } + + paths = []; + + for (var i=0; i<_paths.length; ++i) { + expandProperties(_paths[i], addWatchedProperty); + } + + if (typeof func !== "function") { + throw new Ember.Error("Ember.observer called without a function"); + } + + func.__ember_observes__ = paths; + return func; + }; + + /** + Specify a method that observes property changes. + + ```javascript + Ember.Object.extend({ + valueObserver: Ember.immediateObserver('value', function() { + // Executes whenever the "value" property changes + }) + }); + ``` + + In the future, `Ember.observer` may become asynchronous. In this event, + `Ember.immediateObserver` will maintain the synchronous behavior. + + Also available as `Function.prototype.observesImmediately` if prototype extensions are + enabled. + + @method immediateObserver + @for Ember + @param {String} propertyNames* + @param {Function} func + @return func + */ + function immediateObserver() { + for (var i=0, l=arguments.length; i this.changingFrom ? 'green' : 'red'; + // logic + } + }), + + friendsDidChange: Ember.observer('friends.@each.name', function(obj, keyName) { + // some logic + // obj.get(keyName) returns friends array + }) + }); + ``` + + Also available as `Function.prototype.observesBefore` if prototype extensions are + enabled. + + @method beforeObserver + @for Ember + @param {String} propertyNames* + @param {Function} func + @return func + */ + function beforeObserver() { + var func = a_slice.call(arguments, -1)[0]; + var paths; + + var addWatchedProperty = function(path) { paths.push(path); }; + + var _paths = a_slice.call(arguments, 0, -1); + + if (typeof func !== "function") { + // revert to old, soft-deprecated argument ordering + + func = arguments[0]; + _paths = a_slice.call(arguments, 1); + } + + paths = []; + + for (var i=0; i<_paths.length; ++i) { + expandProperties(_paths[i], addWatchedProperty); + } + + if (typeof func !== "function") { + throw new Ember.Error("Ember.beforeObserver called without a function"); + } + + func.__ember_observesBefore__ = paths; + return func; + }; + + __exports__.IS_BINDING = IS_BINDING; + __exports__.mixin = mixin; + __exports__.Mixin = Mixin; + __exports__.required = required; + __exports__.aliasMethod = aliasMethod; + __exports__.observer = observer; + __exports__.immediateObserver = immediateObserver; + __exports__.beforeObserver = beforeObserver; + }); +define("ember-metal/observer", + ["ember-metal/watching","ember-metal/array","ember-metal/events","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var watch = __dependency1__.watch; + var unwatch = __dependency1__.unwatch; + var map = __dependency2__.map; + var listenersFor = __dependency3__.listenersFor; + var addListener = __dependency3__.addListener; + var removeListener = __dependency3__.removeListener; + var suspendListeners = __dependency3__.suspendListeners; + var suspendListener = __dependency3__.suspendListener; + /** + @module ember-metal + */ + + var AFTER_OBSERVERS = ':change', + BEFORE_OBSERVERS = ':before'; + + function changeEvent(keyName) { + return keyName+AFTER_OBSERVERS; + } + + function beforeEvent(keyName) { + return keyName+BEFORE_OBSERVERS; + } + + /** + @method addObserver + @for Ember + @param obj + @param {String} path + @param {Object|Function} targetOrMethod + @param {Function|String} [method] + */ + function addObserver(obj, _path, target, method) { + addListener(obj, changeEvent(_path), target, method); + watch(obj, _path); + + return this; + }; + + function observersFor(obj, path) { + return listenersFor(obj, changeEvent(path)); + }; + + /** + @method removeObserver + @for Ember + @param obj + @param {String} path + @param {Object|Function} targetOrMethod + @param {Function|String} [method] + */ + function removeObserver(obj, _path, target, method) { + unwatch(obj, _path); + removeListener(obj, changeEvent(_path), target, method); + + return this; + }; + + /** + @method addBeforeObserver + @for Ember + @param obj + @param {String} path + @param {Object|Function} targetOrMethod + @param {Function|String} [method] + */ + function addBeforeObserver(obj, _path, target, method) { + addListener(obj, beforeEvent(_path), target, method); + 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. + function _suspendBeforeObserver(obj, path, target, method, callback) { + return suspendListener(obj, beforeEvent(path), target, method, callback); + }; + + function _suspendObserver(obj, path, target, method, callback) { + return suspendListener(obj, changeEvent(path), target, method, callback); + }; + + function _suspendBeforeObservers(obj, paths, target, method, callback) { + var events = map.call(paths, beforeEvent); + return suspendListeners(obj, events, target, method, callback); + }; + + function _suspendObservers(obj, paths, target, method, callback) { + var events = map.call(paths, changeEvent); + return suspendListeners(obj, events, target, method, callback); + }; + + function beforeObserversFor(obj, path) { + return listenersFor(obj, beforeEvent(path)); + }; + + /** + @method removeBeforeObserver + @for Ember + @param obj + @param {String} path + @param {Object|Function} targetOrMethod + @param {Function|String} [method] + */ + function removeBeforeObserver(obj, _path, target, method) { + unwatch(obj, _path); + removeListener(obj, beforeEvent(_path), target, method); + + return this; + }; + + __exports__.addObserver = addObserver; + __exports__.observersFor = observersFor; + __exports__.removeObserver = removeObserver; + __exports__.addBeforeObserver = addBeforeObserver; + __exports__._suspendBeforeObserver = _suspendBeforeObserver; + __exports__._suspendObserver = _suspendObserver; + __exports__._suspendBeforeObservers = _suspendBeforeObservers; + __exports__._suspendObservers = _suspendObservers; + __exports__.beforeObserversFor = beforeObserversFor; + __exports__.removeBeforeObserver = removeBeforeObserver; + }); +define("ember-metal/observer_set", + ["ember-metal/utils","ember-metal/events","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var guidFor = __dependency1__.guidFor; + var sendEvent = __dependency2__.sendEvent; + + /* + this.observerSet = { + [senderGuid]: { // variable name: `keySet` + [keyName]: listIndex + } + }, + this.observers = [ + { + sender: obj, + keyName: keyName, + eventName: eventName, + listeners: [ + [target, method, flags] + ] + }, + ... + ] + */ + function ObserverSet() { + this.clear(); + }; + + ObserverSet.prototype.add = function(sender, keyName, eventName) { + var observerSet = this.observerSet, + observers = this.observers, + senderGuid = 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; } + sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners); + } + }; + + ObserverSet.prototype.clear = function() { + this.observerSet = {}; + this.observers = []; + }; + + __exports__["default"] = ObserverSet; + }); +define("ember-metal/platform", + ["ember-metal/core","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /*globals Node */ + + var Ember = __dependency1__["default"]; + + /** + @module ember-metal + */ + + /** + Platform specific methods and feature detectors needed by the framework. + + @class platform + @namespace Ember + @static + */ + // TODO remove this + var platform = {}; + + /** + Identical to `Object.create()`. Implements if not available natively. + + @method create + @for Ember + */ + var create = Object.create; + + // IE8 has Object.create but it couldn't treat property descriptors. + if (create) { + if (create({a: 1}, {a: {value: 2}}).a !== 2) { + create = null; + } + } + + // STUB_OBJECT_CREATE allows us to override other libraries that stub + // Object.create different than we would prefer + if (!create || Ember.ENV.STUB_OBJECT_CREATE) { + var K = function() {}; + + create = function(obj, props) { + K.prototype = obj; + obj = new K(); + if (props) { + K.prototype = obj; + for (var prop in props) { + K.prototype[prop] = props[prop].value; + } + obj = new K(); + } + K.prototype = null; + + return obj; + }; + + create.isSimulated = true; + } + + var defineProperty = Object.defineProperty; + var canRedefineProperties, canDefinePropertyOnDOM; + + // Catch IE8 where Object.defineProperty exists but only works on DOM elements + if (defineProperty) { + try { + defineProperty({}, 'a',{get:function() {}}); + } catch (e) { + defineProperty = null; + } + } + + if (defineProperty) { + // Detects a bug in Android <3.2 where you cannot redefine a property using + // Object.defineProperty once accessors have already been set. + canRedefineProperties = (function() { + var obj = {}; + + defineProperty(obj, 'a', { + configurable: true, + enumerable: true, + get: function() { }, + set: function() { } + }); + + defineProperty(obj, 'a', { + configurable: true, + enumerable: true, + writable: true, + value: true + }); + + return obj.a === true; + })(); + + // This is for Safari 5.0, which supports Object.defineProperty, but not + // on DOM nodes. + canDefinePropertyOnDOM = (function() { + try { + defineProperty(document.createElement('div'), 'definePropertyOnDOM', {}); + return true; + } catch(e) { } + + return false; + })(); + + if (!canRedefineProperties) { + defineProperty = null; + } else if (!canDefinePropertyOnDOM) { + defineProperty = function(obj, keyName, desc) { + var isNode; + + if (typeof Node === "object") { + isNode = obj instanceof Node; } else { - meta.values[keyName] = value; + isNode = typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName === "string"; + } + + if (isNode) { + // TODO: Should we have a warning here? + return (obj[keyName] = desc.value); + } else { + return Object.defineProperty(obj, keyName, desc); + } + }; + } + } + + /** + @class platform + @namespace Ember + */ + + /** + Identical to `Object.defineProperty()`. Implements as much functionality + as possible if not available natively. + + @method defineProperty + @param {Object} obj The object to modify + @param {String} keyName property name to modify + @param {Object} desc descriptor hash + @return {void} + */ + platform.defineProperty = defineProperty; + + /** + Set to true if the platform supports native getters and setters. + + @property hasPropertyAccessors + @final + */ + platform.hasPropertyAccessors = true; + + if (!platform.defineProperty) { + platform.hasPropertyAccessors = false; + + platform.defineProperty = function(obj, keyName, desc) { + if (!desc.get) { obj[keyName] = desc.value; } + }; + + platform.defineProperty.isSimulated = true; + } + + if (Ember.ENV.MANDATORY_SETTER && !platform.hasPropertyAccessors) { + Ember.ENV.MANDATORY_SETTER = false; + } + + __exports__.create = create; + __exports__.platform = platform; + }); +define("ember-metal/properties", + ["ember-metal/core","ember-metal/utils","ember-metal/platform","ember-metal/property_events","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + /** + @module ember-metal + */ + + var Ember = __dependency1__["default"]; + var META_KEY = __dependency2__.META_KEY; + var meta = __dependency2__.meta; + var platform = __dependency3__.platform; + var overrideChains = __dependency4__.overrideChains; + var metaFor = meta, + objectDefineProperty = platform.defineProperty; + + var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; + + // .......................................................... + // DESCRIPTOR + // + + /** + Objects of this type can implement an interface to respond to requests to + get and set. The default implementation handles simple properties. + + You generally won't need to create or subclass this directly. + + @class Descriptor + @namespace Ember + @private + @constructor + */ + function Descriptor() {}; + + // .......................................................... + // DEFINING PROPERTIES API + // + + var MANDATORY_SETTER_FUNCTION = Ember.MANDATORY_SETTER_FUNCTION = function(value) { + Ember.assert("You must use Ember.set() to access this property (of " + this + ")", false); + }; + + var DEFAULT_GETTER_FUNCTION = Ember.DEFAULT_GETTER_FUNCTION = function(name) { + return function() { + var meta = this[META_KEY]; + return meta && meta.values[name]; + }; + }; + + /** + NOTE: This is a low-level method used by other parts of the API. You almost + never want to call this method directly. Instead you should use + `Ember.mixin()` to define new properties. + + Defines a property on an object. This method works much like the ES5 + `Object.defineProperty()` method except that it can also accept computed + properties and other special descriptors. + + Normally this method takes only three parameters. However if you pass an + instance of `Ember.Descriptor` as the third param then you can pass an + optional value as the fourth parameter. This is often more efficient than + creating new descriptor hashes for each property. + + ## Examples + + ```javascript + // ES5 compatible mode + Ember.defineProperty(contact, 'firstName', { + writable: true, + configurable: false, + enumerable: true, + value: 'Charles' + }); + + // define a simple property + Ember.defineProperty(contact, 'lastName', undefined, 'Jolley'); + + // define a computed property + Ember.defineProperty(contact, 'fullName', Ember.computed(function() { + return this.firstName+' '+this.lastName; + }).property('firstName', 'lastName')); + ``` + + @private + @method defineProperty + @for Ember + @param {Object} obj the object to define this property on. This may be a prototype. + @param {String} keyName the name of the property + @param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a + computed property) or an ES5 descriptor. + You must provide this or `data` but not both. + @param {*} [data] something other than a descriptor, that will + become the explicit value of this property. + */ + function defineProperty(obj, keyName, desc, data, meta) { + var descs, existingDesc, watching, value; + + if (!meta) meta = metaFor(obj); + descs = meta.descs; + existingDesc = meta.descs[keyName]; + watching = meta.watching[keyName] > 0; + + if (existingDesc instanceof Descriptor) { + existingDesc.teardown(obj, keyName); + } + + if (desc instanceof 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 + } + } 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) { 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; + }; + + __exports__.Descriptor = Descriptor; + __exports__.defineProperty = defineProperty; + }); +define("ember-metal/property_events", + ["ember-metal/utils","ember-metal/events","ember-metal/observer_set","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var META_KEY = __dependency1__.META_KEY; + var guidFor = __dependency1__.guidFor; + var tryFinally = __dependency1__.tryFinally; + var sendEvent = __dependency2__.sendEvent; + var listenersUnion = __dependency2__.listenersUnion; + var listenersDiff = __dependency2__.listenersDiff; + var ObserverSet = __dependency3__["default"]; + + var beforeObserverSet = new ObserverSet(), + observerSet = new ObserverSet(), + deferred = 0; + + // .......................................................... + // 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 = obj[META_KEY], + watching = (m && m.watching[keyName] > 0) || keyName === 'length', + proto = m && m.proto, + desc = m && 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); + notifyBeforeObservers(obj, keyName); + } + + /** + 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 = obj[META_KEY], + watching = (m && m.watching[keyName] > 0) || keyName === 'length', + proto = m && m.proto, + desc = m && 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, false); + notifyObservers(obj, keyName); + } + + 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; } + } + + 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); + } + } + } + + function chainsWillChange(obj, keyName, m) { + if (!(m.hasOwnProperty('chainWatchers') && + m.chainWatchers[keyName])) { + return; + } + + var nodes = m.chainWatchers[keyName], + events = [], + i, l; + + for(i = 0, l = nodes.length; i < l; i++) { + nodes[i].willChange(events); + } + + for (i = 0, l = events.length; i < l; i += 2) { + propertyWillChange(events[i], events[i+1]); + } + } + + function chainsDidChange(obj, keyName, m, suppressEvents) { + if (!(m && m.hasOwnProperty('chainWatchers') && + m.chainWatchers[keyName])) { + return; + } + + var nodes = m.chainWatchers[keyName], + events = suppressEvents ? null : [], + i, l; + + for(i = 0, l = nodes.length; i < l; i++) { + nodes[i].didChange(events); + } + + if (suppressEvents) { + return; + } + + for (i = 0, l = events.length; i < l; i += 2) { + propertyDidChange(events[i], events[i+1]); + } + } + + function overrideChains(obj, keyName, m) { + chainsDidChange(obj, keyName, m, true); + }; + + /** + @method beginPropertyChanges + @chainable + @private + */ + function beginPropertyChanges() { + deferred++; + } + + /** + @method endPropertyChanges + @private + */ + function endPropertyChanges() { + 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] + */ + function changeProperties(cb, binding) { + beginPropertyChanges(); + tryFinally(cb, endPropertyChanges, binding); + }; + + function notifyBeforeObservers(obj, keyName) { + if (obj.isDestroying) { return; } + + var eventName = keyName + ':before', listeners, diff; + if (deferred) { + listeners = beforeObserverSet.add(obj, keyName, eventName); + diff = listenersDiff(obj, eventName, listeners); + sendEvent(obj, eventName, [obj, keyName], diff); + } else { + sendEvent(obj, eventName, [obj, keyName]); + } + } + + function notifyObservers(obj, keyName) { + if (obj.isDestroying) { return; } + + var eventName = keyName + ':change', listeners; + if (deferred) { + listeners = observerSet.add(obj, keyName, eventName); + listenersUnion(obj, eventName, listeners); + } else { + sendEvent(obj, eventName, [obj, keyName]); + } + } + + __exports__.propertyWillChange = propertyWillChange; + __exports__.propertyDidChange = propertyDidChange; + __exports__.overrideChains = overrideChains; + __exports__.beginPropertyChanges = beginPropertyChanges; + __exports__.endPropertyChanges = endPropertyChanges; + __exports__.changeProperties = changeProperties; + }); +define("ember-metal/property_get", + ["ember-metal/core","ember-metal/utils","ember-metal/error","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + /** + @module ember-metal + */ + + var Ember = __dependency1__["default"]; + var META_KEY = __dependency2__.META_KEY; + var EmberError = __dependency3__["default"]; + + var get; + + var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; + + 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; + } + + Ember.assert("Cannot call get with "+ keyName +" key.", !!keyName); + Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined); + + if (obj === null) { return _getPath(obj, keyName); } + + var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret; + + if (desc === undefined && keyName.indexOf('.') !== -1) { + return _getPath(obj, keyName); + } + + 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; + } + }; + + // Currently used only by Ember Data tests + if (Ember.config.overrideAccessors) { + Ember.get = get; + Ember.config.overrideAccessors(); + get = Ember.get; + } + + /** + Normalizes a target/path pair to reflect that actual target/path that should + be observed, etc. This takes into account passing in global property + paths (i.e. a path beginning with a captial letter not defined on the + target) and * separators. + + @private + @method normalizeTuple + @for Ember + @param {Object} target The current target. May be `null`. + @param {String} path A path on the target or a global property path. + @return {Array} a temporary array with the normalized target/path pair. + */ + 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 = path.match(FIRST_KEY)[0]; + 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 EmberError('Path cannot be empty'); + + 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 != null && idx < len; idx++) { + root = get(root, parts[idx], true); + if (root && root.isDestroyed) { return undefined; } + } + return root; + }; + + function getWithDefault(root, key, defaultValue) { + var value = get(root, key); + + if (value === undefined) { return defaultValue; } + return value; + }; + + __exports__["default"] = get; + __exports__.get = get; + __exports__.getWithDefault = getWithDefault; + __exports__.normalizeTuple = normalizeTuple; + __exports__._getPath = _getPath; + }); +define("ember-metal/property_set", + ["ember-metal/core","ember-metal/property_get","ember-metal/utils","ember-metal/property_events","ember-metal/properties","ember-metal/error","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var getPath = __dependency2__._getPath; + var META_KEY = __dependency3__.META_KEY; + var propertyWillChange = __dependency4__.propertyWillChange; + var propertyDidChange = __dependency4__.propertyDidChange; + var defineProperty = __dependency5__.defineProperty; + var EmberError = __dependency6__["default"]; + + var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER, + IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/; + + /** + 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 `setUnknownProperty` + method then that will be invoked as well. + + @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. + */ + var 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; + } + + Ember.assert("Cannot call set with "+ keyName +" key.", !!keyName); + + if (!obj) { + return setPath(obj, keyName, value, tolerant); + } + + var meta = obj[META_KEY], desc = meta && meta.descs[keyName], + isUnknown, currentValue; + + if (desc === undefined && 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); + + if (desc !== undefined) { + desc.set(obj, keyName, value); + } else { + + if (typeof obj === 'object' && obj !== null && obj[keyName] === value) { + return value; + } + + 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) { + propertyWillChange(obj, keyName); + if (MANDATORY_SETTER) { + if ((currentValue === undefined && !(keyName in obj)) || !obj.propertyIsEnumerable(keyName)) { + defineProperty(obj, keyName, null, value); // setup mandatory setter + } else { + meta.values[keyName] = value; + } + } else { + obj[keyName] = value; + } + propertyDidChange(obj, keyName); } } 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.set = set; - Ember.config.overrideAccessors(); - set = Ember.set; -} - -function setPath(root, path, value, tolerant) { - var keyName; - - // get the last part of the path - keyName = path.slice(path.lastIndexOf('.') + 1); - - // get the first part of the part - path = path.slice(0, path.length-(keyName.length+1)); - - // unless the path is this, look up the first part to - // get the root - if (path !== 'this') { - root = getPath(root, path); - } - - if (!keyName || keyName.length === 0) { - throw new Ember.Error('You passed an empty path'); - } - - if (!root) { - if (tolerant) { return; } - else { throw new Ember.Error('Object in path '+path+' could not be found or was destroyed.'); } - } - - return set(root, keyName, value); -} - -Ember.set = set; - -/** - Error-tolerant form of `Ember.set`. Will not blow up if any part of the - chain is `undefined`, `null`, or destroyed. - - This is primarily used when syncing bindings, which may try to update after - an object has been destroyed. - - @method trySet - @for Ember - @param {Object} obj The object to modify. - @param {String} path The property path to set - @param {Object} value The value to set -*/ -Ember.trySet = function(root, path, value) { - return set(root, path, value, true); -}; - -})(); - - - -(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 set = Ember.set, - 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; - newObject.length = original.length; - - 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.toArray(); - - 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.toArray(); - - 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 = { - /** - This property will change as the number of objects in the map changes. - - @property length - @type number - @default 0 - */ - length: 0, - - - /** - Retrieve the value associated with a given key. - - @method get - @param {*} key - @return {*} 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 {*} key - @param {*} value - */ - set: function(key, value) { - var keys = this.keys, - values = this.values, - guid = guidFor(key); - - keys.add(key); - values[guid] = value; - set(this, 'length', keys.list.length); - }, - - /** - Removes a value from the map for an associated key. - - @method remove - @param {*} 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); - - if (values.hasOwnProperty(guid)) { - keys.remove(key); - delete values[guid]; - set(this, 'length', keys.list.length); - return true; - } else { - return false; - } - }, - - /** - Check whether a key is present. - - @method has - @param {*} 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 {*} 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 {*} [options.defaultValue] -*/ -var MapWithDefault = Ember.MapWithDefault = function(options) { - Map.call(this); - this.defaultValue = options.defaultValue; -}; - -/** - @method create - @static - @param [options] - @param {*} [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 {*} key - @return {*} 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() { -function consoleMethod(name) { - var consoleObj; - if (Ember.imports.console) { - consoleObj = Ember.imports.console; - } else if (typeof console !== 'undefined') { - consoleObj = console; - } - - var method = typeof consoleObj === 'object' ? consoleObj[name] : null; - - if (method) { - // Older IE doesn't support apply, but Chrome needs it - if (method.apply) { - return function() { - method.apply(consoleObj, arguments); - }; - } else { - return function() { - var message = Array.prototype.join.call(arguments, ', '); - method(message); - }; - } - } -} - -function assertPolyfill(test, message) { - if (!test) { - try { - // attempt to preserve the stack - throw new Ember.Error("assertion failed: " + message); - } catch(error) { - setTimeout(function() { - throw error; - }, 0); - } - } -} - -/** - Inside Ember-Metal, simply uses the methods from `imports.console`. - Override this to provide more robust logging functionality. - - @class Logger - @namespace Ember -*/ -Ember.Logger = { - /** - Logs the arguments to the console. - You can pass as many arguments as you want and they will be joined together with a space. - - ```javascript - var foo = 1; - Ember.Logger.log('log value of foo:', foo); // "log value of foo: 1" will be printed to the console - ``` - - @method log - @for Ember.Logger - @param {*} arguments - */ - log: consoleMethod('log') || Ember.K, - /** - Prints the arguments to the console with a warning icon. - You can pass as many arguments as you want and they will be joined together with a space. - - ```javascript - Ember.Logger.warn('Something happened!'); // "Something happened!" will be printed to the console with a warning icon. - ``` - - @method warn - @for Ember.Logger - @param {*} arguments - */ - warn: consoleMethod('warn') || Ember.K, - /** - Prints the arguments to the console with an error icon, red text and a stack race. - You can pass as many arguments as you want and they will be joined together with a space. - - ```javascript - Ember.Logger.error('Danger! Danger!'); // "Danger! Danger!" will be printed to the console in red text. - ``` - - @method error - @for Ember.Logger - @param {*} arguments - */ - error: consoleMethod('error') || Ember.K, - /** - Logs the arguments to the console. - You can pass as many arguments as you want and they will be joined together with a space. - - ```javascript - var foo = 1; - Ember.Logger.info('log value of foo:', foo); // "log value of foo: 1" will be printed to the console - ``` - - @method info - @for Ember.Logger - @param {*} arguments - */ - info: consoleMethod('info') || Ember.K, - /** - Logs the arguments to the console in blue text. - You can pass as many arguments as you want and they will be joined together with a space. - - ```javascript - var foo = 1; - Ember.Logger.debug('log value of foo:', foo); // "log value of foo: 1" will be printed to the console - ``` - - @method debug - @for Ember.Logger - @param {*} arguments - */ - debug: consoleMethod('debug') || consoleMethod('info') || Ember.K, - /** - - If the value passed into Ember.Logger.assert is not truthy it will throw an error with a stack trace. - - ```javascript - Ember.Logger.assert(true); // undefined - Ember.Logger.assert(true === false); // Throws an Assertion failed error. - ``` - - @method assert - @for Ember.Logger - @param {Boolean} bool Value to test - */ - assert: consoleMethod('assert') || assertPolyfill -}; - - -})(); - - - -(function() { -/** -@module ember-metal -*/ - -var META_KEY = Ember.META_KEY, - metaFor = Ember.meta, - objectDefineProperty = Ember.platform.defineProperty; - -var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; - -// .......................................................... -// DESCRIPTOR -// - -/** - Objects of this type can implement an interface to respond to requests to - get and set. The default implementation handles simple properties. - - You generally won't need to create or subclass this directly. - - @class Descriptor - @namespace Ember - @private - @constructor -*/ -Ember.Descriptor = function() {}; - -// .......................................................... -// DEFINING PROPERTIES API -// - -var MANDATORY_SETTER_FUNCTION = Ember.MANDATORY_SETTER_FUNCTION = function(value) { - Ember.assert("You must use Ember.set() to access this property (of " + this + ")", false); -}; - -var DEFAULT_GETTER_FUNCTION = Ember.DEFAULT_GETTER_FUNCTION = function(name) { - return function() { - var meta = this[META_KEY]; - return meta && meta.values[name]; - }; -}; - -/** - @private - - NOTE: This is a low-level method used by other parts of the API. You almost - never want to call this method directly. Instead you should use - `Ember.mixin()` to define new properties. - - Defines a property on an object. This method works much like the ES5 - `Object.defineProperty()` method except that it can also accept computed - properties and other special descriptors. - - Normally this method takes only three parameters. However if you pass an - instance of `Ember.Descriptor` as the third param then you can pass an - optional value as the fourth parameter. This is often more efficient than - creating new descriptor hashes for each property. - - ## Examples - - ```javascript - // ES5 compatible mode - Ember.defineProperty(contact, 'firstName', { - writable: true, - configurable: false, - enumerable: true, - value: 'Charles' - }); - - // define a simple property - Ember.defineProperty(contact, 'lastName', undefined, 'Jolley'); - - // define a computed property - Ember.defineProperty(contact, 'fullName', Ember.computed(function() { - return this.firstName+' '+this.lastName; - }).property('firstName', 'lastName')); - ``` - - @method defineProperty - @for Ember - @param {Object} obj the object to define this property on. This may be a prototype. - @param {String} keyName the name of the property - @param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a - computed property) or an ES5 descriptor. - You must provide this or `data` but not both. - @param {*} [data] something other than a descriptor, that will - become the explicit value of this property. -*/ -Ember.defineProperty = function(obj, keyName, desc, data, meta) { - var descs, existingDesc, watching, value; - - if (!meta) meta = metaFor(obj); - descs = meta.descs; - existingDesc = meta.descs[keyName]; - watching = meta.watching[keyName] > 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 - } - } 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() { -var get = Ember.get; - -/** - To get multiple properties at once, call `Ember.getProperties` - with an object followed by a list of strings or an array: - - ```javascript - Ember.getProperties(record, 'firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } - ``` - - is equivalent to: - - ```javascript - Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } - ``` - - @method getProperties - @param obj - @param {String...|Array} list of keys to get - @return {Hash} -*/ -Ember.getProperties = function(obj) { - var ret = {}, - propertyNames = arguments, - i = 1; - - if (arguments.length === 2 && Ember.typeOf(arguments[1]) === 'array') { - i = 0; - propertyNames = arguments[1]; - } - for(var len = propertyNames.length; i < len; i++) { - ret[propertyNames[i]] = get(obj, propertyNames[i]); - } - return ret; -}; - -})(); - - - -(function() { -var changeProperties = Ember.changeProperties, - set = Ember.set; - -/** - Set a list of properties on an object. These properties are set inside - a single `beginPropertyChanges` and `endPropertyChanges` batch, so - observers will be buffered. - - ```javascript - anObject.setProperties({ - firstName: "Stanley", - lastName: "Stuart", - age: "21" - }) - ``` - - @method setProperties - @param self - @param {Object} hash - @return self -*/ -Ember.setProperties = function(self, hash) { - changeProperties(function() { - for(var prop in hash) { - if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); } - } - }); - return self; -}; - -})(); - - - -(function() { -var metaFor = Ember.meta, // utils.js - typeOf = Ember.typeOf, // utils.js - MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER, - o_defineProperty = Ember.platform.defineProperty; - -Ember.watchKey = function(obj, keyName) { - // can't watch length on Array - it is special... - if (keyName === 'length' && typeOf(obj) === 'array') { return; } - - var m = metaFor(obj), watching = m.watching; - - // activate watching first time - if (!watching[keyName]) { - watching[keyName] = 1; - - 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 { - watching[keyName] = (watching[keyName] || 0) + 1; - } -}; - - -Ember.unwatchKey = function(obj, keyName) { - var m = metaFor(obj), watching = m.watching; - - if (watching[keyName] === 1) { - watching[keyName] = 0; - - 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 if (watching[keyName] > 1) { - watching[keyName]--; - } -}; - -})(); - - - -(function() { -var metaFor = Ember.meta, // utils.js - get = Ember.get, // property_get.js - normalizeTuple = Ember.normalizeTuple, // property_get.js - forEach = Ember.ArrayPolyfills.forEach, // array.js - warn = Ember.warn, - watchKey = Ember.watchKey, - unwatchKey = Ember.unwatchKey, - FIRST_KEY = /^([^\.\*]+)/; - -function firstKey(path) { - return path.match(FIRST_KEY)[0]; -} - -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. -Ember.flushPendingChains = function() { - if (pendingQueue.length === 0) { return; } // nothing to do - - var queue = pendingQueue; - pendingQueue = []; - - forEach.call(queue, function(q) { q[0].add(q[1]); }); - - 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 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); - watchKey(obj, keyName); -} - -var removeChainWatcher = Ember.removeChainWatcher = function(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); } - } - } - unwatchKey(obj, keyName); -}; - -// 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 = Ember._ChainNode = function(parent, key, value) { - 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; - -function lazyGet(obj, key) { - if (!obj) return undefined; - - var meta = metaFor(obj, false); - // check if object meant only to be a prototype - if (meta.proto === obj) return undefined; - - if (key === "@each") return get(obj, key); - - // if a CP only return cached value - var desc = meta.descs[key]; - if (desc && desc._cacheable) { - if (key in meta.cache) { - return meta.cache[key]; - } else { - return undefined; - } - } - - return get(obj, key); -} - -ChainNodePrototype.value = function() { - if (this._value === undefined && this._watching) { - var obj = this._parent.value(); - this._value = lazyGet(obj, this._key); - } - 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(events) { - var chains = this._chains; - if (chains) { - for(var key in chains) { - if (!chains.hasOwnProperty(key)) { continue; } - chains[key].willChange(events); - } - } - - if (this._parent) { this._parent.chainWillChange(this, this._key, 1, events); } -}; - -ChainNodePrototype.chainWillChange = function(chain, path, depth, events) { - if (this._key) { path = this._key + '.' + path; } - - if (this._parent) { - this._parent.chainWillChange(this, path, depth+1, events); - } else { - if (depth > 1) { - events.push(this.value(), path); - } - path = 'this.' + path; - if (this._paths[path] > 0) { - events.push(this.value(), path); - } - } -}; - -ChainNodePrototype.chainDidChange = function(chain, path, depth, events) { - if (this._key) { path = this._key + '.' + path; } - if (this._parent) { - this._parent.chainDidChange(this, path, depth+1, events); - } else { - if (depth > 1) { - events.push(this.value(), path); - } - path = 'this.' + path; - if (this._paths[path] > 0) { - events.push(this.value(), path); - } - } -}; - -ChainNodePrototype.didChange = function(events) { - // 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(events); - } - } - - // if no events are passed in then we only care about the above wiring update - if (events === null) { return; } - - // and finally tell parent about my path changing... - if (this._parent) { this._parent.chainDidChange(this, this._key, 1, events); } -}; - -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(null); - } -}; - -})(); - - - -(function() { -var metaFor = Ember.meta, // utils.js - typeOf = Ember.typeOf, // utils.js - ChainNode = Ember._ChainNode; // chains.js - -// 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.watchPath = function(obj, keyPath) { - // can't watch length on Array - it is special... - if (keyPath === 'length' && typeOf(obj) === 'array') { return; } - - var m = metaFor(obj), watching = m.watching; - - if (!watching[keyPath]) { // activate watching first time - watching[keyPath] = 1; - chainsFor(obj).add(keyPath); - } else { - watching[keyPath] = (watching[keyPath] || 0) + 1; - } -}; - -Ember.unwatchPath = function(obj, keyPath) { - var m = metaFor(obj), watching = m.watching; - - if (watching[keyPath] === 1) { - watching[keyPath] = 0; - chainsFor(obj).remove(keyPath); - } else if (watching[keyPath] > 1) { - watching[keyPath]--; - } -}; -})(); - - - -(function() { -/** -@module ember-metal -*/ - -var metaFor = Ember.meta, // utils.js - GUID_KEY = Ember.GUID_KEY, // utils.js - META_KEY = Ember.META_KEY, // utils.js - removeChainWatcher = Ember.removeChainWatcher, - watchKey = Ember.watchKey, // watch_key.js - unwatchKey = Ember.unwatchKey, - watchPath = Ember.watchPath, // watch_path.js - unwatchPath = Ember.unwatchPath, - typeOf = Ember.typeOf, // utils.js - generateGuid = Ember.generateGuid, - IS_PATH = /[\.\*]/; - -// returns true if the passed path is just a keyName -function isKeyName(path) { - return path==='*' || !IS_PATH.test(path); -} - -/** - @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, _keyPath) { - // can't watch length on Array - it is special... - if (_keyPath === 'length' && typeOf(obj) === 'array') { return; } - - if (isKeyName(_keyPath)) { - watchKey(obj, _keyPath); - } else { - watchPath(obj, _keyPath); - } -}; - -Ember.isWatching = function isWatching(obj, key) { - var meta = obj[META_KEY]; - return (meta && meta.watching[key]) > 0; -}; - -Ember.watch.flushPending = Ember.flushPendingChains; - -Ember.unwatch = function(obj, _keyPath) { - // can't watch length on Array - it is special... - if (_keyPath === 'length' && typeOf(obj) === 'array') { return; } - - if (isKeyName(_keyPath)) { - unwatchKey(obj, _keyPath); - } else { - unwatchPath(obj, _keyPath); - } -}; - -/** - @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)) { - generateGuid(obj); - } - - // make sure any chained watchers update. - if (chains && chains.value() !== obj) { - m.chains = chains.copy(obj); - } -}; - -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(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(meta) { - return keysForDep(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(meta); - - for(idx = 0, len = depKeys.length; idx < len; idx++) { - depKey = depKeys[idx]; - // Lookup keys meta for depKey - keys = keysForDep(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(meta); - - for(idx = 0, len = depKeys.length; idx < len; idx++) { - depKey = depKeys[idx]; - // Lookup keys meta for depKey - keys = keysForDep(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 -// - -/** - A computed property transforms an objects function into a property. - - By default the function backing the computed property will only be called - once and the result will be cached. You can specify various properties - that your computed property is dependent on. This will force the cached - result to be recomputed if the dependencies are modified. - - In the following example we declare a computed property (by calling - `.property()` on the fullName function) and setup the properties - dependencies (depending on firstName and lastName). The fullName function - will be called once (regardless of how many times it is accessed) as long - as it's dependencies have not been changed. Once firstName or lastName are updated - any future calls (or anything bound) to fullName will incorporate the new - values. - - ```javascript - Person = Ember.Object.extend({ - // these will be supplied by `create` - firstName: null, - lastName: null, - - fullName: function() { - var firstName = this.get('firstName'); - var lastName = this.get('lastName'); - - return firstName + ' ' + lastName; - }.property('firstName', 'lastName') - }); - - var tom = Person.create({ - firstName: "Tom", - lastName: "Dale" - }); - - tom.get('fullName') // "Tom Dale" - ``` - - You can also define what Ember should do when setting a computed property. - If you try to set a computed property, it will be invoked with the key and - value you want to set it to. You can also accept the previous value as the - third parameter. - - ```javascript - - Person = Ember.Object.extend({ - // these will be supplied by `create` - firstName: null, - lastName: null, - - fullName: function(key, value, oldValue) { - // getter - if (arguments.length === 1) { - var firstName = this.get('firstName'); - var lastName = this.get('lastName'); - - return firstName + ' ' + lastName; - - // setter - } else { - var name = value.split(" "); - - this.set('firstName', name[0]); - this.set('lastName', name[1]); - - return value; - } - }.property('firstName', 'lastName') - }); - - var person = Person.create(); - person.set('fullName', "Peter Wagenet"); - person.get('firstName') // Peter - person.get('lastName') // Wagenet - ``` - - @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; - -/** - Properties are cacheable by default. Computed property will automatically - cache the return value of your function until one of the dependent keys changes. - - Call `volatile()` to set it into non-cached mode. When in this mode - the computed property will not automatically cache the return value. - - However, if a property is properly observable, there is no reason to disable - caching. - - @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 addArg; - - 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.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]; - removeDependentKeys(this, obj, keyName, meta); - } - } -}; - -function finishChains(chainNodes) -{ - for (var i=0, l=chainNodes.length; i 1) { - args = a_slice.call(arguments, 0, -1); - func = a_slice.call(arguments, -1)[0]; - } - - if (typeof func !== "function") { - throw new Ember.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 {Object} the cached value -*/ -Ember.cacheFor = function cacheFor(obj, key) { - var cache = metaFor(obj, false).cache; - - if (cache && key in cache) { - return cache[key]; - } -}; - -function getProperties(self, propertyNames) { - var ret = {}; - for(var i = 0; i < propertyNames.length; i++) { - ret[propertyNames[i]] = get(self, propertyNames[i]); - } - return ret; -} - -function registerComputed(name, macro) { - Ember.computed[name] = function(dependentKey) { - var args = a_slice.call(arguments); - return Ember.computed(dependentKey, function() { - return macro.apply(this, args); - }); - }; -} - -function registerComputedWithProperties(name, macro) { - Ember.computed[name] = function() { - var properties = a_slice.call(arguments); - - var computed = Ember.computed(function() { - return macro.apply(this, [getProperties(this, properties)]); - }); - - return computed.property.apply(computed, properties); - }; -} - -/** - A computed property that returns true if the value of the dependent - property is null, an empty string, empty array, or empty function. - - Note: When using `Ember.computed.empty` to watch an array make sure to - use the `array.[]` syntax so the computed can subscribe to transitions - from empty to non-empty states. - - Example - - ```javascript - var ToDoList = Ember.Object.extend({ - done: Ember.computed.empty('todos.[]') // detect array changes - }); - var todoList = ToDoList.create({todos: ['Unit Test', 'Documentation', 'Release']}); - todoList.get('done'); // false - todoList.get('todos').clear(); // [] - todoList.get('done'); // true - ``` - - @method computed.empty - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which negate - the original value for property -*/ -registerComputed('empty', function(dependentKey) { - return Ember.isEmpty(get(this, dependentKey)); -}); - -/** - A computed property that returns true if the value of the dependent - property is NOT null, an empty string, empty array, or empty function. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - hasStuff: Ember.computed.notEmpty('backpack') - }); - var hamster = Hamster.create({backpack: ['Food', 'Sleeping Bag', 'Tent']}); - hamster.get('hasStuff'); // true - hamster.get('backpack').clear(); // [] - hamster.get('hasStuff'); // false - ``` - - @method computed.notEmpty - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which returns true if - original value for property is not empty. -*/ -registerComputed('notEmpty', function(dependentKey) { - return !Ember.isEmpty(get(this, dependentKey)); -}); - -/** - A computed property that returns true if the value of the dependent - property is null or undefined. This avoids errors from JSLint complaining - about use of ==, which can be technically confusing. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - isHungry: Ember.computed.none('food') - }); - var hamster = Hamster.create(); - hamster.get('isHungry'); // true - hamster.set('food', 'Banana'); - hamster.get('isHungry'); // false - hamster.set('food', null); - hamster.get('isHungry'); // true - ``` - - @method computed.none - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which - returns true if original value for property is null or undefined. -*/ -registerComputed('none', function(dependentKey) { - return Ember.isNone(get(this, dependentKey)); -}); - -/** - A computed property that returns the inverse boolean value - of the original value for the dependent property. - - Example - - ```javascript - var User = Ember.Object.extend({ - isAnonymous: Ember.computed.not('loggedIn') - }); - var user = User.create({loggedIn: false}); - user.get('isAnonymous'); // true - user.set('loggedIn', true); - user.get('isAnonymous'); // false - ``` - - @method computed.not - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which returns - inverse of the original value for property -*/ -registerComputed('not', function(dependentKey) { - return !get(this, dependentKey); -}); - -/** - A computed property that converts the provided dependent property - into a boolean value. - - ```javascript - var Hamster = Ember.Object.extend({ - hasBananas: Ember.computed.bool('numBananas') - }); - var hamster = Hamster.create(); - hamster.get('hasBananas'); // false - hamster.set('numBananas', 0); - hamster.get('hasBananas'); // false - hamster.set('numBananas', 1); - hamster.get('hasBananas'); // true - hamster.set('numBananas', null); - hamster.get('hasBananas'); // false - ``` - - @method computed.bool - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which converts - to boolean the original value for property -*/ -registerComputed('bool', function(dependentKey) { - return !!get(this, dependentKey); -}); - -/** - A computed property which matches the original value for the - dependent property against a given RegExp, returning `true` - if they values matches the RegExp and `false` if it does not. - - Example - - ```javascript - var User = Ember.Object.extend({ - hasValidEmail: Ember.computed.match('email', /^.+@.+\..+$/) - }); - var user = User.create({loggedIn: false}); - user.get('hasValidEmail'); // false - user.set('email', ''); - user.get('hasValidEmail'); // false - user.set('email', 'ember_hamster@example.com'); - user.get('hasValidEmail'); // true - ``` - - @method computed.match - @for Ember - @param {String} dependentKey - @param {RegExp} regexp - @return {Ember.ComputedProperty} computed property which match - the original value for property against a given RegExp -*/ -registerComputed('match', function(dependentKey, regexp) { - var value = get(this, dependentKey); - return typeof value === 'string' ? regexp.test(value) : false; -}); - -/** - A computed property that returns true if the provided dependent property - is equal to the given value. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - napTime: Ember.computed.equal('state', 'sleepy') - }); - var hamster = Hamster.create(); - hamster.get('napTime'); // false - hamster.set('state', 'sleepy'); - hamster.get('napTime'); // true - hamster.set('state', 'hungry'); - hamster.get('napTime'); // false - ``` - - @method computed.equal - @for Ember - @param {String} dependentKey - @param {String|Number|Object} value - @return {Ember.ComputedProperty} computed property which returns true if - the original value for property is equal to the given value. -*/ -registerComputed('equal', function(dependentKey, value) { - return get(this, dependentKey) === value; -}); - -/** - A computed property that returns true if the provied dependent property - is greater than the provided value. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - hasTooManyBananas: Ember.computed.gt('numBananas', 10) - }); - var hamster = Hamster.create(); - hamster.get('hasTooManyBananas'); // false - hamster.set('numBananas', 3); - hamster.get('hasTooManyBananas'); // false - hamster.set('numBananas', 11); - hamster.get('hasTooManyBananas'); // true - ``` - - @method computed.gt - @for Ember - @param {String} dependentKey - @param {Number} value - @return {Ember.ComputedProperty} computed property which returns true if - the original value for property is greater then given value. -*/ -registerComputed('gt', function(dependentKey, value) { - return get(this, dependentKey) > value; -}); - -/** - A computed property that returns true if the provided dependent property - is greater than or equal to the provided value. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - hasTooManyBananas: Ember.computed.gte('numBananas', 10) - }); - var hamster = Hamster.create(); - hamster.get('hasTooManyBananas'); // false - hamster.set('numBananas', 3); - hamster.get('hasTooManyBananas'); // false - hamster.set('numBananas', 10); - hamster.get('hasTooManyBananas'); // true - ``` - - @method computed.gte - @for Ember - @param {String} dependentKey - @param {Number} value - @return {Ember.ComputedProperty} computed property which returns true if - the original value for property is greater or equal then given value. -*/ -registerComputed('gte', function(dependentKey, value) { - return get(this, dependentKey) >= value; -}); - -/** - A computed property that returns true if the provided dependent property - is less than the provided value. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - needsMoreBananas: Ember.computed.lt('numBananas', 3) - }); - var hamster = Hamster.create(); - hamster.get('needsMoreBananas'); // true - hamster.set('numBananas', 3); - hamster.get('needsMoreBananas'); // false - hamster.set('numBananas', 2); - hamster.get('needsMoreBananas'); // true - ``` - - @method computed.lt - @for Ember - @param {String} dependentKey - @param {Number} value - @return {Ember.ComputedProperty} computed property which returns true if - the original value for property is less then given value. -*/ -registerComputed('lt', function(dependentKey, value) { - return get(this, dependentKey) < value; -}); - -/** - A computed property that returns true if the provided dependent property - is less than or equal to the provided value. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - needsMoreBananas: Ember.computed.lte('numBananas', 3) - }); - var hamster = Hamster.create(); - hamster.get('needsMoreBananas'); // true - hamster.set('numBananas', 5); - hamster.get('needsMoreBananas'); // false - hamster.set('numBananas', 3); - hamster.get('needsMoreBananas'); // true - ``` - - @method computed.lte - @for Ember - @param {String} dependentKey - @param {Number} value - @return {Ember.ComputedProperty} computed property which returns true if - the original value for property is less or equal then given value. -*/ -registerComputed('lte', function(dependentKey, value) { - return get(this, dependentKey) <= value; -}); - -/** - A computed property that performs a logical `and` on the - original values for the provided dependent properties. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - readyForCamp: Ember.computed.and('hasTent', 'hasBackpack') - }); - var hamster = Hamster.create(); - hamster.get('readyForCamp'); // false - hamster.set('hasTent', true); - hamster.get('readyForCamp'); // false - hamster.set('hasBackpack', true); - hamster.get('readyForCamp'); // true - ``` - - @method computed.and - @for Ember - @param {String} dependentKey* - @return {Ember.ComputedProperty} computed property which performs - a logical `and` on the values of all the original values for properties. -*/ -registerComputedWithProperties('and', function(properties) { - for (var key in properties) { - if (properties.hasOwnProperty(key) && !properties[key]) { - return false; - } - } - return true; -}); - -/** - A computed property which performs a logical `or` on the - original values for the provided dependent properties. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - readyForRain: Ember.computed.or('hasJacket', 'hasUmbrella') - }); - var hamster = Hamster.create(); - hamster.get('readyForRain'); // false - hamster.set('hasJacket', true); - hamster.get('readyForRain'); // true - ``` - - @method computed.or - @for Ember - @param {String} dependentKey* - @return {Ember.ComputedProperty} computed property which performs - a logical `or` on the values of all the original values for properties. -*/ -registerComputedWithProperties('or', function(properties) { - for (var key in properties) { - if (properties.hasOwnProperty(key) && properties[key]) { - return true; - } - } - return false; -}); - -/** - A computed property that returns the first truthy value - from a list of dependent properties. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - hasClothes: Ember.computed.any('hat', 'shirt') - }); - var hamster = Hamster.create(); - hamster.get('hasClothes'); // null - hamster.set('shirt', 'Hawaiian Shirt'); - hamster.get('hasClothes'); // 'Hawaiian Shirt' - ``` - - @method computed.any - @for Ember - @param {String} dependentKey* - @return {Ember.ComputedProperty} computed property which returns - the first truthy value of given list of properties. -*/ -registerComputedWithProperties('any', function(properties) { - for (var key in properties) { - if (properties.hasOwnProperty(key) && properties[key]) { - return properties[key]; - } - } - return null; -}); - -/** - A computed property that returns the array of values - for the provided dependent properties. - - Example - - ```javascript - var Hamster = Ember.Object.extend({ - clothes: Ember.computed.map('hat', 'shirt') - }); - var hamster = Hamster.create(); - hamster.get('clothes'); // [null, null] - hamster.set('hat', 'Camp Hat'); - hamster.set('shirt', 'Camp Shirt'); - hamster.get('clothes'); // ['Camp Hat', 'Camp Shirt'] - ``` - - @method computed.map - @for Ember - @param {String} dependentKey* - @return {Ember.ComputedProperty} computed property which maps - values of all passed properties in to an array. -*/ -registerComputedWithProperties('collect', function(properties) { - var res = []; - for (var key in properties) { - if (properties.hasOwnProperty(key)) { - if (Ember.isNone(properties[key])) { - res.push(null); - } else { - res.push(properties[key]); - } - } - } - return res; -}); - -/** - Creates a new property that is an alias for another property - on an object. Calls to `get` or `set` this property behave as - though they were called on the original property. - - ```javascript - Person = Ember.Object.extend({ - name: 'Alex Matchneer', - nomen: Ember.computed.alias('name') - }); - - alex = Person.create(); - alex.get('nomen'); // 'Alex Matchneer' - alex.get('name'); // 'Alex Matchneer' - - alex.set('nomen', '@machty'); - alex.get('name'); // '@machty' - ``` - @method computed.alias - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which creates an - alias to the original value for property. -*/ -Ember.computed.alias = function(dependentKey) { - return Ember.computed(dependentKey, function(key, value) { - if (arguments.length > 1) { - set(this, dependentKey, value); return value; + }; + + // Currently used only by Ember Data tests + // ES6TODO: Verify still true + if (Ember.config.overrideAccessors) { + Ember.set = set; + Ember.config.overrideAccessors(); + set = Ember.set; + } + + function setPath(root, path, value, tolerant) { + var keyName; + + // get the last part of the path + keyName = path.slice(path.lastIndexOf('.') + 1); + + // get the first part of the part + path = (path === keyName) ? keyName : path.slice(0, path.length-(keyName.length+1)); + + // unless the path is this, look up the first part to + // get the root + if (path !== 'this') { + root = getPath(root, path); + } + + if (!keyName || keyName.length === 0) { + throw new EmberError('Property set failed: You passed an empty path'); + } + + if (!root) { + if (tolerant) { return; } + else { throw new EmberError('Property set failed: object in path "'+path+'" could not be found or was destroyed.'); } + } + + return set(root, keyName, value); + } + + /** + Error-tolerant form of `Ember.set`. Will not blow up if any part of the + chain is `undefined`, `null`, or destroyed. + + This is primarily used when syncing bindings, which may try to update after + an object has been destroyed. + + @method trySet + @for Ember + @param {Object} obj The object to modify. + @param {String} path The property path to set + @param {Object} value The value to set + */ + function trySet(root, path, value) { + return set(root, path, value, true); + }; + + __exports__.set = set; + __exports__.trySet = trySet; + }); +define("ember-metal/run_loop", + ["ember-metal/core","ember-metal/utils","ember-metal/array","ember-metal/property_events","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var apply = __dependency2__.apply; + var indexOf = __dependency3__.indexOf; + var beginPropertyChanges = __dependency4__.beginPropertyChanges; + var endPropertyChanges = __dependency4__.endPropertyChanges; + + var onBegin = function(current) { + run.currentRunLoop = current; + }; + + var onEnd = function(current, next) { + run.currentRunLoop = next; + }; + + // ES6TODO: should Backburner become es6? + var Backburner = requireModule('backburner').Backburner, + backburner = new Backburner(['sync', 'actions', 'destroy'], { + sync: { + before: beginPropertyChanges, + after: endPropertyChanges + }, + defaultQueue: 'actions', + onBegin: onBegin, + onEnd: onEnd + }), + slice = [].slice, + concat = [].concat; + + // .......................................................... + // 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 + 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. + */ + var run = function() { + if (Ember.onerror) { + return onerror(arguments); + } else { + return apply(backburner, backburner.run, arguments); + } + }; + + function onerror(args) { + try { + return apply(backburner, backburner.run, args); + } catch(error) { + Ember.onerror(error); + } + } + /** + If no run-loop is present, it creates a new one. If a run loop is + present it will queue itself to run on the existing run-loops action + queue. + + Please note: This is not for normal usage, and should be used sparingly. + + If invoked when not within a run loop: + + ```javascript + run.join(function() { + // creates a new run-loop + }); + ``` + + Alternatively, if called within an existing run loop: + + ```javascript + run(function() { + // creates a new run-loop + run.join(function() { + // joins with the existing run-loop, and queues for invocation on + // the existing run-loops action queue. + }); + }); + ``` + + @method join + @namespace Ember + @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. Please note, + when called within an existing loop, no return value is possible. + */ + run.join = function(target, method /* args */) { + if (!run.currentRunLoop) { + return apply(Ember, run, arguments); + } + + var args = slice.call(arguments); + args.unshift('actions'); + apply(run, run.schedule, args); + }; + + /** + Provides a useful utility for when integrating with non-Ember libraries + that provide asynchronous callbacks. + + Ember utilizes a run-loop to batch and coalesce changes. This works by + marking the start and end of Ember-related Javascript execution. + + When using events such as a View's click handler, Ember wraps the event + handler in a run-loop, but when integrating with non-Ember libraries this + can be tedious. + + For example, the following is rather verbose but is the correct way to combine + third-party events and Ember code. + + ```javascript + var that = this; + jQuery(window).on('resize', function(){ + run(function(){ + that.handleResize(); + }); + }); + ``` + + To reduce the boilerplate, the following can be used to construct a + run-loop-wrapped callback handler. + + ```javascript + jQuery(window).on('resize', run.bind(this, this.handleResize)); + ``` + + @method bind + @namespace run + @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. Please note, + when called within an existing loop, no return value is possible. + */ + run.bind = function(target, method /* args*/) { + var args = slice.call(arguments); + return function() { + return apply(run, run.join, args.concat(slice.call(arguments))); + }; + }; + + run.backburner = backburner; + run.currentRunLoop = null; + run.queues = backburner.queueNames; + + /** + Begins a new RunLoop. Any deferred actions invoked after the begin will + be buffered until you invoke a matching call to `run.end()`. This is + a lower-level way to use a RunLoop instead of using `run()`. + + ```javascript + run.begin(); + // code to be execute within a RunLoop + run.end(); + ``` + + @method begin + @return {void} + */ + run.begin = function() { + backburner.begin(); + }; + + /** + Ends a RunLoop. This must be called sometime after you call + `run.begin()` to flush any deferred actions. This is a lower-level way + to use a RunLoop instead of using `run()`. + + ```javascript + run.begin(); + // code to be execute within a RunLoop + run.end(); + ``` + + @method end + @return {void} + */ + run.end = function() { + backburner.end(); + }; + + /** + 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'] + */ + + /** + 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 + run.schedule('sync', this, function() { + // this will be executed in the first RunLoop queue, when bindings are synced + console.log("scheduled on sync queue"); + }); + + 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} + */ + run.schedule = function(queue, target, method) { + checkAutoRun(); + apply(backburner, backburner.schedule, arguments); + }; + + // Used by global test teardown + run.hasScheduledTimers = function() { + return backburner.hasTimers(); + }; + + // Used by global test teardown + run.cancelTimers = function () { + backburner.cancelTimers(); + }; + + /** + 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 + run.sync(); + ``` + + @method sync + @return {void} + */ + run.sync = function() { + if (backburner.currentInstance) { + backburner.currentInstance.queues.sync.flush(); + } + }; + + /** + 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 + 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 + `run.cancel` later. + */ + run.later = function(target, method) { + return apply(backburner, backburner.later, arguments); + }; + + /** + Schedule a function to run one time during the current RunLoop. This is equivalent + to calling `scheduleOnce` with the "actions" queue. + + @method once + @param {Object} [target] The target of the 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 information for use in cancelling, see `run.cancel`. + */ + run.once = function(target, method) { + checkAutoRun(); + var args = slice.call(arguments); + args.unshift('actions'); + return apply(backburner, backburner.scheduleOnce, args); + }; + + /** + Schedules a function to run one time in a given queue of the current RunLoop. + Calling this method with the same queue/target/method combination will have + no effect (past the initial call). + + Note that although you can pass optional arguments these will not be + considered when looking for duplicates. New arguments will replace previous + calls. + + ```javascript + run(function() { + var sayHi = function() { console.log('hi'); } + run.scheduleOnce('afterRender', myContext, sayHi); + run.scheduleOnce('afterRender', myContext, sayHi); + // sayHi will only be executed once, in the afterRender queue of the RunLoop + }); + ``` + + Also note that passing an anonymous function to `run.scheduleOnce` will + not prevent additional calls with an identical anonymous function from + scheduling the items multiple times, e.g.: + + ```javascript + function scheduleIt() { + run.scheduleOnce('actions', myContext, function() { console.log("Closure"); }); + } + scheduleIt(); + scheduleIt(); + // "Closure" will print twice, even though we're using `run.scheduleOnce`, + // because the function we pass to it is anonymous and won't match the + // previously scheduled operation. + ``` + + Available queues, and their order, can be found at `run.queues` + + @method scheduleOnce + @param {String} [queue] The name of the queue to schedule against. Default queues are 'sync' and 'actions'. + @param {Object} [target] The target of the 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 information for use in cancelling, see `run.cancel`. + */ + run.scheduleOnce = function(queue, target, method) { + checkAutoRun(); + return apply(backburner, backburner.scheduleOnce, arguments); + }; + + /** + Schedules an item to run from within a separate run loop, after + control has been returned to the system. This is equivalent to calling + `run.later` with a wait time of 1ms. + + ```javascript + 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 `run.next` will coalesce + into the same later run loop, along with any other operations + scheduled by `run.later` that expire right around the same + time that `run.next` operations will fire. + + Note that there are often alternatives to using `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() { + 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 `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 `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 `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 information for use in cancelling, see `run.cancel`. + */ + run.next = function() { + var args = slice.call(arguments); + args.push(1); + return apply(backburner, backburner.later, args); + }; + + /** + Cancels a scheduled item. Must be a value returned by `run.later()`, + `run.once()`, `run.next()`, `run.debounce()`, or + `run.throttle()`. + + ```javascript + var runNext = run.next(myContext, function() { + // will not be executed + }); + run.cancel(runNext); + + var runLater = run.later(myContext, function() { + // will not be executed + }, 500); + run.cancel(runLater); + + var runOnce = run.once(myContext, function() { + // will not be executed + }); + run.cancel(runOnce); + + var throttle = run.throttle(myContext, function() { + // will not be executed + }, 1, false); + run.cancel(throttle); + + var debounce = run.debounce(myContext, function() { + // will not be executed + }, 1); + run.cancel(debounce); + + var debounceImmediate = run.debounce(myContext, function() { + // will be executed since we passed in true (immediate) + }, 100, true); + // the 100ms delay until this method can be called again will be cancelled + run.cancel(debounceImmediate); + ``` + + @method cancel + @param {Object} timer Timer object to cancel + @return {Boolean} true if cancelled or false/undefined if it wasn't found + */ + run.cancel = function(timer) { + return backburner.cancel(timer); + }; + + /** + Delay calling the target method until the debounce period has elapsed + with no additional debounce calls. If `debounce` is called again before + the specified time has elapsed, the timer is reset and the entire period + must pass again before the target method is called. + + This method should be used when an event may be called multiple times + but the action should only be called once when the event is done firing. + A common example is for scroll events where you only want updates to + happen once scrolling has ceased. + + ```javascript + var myFunc = function() { console.log(this.name + ' ran.'); }; + var myContext = {name: 'debounce'}; + + run.debounce(myContext, myFunc, 150); + + // less than 150ms passes + + run.debounce(myContext, myFunc, 150); + + // 150ms passes + // myFunc is invoked with context myContext + // console logs 'debounce ran.' one time. + ``` + + Immediate allows you to run the function immediately, but debounce + other calls for this function until the wait time has elapsed. If + `debounce` is called again before the specified time has elapsed, + the timer is reset and the entire period must pass again before + the method can be called again. + + ```javascript + var myFunc = function() { console.log(this.name + ' ran.'); }; + var myContext = {name: 'debounce'}; + + run.debounce(myContext, myFunc, 150, true); + + // console logs 'debounce ran.' one time immediately. + // 100ms passes + + run.debounce(myContext, myFunc, 150, true); + + // 150ms passes and nothing else is logged to the console and + // the debouncee is no longer being watched + + run.debounce(myContext, myFunc, 150, true); + + // console logs 'debounce ran.' one time immediately. + // 150ms passes and nothing else is logged tot he console and + // the debouncee is no longer being watched + + ``` + + @method debounce + @param {Object} [target] target of method to invoke + @param {Function|String} method The 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*] Optional arguments to pass to the timeout. + @param {Number} wait Number of milliseconds to wait. + @param {Boolean} immediate Trigger the function on the leading instead + of the trailing edge of the wait interval. Defaults to false. + @return {Array} Timer information for use in cancelling, see `run.cancel`. + */ + run.debounce = function() { + return apply(backburner, backburner.debounce, arguments); + }; + + /** + Ensure that the target method is never called more frequently than + the specified spacing period. + + ```javascript + var myFunc = function() { console.log(this.name + ' ran.'); }; + var myContext = {name: 'throttle'}; + + run.throttle(myContext, myFunc, 150); + // myFunc is invoked with context myContext + + // 50ms passes + run.throttle(myContext, myFunc, 150); + + // 50ms passes + run.throttle(myContext, myFunc, 150); + + // 150ms passes + run.throttle(myContext, myFunc, 150); + // myFunc is invoked with context myContext + // console logs 'throttle ran.' twice, 250ms apart. + ``` + + @method throttle + @param {Object} [target] target of method to invoke + @param {Function|String} method The 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*] Optional arguments to pass to the timeout. + @param {Number} spacing Number of milliseconds to space out requests. + @return {Array} Timer information for use in cancelling, see `run.cancel`. + */ + run.throttle = function() { + return apply(backburner, backburner.throttle, arguments); + }; + + // Make sure it's not an autorun during testing + function checkAutoRun() { + 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 run", !Ember.testing); + } + } + + /** + Add a new named queue after the specified queue. + + The queue to add will only be added once. + + @method _addQueue + @param {String} name the name of the queue to add. + @param {String} after the name of the queue to add after. + @private + */ + run._addQueue = function(name, after) { + if (indexOf.call(run.queues, name) === -1) { + run.queues.splice(indexOf.call(run.queues, after)+1, 0, name); + } + } + + __exports__["default"] = run + }); +define("ember-metal/set_properties", + ["ember-metal/property_events","ember-metal/property_set","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var changeProperties = __dependency1__.changeProperties; + var set = __dependency2__.set; + + /** + Set a list of properties on an object. These properties are set inside + a single `beginPropertyChanges` and `endPropertyChanges` batch, so + observers will be buffered. + + ```javascript + anObject.setProperties({ + firstName: "Stanley", + lastName: "Stuart", + age: "21" + }) + ``` + + @method setProperties + @param self + @param {Object} hash + @return self + */ + function setProperties(self, hash) { + changeProperties(function() { + for(var prop in hash) { + if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); } + } + }); + return self; + }; + + __exports__["default"] = setProperties; + }); +define("ember-metal/utils", + ["ember-metal/core","ember-metal/platform","ember-metal/array","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var platform = __dependency2__.platform; + var create = __dependency2__.create; + var forEach = __dependency3__.forEach; + + /** + @module ember-metal + */ + + /** + Prefix used for guids through out Ember. + @private + */ + var GUID_PREFIX = 'ember'; + + + var o_defineProperty = platform.defineProperty, + o_create = create, + // Used for guid generation... + numberCache = [], + stringCache = {}, + uuid = 0; + + var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; + + /** + A unique key used to assign guids and other private metadata to objects. + If you inspect an object in your browser debugger you will often see these. + They can be safely ignored. + + On browsers that support it, these properties are added with enumeration + disabled so they won't show up when you iterate over your properties. + + @private + @property GUID_KEY + @for Ember + @type String + @final + */ + var GUID_KEY = '__ember' + (+ new Date()); + + var GUID_DESC = { + writable: false, + configurable: false, + enumerable: false, + value: null + }; + + /** + Generates a new guid, optionally saving the guid to the object that you + pass in. You will rarely need to use this method. Instead you should + call `Ember.guidFor(obj)`, which return an existing guid if available. + + @private + @method generateGuid + @for Ember + @param {Object} [obj] Object the guid will be used for. If passed in, the guid will + be saved on the object and reused whenever you pass the same object + again. + + If no object is passed, just generate a new guid. + @param {String} [prefix] Prefix to place in front of the guid. Useful when you want to + separate the guid into separate namespaces. + @return {String} the guid + */ + function generateGuid(obj, prefix) { + if (!prefix) prefix = GUID_PREFIX; + var ret = (prefix + (uuid++)); + if (obj) { + if (obj[GUID_KEY] === null) { + obj[GUID_KEY] = ret; + } else { + GUID_DESC.value = ret; + o_defineProperty(obj, GUID_KEY, GUID_DESC); + } + } + return ret; + } + + /** + Returns a unique id for the object. If the object does not yet have a guid, + one will be assigned to it. You can call this on any object, + `Ember.Object`-based or not, but be aware that it will add a `_guid` + property. + + You can also use this method on DOM Element objects. + + @private + @method guidFor + @for Ember + @param {Object} obj any object, string, number, Element, or primitive + @return {String} the unique guid for this instance. + */ + function guidFor(obj) { + + // special cases where we don't want to add a key to object + if (obj === undefined) return "(undefined)"; + if (obj === null) return "(null)"; + + var ret; + var type = typeof obj; + + // Don't allow prototype changes to String etc. to change the guidFor + switch(type) { + case 'number': + ret = numberCache[obj]; + if (!ret) ret = numberCache[obj] = 'nu'+obj; + return ret; + + case 'string': + ret = stringCache[obj]; + if (!ret) ret = stringCache[obj] = 'st'+(uuid++); + return ret; + + case 'boolean': + return obj ? '(true)' : '(false)'; + + default: + if (obj[GUID_KEY]) return obj[GUID_KEY]; + if (obj === Object) return '(Object)'; + if (obj === Array) return '(Array)'; + ret = 'ember' + (uuid++); + + if (obj[GUID_KEY] === null) { + obj[GUID_KEY] = ret; + } else { + GUID_DESC.value = ret; + o_defineProperty(obj, GUID_KEY, GUID_DESC); + } + return ret; + } + }; + + // .......................................................... + // META + // + + var META_DESC = { + writable: true, + configurable: false, + enumerable: false, + value: null + }; + + + /** + The key used to store meta information on object for property observing. + + @property META_KEY + @for Ember + @private + @final + @type String + */ + var META_KEY = '__ember_meta__'; + + var isDefinePropertySimulated = platform.defineProperty.isSimulated; + + function Meta(obj) { + this.descs = {}; + this.watching = {}; + this.cache = {}; + this.source = obj; + } + + Meta.prototype = { + descs: null, + deps: null, + watching: null, + listeners: null, + cache: null, + source: null, + mixins: null, + bindings: null, + chains: null, + chainWatchers: null, + values: null, + proto: null + }; + + if (isDefinePropertySimulated) { + // on platforms that don't support enumerable false + // make meta fail jQuery.isPlainObject() to hide from + // jQuery.extend() by having a property that fails + // hasOwnProperty check. + Meta.prototype.__preventPlainObject__ = true; + + // Without non-enumerable properties, meta objects will be output in JSON + // unless explicitly suppressed + Meta.prototype.toJSON = function () { }; + } + + // Placeholder for non-writable metas. + var EMPTY_META = new Meta(null); + + if (MANDATORY_SETTER) { EMPTY_META.values = {}; } + + /** + Retrieves the meta hash for an object. If `writable` is true ensures the + hash is writable for this object as well. + + The meta object contains information about computed property descriptors as + well as any watched properties and other information. You generally will + not access this information directly but instead work with higher level + methods that manipulate this hash indirectly. + + @method meta + @for Ember + @private + + @param {Object} obj The object to retrieve meta for + @param {Boolean} [writable=true] Pass `false` if you do not intend to modify + the meta hash, allowing the method to avoid making an unnecessary copy. + @return {Object} the meta hash for an object + */ + function meta(obj, writable) { + + var ret = obj[META_KEY]; + if (writable===false) return ret || EMPTY_META; + + if (!ret) { + if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC); + + ret = new Meta(obj); + + if (MANDATORY_SETTER) { ret.values = {}; } + + obj[META_KEY] = ret; + + // make sure we don't accidentally try to create constructor like desc + ret.descs.constructor = null; + + } else if (ret.source !== obj) { + if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC); + + ret = o_create(ret); + ret.descs = o_create(ret.descs); + ret.watching = o_create(ret.watching); + ret.cache = {}; + ret.source = obj; + + if (MANDATORY_SETTER) { ret.values = o_create(ret.values); } + + obj[META_KEY] = ret; + } + return ret; + }; + + function getMeta(obj, property) { + var _meta = meta(obj, false); + return _meta[property]; + }; + + function setMeta(obj, property, value) { + var _meta = meta(obj, true); + _meta[property] = value; + return value; + }; + + /** + @deprecated + @private + + In order to store defaults for a class, a prototype may need to create + a default meta object, which will be inherited by any objects instantiated + from the class's constructor. + + However, the properties of that meta object are only shallow-cloned, + so if a property is a hash (like the event system's `listeners` hash), + it will by default be shared across all instances of that class. + + This method allows extensions to deeply clone a series of nested hashes or + other complex objects. For instance, the event system might pass + `['listeners', 'foo:change', 'ember157']` to `prepareMetaPath`, which will + walk down the keys provided. + + For each key, if the key does not exist, it is created. If it already + exists and it was inherited from its constructor, the constructor's + key is cloned. + + You can also pass false for `writable`, which will simply return + undefined if `prepareMetaPath` discovers any part of the path that + shared or undefined. + + @method metaPath + @for Ember + @param {Object} obj The object whose meta we are examining + @param {Array} path An array of keys to walk down + @param {Boolean} writable whether or not to create a new meta + (or meta property) if one does not already exist or if it's + shared with its constructor + */ + function metaPath(obj, path, writable) { + Ember.deprecate("Ember.metaPath is deprecated and will be removed from future releases."); + var _meta = meta(obj, writable), keyName, value; + + for (var i=0, l=path.length; i 3 ? slice.call(arguments, 3) : undefined; if (!this.currentInstance) { createAutorun(this); } return this.currentInstance.schedule(queueName, target, method, args, false, stack); @@ -5724,7 +8331,7 @@ define("backburner", method = target[method]; } - var stack = this.DEBUG ? new Error().stack : undefined, + var stack = this.DEBUG ? new Error() : undefined, args = arguments.length > 3 ? slice.call(arguments, 3) : undefined; if (!this.currentInstance) { createAutorun(this); } return this.currentInstance.schedule(queueName, target, method, args, true, stack); @@ -5795,50 +8402,49 @@ define("backburner", timers.splice(i, 0, executeAt, fn); - if (laterTimer && laterTimerExpiresAt < executeAt) { return fn; } - - if (laterTimer) { - clearTimeout(laterTimer); - laterTimer = null; - } - laterTimer = global.setTimeout(function() { - executeTimers(self); - laterTimer = null; - laterTimerExpiresAt = null; - }, wait); - laterTimerExpiresAt = executeAt; + updateLaterTimer(self, executeAt, wait); return fn; }, - throttle: function(target, method /* , args, wait */) { + throttle: function(target, method /* , args, wait, [immediate] */) { var self = this, args = arguments, - wait = parseInt(pop.call(args), 10), - throttler; + immediate = pop.call(args), + wait, + throttler, + index, + timer; - for (var i = 0, l = throttlers.length; i < l; i++) { - throttler = throttlers[i]; - if (throttler[0] === target && throttler[1] === method) { return; } // do nothing + if (typeof immediate === "number" || typeof immediate === "string") { + wait = immediate; + immediate = true; + } else { + wait = pop.call(args); } - var timer = global.setTimeout(function() { - self.run.apply(self, args); + wait = parseInt(wait, 10); - // remove throttler - var index = -1; - for (var i = 0, l = throttlers.length; i < l; i++) { - throttler = throttlers[i]; - if (throttler[0] === target && throttler[1] === method) { - index = i; - break; - } + index = findThrottler(target, method); + if (index > -1) { return throttlers[index]; } // throttled + + timer = global.setTimeout(function() { + if (!immediate) { + self.run.apply(self, args); } - + var index = findThrottler(target, method); if (index > -1) { throttlers.splice(index, 1); } }, wait); - throttlers.push([target, method, timer]); + if (immediate) { + self.run.apply(self, args); + } + + throttler = [target, method, timer]; + + throttlers.push(throttler); + + return throttler; }, debounce: function(target, method /* , args, wait, [immediate] */) { @@ -5847,7 +8453,8 @@ define("backburner", immediate = pop.call(args), wait, index, - debouncee; + debouncee, + timer; if (typeof immediate === "number" || typeof immediate === "string") { wait = immediate; @@ -5860,18 +8467,18 @@ define("backburner", // Remove debouncee index = findDebouncee(target, method); - if (index !== -1) { + if (index > -1) { debouncee = debouncees[index]; debouncees.splice(index, 1); clearTimeout(debouncee[2]); } - var timer = global.setTimeout(function() { + timer = global.setTimeout(function() { if (!immediate) { self.run.apply(self, args); } - index = findDebouncee(target, method); - if (index) { + var index = findDebouncee(target, method); + if (index > -1) { debouncees.splice(index, 1); } }, wait); @@ -5880,7 +8487,11 @@ define("backburner", self.run.apply(self, args); } - debouncees.push([target, method, timer]); + debouncee = [target, method, timer]; + + debouncees.push(debouncee); + + return debouncee; }, cancelTimers: function() { @@ -5913,19 +8524,47 @@ define("backburner", }, cancel: function(timer) { - if (timer && typeof timer === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce + var timerType = typeof timer; + + if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce return timer.queue.cancel(timer); - } else if (typeof timer === 'function') { // we're cancelling a setTimeout + } else if (timerType === 'function') { // we're cancelling a setTimeout for (var i = 0, l = timers.length; i < l; i += 2) { if (timers[i + 1] === timer) { timers.splice(i, 2); // remove the two elements return true; } } + } else if (Object.prototype.toString.call(timer) === "[object Array]"){ // we're cancelling a throttle or debounce + return this._cancelItem(findThrottler, throttlers, timer) || + this._cancelItem(findDebouncee, debouncees, timer); } else { return; // timer was null or not a timer } + }, + + _cancelItem: function(findMethod, array, timer){ + var item, + index; + + if (timer.length < 3) { return false; } + + index = findMethod(timer[0], timer[1]); + + if(index > -1) { + + item = array[index]; + + if(item[2] === timer[2]){ + array.splice(index, 1); + clearTimeout(timer[2]); + return true; + } + } + + return false; } + }; Backburner.prototype.schedule = Backburner.prototype.defer; @@ -5940,6 +8579,20 @@ define("backburner", }); } + function updateLaterTimer(self, executeAt, wait) { + if (!laterTimer || executeAt < laterTimerExpiresAt) { + if (laterTimer) { + clearTimeout(laterTimer); + } + laterTimer = global.setTimeout(function() { + laterTimer = null; + laterTimerExpiresAt = null; + executeTimers(self); + }, wait); + laterTimerExpiresAt = executeAt; + } + } + function executeTimers(self) { var now = +new Date(), time, fns, i, l; @@ -5959,12 +8612,7 @@ define("backburner", }); if (timers.length) { - laterTimer = global.setTimeout(function() { - executeTimers(self); - laterTimer = null; - laterTimerExpiresAt = null; - }, timers[0] - now); - laterTimerExpiresAt = timers[0]; + updateLaterTimer(self, timers[0], timers[0] - now); } } @@ -5983,1881 +8631,406 @@ define("backburner", return index; } + function findThrottler(target, method) { + var throttler, + index = -1; + + for (var i = 0, l = throttlers.length; i < l; i++) { + throttler = throttlers[i]; + if (throttler[0] === target && throttler[1] === method) { + index = i; + break; + } + } + + return index; + } __exports__.Backburner = Backburner; }); -})(); - - - -(function() { -var onBegin = function(current) { - Ember.run.currentRunLoop = current; -}; - -var onEnd = function(current, next) { - Ember.run.currentRunLoop = next; -}; - -var Backburner = requireModule('backburner').Backburner, - backburner = new Backburner(['sync', 'actions', 'destroy'], { - sync: { - before: Ember.beginPropertyChanges, - after: Ember.endPropertyChanges - }, - defaultQueue: 'actions', - onBegin: onBegin, - onEnd: onEnd - }), - slice = [].slice; - -// .......................................................... -// 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 ret; - - if (Ember.onerror) { - try { - ret = backburner.run.apply(backburner, arguments); - } catch (e) { - Ember.onerror(e); - } - } else { - ret = backburner.run.apply(backburner, arguments); - } - - return ret; -}; - -/** - - If no run-loop is present, it creates a new one. If a run loop is - present it will queue itself to run on the existing run-loops action - queue. - - Please note: This is not for normal usage, and should be used sparingly. - - If invoked when not within a run loop: - - ```javascript - Ember.run.join(function() { - // creates a new run-loop - }); - ``` - - Alternatively, if called within an existing run loop: - - ```javascript - Ember.run(function() { - // creates a new run-loop - Ember.run.join(function() { - // joins with the existing run-loop, and queues for invocation on - // the existing run-loops action queue. - }); - }); - ``` - - @method join - @namespace Ember - @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. Please note, - when called within an existing loop, no return value is possible. -*/ -Ember.run.join = function(target, method) { - if (!Ember.run.currentRunLoop) { - return Ember.run.apply(Ember.run, arguments); - } - - var args = slice.call(arguments); - args.unshift('actions'); - Ember.run.schedule.apply(Ember.run, args); -}; - -Ember.run.backburner = backburner; - -var run = Ember.run; - -Ember.run.currentRunLoop = null; - -Ember.run.queues = backburner.queueNames; - -/** - 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() { - backburner.begin(); -}; - -/** - 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() { - backburner.end(); -}; - -/** - 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'] -*/ - -/** - 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 `Ember.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) { - checkAutoRun(); - backburner.schedule.apply(backburner, arguments); -}; - -// Used by global test teardown -Ember.run.hasScheduledTimers = function() { - return backburner.hasTimers(); -}; - -// Used by global test teardown -Ember.run.cancelTimers = function () { - backburner.cancelTimers(); -}; - -/** - 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() { - if (backburner.currentInstance) { - backburner.currentInstance.queues.sync.flush(); - } -}; - -/** - 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 - `Ember.run.cancel` later. -*/ -Ember.run.later = function(target, method) { - return backburner.later.apply(backburner, arguments); -}; - -/** - Schedule a function to run one time during the current RunLoop. This is equivalent - to calling `scheduleOnce` with the "actions" queue. - - @method once - @param {Object} [target] The target of the 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) { - checkAutoRun(); - var args = slice.call(arguments); - args.unshift('actions'); - return backburner.scheduleOnce.apply(backburner, args); -}; - -/** - Schedules a function to run one time in a given queue of the current RunLoop. - Calling this method with the same queue/target/method combination will have - no effect (past the initial call). - - 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 sayHi = function() { console.log('hi'); } - Ember.run.scheduleOnce('afterRender', myContext, sayHi); - Ember.run.scheduleOnce('afterRender', myContext, sayHi); - // sayHi will only be executed once, in the afterRender queue of the RunLoop - }); - ``` - - Also note that passing an anonymous function to `Ember.run.scheduleOnce` will - not prevent additional calls with an identical anonymous function from - scheduling the items multiple times, e.g.: - - ```javascript - function scheduleIt() { - Ember.run.scheduleOnce('actions', myContext, function() { console.log("Closure"); }); - } - scheduleIt(); - scheduleIt(); - // "Closure" will print twice, even though we're using `Ember.run.scheduleOnce`, - // because the function we pass to it is anonymous and won't match the - // previously scheduled operation. - ``` - - Available queues, and their order, can be found at `Ember.run.queues` - - @method scheduleOnce - @param {String} [queue] The name of the queue to schedule against. Default queues are 'sync' and 'actions'. - @param {Object} [target] The target of the 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.scheduleOnce = function(queue, target, method) { - checkAutoRun(); - return backburner.scheduleOnce.apply(backburner, arguments); -}; - -/** - 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); - return backburner.later.apply(backburner, 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) { - return backburner.cancel(timer); -}; - -/** - Delay calling the target method until the debounce period has elapsed - with no additional debounce calls. If `debounce` is called again before - the specified time has elapsed, the timer is reset and the entire period - must pass again before the target method is called. - - This method should be used when an event may be called multiple times - but the action should only be called once when the event is done firing. - A common example is for scroll events where you only want updates to - happen once scrolling has ceased. - - ```javascript - var myFunc = function() { console.log(this.name + ' ran.'); }; - var myContext = {name: 'debounce'}; - - Ember.run.debounce(myContext, myFunc, 150); - - // less than 150ms passes - - Ember.run.debounce(myContext, myFunc, 150); - - // 150ms passes - // myFunc is invoked with context myContext - // console logs 'debounce ran.' one time. - ``` - - @method debounce - @param {Object} [target] target of method to invoke - @param {Function|String} method The 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*] Optional arguments to pass to the timeout. - @param {Number} wait Number of milliseconds to wait. - @param {Boolean} immediate Trigger the function on the leading instead of the trailing edge of the wait interval. - @return {void} -*/ -Ember.run.debounce = function() { - return backburner.debounce.apply(backburner, arguments); -}; - -/** - Ensure that the target method is never called more frequently than - the specified spacing period. - - ```javascript - var myFunc = function() { console.log(this.name + ' ran.'); }; - var myContext = {name: 'throttle'}; - - Ember.run.throttle(myContext, myFunc, 150); - - // 50ms passes - Ember.run.throttle(myContext, myFunc, 150); - - // 50ms passes - Ember.run.throttle(myContext, myFunc, 150); - - // 50ms passes - Ember.run.throttle(myContext, myFunc, 150); - - // 150ms passes - // myFunc is invoked with context myContext - // console logs 'throttle ran.' twice, 150ms apart. - ``` - - @method throttle - @param {Object} [target] target of method to invoke - @param {Function|String} method The 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*] Optional arguments to pass to the timeout. - @param {Number} spacing Number of milliseconds to space out requests. - @return {void} -*/ -Ember.run.throttle = function() { - return backburner.throttle.apply(backburner, arguments); -}; - -// Make sure it's not an autorun during testing -function checkAutoRun() { - if (!Ember.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); - } -} - -})(); - - - -(function() { -// Ember.Logger -// get -// set -// guidFor, 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, - IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/; - -/** - Returns true if the provided path is global (e.g., `MyApp.fooController.bar`) - instead of local (`foo.bar.baz`). - - @method isGlobalPath - @for Ember - @private - @param {String} path - @return Boolean -*/ -var isGlobalPath = Ember.isGlobalPath = function(path) { - return IS_GLOBAL.test(path); -}; - -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} `this` - */ - 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); +define("ember-metal/watch_key", + ["ember-metal/core","ember-metal/utils","ember-metal/platform","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var meta = __dependency2__.meta; + var typeOf = __dependency2__.typeOf; + var platform = __dependency3__.platform; + + var metaFor = meta, // utils.js + MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER, + o_defineProperty = platform.defineProperty; + + function watchKey(obj, keyName, meta) { + // can't watch length on Array - it is special... + if (keyName === 'length' && typeOf(obj) === 'array') { return; } + + var m = meta || metaFor(obj), watching = m.watching; + + // activate watching first time + if (!watching[keyName]) { + watching[keyName] = 1; + + 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: obj.propertyIsEnumerable(keyName), + set: Ember.MANDATORY_SETTER_FUNCTION, + get: Ember.DEFAULT_GETTER_FUNCTION(keyName) + }); + } } else { - Ember._suspendObserver(obj, toPath, this, this.toDidChange, function () { - Ember.trySet(obj, toPath, fromValue); - }); + watching[keyName] = (watching[keyName] || 0) + 1; } - // 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 unwatchKey(obj, keyName, meta) { + var m = meta || metaFor(obj), watching = m.watching; -function mixinProperties(to, from) { - for (var key in from) { - if (from.hasOwnProperty(key)) { - to[key] = from[key]; - } - } -} + if (watching[keyName] === 1) { + watching[keyName] = 0; -mixinProperties(Binding, { - - /* - See `Ember.Binding.from`. - - @method from - @static - */ - from: function() { - var C = this, binding = new C(); - return binding.from.apply(binding, arguments); - }, - - /* - See `Ember.Binding.to`. - - @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 `Binding.oneWay`. - - @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. - @return {Ember.Binding} `this` - */ - 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 "One Way 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 -@submodule 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 concatenatedMixinProperties(concatProp, props, values, base) { - var concats; - - // reset before adding each new mixin to pickup concats from previous - concats = values[concatProp] || base[concatProp]; - if (props[concatProp]) { - concats = concats ? concats.concat(props[concatProp]) : props[concatProp]; - } - - 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 applyMergedProperties(obj, key, value, values) { - var baseValue = values[key] || obj[key]; - - if (!baseValue) { return value; } - - var newBase = Ember.merge({}, baseValue); - for (var prop in value) { - if (!value.hasOwnProperty(prop)) { continue; } - - var propValue = value[prop]; - if (isMethod(propValue)) { - // TODO: support for Computed Properties, etc? - newBase[prop] = giveMethodSuper(obj, prop, propValue, baseValue, {}); - } else { - newBase[prop] = propValue; - } - } - - return newBase; -} - -function addNormalizedProperty(base, key, value, meta, descs, values, concats, mergings) { - 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 { - if ((concats && a_indexOf.call(concats, key) >= 0) || - key === 'concatenatedProperties' || - key === 'mergedProperties') { - value = applyConcatenatedProperties(base, key, value, values); - } else if ((mergings && a_indexOf.call(mergings, key) >= 0)) { - value = applyMergedProperties(base, key, value, values); - } else if (isMethod(value)) { - value = giveMethodSuper(base, key, value, values, descs); - } - - descs[key] = undefined; - values[key] = value; - } -} - -function mergeMixins(mixins, m, descs, values, base, keys) { - var mixin, props, key, concats, mergings, 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) { - Ember.deprecate("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead."); - return new Alias(methodName); -}; - -/** - 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 -// - -/** - Specify a method that observes property changes. - - ```javascript - Ember.Object.extend({ - valueObserver: Ember.observer('value', function() { - // Executes whenever the "value" property changes - }) - }); - ``` - - In the future this method may become asynchronous. If you want to ensure - synchronous behavior, use `immediateObserver`. - - Also available as `Function.prototype.observes` if prototype extensions are - enabled. - - @method observer - @for Ember - @param {String} propertyNames* - @param {Function} func - @return func -*/ -Ember.observer = function() { - var func = a_slice.call(arguments, -1)[0]; - var paths = a_slice.call(arguments, 0, -1); - - if (typeof func !== "function") { - // revert to old, soft-deprecated argument ordering - - func = arguments[0]; - paths = a_slice.call(arguments, 1); - } - - if (typeof func !== "function") { - throw new Ember.Error("Ember.observer called without a function"); - } - - func.__ember_observes__ = paths; - return func; -}; - -/** - Specify a method that observes property changes. - - ```javascript - Ember.Object.extend({ - valueObserver: Ember.immediateObserver('value', function() { - // Executes whenever the "value" property changes - }) - }); - ``` - - In the future, `Ember.observer` may become asynchronous. In this event, - `Ember.immediateObserver` will maintain the synchronous behavior. - - Also available as `Function.prototype.observesImmediately` if prototype extensions are - enabled. - - @method immediateObserver - @for Ember - @param {String} propertyNames* - @param {Function} func - @return func -*/ -Ember.immediateObserver = function() { - for (var i=0, l=arguments.length; i this.changingFrom ? 'green' : 'red'; - // logic + if (MANDATORY_SETTER && keyName in obj) { + o_defineProperty(obj, keyName, { + configurable: true, + enumerable: obj.propertyIsEnumerable(keyName), + set: function(val) { + // redefine to set as enumerable + o_defineProperty(obj, keyName, { + configurable: true, + writable: true, + enumerable: true, + value: val + }); + delete m.values[keyName]; + }, + get: Ember.DEFAULT_GETTER_FUNCTION(keyName) + }); } - }), - - friendsDidChange: Ember.observer('friends.@each.name', function(obj, keyName) { - // some logic - // obj.get(keyName) returns friends array - }) - }); - ``` - - Also available as `Function.prototype.observesBefore` if prototype extensions are - enabled. - - @method beforeObserver - @for Ember - @param {String} propertyNames* - @param {Function} func - @return func -*/ -Ember.beforeObserver = function() { - var func = a_slice.call(arguments, -1)[0]; - var paths = a_slice.call(arguments, 0, -1); - - if (typeof func !== "function") { - // revert to old, soft-deprecated argument ordering - - func = arguments[0]; - paths = a_slice.call(arguments, 1); - } - - if (typeof func !== "function") { - throw new Ember.Error("Ember.beforeObserver called without a function"); - } - - func.__ember_observesBefore__ = paths; - return func; -}; - -})(); - - - -(function() { -// Provides a way to register library versions with ember. -var forEach = Ember.EnumerableUtils.forEach, - indexOf = Ember.EnumerableUtils.indexOf; - -Ember.libraries = function() { - var libraries = []; - var coreLibIndex = 0; - - var getLibrary = function(name) { - for (var i = 0; i < libraries.length; i++) { - if (libraries[i].name === name) { - return libraries[i]; + } else if (watching[keyName] > 1) { + watching[keyName]--; } + }; + + __exports__.watchKey = watchKey; + __exports__.unwatchKey = unwatchKey; + }); +define("ember-metal/watch_path", + ["ember-metal/utils","ember-metal/chains","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var meta = __dependency1__.meta; + var typeOf = __dependency1__.typeOf; + var ChainNode = __dependency2__.ChainNode; + + var metaFor = meta; + + // 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, meta) { + var m = meta || 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; } - }; - libraries.register = function(name, version) { - if (!getLibrary(name)) { - libraries.push({name: name, version: version}); + function watchPath(obj, keyPath, meta) { + // can't watch length on Array - it is special... + if (keyPath === 'length' && typeOf(obj) === 'array') { return; } + + var m = meta || metaFor(obj), watching = m.watching; + + if (!watching[keyPath]) { // activate watching first time + watching[keyPath] = 1; + chainsFor(obj, m).add(keyPath); + } else { + watching[keyPath] = (watching[keyPath] || 0) + 1; + } + }; + + function unwatchPath(obj, keyPath, meta) { + var m = meta || metaFor(obj), watching = m.watching; + + if (watching[keyPath] === 1) { + watching[keyPath] = 0; + chainsFor(obj, m).remove(keyPath); + } else if (watching[keyPath] > 1) { + watching[keyPath]--; + } + }; + + __exports__.watchPath = watchPath; + __exports__.unwatchPath = unwatchPath; + }); +define("ember-metal/watching", + ["ember-metal/utils","ember-metal/chains","ember-metal/watch_key","ember-metal/watch_path","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + /** + @module ember-metal + */ + + var meta = __dependency1__.meta; + var META_KEY = __dependency1__.META_KEY; + var GUID_KEY = __dependency1__.GUID_KEY; + var typeOf = __dependency1__.typeOf; + var generateGuid = __dependency1__.generateGuid; + var removeChainWatcher = __dependency2__.removeChainWatcher; + var flushPendingChains = __dependency2__.flushPendingChains; + var watchKey = __dependency3__.watchKey; + var unwatchKey = __dependency3__.unwatchKey; + var watchPath = __dependency4__.watchPath; + var unwatchPath = __dependency4__.unwatchPath; + + var metaFor = meta, // utils.js + IS_PATH = /[\.\*]/; + + // returns true if the passed path is just a keyName + function isKeyName(path) { + return path==='*' || !IS_PATH.test(path); } - }; - libraries.registerCoreLibrary = function(name, version) { - if (!getLibrary(name)) { - libraries.splice(coreLibIndex++, 0, {name: name, version: version}); - } - }; + /** + 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()` - libraries.deRegister = function(name) { - var lib = getLibrary(name); - if (lib) libraries.splice(indexOf(libraries, lib), 1); - }; + @private + @method watch + @for Ember + @param obj + @param {String} keyName + */ + function watch(obj, _keyPath, m) { + // can't watch length on Array - it is special... + if (_keyPath === 'length' && typeOf(obj) === 'array') { return; } - libraries.each = function (callback) { - forEach(libraries, function(lib) { - callback(lib.name, lib.version); - }); - }; + if (isKeyName(_keyPath)) { + watchKey(obj, _keyPath, m); + } else { + watchPath(obj, _keyPath, m); + } + }; - return libraries; -}(); + function isWatching(obj, key) { + var meta = obj[META_KEY]; + return (meta && meta.watching[key]) > 0; + }; -Ember.libraries.registerCoreLibrary('Ember', Ember.VERSION); + watch.flushPending = flushPendingChains; + function unwatch(obj, _keyPath, m) { + // can't watch length on Array - it is special... + if (_keyPath === 'length' && typeOf(obj) === 'array') { return; } + + if (isKeyName(_keyPath)) { + unwatchKey(obj, _keyPath, m); + } else { + unwatchPath(obj, _keyPath, m); + } + }; + + /** + 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. + + @private + @method rewatch + @for Ember + @param obj + */ + function rewatch(obj) { + var m = obj[META_KEY], chains = m && m.chains; + + // make sure the object has its own guid. + if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) { + generateGuid(obj); + } + + // make sure any chained watchers update. + if (chains && chains.value() !== obj) { + m.chains = chains.copy(obj); + } + }; + + 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} + */ + function destroy(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); + } + } + } + } + } + }; + + __exports__.watch = watch; + __exports__.isWatching = isWatching; + __exports__.unwatch = unwatch; + __exports__.rewatch = rewatch; + __exports__.destroy = destroy; + }); })(); - - (function() { /** -Ember Metal - -@module ember -@submodule ember-metal -*/ - -})(); - -(function() { -define("rsvp/all", - ["rsvp/promise","exports"], + @class RSVP + @module RSVP + */ +define("rsvp/all", + ["./promise","exports"], function(__dependency1__, __exports__) { "use strict"; - var Promise = __dependency1__.Promise; - /* global toString */ + var Promise = __dependency1__["default"]; + /** + This is a convenient alias for `RSVP.Promise.all`. - function all(promises) { - if (Object.prototype.toString.call(promises) !== "[object Array]") { - throw new TypeError('You must pass an array to all.'); - } + @method all + @for RSVP + @param {Array} array Array of promises. + @param {String} label An optional label. This is useful + for tooling. + @static + */ + __exports__["default"] = function all(array, label) { + return Promise.all(array, label); + }; + }); +define("rsvp/all_settled", + ["./promise","./utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var Promise = __dependency1__["default"]; + var isArray = __dependency2__.isArray; + var isNonThenable = __dependency2__.isNonThenable; + /** + `RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing + a fail-fast method, it waits until all the promises have returned and + shows you all the results. This is useful if you want to handle multiple + promises' failure states together as a set. + + Returns a promise that is fulfilled when all the given promises have been + settled. The return promise is fulfilled with an array of the states of + the promises passed into the `promises` array argument. + + Each state object will either indicate fulfillment or rejection, and + provide the corresponding value or reason. The states will take one of + the following formats: + + ```javascript + { state: 'fulfilled', value: value } + or + { state: 'rejected', reason: reason } + ``` + + Example: + + ```javascript + var promise1 = RSVP.Promise.resolve(1); + var promise2 = RSVP.Promise.reject(new Error('2')); + var promise3 = RSVP.Promise.reject(new Error('3')); + var promises = [ promise1, promise2, promise3 ]; + + RSVP.allSettled(promises).then(function(array){ + // array == [ + // { state: 'fulfilled', value: 1 }, + // { state: 'rejected', reason: Error }, + // { state: 'rejected', reason: Error } + // ] + // Note that for the second item, reason.message will be "2", and for the + // third item, reason.message will be "3". + }, function(error) { + // Not run. (This block would only be called if allSettled had failed, + // for instance if passed an incorrect argument type.) + }); + ``` + + @method allSettled + @for RSVP + @param {Array} promises + @param {String} label - optional string that describes the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled with an array of the settled + states of the constituent promises. + @static + */ + + __exports__["default"] = function allSettled(entries, label) { return new Promise(function(resolve, reject) { - var results = [], remaining = promises.length, - promise; + if (!isArray(entries)) { + throw new TypeError('You must pass an array to allSettled.'); + } + + var remaining = entries.length; + var entry; if (remaining === 0) { resolve([]); + return; } - function resolver(index) { + var results = new Array(remaining); + + function fulfilledResolver(index) { return function(value) { - resolveAll(index, value); + resolveAll(index, fulfilled(value)); + }; + } + + function rejectedResolver(index) { + return function(reason) { + resolveAll(index, rejected(reason)); }; } @@ -7868,152 +9041,113 @@ define("rsvp/all", } } - for (var i = 0; i < promises.length; i++) { - promise = promises[i]; + for (var index = 0; index < entries.length; index++) { + entry = entries[index]; - if (promise && typeof promise.then === 'function') { - promise.then(resolver(i), reject); + if (isNonThenable(entry)) { + resolveAll(index, fulfilled(entry)); } else { - resolveAll(i, promise); + Promise.cast(entry).then(fulfilledResolver(index), rejectedResolver(index)); } } - }); + }, label); + }; + + function fulfilled(value) { + return { state: 'fulfilled', value: value }; } - - __exports__.all = all; + function rejected(reason) { + return { state: 'rejected', reason: reason }; + } }); -define("rsvp/async", - ["exports"], - function(__exports__) { - "use strict"; - var browserGlobal = (typeof window !== 'undefined') ? window : {}; - var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; - var async; - var local = (typeof global !== 'undefined') ? global : this; - - // old node - function useNextTick() { - return function(callback, arg) { - process.nextTick(function() { - callback(arg); - }); - }; - } - - // node >= 0.10.x - function useSetImmediate() { - return function(callback, arg) { - /* global setImmediate */ - setImmediate(function(){ - callback(arg); - }); - }; - } - - function useMutationObserver() { - var queue = []; - - var observer = new BrowserMutationObserver(function() { - var toProcess = queue.slice(); - queue = []; - - toProcess.forEach(function(tuple) { - var callback = tuple[0], arg= tuple[1]; - callback(arg); - }); - }); - - var element = document.createElement('div'); - observer.observe(element, { attributes: true }); - - // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661 - window.addEventListener('unload', function(){ - observer.disconnect(); - observer = null; - }, false); - - return function(callback, arg) { - queue.push([callback, arg]); - element.setAttribute('drainQueue', 'drainQueue'); - }; - } - - function useSetTimeout() { - return function(callback, arg) { - local.setTimeout(function() { - callback(arg); - }, 1); - }; - } - - if (typeof setImmediate === 'function') { - async = useSetImmediate(); - } else if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { - async = useNextTick(); - } else if (BrowserMutationObserver) { - async = useMutationObserver(); - } else { - async = useSetTimeout(); - } - - - __exports__.async = async; - }); -define("rsvp/config", - ["rsvp/async","exports"], +define("rsvp/config", + ["./events","exports"], function(__dependency1__, __exports__) { "use strict"; - var async = __dependency1__.async; + var EventTarget = __dependency1__["default"]; - var config = {}; - config.async = async; + var config = { + instrument: false + }; + EventTarget.mixin(config); + + function configure(name, value) { + if (name === 'onerror') { + // handle for legacy users that expect the actual + // error to be passed to their function added via + // `RSVP.configure('onerror', someFunctionHere);` + config.on('error', value); + return; + } + + if (arguments.length === 2) { + config[name] = value; + } else { + return config[name]; + } + } __exports__.config = config; + __exports__.configure = configure; }); -define("rsvp/defer", - ["rsvp/promise","exports"], +define("rsvp/defer", + ["./promise","exports"], function(__dependency1__, __exports__) { "use strict"; - var Promise = __dependency1__.Promise; + var Promise = __dependency1__["default"]; - function defer() { - var deferred = { - // pre-allocate shape - resolve: undefined, - reject: undefined, - promise: undefined - }; + /** + `RSVP.defer` returns an object similar to jQuery's `$.Deferred`. + `RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s + interface. New code should use the `RSVP.Promise` constructor instead. + + The object returned from `RSVP.defer` is a plain object with three properties: + + * promise - an `RSVP.Promise`. + * reject - a function that causes the `promise` property on this object to + become rejected + * resolve - a function that causes the `promise` property on this object to + become fulfilled. + + Example: + + ```javascript + var deferred = RSVP.defer(); + + deferred.resolve("Success!"); + + deferred.promise.then(function(value){ + // value here is "Success!" + }); + ``` + + @method defer + @for RSVP + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Object} + */ + + __exports__["default"] = function defer(label) { + var deferred = { }; deferred.promise = new Promise(function(resolve, reject) { deferred.resolve = resolve; deferred.reject = reject; - }); + }, label); return deferred; - } - - - __exports__.defer = defer; + }; }); -define("rsvp/events", +define("rsvp/events", ["exports"], function(__exports__) { "use strict"; - var Event = function(type, options) { - this.type = type; - - for (var option in options) { - if (!options.hasOwnProperty(option)) { continue; } - - this[option] = options[option]; - } - }; - var indexOf = function(callbacks, callback) { for (var i=0, l=callbacks.length; i 1; + }; + + RSVP.filter(promises, filterFn).then(function(result){ + // result is [ 2, 3 ] + }); + ``` + + If any of the `promises` given to `RSVP.filter` are rejected, the first promise + that is rejected will be given as an argument to the returned promise's + rejection handler. For example: + + ```javascript + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.reject(new Error("2")); + var promise3 = RSVP.reject(new Error("3")); + var promises = [ promise1, promise2, promise3 ]; + + var filterFn = function(item){ + return item > 1; + }; + + RSVP.filter(promises, filterFn).then(function(array){ + // Code here never runs because there are rejected promises! + }, function(reason) { + // reason.message === "2" + }); + ``` + + `RSVP.filter` will also wait for any promises returned from `filterFn`. + For instance, you may want to fetch a list of users then return a subset + of those users based on some asynchronous operation: + + ```javascript + + var alice = { name: 'alice' }; + var bob = { name: 'bob' }; + var users = [ alice, bob ]; + + var promises = users.map(function(user){ + return RSVP.resolve(user); + }); + + var filterFn = function(user){ + // Here, Alice has permissions to create a blog post, but Bob does not. + return getPrivilegesForUser(user).then(function(privs){ + return privs.can_create_blog_post === true; + }); + }; + RSVP.filter(promises, filterFn).then(function(users){ + // true, because the server told us only Alice can create a blog post. + users.length === 1; + // false, because Alice is the only user present in `users` + users[0] === bob; + }); + ``` + + @method filter + @for RSVP + @param {Array} promises + @param {Function} filterFn - function to be called on each resolved value to + filter the final results. + @param {String} label optional string describing the promise. Useful for + tooling. + @return {Promise} + */ + function filter(promises, filterFn, label) { + return all(promises, label).then(function(values){ + if (!isArray(promises)) { + throw new TypeError('You must pass an array to filter.'); + } + + if (!isFunction(filterFn)){ + throw new TypeError("You must pass a function to filter's second argument."); + } + + return map(promises, filterFn, label).then(function(filterResults){ + var i, + valuesLen = values.length, + filtered = []; + + for (i = 0; i < valuesLen; i++){ + if(filterResults[i]) filtered.push(values[i]); + } + return filtered; + }); + }); } - function hash(promises) { - var results = {}, deferred = defer(), remaining = size(promises); - - if (remaining === 0) { - deferred.resolve({}); - } - - var resolver = function(prop) { - return function(value) { - resolveAll(prop, value); - }; - }; - - var resolveAll = function(prop, value) { - results[prop] = value; - if (--remaining === 0) { - deferred.resolve(results); - } - }; - - var rejectAll = function(error) { - deferred.reject(error); - }; - - for (var prop in promises) { - if (promises[prop] && typeof promises[prop].then === 'function') { - promises[prop].then(resolver(prop), rejectAll); - } else { - resolveAll(prop, promises[prop]); - } - } - - return deferred.promise; - } - - - __exports__.hash = hash; + __exports__["default"] = filter; }); -define("rsvp/node", - ["rsvp/promise","rsvp/all","exports"], +define("rsvp/hash", + ["./promise","./utils","exports"], function(__dependency1__, __dependency2__, __exports__) { "use strict"; - var Promise = __dependency1__.Promise; - var all = __dependency2__.all; + var Promise = __dependency1__["default"]; + var isNonThenable = __dependency2__.isNonThenable; + var keysOf = __dependency2__.keysOf; + + /** + `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array + for its `promises` argument. + + Returns a promise that is fulfilled when all the given promises have been + fulfilled, or rejected if any of them become rejected. The returned promise + is fulfilled with a hash that has the same key names as the `promises` object + argument. If any of the values in the object are not promises, they will + simply be copied over to the fulfilled object. + + Example: + + ```javascript + var promises = { + myPromise: RSVP.resolve(1), + yourPromise: RSVP.resolve(2), + theirPromise: RSVP.resolve(3), + notAPromise: 4 + }; + + RSVP.hash(promises).then(function(hash){ + // hash here is an object that looks like: + // { + // myPromise: 1, + // yourPromise: 2, + // theirPromise: 3, + // notAPromise: 4 + // } + }); + ```` + + If any of the `promises` given to `RSVP.hash` are rejected, the first promise + that is rejected will be given as the reason to the rejection handler. + + Example: + + ```javascript + var promises = { + myPromise: RSVP.resolve(1), + rejectedPromise: RSVP.reject(new Error("rejectedPromise")), + anotherRejectedPromise: RSVP.reject(new Error("anotherRejectedPromise")), + }; + + RSVP.hash(promises).then(function(hash){ + // Code here never runs because there are rejected promises! + }, function(reason) { + // reason.message === "rejectedPromise" + }); + ``` + + An important note: `RSVP.hash` is intended for plain JavaScript objects that + are just a set of keys and values. `RSVP.hash` will NOT preserve prototype + chains. + + Example: + + ```javascript + function MyConstructor(){ + this.example = RSVP.resolve("Example"); + } + + MyConstructor.prototype = { + protoProperty: RSVP.resolve("Proto Property") + }; + + var myObject = new MyConstructor(); + + RSVP.hash(myObject).then(function(hash){ + // protoProperty will not be present, instead you will just have an + // object that looks like: + // { + // example: "Example" + // } + // + // hash.hasOwnProperty('protoProperty'); // false + // 'undefined' === typeof hash.protoProperty + }); + ``` + + @method hash + @for RSVP + @param {Object} promises + @param {String} label optional string that describes the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled when all properties of `promises` + have been fulfilled, or rejected if any of them become rejected. + @static + */ + __exports__["default"] = function hash(object, label) { + return new Promise(function(resolve, reject){ + var results = {}; + var keys = keysOf(object); + var remaining = keys.length; + var entry, property; + + if (remaining === 0) { + resolve(results); + return; + } + + function fulfilledTo(property) { + return function(value) { + results[property] = value; + if (--remaining === 0) { + resolve(results); + } + }; + } + + function onRejection(reason) { + remaining = 0; + reject(reason); + } + + for (var i = 0; i < keys.length; i++) { + property = keys[i]; + entry = object[property]; + + if (isNonThenable(entry)) { + results[property] = entry; + if (--remaining === 0) { + resolve(results); + } + } else { + Promise.cast(entry).then(fulfilledTo(property), onRejection); + } + } + }); + }; + }); +define("rsvp/instrument", + ["./config","./utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var config = __dependency1__.config; + var now = __dependency2__.now; + + __exports__["default"] = function instrument(eventName, promise, child) { + // instrumentation should not disrupt normal usage. + try { + config.trigger(eventName, { + guid: promise._guidKey + promise._id, + eventName: eventName, + detail: promise._detail, + childGuid: child && promise._guidKey + child._id, + label: promise._label, + timeStamp: now(), + stack: new Error(promise._label).stack + }); + } catch(error) { + setTimeout(function(){ + throw error; + }, 0); + } + }; + }); +define("rsvp/map", + ["./promise","./all","./utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var Promise = __dependency1__["default"]; + var all = __dependency2__["default"]; + var isArray = __dependency3__.isArray; + var isFunction = __dependency3__.isFunction; + + /** + `RSVP.map` is similar to JavaScript's native `map` method, except that it + waits for all promises to become fulfilled before running the `mapFn` on + each item in given to `promises`. `RSVP.map` returns a promise that will + become fulfilled with the result of running `mapFn` on the values the promises + become fulfilled with. + + For example: + + ```javascript + + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.resolve(2); + var promise3 = RSVP.resolve(3); + var promises = [ promise1, promise2, promise3 ]; + + var mapFn = function(item){ + return item + 1; + }; + + RSVP.map(promises, mapFn).then(function(result){ + // result is [ 2, 3, 4 ] + }); + ``` + + If any of the `promises` given to `RSVP.map` are rejected, the first promise + that is rejected will be given as an argument to the returned promise's + rejection handler. For example: + + ```javascript + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.reject(new Error("2")); + var promise3 = RSVP.reject(new Error("3")); + var promises = [ promise1, promise2, promise3 ]; + + var mapFn = function(item){ + return item + 1; + }; + + RSVP.map(promises, mapFn).then(function(array){ + // Code here never runs because there are rejected promises! + }, function(reason) { + // reason.message === "2" + }); + ``` + + `RSVP.map` will also wait if a promise is returned from `mapFn`. For example, + say you want to get all comments from a set of blog posts, but you need + the blog posts first becuase they contain a url to those comments. + + ```javscript + + var mapFn = function(blogPost){ + // getComments does some ajax and returns an RSVP.Promise that is fulfilled + // with some comments data + return getComments(blogPost.comments_url); + }; + + // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled + // with some blog post data + RSVP.map(getBlogPosts(), mapFn).then(function(comments){ + // comments is the result of asking the server for the comments + // of all blog posts returned from getBlogPosts() + }); + ``` + + @method map + @for RSVP + @param {Array} promises + @param {Function} mapFn function to be called on each fulfilled promise. + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled with the result of calling + `mapFn` on each fulfilled promise or value when they become fulfilled. + The promise will be rejected if any of the given `promises` become rejected. + @static + */ + __exports__["default"] = function map(promises, mapFn, label) { + return all(promises, label).then(function(results){ + if (!isArray(promises)) { + throw new TypeError('You must pass an array to map.'); + } + + if (!isFunction(mapFn)){ + throw new TypeError("You must pass a function to map's second argument."); + } + + + var resultLen = results.length, + mappedResults = [], + i; + + for (i = 0; i < resultLen; i++){ + mappedResults.push(mapFn(results[i])); + } + + return all(mappedResults, label); + }); + }; + }); +define("rsvp/node", + ["./promise","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Promise = __dependency1__["default"]; + + var slice = Array.prototype.slice; function makeNodeCallbackFor(resolve, reject) { return function (error, value) { if (error) { reject(error); } else if (arguments.length > 2) { - resolve(Array.prototype.slice.call(arguments, 1)); + resolve(slice.call(arguments, 1)); } else { resolve(value); } }; } - function denodeify(nodeFunc) { + /** + `RSVP.denodeify` takes a "node-style" function and returns a function that + will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the + browser when you'd prefer to use promises over using callbacks. For example, + `denodeify` transforms the following: + + ```javascript + var fs = require('fs'); + + fs.readFile('myfile.txt', function(err, data){ + if (err) return handleError(err); + handleData(data); + }); + ``` + + into: + + ```javascript + var fs = require('fs'); + + var readFile = RSVP.denodeify(fs.readFile); + + readFile('myfile.txt').then(handleData, handleError); + ``` + + Using `denodeify` makes it easier to compose asynchronous operations instead + of using callbacks. For example, instead of: + + ```javascript + var fs = require('fs'); + var log = require('some-async-logger'); + + fs.readFile('myfile.txt', function(err, data){ + if (err) return handleError(err); + fs.writeFile('myfile2.txt', data, function(err){ + if (err) throw err; + log('success', function(err) { + if (err) throw err; + }); + }); + }); + ``` + + You can chain the operations together using `then` from the returned promise: + + ```javascript + var fs = require('fs'); + var denodeify = RSVP.denodeify; + var readFile = denodeify(fs.readFile); + var writeFile = denodeify(fs.writeFile); + var log = denodeify(require('some-async-logger')); + + readFile('myfile.txt').then(function(data){ + return writeFile('myfile2.txt', data); + }).then(function(){ + return log('SUCCESS'); + }).then(function(){ + // success handler + }, function(reason){ + // rejection handler + }); + ``` + + @method denodeify + @for RSVP + @param {Function} nodeFunc a "node-style" function that takes a callback as + its last argument. The callback expects an error to be passed as its first + argument (if an error occurred, otherwise null), and the value from the + operation as its second argument ("function(err, value){ }"). + @param {Any} binding optional argument for binding the "this" value when + calling the `nodeFunc` function. + @return {Function} a function that wraps `nodeFunc` to return an + `RSVP.Promise` + @static + */ + __exports__["default"] = function denodeify(nodeFunc, binding) { return function() { - var nodeArgs = Array.prototype.slice.call(arguments), resolve, reject; - var thisArg = this; + var nodeArgs = slice.call(arguments), resolve, reject; + var thisArg = this || binding; - var promise = new Promise(function(nodeResolve, nodeReject) { - resolve = nodeResolve; - reject = nodeReject; + return new Promise(function(resolve, reject) { + Promise.all(nodeArgs).then(function(nodeArgs) { + try { + nodeArgs.push(makeNodeCallbackFor(resolve, reject)); + nodeFunc.apply(thisArg, nodeArgs); + } catch(e) { + reject(e); + } + }); }); - - all(nodeArgs).then(function(nodeArgs) { - nodeArgs.push(makeNodeCallbackFor(resolve, reject)); - - try { - nodeFunc.apply(thisArg, nodeArgs); - } catch(e) { - reject(e); - } - }); - - return promise; }; - } - - - __exports__.denodeify = denodeify; + }; }); -define("rsvp/promise", - ["rsvp/config","rsvp/events","exports"], - function(__dependency1__, __dependency2__, __exports__) { +define("rsvp/promise", + ["./config","./events","./instrument","./utils","./promise/cast","./promise/all","./promise/race","./promise/resolve","./promise/reject","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { "use strict"; var config = __dependency1__.config; - var EventTarget = __dependency2__.EventTarget; + var EventTarget = __dependency2__["default"]; + var instrument = __dependency3__["default"]; + var objectOrFunction = __dependency4__.objectOrFunction; + var isFunction = __dependency4__.isFunction; + var now = __dependency4__.now; + var cast = __dependency5__["default"]; + var all = __dependency6__["default"]; + var race = __dependency7__["default"]; + var Resolve = __dependency8__["default"]; + var Reject = __dependency9__["default"]; - function objectOrFunction(x) { - return isFunction(x) || (typeof x === "object" && x !== null); - } + var guidKey = 'rsvp_' + now() + '-'; + var counter = 0; - function isFunction(x){ - return typeof x === "function"; - } + function noop() {} - var Promise = function(resolver) { - var promise = this, - resolved = false; + __exports__["default"] = Promise; - if (typeof resolver !== 'function') { - throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor'); + + /** + Promise objects represent the eventual result of an asynchronous operation. The + primary way of interacting with a promise is through its `then` method, which + registers callbacks to receive either a promise’s eventual value or the reason + why the promise cannot be fulfilled. + + Terminology + ----------- + + - `promise` is an object or function with a `then` method whose behavior conforms to this specification. + - `thenable` is an object or function that defines a `then` method. + - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). + - `exception` is a value that is thrown using the throw statement. + - `reason` is a value that indicates why a promise was rejected. + - `settled` the final resting state of a promise, fulfilled or rejected. + + A promise can be in one of three states: pending, fulfilled, or rejected. + + Promises that are fulfilled have a fulfillment value and are in the fulfilled + state. Promises that are rejected have a rejection reason and are in the + rejected state. A fulfillment value is never a thenable. Similarly, a + rejection reason is never a thenable. + + Promises can also be said to *resolve* a value. If this value is also a + promise, then the original promise's settled state will match the value's + settled state. So a promise that *resolves* a promise that rejects will + itself reject, and a promise that *resolves* a promise that fulfills will + itself fulfill. + + + Basic Usage: + ------------ + + ```js + var promise = new Promise(function(resolve, reject) { + // on success + resolve(value); + + // on failure + reject(reason); + }); + + promise.then(function(value) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Advanced Usage: + --------------- + + Promises shine when abstracting away asynchronous interactions such as + `XMLHttpRequest`s. + + ```js + function getJSON(url) { + return new Promise(function(resolve, reject){ + var xhr = new XMLHttpRequest(); + + xhr.open('GET', url); + xhr.onreadystatechange = handler; + xhr.responseType = 'json'; + xhr.setRequestHeader('Accept', 'application/json'); + xhr.send(); + + function handler() { + if (this.readyState === this.DONE) { + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error("getJSON: `" + url + "` failed with status: [" + this.status + "]"); + } + } + }; + }); } - if (!(promise instanceof Promise)) { - return new Promise(resolver); + getJSON('/posts.json').then(function(json) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Unlike callbacks, promises are great composable primitives. + + ```js + Promise.all([ + getJSON('/posts'), + getJSON('/comments') + ]).then(function(values){ + values[0] // => postsJSON + values[1] // => commentsJSON + + return values; + }); + ``` + + @class RSVP.Promise + @param {function} + @param {String} label optional string for labeling the promise. + Useful for tooling. + @constructor + */ + function Promise(resolver, label) { + if (!isFunction(resolver)) { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); } - var resolvePromise = function(value) { - if (resolved) { return; } - resolved = true; + if (!(this instanceof Promise)) { + throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); + } + + this._id = counter++; + this._label = label; + this._subscribers = []; + + if (config.instrument) { + instrument('created', this); + } + + if (noop !== resolver) { + invokeResolver(resolver, this); + } + } + + function invokeResolver(resolver, promise) { + function resolvePromise(value) { resolve(promise, value); - }; + } - var rejectPromise = function(value) { - if (resolved) { return; } - resolved = true; - reject(promise, value); - }; - - this.on('promise:resolved', function(event) { - this.trigger('success', { detail: event.detail }); - }, this); - - this.on('promise:failed', function(event) { - this.trigger('error', { detail: event.detail }); - }, this); - - this.on('error', onerror); + function rejectPromise(reason) { + reject(promise, reason); + } try { resolver(resolvePromise, rejectPromise); } catch(e) { rejectPromise(e); } - }; - - function onerror(event) { - if (config.onerror) { - config.onerror(event.detail); - } } - var invokeCallback = function(type, promise, callback, event) { + Promise.cast = cast; + Promise.all = all; + Promise.race = race; + Promise.resolve = Resolve; + Promise.reject = Reject; + + var PENDING = void 0; + var SEALED = 0; + var FULFILLED = 1; + var REJECTED = 2; + + function subscribe(parent, child, onFulfillment, onRejection) { + var subscribers = parent._subscribers; + var length = subscribers.length; + + subscribers[length] = child; + subscribers[length + FULFILLED] = onFulfillment; + subscribers[length + REJECTED] = onRejection; + } + + function publish(promise, settled) { + var child, callback, subscribers = promise._subscribers, detail = promise._detail; + + if (config.instrument) { + instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise); + } + + for (var i = 0; i < subscribers.length; i += 3) { + child = subscribers[i]; + callback = subscribers[i + settled]; + + invokeCallback(settled, child, callback, detail); + } + + promise._subscribers = null; + } + + Promise.prototype = { + constructor: Promise, + + _id: undefined, + _guidKey: guidKey, + _label: undefined, + + _state: undefined, + _detail: undefined, + _subscribers: undefined, + + _onerror: function (reason) { + config.trigger('error', reason); + }, + + /** + The primary way of interacting with a promise is through its `then` method, + which registers callbacks to receive either a promise's eventual value or the + reason why the promise cannot be fulfilled. + + ```js + findUser().then(function(user){ + // user is available + }, function(reason){ + // user is unavailable, and you are given the reason why + }); + ``` + + Chaining + -------- + + The return value of `then` is itself a promise. This second, "downstream" + promise is resolved with the return value of the first promise's fulfillment + or rejection handler, or rejected if the handler throws an exception. + + ```js + findUser().then(function (user) { + return user.name; + }, function (reason) { + return "default name"; + }).then(function (userName) { + // If `findUser` fulfilled, `userName` will be the user's name, otherwise it + // will be `"default name"` + }); + + findUser().then(function (user) { + throw new Error("Found user, but still unhappy"); + }, function (reason) { + throw new Error("`findUser` rejected and we're unhappy"); + }).then(function (value) { + // never reached + }, function (reason) { + // if `findUser` fulfilled, `reason` will be "Found user, but still unhappy". + // If `findUser` rejected, `reason` will be "`findUser` rejected and we're unhappy". + }); + ``` + If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. + + ```js + findUser().then(function (user) { + throw new PedagogicalException("Upstream error"); + }).then(function (value) { + // never reached + }).then(function (value) { + // never reached + }, function (reason) { + // The `PedgagocialException` is propagated all the way down to here + }); + ``` + + Assimilation + ------------ + + Sometimes the value you want to propagate to a downstream promise can only be + retrieved asynchronously. This can be achieved by returning a promise in the + fulfillment or rejection handler. The downstream promise will then be pending + until the returned promise is settled. This is called *assimilation*. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // The user's comments are now available + }); + ``` + + If the assimliated promise rejects, then the downstream promise will also reject. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // If `findCommentsByAuthor` fulfills, we'll have the value here + }, function (reason) { + // If `findCommentsByAuthor` rejects, we'll have the reason here + }); + ``` + + Simple Example + -------------- + + Synchronous Example + + ```javascript + var result; + + try { + result = findResult(); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + findResult(function(result, err){ + if (err) { + // failure + } else { + // success + } + }); + ``` + + Promise Example; + + ```javascript + findResult().then(function(result){ + // success + }, function(reason){ + // failure + }); + ``` + + Advanced Example + -------------- + + Synchronous Example + + ```javascript + var author, books; + + try { + author = findAuthor(); + books = findBooksByAuthor(author); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + + function foundBooks(books) { + + } + + function failure(reason) { + + } + + findAuthor(function(author, err){ + if (err) { + failure(err); + // failure + } else { + try { + findBoooksByAuthor(author, function(books, err) { + if (err) { + failure(err); + } else { + try { + foundBooks(books); + } catch(reason) { + failure(reason); + } + } + }); + } catch(error) { + failure(err); + } + // success + } + }); + ``` + + Promise Example; + + ```javascript + findAuthor(). + then(findBooksByAuthor). + then(function(books){ + // found books + }).catch(function(reason){ + // something went wrong + }); + ``` + + @method then + @param {Function} onFulfilled + @param {Function} onRejected + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} + */ + then: function(onFulfillment, onRejection, label) { + var promise = this; + this._onerror = null; + + var thenPromise = new this.constructor(noop, label); + + if (this._state) { + var callbacks = arguments; + config.async(function invokePromiseCallback() { + invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail); + }); + } else { + subscribe(this, thenPromise, onFulfillment, onRejection); + } + + if (config.instrument) { + instrument('chained', promise, thenPromise); + } + + return thenPromise; + }, + + /** + `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same + as the catch block of a try/catch statement. + + ```js + function findAuthor(){ + throw new Error("couldn't find that author"); + } + + // synchronous + try { + findAuthor(); + } catch(reason) { + // something went wrong + } + + // async with promises + findAuthor().catch(function(reason){ + // something went wrong + }); + ``` + + @method catch + @param {Function} onRejection + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} + */ + 'catch': function(onRejection, label) { + return this.then(null, onRejection, label); + }, + + /** + `finally` will be invoked regardless of the promise's fate just as native + try/catch/finally behaves + + Synchronous example: + + ```js + findAuthor() { + if (Math.random() > 0.5) { + throw new Error(); + } + return new Author(); + } + + try { + return findAuthor(); // succeed or fail + } catch(error) { + return findOtherAuther(); + } finally { + // always runs + // doesn't affect the return value + } + ``` + + Asynchronous example: + + ```js + findAuthor().catch(function(reason){ + return findOtherAuther(); + }).finally(function(){ + // author was either found, or not + }); + ``` + + @method finally + @param {Function} callback + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} + */ + 'finally': function(callback, label) { + var constructor = this.constructor; + + return this.then(function(value) { + return constructor.cast(callback()).then(function(){ + return value; + }); + }, function(reason) { + return constructor.cast(callback()).then(function(){ + throw reason; + }); + }, label); + } + }; + + function invokeCallback(settled, promise, callback, detail) { var hasCallback = isFunction(callback), value, error, succeeded, failed; if (hasCallback) { try { - value = callback(event.detail); + value = callback(detail); succeeded = true; } catch(e) { failed = true; error = e; } } else { - value = event.detail; + value = detail; succeeded = true; } @@ -8283,62 +10392,11 @@ define("rsvp/promise", resolve(promise, value); } else if (failed) { reject(promise, error); - } else if (type === 'resolve') { + } else if (settled === FULFILLED) { resolve(promise, value); - } else if (type === 'reject') { + } else if (settled === REJECTED) { reject(promise, value); } - }; - - Promise.prototype = { - constructor: Promise, - - isRejected: undefined, - isFulfilled: undefined, - rejectedReason: undefined, - fulfillmentValue: undefined, - - then: function(done, fail) { - this.off('error', onerror); - - var thenPromise = new this.constructor(function() {}); - - if (this.isFulfilled) { - config.async(function(promise) { - invokeCallback('resolve', thenPromise, done, { detail: promise.fulfillmentValue }); - }, this); - } - - if (this.isRejected) { - config.async(function(promise) { - invokeCallback('reject', thenPromise, fail, { detail: promise.rejectedReason }); - }, this); - } - - this.on('promise:resolved', function(event) { - invokeCallback('resolve', thenPromise, done, event); - }); - - this.on('promise:failed', function(event) { - invokeCallback('reject', thenPromise, fail, event); - }); - - return thenPromise; - }, - - fail: function(fail) { - return this.then(null, fail); - } - }; - - EventTarget.mixin(Promise.prototype); - - function resolve(promise, value) { - if (promise === value) { - fulfill(promise, value); - } else if (!handleThenable(promise, value)) { - fulfill(promise, value); - } } function handleThenable(promise, value) { @@ -8368,12 +10426,13 @@ define("rsvp/promise", resolved = true; reject(promise, val); - }); + }, 'derived from: ' + (promise._label || ' unknown promise')); return true; } } } catch (error) { + if (resolved) { return true; } reject(promise, error); return true; } @@ -8381,230 +10440,640 @@ define("rsvp/promise", return false; } + function resolve(promise, value) { + if (promise === value) { + fulfill(promise, value); + } else if (!handleThenable(promise, value)) { + fulfill(promise, value); + } + } + function fulfill(promise, value) { - config.async(function() { - promise.trigger('promise:resolved', { detail: value }); - promise.isFulfilled = true; - promise.fulfillmentValue = value; - }); + if (promise._state !== PENDING) { return; } + promise._state = SEALED; + promise._detail = value; + + config.async(publishFulfillment, promise); } - function reject(promise, value) { - config.async(function() { - promise.trigger('promise:failed', { detail: value }); - promise.isRejected = true; - promise.rejectedReason = value; - }); + function reject(promise, reason) { + if (promise._state !== PENDING) { return; } + promise._state = SEALED; + promise._detail = reason; + + config.async(publishRejection, promise); } + function publishFulfillment(promise) { + publish(promise, promise._state = FULFILLED); + } - __exports__.Promise = Promise; + function publishRejection(promise) { + if (promise._onerror) { + promise._onerror(promise._detail); + } + + publish(promise, promise._state = REJECTED); + } }); -define("rsvp/reject", - ["rsvp/promise","exports"], +define("rsvp/promise/all", + ["../utils","exports"], function(__dependency1__, __exports__) { "use strict"; - var Promise = __dependency1__.Promise; + var isArray = __dependency1__.isArray; + var isNonThenable = __dependency1__.isNonThenable; - function reject(reason) { - return new Promise(function (resolve, reject) { - reject(reason); + /** + `RSVP.Promise.all` accepts an array of promises, and returns a new promise which + is fulfilled with an array of fulfillment values for the passed promises, or + rejected with the reason of the first passed promise to be rejected. It casts all + elements of the passed iterable to promises as it runs this algorithm. + + Example: + + ```javascript + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.resolve(2); + var promise3 = RSVP.resolve(3); + var promises = [ promise1, promise2, promise3 ]; + + RSVP.Promise.all(promises).then(function(array){ + // The array here would be [ 1, 2, 3 ]; }); - } + ``` + If any of the `promises` given to `RSVP.all` are rejected, the first promise + that is rejected will be given as an argument to the returned promises's + rejection handler. For example: - __exports__.reject = reject; - }); -define("rsvp/resolve", - ["rsvp/promise","exports"], - function(__dependency1__, __exports__) { - "use strict"; - var Promise = __dependency1__.Promise; + Example: - function resolve(thenable) { - return new Promise(function(resolve, reject) { - resolve(thenable); + ```javascript + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.reject(new Error("2")); + var promise3 = RSVP.reject(new Error("3")); + var promises = [ promise1, promise2, promise3 ]; + + RSVP.Promise.all(promises).then(function(array){ + // Code here never runs because there are rejected promises! + }, function(error) { + // error.message === "2" }); - } + ``` + @method all + @for Ember.RSVP.Promise + @param {Array} entries array of promises + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled when all `promises` have been + fulfilled, or rejected if any of them become rejected. + @static + */ + __exports__["default"] = function all(entries, label) { - __exports__.resolve = resolve; + /*jshint validthis:true */ + var Constructor = this; + + return new Constructor(function(resolve, reject) { + if (!isArray(entries)) { + throw new TypeError('You must pass an array to all.'); + } + + var remaining = entries.length; + var results = new Array(remaining); + var entry, pending = true; + + if (remaining === 0) { + resolve(results); + return; + } + + function fulfillmentAt(index) { + return function(value) { + results[index] = value; + if (--remaining === 0) { + resolve(results); + } + }; + } + + function onRejection(reason) { + remaining = 0; + reject(reason); + } + + for (var index = 0; index < entries.length; index++) { + entry = entries[index]; + if (isNonThenable(entry)) { + results[index] = entry; + if (--remaining === 0) { + resolve(results); + } + } else { + Constructor.cast(entry).then(fulfillmentAt(index), onRejection); + } + } + }, label); + }; }); -define("rsvp/rethrow", +define("rsvp/promise/cast", ["exports"], function(__exports__) { "use strict"; - var local = (typeof global === "undefined") ? this : global; + /** + `RSVP.Promise.cast` coerces its argument to a promise, or returns the + argument if it is already a promise which shares a constructor with the caster. - function rethrow(reason) { - local.setTimeout(function() { + Example: + + ```javascript + var promise = RSVP.Promise.resolve(1); + var casted = RSVP.Promise.cast(promise); + + console.log(promise === casted); // true + ``` + + In the case of a promise whose constructor does not match, it is assimilated. + The resulting promise will fulfill or reject based on the outcome of the + promise being casted. + + Example: + + ```javascript + var thennable = $.getJSON('/api/foo'); + var casted = RSVP.Promise.cast(thennable); + + console.log(thennable === casted); // false + console.log(casted instanceof RSVP.Promise) // true + + casted.then(function(data) { + // data is the value getJSON fulfills with + }); + ``` + + In the case of a non-promise, a promise which will fulfill with that value is + returned. + + Example: + + ```javascript + var value = 1; // could be a number, boolean, string, undefined... + var casted = RSVP.Promise.cast(value); + + console.log(value === casted); // false + console.log(casted instanceof RSVP.Promise) // true + + casted.then(function(val) { + val === value // => true + }); + ``` + + `RSVP.Promise.cast` is similar to `RSVP.Promise.resolve`, but `RSVP.Promise.cast` differs in the + following ways: + + * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you + have something that could either be a promise or a value. RSVP.resolve + will have the same effect but will create a new promise wrapper if the + argument is a promise. + * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to + promises of the exact class specified, so that the resulting object's `then` is + ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise). + + @method cast + @param {Object} object to be casted + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} promise + @static + */ + + __exports__["default"] = function cast(object, label) { + /*jshint validthis:true */ + var Constructor = this; + + if (object && typeof object === 'object' && object.constructor === Constructor) { + return object; + } + + return new Constructor(function(resolve) { + resolve(object); + }, label); + }; + }); +define("rsvp/promise/race", + ["../utils","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /* global toString */ + + var isArray = __dependency1__.isArray; + var isFunction = __dependency1__.isFunction; + var isNonThenable = __dependency1__.isNonThenable; + + /** + `RSVP.Promise.race` returns a new promise which is settled in the same way as the + first passed promise to settle. + + Example: + + ```javascript + var promise1 = new RSVP.Promise(function(resolve, reject){ + setTimeout(function(){ + resolve("promise 1"); + }, 200); + }); + + var promise2 = new RSVP.Promise(function(resolve, reject){ + setTimeout(function(){ + resolve("promise 2"); + }, 100); + }); + + RSVP.Promise.race([promise1, promise2]).then(function(result){ + // result === "promise 2" because it was resolved before promise1 + // was resolved. + }); + ``` + + `RSVP.Promise.race` is deterministic in that only the state of the first + settled promise matters. For example, even if other promises given to the + `promises` array argument are resolved, but the first settled promise has + become rejected before the other promises became fulfilled, the returned + promise will become rejected: + + ```javascript + var promise1 = new RSVP.Promise(function(resolve, reject){ + setTimeout(function(){ + resolve("promise 1"); + }, 200); + }); + + var promise2 = new RSVP.Promise(function(resolve, reject){ + setTimeout(function(){ + reject(new Error("promise 2")); + }, 100); + }); + + RSVP.Promise.race([promise1, promise2]).then(function(result){ + // Code here never runs + }, function(reason){ + // reason.message === "promise2" because promise 2 became rejected before + // promise 1 became fulfilled + }); + ``` + + An example real-world use case is implementing timeouts: + + ```javascript + RSVP.Promise.race([ajax('foo.json'), timeout(5000)]) + ``` + + @method race + @param {Array} promises array of promises to observe + @param {String} label optional string for describing the promise returned. + Useful for tooling. + @return {Promise} a promise which settles in the same way as the first passed + promise to settle. + @static + */ + __exports__["default"] = function race(entries, label) { + /*jshint validthis:true */ + var Constructor = this, entry; + + return new Constructor(function(resolve, reject) { + if (!isArray(entries)) { + throw new TypeError('You must pass an array to race.'); + } + + var pending = true; + + function onFulfillment(value) { if (pending) { pending = false; resolve(value); } } + function onRejection(reason) { if (pending) { pending = false; reject(reason); } } + + for (var i = 0; i < entries.length; i++) { + entry = entries[i]; + if (isNonThenable(entry)) { + pending = false; + resolve(entry); + return; + } else { + Constructor.cast(entry).then(onFulfillment, onRejection); + } + } + }, label); + }; + }); +define("rsvp/promise/reject", + ["exports"], + function(__exports__) { + "use strict"; + /** + `RSVP.Promise.reject` returns a promise rejected with the passed `reason`. + It is shorthand for the following: + + ```javascript + var promise = new RSVP.Promise(function(resolve, reject){ + reject(new Error('WHOOPS')); + }); + + promise.then(function(value){ + // Code here doesn't run because the promise is rejected! + }, function(reason){ + // reason.message === 'WHOOPS' + }); + ``` + + Instead of writing the above, your code now simply becomes the following: + + ```javascript + var promise = RSVP.Promise.reject(new Error('WHOOPS')); + + promise.then(function(value){ + // Code here doesn't run because the promise is rejected! + }, function(reason){ + // reason.message === 'WHOOPS' + }); + ``` + + @method reject + @param {Any} reason value that the returned promise will be rejected with. + @param {String} label optional string for identifying the returned promise. + Useful for tooling. + @return {Promise} a promise rejected with the given `reason`. + @static + */ + __exports__["default"] = function reject(reason, label) { + /*jshint validthis:true */ + var Constructor = this; + + return new Constructor(function (resolve, reject) { + reject(reason); + }, label); + }; + }); +define("rsvp/promise/resolve", + ["exports"], + function(__exports__) { + "use strict"; + /** + `RSVP.Promise.resolve` returns a promise that will become resolved with the + passed `value`. It is shorthand for the following: + + ```javascript + var promise = new RSVP.Promise(function(resolve, reject){ + resolve(1); + }); + + promise.then(function(value){ + // value === 1 + }); + ``` + + Instead of writing the above, your code now simply becomes the following: + + ```javascript + var promise = RSVP.Promise.resolve(1); + + promise.then(function(value){ + // value === 1 + }); + ``` + + @method resolve + @param {Any} value value that the returned promise will be resolved with + @param {String} label optional string for identifying the returned promise. + Useful for tooling. + @return {Promise} a promise that will become fulfilled with the given + `value` + @static + */ + __exports__["default"] = function resolve(value, label) { + /*jshint validthis:true */ + var Constructor = this; + + return new Constructor(function(resolve, reject) { + resolve(value); + }, label); + }; + }); +define("rsvp/race", + ["./promise","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Promise = __dependency1__["default"]; + + /** + This is a convenient alias for `RSVP.Promise.race`. + + @method race + @param {Array} array Array of promises. + @param {String} label An optional label. This is useful + for tooling. + @static + */ + __exports__["default"] = function race(array, label) { + return Promise.race(array, label); + }; + }); +define("rsvp/reject", + ["./promise","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Promise = __dependency1__["default"]; + + /** + This is a convenient alias for `RSVP.Promise.reject`. + + @method reject + @for RSVP + @param {Any} reason value that the returned promise will be rejected with. + @param {String} label optional string for identifying the returned promise. + Useful for tooling. + @return {Promise} a promise rejected with the given `reason`. + @static + */ + __exports__["default"] = function reject(reason, label) { + return Promise.reject(reason, label); + }; + }); +define("rsvp/resolve", + ["./promise","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Promise = __dependency1__["default"]; + + /** + This is a convenient alias for `RSVP.Promise.resolve`. + + @method resolve + @for RSVP + @param {Any} value value that the returned promise will be resolved with + @param {String} label optional string for identifying the returned promise. + Useful for tooling. + @return {Promise} a promise that will become fulfilled with the given + `value` + @static + */ + __exports__["default"] = function resolve(value, label) { + return Promise.resolve(value, label); + }; + }); +define("rsvp/rethrow", + ["exports"], + function(__exports__) { + "use strict"; + /** + `RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event + loop in order to aid debugging. + + Promises A+ specifies that any exceptions that occur with a promise must be + caught by the promises implementation and bubbled to the last handler. For + this reason, it is recommended that you always specify a second rejection + handler function to `then`. However, `RSVP.rethrow` will throw the exception + outside of the promise, so it bubbles up to your console if in the browser, + or domain/cause uncaught exception in Node. `rethrow` will also throw the + error again so the error can be handled by the promise per the spec. + + ```javascript + function throws(){ + throw new Error('Whoops!'); + } + + var promise = new RSVP.Promise(function(resolve, reject){ + throws(); + }); + + promise.catch(RSVP.rethrow).then(function(){ + // Code here doesn't run because the promise became rejected due to an + // error! + }, function (err){ + // handle the error here + }); + ``` + + The 'Whoops' error will be thrown on the next turn of the event loop + and you can watch for it in your console. You can also handle it using a + rejection handler given to `.then` or `.catch` on the returned promise. + + @method rethrow + @for RSVP + @param {Error} reason reason the promise became rejected. + @throws Error + @static + */ + __exports__["default"] = function rethrow(reason) { + setTimeout(function() { throw reason; }); throw reason; - } - - - __exports__.rethrow = rethrow; + }; }); -define("rsvp", - ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/rethrow","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { +define("rsvp/utils", + ["exports"], + function(__exports__) { "use strict"; - var EventTarget = __dependency1__.EventTarget; - var Promise = __dependency2__.Promise; - var denodeify = __dependency3__.denodeify; - var all = __dependency4__.all; - var hash = __dependency5__.hash; - var rethrow = __dependency6__.rethrow; - var defer = __dependency7__.defer; - var config = __dependency8__.config; - var resolve = __dependency9__.resolve; - var reject = __dependency10__.reject; - - function configure(name, value) { - config[name] = value; + function objectOrFunction(x) { + return typeof x === "function" || (typeof x === "object" && x !== null); } + __exports__.objectOrFunction = objectOrFunction;function isFunction(x) { + return typeof x === "function"; + } + + __exports__.isFunction = isFunction;function isNonThenable(x) { + return !objectOrFunction(x); + } + + __exports__.isNonThenable = isNonThenable;function isArray(x) { + return Object.prototype.toString.call(x) === "[object Array]"; + } + + __exports__.isArray = isArray;// Date.now is not available in browsers < IE9 + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility + var now = Date.now || function() { return new Date().getTime(); }; + __exports__.now = now; + var keysOf = Object.keys || function(object) { + var result = []; + + for (var prop in object) { + result.push(prop); + } + + return result; + }; + __exports__.keysOf = keysOf; + }); +define("rsvp", + ["./rsvp/promise","./rsvp/events","./rsvp/node","./rsvp/all","./rsvp/all_settled","./rsvp/race","./rsvp/hash","./rsvp/rethrow","./rsvp/defer","./rsvp/config","./rsvp/map","./rsvp/resolve","./rsvp/reject","./rsvp/filter","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __exports__) { + "use strict"; + var Promise = __dependency1__["default"]; + var EventTarget = __dependency2__["default"]; + var denodeify = __dependency3__["default"]; + var all = __dependency4__["default"]; + var allSettled = __dependency5__["default"]; + var race = __dependency6__["default"]; + var hash = __dependency7__["default"]; + var rethrow = __dependency8__["default"]; + var defer = __dependency9__["default"]; + var config = __dependency10__.config; + var configure = __dependency10__.configure; + var map = __dependency11__["default"]; + var resolve = __dependency12__["default"]; + var reject = __dependency13__["default"]; + var filter = __dependency14__["default"]; + + function async(callback, arg) { + config.async(callback, arg); + } + + function on() { + config.on.apply(config, arguments); + } + + function off() { + config.off.apply(config, arguments); + } + + // Set up instrumentation through `window.__PROMISE_INTRUMENTATION__` + if (typeof window !== 'undefined' && typeof window.__PROMISE_INSTRUMENTATION__ === 'object') { + var callbacks = window.__PROMISE_INSTRUMENTATION__; + configure('instrument', true); + for (var eventName in callbacks) { + if (callbacks.hasOwnProperty(eventName)) { + on(eventName, callbacks[eventName]); + } + } + } __exports__.Promise = Promise; __exports__.EventTarget = EventTarget; __exports__.all = all; + __exports__.allSettled = allSettled; + __exports__.race = race; __exports__.hash = hash; __exports__.rethrow = rethrow; __exports__.defer = defer; __exports__.denodeify = denodeify; __exports__.configure = configure; + __exports__.on = on; + __exports__.off = off; __exports__.resolve = resolve; __exports__.reject = reject; + __exports__.async = async; + __exports__.map = map; + __exports__.filter = filter; }); + })(); (function() { -/** -@private -Public api for the container is still in flux. -The public api, specified on the application namespace should be considered the stable api. -// @module container -*/ - -/* - Flag to enable/disable model factory injections (disabled by default) - If model factory injections are enabled, models should not be - accessed globally (only through `container.lookupFactory('model:modelName'))`); -*/ -Ember.MODEL_FACTORY_INJECTIONS = false || !!Ember.ENV.MODEL_FACTORY_INJECTIONS; - -define("container", - [], - function() { - - // A safe and simple inheriting object. - function InheritingDict(parent) { - this.parent = parent; - this.dict = {}; - } - - InheritingDict.prototype = { - - /** - @property parent - @type InheritingDict - @default null - */ - - parent: null, - - /** - Object used to store the current nodes data. - - @property dict - @type Object - @default Object - */ - dict: null, - - /** - Retrieve the value given a key, if the value is present at the current - level use it, otherwise walk up the parent hierarchy and try again. If - no matching key is found, return undefined. - - @method get - @return {any} - */ - get: function(key) { - var dict = this.dict; - - if (dict.hasOwnProperty(key)) { - return dict[key]; - } - - if (this.parent) { - return this.parent.get(key); - } - }, - - /** - Set the given value for the given key, at the current level. - - @method set - @param {String} key - @param {Any} value - */ - set: function(key, value) { - this.dict[key] = value; - }, - - /** - Delete the given key - - @method remove - @param {String} key - */ - remove: function(key) { - delete this.dict[key]; - }, - - /** - Check for the existence of given a key, if the key is present at the current - level return true, otherwise walk up the parent hierarchy and try again. If - no matching key is found, return false. - - @method has - @param {String} key - @return {Boolean} - */ - has: function(key) { - var dict = this.dict; - - if (dict.hasOwnProperty(key)) { - return true; - } - - if (this.parent) { - return this.parent.has(key); - } - - return false; - }, - - /** - Iterate and invoke a callback for each local key-value pair. - - @method eachLocal - @param {Function} callback - @param {Object} binding - */ - eachLocal: function(callback, binding) { - var dict = this.dict; - - for (var prop in dict) { - if (dict.hasOwnProperty(prop)) { - callback.call(binding, prop, dict[prop]); - } - } - } - }; - +define("container/container", + ["container/inheriting_dict","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var InheritingDict = __dependency1__["default"]; // A lightweight container that helps to assemble and decouple components. // Public api for the container is still in flux. @@ -8617,7 +11086,8 @@ define("container", this.registry = new InheritingDict(parent && parent.registry); this.cache = new InheritingDict(parent && parent.cache); - this.factoryCache = new InheritingDict(parent && parent.cache); + this.factoryCache = new InheritingDict(parent && parent.factoryCache); + this.resolveCache = new InheritingDict(parent && parent.resolveCache); this.typeInjections = new InheritingDict(parent && parent.typeInjections); this.injections = {}; @@ -8738,9 +11208,7 @@ define("container", @param {Object} options */ register: function(fullName, factory, options) { - if (fullName.indexOf(':') === -1) { - throw new TypeError("malformed fullName, expected: `type:name` got: " + fullName + ""); - } + validateFullName(fullName); if (factory === undefined) { throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`'); @@ -8773,11 +11241,14 @@ define("container", @param {String} fullName */ unregister: function(fullName) { + validateFullName(fullName); + var normalizedName = this.normalize(fullName); this.registry.remove(normalizedName); this.cache.remove(normalizedName); this.factoryCache.remove(normalizedName); + this.resolveCache.remove(normalizedName); this._options.remove(normalizedName); }, @@ -8814,7 +11285,18 @@ define("container", @return {Function} fullName's factory */ resolve: function(fullName) { - return this.resolver(fullName) || this.registry.get(fullName); + validateFullName(fullName); + + var normalizedName = this.normalize(fullName); + var cached = this.resolveCache.get(normalizedName); + + if (cached) { return cached; } + + var resolved = this.resolver(normalizedName) || this.registry.get(normalizedName); + + this.resolveCache.set(normalizedName, resolved); + + return resolved; }, /** @@ -8826,6 +11308,8 @@ define("container", to find the `fullName`. @method describe + @param {String} fullName + @return {string} described fullName */ describe: function(fullName) { return fullName; @@ -8869,7 +11353,7 @@ define("container", twitter instanceof Twitter; // => true // by default the container will return singletons - twitter2 = container.lookup('api:twitter'); + var twitter2 = container.lookup('api:twitter'); twitter instanceof Twitter; // => true twitter === twitter2; //=> true @@ -8893,23 +11377,8 @@ define("container", @return {any} */ lookup: function(fullName, options) { - fullName = this.normalize(fullName); - - options = options || {}; - - if (this.cache.has(fullName) && options.singleton !== false) { - return this.cache.get(fullName); - } - - var value = instantiate(this, fullName); - - if (value === undefined) { return; } - - if (isSingleton(this, fullName) && options.singleton !== false) { - this.cache.set(fullName, value); - } - - return value; + validateFullName(fullName); + return lookup(this, this.normalize(fullName), options); }, /** @@ -8920,7 +11389,8 @@ define("container", @return {any} */ lookupFactory: function(fullName) { - return factoryFor(this, fullName); + validateFullName(fullName); + return factoryFor(this, this.normalize(fullName)); }, /** @@ -8932,11 +11402,8 @@ define("container", @return {Boolean} */ has: function(fullName) { - if (this.cache.has(fullName)) { - return true; - } - - return !!factoryFor(this, fullName); + validateFullName(fullName); + return has(this, this.normalize(fullName)); }, /** @@ -8982,8 +11449,6 @@ define("container", }, /** - @private - Used only via `injection`. Provides a specialized form of injection, specifically enabling @@ -9012,14 +11477,20 @@ define("container", user.router === post.router; //=> true ``` + @private @method typeInjection @param {String} type @param {String} property @param {String} fullName */ typeInjection: function(type, property, fullName) { + validateFullName(fullName); if (this.parent) { illegalChildOperation('typeInjection'); } + var fullNameType = fullName.split(':')[0]; + if(fullNameType === type) { + throw new Error('Cannot inject a `' + fullName + '` on other ' + type + '(s). Register the `' + fullName + '` as a different type and perform the typeInjection.'); + } addTypeInjection(this.typeInjections, type, property, fullName); }, @@ -9031,8 +11502,8 @@ define("container", Two forms of injections are possible: - * Injecting one fullName on another fullName - * Injecting one fullName on a type + * Injecting one fullName on another fullName + * Injecting one fullName on a type Example: @@ -9067,20 +11538,24 @@ define("container", @param {String} property @param {String} injectionName */ - injection: function(factoryName, property, injectionName) { + injection: function(fullName, property, injectionName) { if (this.parent) { illegalChildOperation('injection'); } - if (factoryName.indexOf(':') === -1) { - return this.typeInjection(factoryName, property, injectionName); + validateFullName(injectionName); + var normalizedInjectionName = this.normalize(injectionName); + + if (fullName.indexOf(':') === -1) { + return this.typeInjection(fullName, property, normalizedInjectionName); } - addInjection(this.injections, factoryName, property, injectionName); + validateFullName(fullName); + var normalizedName = this.normalize(fullName); + + addInjection(this.injections, normalizedName, property, normalizedInjectionName); }, /** - @private - Used only via `factoryInjection`. Provides a specialized form of injection, specifically enabling @@ -9093,7 +11568,6 @@ define("container", ```javascript var container = new Container(); - container.registerFactory('model:user', User); container.register('store:main', SomeStore); container.factoryTypeInjection('model', 'store', 'store:main'); @@ -9104,6 +11578,7 @@ define("container", UserFactory.store instanceof SomeStore; //=> true ``` + @private @method factoryTypeInjection @param {String} type @param {String} property @@ -9112,7 +11587,7 @@ define("container", factoryTypeInjection: function(type, property, fullName) { if (this.parent) { illegalChildOperation('factoryTypeInjection'); } - addTypeInjection(this.factoryTypeInjections, type, property, fullName); + addTypeInjection(this.factoryTypeInjections, type, property, this.normalize(fullName)); }, /** @@ -9164,14 +11639,21 @@ define("container", @param {String} property @param {String} injectionName */ - factoryInjection: function(factoryName, property, injectionName) { + factoryInjection: function(fullName, property, injectionName) { if (this.parent) { illegalChildOperation('injection'); } - if (factoryName.indexOf(':') === -1) { - return this.factoryTypeInjection(factoryName, property, injectionName); + var normalizedName = this.normalize(fullName); + var normalizedInjectionName = this.normalize(injectionName); + + validateFullName(injectionName); + + if (fullName.indexOf(':') === -1) { + return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName); } - addInjection(this.factoryInjections, factoryName, property, injectionName); + validateFullName(fullName); + + addInjection(this.factoryInjections, normalizedName, property, normalizedInjectionName); }, /** @@ -9181,8 +11663,6 @@ define("container", @method destroy */ destroy: function() { - this.isDestroyed = true; - for (var i=0, l=this.children.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); + __exports__["default"] = Container; + }); +define("container/inheriting_dict", + ["exports"], + function(__exports__) { + "use strict"; + // A safe and simple inheriting object. + function InheritingDict(parent) { + this.parent = parent; + this.dict = {}; } - if (type2 === 'instance' && Comparable.detect(w.constructor)) { - return 1-w.constructor.compare(w, v); - } - } + InheritingDict.prototype = { - // 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; - } + /** + @property parent + @type InheritingDict + @default null + */ - // We no longer need Ember.ORDER_DEFINITION. - delete Ember.ORDER_DEFINITION; - } + parent: null, - var type1Index = mapping[type1]; - var type2Index = mapping[type2]; + /** + Object used to store the current nodes data. - if (type1Index < type2Index) { return -1; } - if (type1Index > type2Index) { return 1; } + @property dict + @type Object + @default Object + */ + dict: null, - // 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; + /** + Retrieve the value given a key, if the value is present at the current + level use it, otherwise walk up the parent hierarchy and try again. If + no matching key is found, return undefined. - case 'string': - var comp = v.localeCompare(w); - if (comp < 0) { return -1; } - if (comp > 0) { return 1; } - return 0; + @method get + @param {String} key + @return {any} + */ + get: function(key) { + var dict = this.dict; - 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 (dict.hasOwnProperty(key)) { + return dict[key]; + } + + if (this.parent) { + return this.parent.get(key); + } + }, + + /** + Set the given value for the given key, at the current level. + + @method set + @param {String} key + @param {Any} value + */ + set: function(key, value) { + this.dict[key] = value; + }, + + /** + Delete the given key + + @method remove + @param {String} key + */ + remove: function(key) { + delete this.dict[key]; + }, + + /** + Check for the existence of given a key, if the key is present at the current + level return true, otherwise walk up the parent hierarchy and try again. If + no matching key is found, return false. + + @method has + @param {String} key + @return {Boolean} + */ + has: function(key) { + var dict = this.dict; + + if (dict.hasOwnProperty(key)) { + return true; + } + + if (this.parent) { + return this.parent.has(key); + } + + return false; + }, + + /** + Iterate and invoke a callback for each local key-value pair. + + @method eachLocal + @param {Function} callback + @param {Object} binding + */ + eachLocal: function(callback, binding) { + var dict = this.dict; + + for (var prop in dict) { + if (dict.hasOwnProperty(prop)) { + callback.call(binding, prop, dict[prop]); + } + } } - 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) { - var type = Ember.typeOf(obj); - if (type === 'array') { - return '[' + obj + ']'; - } - if (type !== 'object') { - 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.create.isSimulated) { - var prototypeProperties = [ - 'constructor', - 'hasOwnProperty', - 'isPrototypeOf', - 'propertyIsEnumerable', - 'valueOf', - 'toLocaleString', - 'toString' - ], - pushPropertyName = function(obj, array, key) { - // Prevents browsers that don't respect non-enumerability from - // copying internal Ember properties - if (key.substring(0,2) === '__') return; - if (key === '_super') return; - if (indexOf(array, key) >= 0) return; - if (!obj.hasOwnProperty(key)) return; - - array.push(key); - }; - - Ember.keys = function(obj) { - var ret = [], key; - for (key in obj) { - pushPropertyName(obj, ret, key); - } - - // IE8 doesn't enumerate property that named the same as prototype properties. - for (var i = 0, l = prototypeProperties.length; i < l; i++) { - key = prototypeProperties[i]; - - pushPropertyName(obj, ret, key); - } - - return ret; - }; -} - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var STRING_DASHERIZE_REGEXP = (/[ _]/g); -var STRING_DASHERIZE_CACHE = {}; -var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([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 {String} str The string to format - @param {Array} formats An array of parameters to interpolate into string. - @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, 10) - 1 : idx++; - s = formats[argIndex]; - return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); - }) ; - }, - - /** - 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, spaces, or camelCase 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; i w. + */ + function compare(v, w) { + if (v === w) { return 0; } - // 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 type1 = typeOf(v); + var type2 = typeOf(w); - 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), proto = m.proto; - 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]; - - Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin)); - - if (properties === null || typeof properties !== 'object') { - Ember.assert("Ember.Object.create only accepts objects."); - continue; + if (Comparable) { + if (type1==='instance' && Comparable.detect(v.constructor)) { + return v.constructor.compare(v, w); } - var keyNames = Ember.keys(properties); - for (var j = 0, ll = keyNames.length; j < ll; j++) { - var keyName = keyNames[j]; - if (!properties.hasOwnProperty(keyName)) { continue; } + if (type2 === 'instance' && Comparable.detect(w.constructor)) { + return 1-w.constructor.compare(w, v); + } + } - var value = properties[keyName], - IS_BINDING = Ember.IS_BINDING; + // 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; + } - 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; + // 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 (Comparable && 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; + } + }; + + __exports__["default"] = compare; + }); +define("ember-runtime/computed/array_computed", + ["ember-metal/core","ember-runtime/computed/reduce_computed","ember-metal/enumerable_utils","ember-metal/platform","ember-metal/observer","ember-metal/error","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var reduceComputed = __dependency2__.reduceComputed; + var ReduceComputedProperty = __dependency2__.ReduceComputedProperty; + var EnumerableUtils = __dependency3__["default"]; + var create = __dependency4__.create; + var addObserver = __dependency5__.addObserver; + var EmberError = __dependency6__["default"]; + + var a_slice = [].slice, + o_create = create, + forEach = EnumerableUtils.forEach; + + function ArrayComputedProperty() { + var cp = this; + + ReduceComputedProperty.apply(this, arguments); + + this.func = (function(reduceFunc) { + return function (propertyName) { + if (!cp._hasInstanceMeta(this, propertyName)) { + // When we recompute an array computed property, we need already + // retrieved arrays to be updated; we can't simply empty the cache and + // hope the array is re-retrieved. + forEach(cp._dependentKeys, function(dependentKey) { + addObserver(this, dependentKey, function() { + cp.recomputeOnce.call(this, propertyName); + }); + }, this); } - var desc = m.descs[keyName]; + return reduceFunc.apply(this, arguments); + }; + })(this.func); - 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)); - Ember.assert("`actions` must be provided at extend time, not at create time, when Ember.ActionHandler is used (i.e. views, controllers & routes).", !((keyName === 'actions') && Ember.ActionHandler.detect(this))); + return this; + } - if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) { - var baseValue = this[keyName]; + ArrayComputedProperty.prototype = o_create(ReduceComputedProperty.prototype); + ArrayComputedProperty.prototype.initialValue = function () { + return Ember.A(); + }; + ArrayComputedProperty.prototype.resetValue = function (array) { + array.clear(); + return array; + }; - if (baseValue) { - if ('function' === typeof baseValue.concat) { - value = baseValue.concat(value); - } else { - value = Ember.makeArray(baseValue).concat(value); + // This is a stopgap to keep the reference counts correct with lazy CPs. + ArrayComputedProperty.prototype.didChange = function (obj, keyName) { + return; + }; + + /** + Creates a computed property which operates on dependent arrays and + is updated with "one at a time" semantics. When items are added or + removed from the dependent array(s) an array computed only operates + on the change instead of re-evaluating the entire array. This should + return an array, if you'd like to use "one at a time" semantics and + compute some value other then an array look at + `Ember.reduceComputed`. + + If there are more than one arguments the first arguments are + considered to be dependent property keys. The last argument is + required to be an options object. The options object can have the + following three properties. + + `initialize` - An optional initialize function. Typically this will be used + to set up state on the instanceMeta object. + + `removedItem` - A function that is called each time an element is + removed from the array. + + `addedItem` - A function that is called each time an element is + added to the array. + + + The `initialize` function has the following signature: + + ```javascript + function (array, changeMeta, instanceMeta) + ``` + + `array` - The initial value of the arrayComputed, an empty array. + + `changeMeta` - An object which contains meta information about the + computed. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + + The `removedItem` and `addedItem` functions both have the following signature: + + ```javascript + function (accumulatedValue, item, changeMeta, instanceMeta) + ``` + + `accumulatedValue` - The value returned from the last time + `removedItem` or `addedItem` was called or an empty array. + + `item` - the element added or removed from the array + + `changeMeta` - An object which contains meta information about the + change. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + - `index` the index of the added or removed item + - `item` the added or removed item: this is exactly the same as + the second arg + - `arrayChanged` the array that triggered the change. Can be + useful when depending on multiple arrays. + + For property changes triggered on an item property change (when + depKey is something like `someArray.@each.someProperty`), + `changeMeta` will also contain the following property: + + - `previousValues` an object whose keys are the properties that changed on + the item, and whose values are the item's previous values. + + `previousValues` is important Ember coalesces item property changes via + Ember.run.once. This means that by the time removedItem gets called, item has + the new values, but you may need the previous value (eg for sorting & + filtering). + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + The `removedItem` and `addedItem` functions should return the accumulated + value. It is acceptable to not return anything (ie return undefined) + to invalidate the computation. This is generally not a good idea for + arrayComputed but it's used in eg max and min. + + Example + + ```javascript + Ember.computed.map = function(dependentKey, callback) { + var options = { + addedItem: function(array, item, changeMeta, instanceMeta) { + var mapped = callback(item); + array.insertAt(changeMeta.index, mapped); + return array; + }, + removedItem: function(array, item, changeMeta, instanceMeta) { + array.removeAt(changeMeta.index, 1); + return array; + } + }; + + return Ember.arrayComputed(dependentKey, options); + }; + ``` + + @method arrayComputed + @for Ember + @param {String} [dependentKeys*] + @param {Object} options + @return {Ember.ComputedProperty} + */ + function arrayComputed (options) { + var args; + + if (arguments.length > 1) { + args = a_slice.call(arguments, 0, -1); + options = a_slice.call(arguments, -1)[0]; + } + + if (typeof options !== "object") { + throw new EmberError("Array Computed Property declared without an options hash"); + } + + var cp = new ArrayComputedProperty(options); + + if (args) { + cp.property.apply(cp, args); + } + + return cp; + }; + + __exports__.arrayComputed = arrayComputed; + __exports__.ArrayComputedProperty = ArrayComputedProperty; + }); +define("ember-runtime/computed/reduce_computed", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/error","ember-metal/property_events","ember-metal/expand_properties","ember-metal/observer","ember-metal/computed","ember-metal/platform","ember-metal/enumerable_utils","ember-runtime/system/tracked_array","ember-runtime/mixins/array","ember-metal/run_loop","ember-runtime/system/set","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.assert + var e_get = __dependency2__.get; + var set = __dependency3__.set; + var guidFor = __dependency4__.guidFor; + var meta = __dependency4__.meta; + var EmberError = __dependency5__["default"]; + var propertyWillChange = __dependency6__.propertyWillChange; + var propertyDidChange = __dependency6__.propertyDidChange; + var expandProperties = __dependency7__["default"]; + var addObserver = __dependency8__.addObserver; + var observersFor = __dependency8__.observersFor; + var removeObserver = __dependency8__.removeObserver; + var addBeforeObserver = __dependency8__.addBeforeObserver; + var removeBeforeObserver = __dependency8__.removeBeforeObserver; + var ComputedProperty = __dependency9__.ComputedProperty; + var cacheFor = __dependency9__.cacheFor; + var create = __dependency10__.create; + var EnumerableUtils = __dependency11__["default"]; + var TrackedArray = __dependency12__["default"]; + var EmberArray = __dependency13__["default"]; + var run = __dependency14__["default"]; + var Set = __dependency15__["default"]; + var isArray = __dependency4__.isArray; + + var cacheSet = cacheFor.set, + cacheGet = cacheFor.get, + cacheRemove = cacheFor.remove, + a_slice = [].slice, + o_create = create, + forEach = EnumerableUtils.forEach, + metaFor = meta, + // Here we explicitly don't allow `@each.foo`; it would require some special + // testing, but there's no particular reason why it should be disallowed. + eachPropertyPattern = /^(.*)\.@each\.(.*)/, + doubleEachPropertyPattern = /(.*\.@each){2,}/, + arrayBracketPattern = /\.\[\]$/; + + function get(obj, key) { + if (key === '@this') { + return obj; + } + + return e_get(obj, key); + } + + /* + Tracks changes to dependent arrays, as well as to properties of items in + dependent arrays. + + @class DependentArraysObserver + */ + function DependentArraysObserver(callbacks, cp, instanceMeta, context, propertyName, sugarMeta) { + // user specified callbacks for `addedItem` and `removedItem` + this.callbacks = callbacks; + + // the computed property: remember these are shared across instances + this.cp = cp; + + // the ReduceComputedPropertyInstanceMeta this DependentArraysObserver is + // associated with + this.instanceMeta = instanceMeta; + + // A map of array guids to dependentKeys, for the given context. We track + // this because we want to set up the computed property potentially before the + // dependent array even exists, but when the array observer fires, we lack + // enough context to know what to update: we can recover that context by + // getting the dependentKey. + this.dependentKeysByGuid = {}; + + // a map of dependent array guids -> TrackedArray instances. We use + // this to lazily recompute indexes for item property observers. + this.trackedArraysByGuid = {}; + + // We suspend observers to ignore replacements from `reset` when totally + // recomputing. Unfortunately we cannot properly suspend the observers + // because we only have the key; instead we make the observers no-ops + this.suspended = false; + + // This is used to coalesce item changes from property observers. + this.changedItems = {}; + } + + function ItemPropertyObserverContext (dependentArray, index, trackedArray) { + Ember.assert("Internal error: trackedArray is null or undefined", trackedArray); + + this.dependentArray = dependentArray; + this.index = index; + this.item = dependentArray.objectAt(index); + this.trackedArray = trackedArray; + this.beforeObserver = null; + this.observer = null; + + this.destroyed = false; + } + + DependentArraysObserver.prototype = { + setValue: function (newValue) { + this.instanceMeta.setValue(newValue, true); + }, + getValue: function () { + return this.instanceMeta.getValue(); + }, + + setupObservers: function (dependentArray, dependentKey) { + this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey; + + dependentArray.addArrayObserver(this, { + willChange: 'dependentArrayWillChange', + didChange: 'dependentArrayDidChange' + }); + + if (this.cp._itemPropertyKeys[dependentKey]) { + this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]); + } + }, + + teardownObservers: function (dependentArray, dependentKey) { + var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || []; + + delete this.dependentKeysByGuid[guidFor(dependentArray)]; + + this.teardownPropertyObservers(dependentKey, itemPropertyKeys); + + dependentArray.removeArrayObserver(this, { + willChange: 'dependentArrayWillChange', + didChange: 'dependentArrayDidChange' + }); + }, + + suspendArrayObservers: function (callback, binding) { + var oldSuspended = this.suspended; + this.suspended = true; + callback.call(binding); + this.suspended = oldSuspended; + }, + + setupPropertyObservers: function (dependentKey, itemPropertyKeys) { + var dependentArray = get(this.instanceMeta.context, dependentKey), + length = get(dependentArray, 'length'), + observerContexts = new Array(length); + + this.resetTransformations(dependentKey, observerContexts); + + forEach(dependentArray, function (item, index) { + var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]); + observerContexts[index] = observerContext; + + forEach(itemPropertyKeys, function (propertyKey) { + addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); + addObserver(item, propertyKey, this, observerContext.observer); + }, this); + }, this); + }, + + teardownPropertyObservers: function (dependentKey, itemPropertyKeys) { + var dependentArrayObserver = this, + trackedArray = this.trackedArraysByGuid[dependentKey], + beforeObserver, + observer, + item; + + if (!trackedArray) { return; } + + trackedArray.apply(function (observerContexts, offset, operation) { + if (operation === TrackedArray.DELETE) { return; } + + forEach(observerContexts, function (observerContext) { + observerContext.destroyed = true; + beforeObserver = observerContext.beforeObserver; + observer = observerContext.observer; + item = observerContext.item; + + forEach(itemPropertyKeys, function (propertyKey) { + removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver); + removeObserver(item, propertyKey, dependentArrayObserver, observer); + }); + }); + }); + }, + + createPropertyObserverContext: function (dependentArray, index, trackedArray) { + var observerContext = new ItemPropertyObserverContext(dependentArray, index, trackedArray); + + this.createPropertyObserver(observerContext); + + return observerContext; + }, + + createPropertyObserver: function (observerContext) { + var dependentArrayObserver = this; + + observerContext.beforeObserver = function (obj, keyName) { + return dependentArrayObserver.itemPropertyWillChange(obj, keyName, observerContext.dependentArray, observerContext); + }; + observerContext.observer = function (obj, keyName) { + return dependentArrayObserver.itemPropertyDidChange(obj, keyName, observerContext.dependentArray, observerContext); + }; + }, + + resetTransformations: function (dependentKey, observerContexts) { + this.trackedArraysByGuid[dependentKey] = new TrackedArray(observerContexts); + }, + + trackAdd: function (dependentKey, index, newItems) { + var trackedArray = this.trackedArraysByGuid[dependentKey]; + if (trackedArray) { + trackedArray.addItems(index, newItems); + } + }, + + trackRemove: function (dependentKey, index, removedCount) { + var trackedArray = this.trackedArraysByGuid[dependentKey]; + + if (trackedArray) { + return trackedArray.removeItems(index, removedCount); + } + + return []; + }, + + updateIndexes: function (trackedArray, array) { + var length = get(array, 'length'); + // OPTIMIZE: we could stop updating once we hit the object whose observer + // fired; ie partially apply the transformations + trackedArray.apply(function (observerContexts, offset, operation) { + // we don't even have observer contexts for removed items, even if we did, + // they no longer have any index in the array + if (operation === TrackedArray.DELETE) { return; } + if (operation === TrackedArray.RETAIN && observerContexts.length === length && offset === 0) { + // If we update many items we don't want to walk the array each time: we + // only need to update the indexes at most once per run loop. + return; + } + + forEach(observerContexts, function (context, index) { + context.index = index + offset; + }); + }); + }, + + dependentArrayWillChange: function (dependentArray, index, removedCount, addedCount) { + if (this.suspended) { return; } + + var removedItem = this.callbacks.removedItem, + changeMeta, + guid = guidFor(dependentArray), + dependentKey = this.dependentKeysByGuid[guid], + itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [], + length = get(dependentArray, 'length'), + normalizedIndex = normalizeIndex(index, length, 0), + normalizedRemoveCount = normalizeRemoveCount(normalizedIndex, length, removedCount), + item, + itemIndex, + sliceIndex, + observerContexts; + + observerContexts = this.trackRemove(dependentKey, normalizedIndex, normalizedRemoveCount); + + function removeObservers(propertyKey) { + observerContexts[sliceIndex].destroyed = true; + removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver); + removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer); + } + + for (sliceIndex = normalizedRemoveCount - 1; sliceIndex >= 0; --sliceIndex) { + itemIndex = normalizedIndex + sliceIndex; + if (itemIndex >= length) { break; } + + item = dependentArray.objectAt(itemIndex); + + forEach(itemPropertyKeys, removeObservers, this); + + changeMeta = createChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp); + this.setValue( removedItem.call( + this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); + } + }, + + dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) { + if (this.suspended) { return; } + + var addedItem = this.callbacks.addedItem, + guid = guidFor(dependentArray), + dependentKey = this.dependentKeysByGuid[guid], + observerContexts = new Array(addedCount), + itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey], + length = get(dependentArray, 'length'), + normalizedIndex = normalizeIndex(index, length, addedCount), + changeMeta, + observerContext; + + forEach(dependentArray.slice(normalizedIndex, normalizedIndex + addedCount), function (item, sliceIndex) { + if (itemPropertyKeys) { + observerContext = + observerContexts[sliceIndex] = + this.createPropertyObserverContext(dependentArray, normalizedIndex + sliceIndex, this.trackedArraysByGuid[dependentKey]); + forEach(itemPropertyKeys, function (propertyKey) { + addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); + addObserver(item, propertyKey, this, observerContext.observer); + }, this); + } + + changeMeta = createChangeMeta(dependentArray, item, normalizedIndex + sliceIndex, this.instanceMeta.propertyName, this.cp); + this.setValue( addedItem.call( + this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); + }, this); + + this.trackAdd(dependentKey, normalizedIndex, observerContexts); + }, + + itemPropertyWillChange: function (obj, keyName, array, observerContext) { + var guid = guidFor(obj); + + if (!this.changedItems[guid]) { + this.changedItems[guid] = { + array: array, + observerContext: observerContext, + obj: obj, + previousValues: {} + }; + } + + this.changedItems[guid].previousValues[keyName] = get(obj, keyName); + }, + + itemPropertyDidChange: function(obj, keyName, array, observerContext) { + this.flushChanges(); + }, + + flushChanges: function() { + var changedItems = this.changedItems, key, c, changeMeta; + + for (key in changedItems) { + c = changedItems[key]; + if (c.observerContext.destroyed) { continue; } + + this.updateIndexes(c.observerContext.trackedArray, c.observerContext.dependentArray); + + changeMeta = createChangeMeta(c.array, c.obj, c.observerContext.index, this.instanceMeta.propertyName, this.cp, c.previousValues); + this.setValue( + this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta)); + this.setValue( + this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta)); + } + this.changedItems = {}; + } + }; + + function normalizeIndex(index, length, newItemsOffset) { + if (index < 0) { + return Math.max(0, length + index); + } else if (index < length) { + return index; + } else /* index > length */ { + return Math.min(length - newItemsOffset, index); + } + } + + function normalizeRemoveCount(index, length, removedCount) { + return Math.min(removedCount, length - index); + } + + function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) { + var meta = { + arrayChanged: dependentArray, + index: index, + item: item, + propertyName: propertyName, + property: property + }; + + if (previousValues) { + // previous values only available for item property changes + meta.previousValues = previousValues; + } + + return meta; + } + + function addItems (dependentArray, callbacks, cp, propertyName, meta) { + forEach(dependentArray, function (item, index) { + meta.setValue( callbacks.addedItem.call( + this, meta.getValue(), item, createChangeMeta(dependentArray, item, index, propertyName, cp), meta.sugarMeta)); + }, this); + } + + function reset(cp, propertyName) { + var callbacks = cp._callbacks(), + meta; + + if (cp._hasInstanceMeta(this, propertyName)) { + meta = cp._instanceMeta(this, propertyName); + meta.setValue(cp.resetValue(meta.getValue())); + } else { + meta = cp._instanceMeta(this, propertyName); + } + + if (cp.options.initialize) { + cp.options.initialize.call(this, meta.getValue(), { property: cp, propertyName: propertyName }, meta.sugarMeta); + } + } + + function partiallyRecomputeFor(obj, dependentKey) { + if (arrayBracketPattern.test(dependentKey)) { + return false; + } + + var value = get(obj, dependentKey); + return EmberArray.detect(value); + } + + function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) { + this.context = context; + this.propertyName = propertyName; + this.cache = metaFor(context).cache; + + this.dependentArrays = {}; + this.sugarMeta = {}; + + this.initialValue = initialValue; + } + + ReduceComputedPropertyInstanceMeta.prototype = { + getValue: function () { + var value = cacheGet(this.cache, this.propertyName); + if (value !== undefined) { + return value; + } else { + return this.initialValue; + } + }, + + setValue: function(newValue, triggerObservers) { + // This lets sugars force a recomputation, handy for very simple + // implementations of eg max. + if (newValue === cacheGet(this.cache, this.propertyName)) { + return; + } + + if (triggerObservers) { + propertyWillChange(this.context, this.propertyName); + } + + if (newValue === undefined) { + cacheRemove(this.cache, this.propertyName); + } else { + cacheSet(this.cache, this.propertyName, newValue); + } + + if (triggerObservers) { + propertyDidChange(this.context, this.propertyName); + } + } + }; + + /** + A computed property whose dependent keys are arrays and which is updated with + "one at a time" semantics. + + @class ReduceComputedProperty + @namespace Ember + @extends Ember.ComputedProperty + @constructor + */ + function ReduceComputedProperty(options) { + var cp = this; + + this.options = options; + this._instanceMetas = {}; + + this._dependentKeys = null; + // A map of dependentKey -> [itemProperty, ...] that tracks what properties of + // items in the array we must track to update this property. + this._itemPropertyKeys = {}; + this._previousItemPropertyKeys = {}; + + this.readOnly(); + this.cacheable(); + + this.recomputeOnce = function(propertyName) { + // What we really want to do is coalesce by . + // We need a form of `scheduleOnce` that accepts an arbitrary token to + // coalesce by, in addition to the target and method. + run.once(this, recompute, propertyName); + }; + var recompute = function(propertyName) { + var dependentKeys = cp._dependentKeys, + meta = cp._instanceMeta(this, propertyName), + callbacks = cp._callbacks(); + + reset.call(this, cp, propertyName); + + meta.dependentArraysObserver.suspendArrayObservers(function () { + forEach(cp._dependentKeys, function (dependentKey) { + Ember.assert( + "dependent array " + dependentKey + " must be an `Ember.Array`. " + + "If you are not extending arrays, you will need to wrap native arrays with `Ember.A`", + !(isArray(get(this, dependentKey)) && !EmberArray.detect(get(this, dependentKey)))); + + if (!partiallyRecomputeFor(this, dependentKey)) { return; } + + var dependentArray = get(this, dependentKey), + previousDependentArray = meta.dependentArrays[dependentKey]; + + if (dependentArray === previousDependentArray) { + // The array may be the same, but our item property keys may have + // changed, so we set them up again. We can't easily tell if they've + // changed: the array may be the same object, but with different + // contents. + if (cp._previousItemPropertyKeys[dependentKey]) { + delete cp._previousItemPropertyKeys[dependentKey]; + meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]); } } else { - value = Ember.makeArray(value); + meta.dependentArrays[dependentKey] = dependentArray; + + if (previousDependentArray) { + meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey); + } + + if (dependentArray) { + meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey); + } + } + }, this); + }, this); + + forEach(cp._dependentKeys, function(dependentKey) { + if (!partiallyRecomputeFor(this, dependentKey)) { return; } + + var dependentArray = get(this, dependentKey); + if (dependentArray) { + addItems.call(this, dependentArray, callbacks, cp, propertyName, meta); + } + }, this); + }; + + + this.func = function (propertyName) { + Ember.assert("Computed reduce values require at least one dependent key", cp._dependentKeys); + + recompute.call(this, propertyName); + + return cp._instanceMeta(this, propertyName).getValue(); + }; + } + + ReduceComputedProperty.prototype = o_create(ComputedProperty.prototype); + + function defaultCallback(computedValue) { + return computedValue; + } + + ReduceComputedProperty.prototype._callbacks = function () { + if (!this.callbacks) { + var options = this.options; + this.callbacks = { + removedItem: options.removedItem || defaultCallback, + addedItem: options.addedItem || defaultCallback + }; + } + return this.callbacks; + }; + + ReduceComputedProperty.prototype._hasInstanceMeta = function (context, propertyName) { + var guid = guidFor(context), + key = guid + ':' + propertyName; + + return !!this._instanceMetas[key]; + }; + + ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) { + var guid = guidFor(context), + key = guid + ':' + propertyName, + meta = this._instanceMetas[key]; + + if (!meta) { + meta = this._instanceMetas[key] = new ReduceComputedPropertyInstanceMeta(context, propertyName, this.initialValue()); + meta.dependentArraysObserver = new DependentArraysObserver(this._callbacks(), this, meta, context, propertyName, meta.sugarMeta); + } + + return meta; + }; + + ReduceComputedProperty.prototype.initialValue = function () { + if (typeof this.options.initialValue === 'function') { + return this.options.initialValue(); + } + else { + return this.options.initialValue; + } + }; + + ReduceComputedProperty.prototype.resetValue = function (value) { + return this.initialValue(); + }; + + ReduceComputedProperty.prototype.itemPropertyKey = function (dependentArrayKey, itemPropertyKey) { + this._itemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey] || []; + this._itemPropertyKeys[dependentArrayKey].push(itemPropertyKey); + }; + + ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArrayKey) { + if (this._itemPropertyKeys[dependentArrayKey]) { + this._previousItemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey]; + this._itemPropertyKeys[dependentArrayKey] = []; + } + }; + + ReduceComputedProperty.prototype.property = function () { + var cp = this, + args = a_slice.call(arguments), + propertyArgs = new Set(), + match, + dependentArrayKey, + itemPropertyKey; + + forEach(args, function (dependentKey) { + if (doubleEachPropertyPattern.test(dependentKey)) { + throw new EmberError("Nested @each properties not supported: " + dependentKey); + } else if (match = eachPropertyPattern.exec(dependentKey)) { + dependentArrayKey = match[1]; + + var itemPropertyKeyPattern = match[2], + addItemPropertyKey = function (itemPropertyKey) { + cp.itemPropertyKey(dependentArrayKey, itemPropertyKey); + }; + + expandProperties(itemPropertyKeyPattern, addItemPropertyKey); + propertyArgs.add(dependentArrayKey); + } else { + propertyArgs.add(dependentKey); + } + }); + + return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray()); + + }; + + /** + Creates a computed property which operates on dependent arrays and + is updated with "one at a time" semantics. When items are added or + removed from the dependent array(s) a reduce computed only operates + on the change instead of re-evaluating the entire array. + + If there are more than one arguments the first arguments are + considered to be dependent property keys. The last argument is + required to be an options object. The options object can have the + following four properties: + + `initialValue` - A value or function that will be used as the initial + value for the computed. If this property is a function the result of calling + the function will be used as the initial value. This property is required. + + `initialize` - An optional initialize function. Typically this will be used + to set up state on the instanceMeta object. + + `removedItem` - A function that is called each time an element is removed + from the array. + + `addedItem` - A function that is called each time an element is added to + the array. + + + The `initialize` function has the following signature: + + ```javascript + function (initialValue, changeMeta, instanceMeta) + ``` + + `initialValue` - The value of the `initialValue` property from the + options object. + + `changeMeta` - An object which contains meta information about the + computed. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + + The `removedItem` and `addedItem` functions both have the following signature: + + ```javascript + function (accumulatedValue, item, changeMeta, instanceMeta) + ``` + + `accumulatedValue` - The value returned from the last time + `removedItem` or `addedItem` was called or `initialValue`. + + `item` - the element added or removed from the array + + `changeMeta` - An object which contains meta information about the + change. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + - `index` the index of the added or removed item + - `item` the added or removed item: this is exactly the same as + the second arg + - `arrayChanged` the array that triggered the change. Can be + useful when depending on multiple arrays. + + For property changes triggered on an item property change (when + depKey is something like `someArray.@each.someProperty`), + `changeMeta` will also contain the following property: + + - `previousValues` an object whose keys are the properties that changed on + the item, and whose values are the item's previous values. + + `previousValues` is important Ember coalesces item property changes via + Ember.run.once. This means that by the time removedItem gets called, item has + the new values, but you may need the previous value (eg for sorting & + filtering). + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + The `removedItem` and `addedItem` functions should return the accumulated + value. It is acceptable to not return anything (ie return undefined) + to invalidate the computation. This is generally not a good idea for + arrayComputed but it's used in eg max and min. + + Note that observers will be fired if either of these functions return a value + that differs from the accumulated value. When returning an object that + mutates in response to array changes, for example an array that maps + everything from some other array (see `Ember.computed.map`), it is usually + important that the *same* array be returned to avoid accidentally triggering observers. + + Example + + ```javascript + Ember.computed.max = function (dependentKey) { + return Ember.reduceComputed(dependentKey, { + initialValue: -Infinity, + + addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + return Math.max(accumulatedValue, item); + }, + + removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + if (item < accumulatedValue) { + return accumulatedValue; } } + }); + }; + ``` - if (desc) { - desc.set(this, keyName, value); + Dependent keys may refer to `@this` to observe changes to the object itself, + which must be array-like, rather than a property of the object. This is + mostly useful for array proxies, to ensure objects are retrieved via + `objectAtContent`. This is how you could sort items by properties defined on an item controller. + + Example + + ```javascript + App.PeopleController = Ember.ArrayController.extend({ + itemController: 'person', + + sortedPeople: Ember.computed.sort('@this.@each.reversedName', function(personA, personB) { + // `reversedName` isn't defined on Person, but we have access to it via + // the item controller App.PersonController. If we'd used + // `content.@each.reversedName` above, we would be getting the objects + // directly and not have access to `reversedName`. + // + var reversedNameA = get(personA, 'reversedName'), + reversedNameB = get(personB, 'reversedName'); + + return Ember.compare(reversedNameA, reversedNameB); + }) + }); + + App.PersonController = Ember.ObjectController.extend({ + reversedName: function () { + return reverse(get(this, 'name')); + }.property('name') + }) + ``` + + Dependent keys whose values are not arrays are treated as regular + dependencies: when they change, the computed property is completely + recalculated. It is sometimes useful to have dependent arrays with similar + semantics. Dependent keys which end in `.[]` do not use "one at a time" + semantics. When an item is added or removed from such a dependency, the + computed property is completely recomputed. + + Example + + ```javascript + Ember.Object.extend({ + // When `string` is changed, `computed` is completely recomputed. + string: 'a string', + + // When an item is added to `array`, `addedItem` is called. + array: [], + + // When an item is added to `anotherArray`, `computed` is completely + // recomputed. + anotherArray: [], + + computed: Ember.reduceComputed('string', 'array', 'anotherArray.[]', { + addedItem: addedItemCallback, + removedItem: removedItemCallback + }) + }); + ``` + + @method reduceComputed + @for Ember + @param {String} [dependentKeys*] + @param {Object} options + @return {Ember.ComputedProperty} + */ + function reduceComputed(options) { + var args; + + if (arguments.length > 1) { + args = a_slice.call(arguments, 0, -1); + options = a_slice.call(arguments, -1)[0]; + } + + if (typeof options !== "object") { + throw new EmberError("Reduce Computed Property declared without an options hash"); + } + + if (!('initialValue' in options)) { + throw new EmberError("Reduce Computed Property declared without an initial value"); + } + + var cp = new ReduceComputedProperty(options); + + if (args) { + cp.property.apply(cp, args); + } + + return cp; + }; + + __exports__.reduceComputed = reduceComputed; + __exports__.ReduceComputedProperty = ReduceComputedProperty; + }); +define("ember-runtime/computed/reduce_computed_macros", + ["ember-metal/core","ember-metal/merge","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/error","ember-metal/enumerable_utils","ember-metal/run_loop","ember-metal/observer","ember-runtime/computed/array_computed","ember-runtime/computed/reduce_computed","ember-runtime/system/object_proxy","ember-runtime/system/subarray","ember-runtime/keys","ember-runtime/compare","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + var Ember = __dependency1__["default"]; + // Ember.assert + var merge = __dependency2__["default"]; + var get = __dependency3__.get; + var set = __dependency4__.set; + var isArray = __dependency5__.isArray; + var guidFor = __dependency5__.guidFor; + var EmberError = __dependency6__["default"]; + var EnumerableUtils = __dependency7__["default"]; + var run = __dependency8__["default"]; + var addObserver = __dependency9__.addObserver; + var arrayComputed = __dependency10__.arrayComputed; + var reduceComputed = __dependency11__.reduceComputed; + var ObjectProxy = __dependency12__["default"]; + var SubArray = __dependency13__["default"]; + var keys = __dependency14__["default"]; + var compare = __dependency15__["default"]; + + var a_slice = [].slice, + forEach = EnumerableUtils.forEach, + SearchProxy; + + /** + A computed property that returns the sum of the value + in the dependent array. + + @method computed.sum + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computes the sum of all values in the dependentKey's array + */ + + function sum(dependentKey){ + return reduceComputed(dependentKey, { + initialValue: 0, + + addedItem: function(accumulatedValue, item, changeMeta, instanceMeta){ + return accumulatedValue + item; + }, + + removedItem: function(accumulatedValue, item, changeMeta, instanceMeta){ + return accumulatedValue - item; + } + }); + }; + + /** + A computed property that calculates the maximum value in the + dependent array. This will return `-Infinity` when the dependent + array is empty. + + ```javascript + App.Person = Ember.Object.extend({ + childAges: Ember.computed.mapBy('children', 'age'), + maxChildAge: Ember.computed.max('childAges') + }); + + var lordByron = App.Person.create({children: []}); + lordByron.get('maxChildAge'); // -Infinity + lordByron.get('children').pushObject({ + name: 'Augusta Ada Byron', age: 7 + }); + lordByron.get('maxChildAge'); // 7 + lordByron.get('children').pushObjects([{ + name: 'Allegra Byron', + age: 5 + }, { + name: 'Elizabeth Medora Leigh', + age: 8 + }]); + lordByron.get('maxChildAge'); // 8 + ``` + + @method computed.max + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array + */ + function max (dependentKey) { + return reduceComputed(dependentKey, { + initialValue: -Infinity, + + addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + return Math.max(accumulatedValue, item); + }, + + removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + if (item < accumulatedValue) { + return accumulatedValue; + } + } + }); + }; + + /** + A computed property that calculates the minimum value in the + dependent array. This will return `Infinity` when the dependent + array is empty. + + ```javascript + App.Person = Ember.Object.extend({ + childAges: Ember.computed.mapBy('children', 'age'), + minChildAge: Ember.computed.min('childAges') + }); + + var lordByron = App.Person.create({children: []}); + lordByron.get('minChildAge'); // Infinity + lordByron.get('children').pushObject({ + name: 'Augusta Ada Byron', age: 7 + }); + lordByron.get('minChildAge'); // 7 + lordByron.get('children').pushObjects([{ + name: 'Allegra Byron', + age: 5 + }, { + name: 'Elizabeth Medora Leigh', + age: 8 + }]); + lordByron.get('minChildAge'); // 5 + ``` + + @method computed.min + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array + */ + function min(dependentKey) { + return reduceComputed(dependentKey, { + initialValue: Infinity, + + addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + return Math.min(accumulatedValue, item); + }, + + removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + if (item > accumulatedValue) { + return accumulatedValue; + } + } + }); + }; + + /** + Returns an array mapped via the callback + + The callback method you provide should have the following signature. + `item` is the current item in the iteration. + + ```javascript + function(item); + ``` + + Example + + ```javascript + App.Hamster = Ember.Object.extend({ + excitingChores: Ember.computed.map('chores', function(chore) { + return chore.toUpperCase() + '!'; + }) + }); + + var hamster = App.Hamster.create({ + chores: ['clean', 'write more unit tests'] + }); + hamster.get('excitingChores'); // ['CLEAN!', 'WRITE MORE UNIT TESTS!'] + ``` + + @method computed.map + @for Ember + @param {String} dependentKey + @param {Function} callback + @return {Ember.ComputedProperty} an array mapped via the callback + */ + function map(dependentKey, callback) { + var options = { + addedItem: function(array, item, changeMeta, instanceMeta) { + var mapped = callback.call(this, item); + array.insertAt(changeMeta.index, mapped); + return array; + }, + removedItem: function(array, item, changeMeta, instanceMeta) { + array.removeAt(changeMeta.index, 1); + return array; + } + }; + + return arrayComputed(dependentKey, options); + }; + + /** + Returns an array mapped to the specified key. + + ```javascript + App.Person = Ember.Object.extend({ + childAges: Ember.computed.mapBy('children', 'age') + }); + + var lordByron = App.Person.create({children: []}); + lordByron.get('childAges'); // [] + lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); + lordByron.get('childAges'); // [7] + lordByron.get('children').pushObjects([{ + name: 'Allegra Byron', + age: 5 + }, { + name: 'Elizabeth Medora Leigh', + age: 8 + }]); + lordByron.get('childAges'); // [7, 5, 8] + ``` + + @method computed.mapBy + @for Ember + @param {String} dependentKey + @param {String} propertyKey + @return {Ember.ComputedProperty} an array mapped to the specified key + */ + function mapBy (dependentKey, propertyKey) { + var callback = function(item) { return get(item, propertyKey); }; + return map(dependentKey + '.@each.' + propertyKey, callback); + }; + + /** + @method computed.mapProperty + @for Ember + @deprecated Use `Ember.computed.mapBy` instead + @param dependentKey + @param propertyKey + */ + var mapProperty = mapBy; + + /** + Filters the array by the callback. + + The callback method you provide should have the following signature. + `item` is the current item in the iteration. + + ```javascript + function(item); + ``` + + ```javascript + App.Hamster = Ember.Object.extend({ + remainingChores: Ember.computed.filter('chores', function(chore) { + return !chore.done; + }) + }); + + var hamster = App.Hamster.create({chores: [ + {name: 'cook', done: true}, + {name: 'clean', done: true}, + {name: 'write more unit tests', done: false} + ]}); + hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] + ``` + + @method computed.filter + @for Ember + @param {String} dependentKey + @param {Function} callback + @return {Ember.ComputedProperty} the filtered array + */ + function filter(dependentKey, callback) { + var options = { + initialize: function (array, changeMeta, instanceMeta) { + instanceMeta.filteredArrayIndexes = new SubArray(); + }, + + addedItem: function(array, item, changeMeta, instanceMeta) { + var match = !!callback.call(this, item), + filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match); + + if (match) { + array.insertAt(filterIndex, item); + } + + return array; + }, + + removedItem: function(array, item, changeMeta, instanceMeta) { + var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index); + + if (filterIndex > -1) { + array.removeAt(filterIndex); + } + + return array; + } + }; + + return arrayComputed(dependentKey, options); + }; + + /** + Filters the array by the property and value + + ```javascript + App.Hamster = Ember.Object.extend({ + remainingChores: Ember.computed.filterBy('chores', 'done', false) + }); + + var hamster = App.Hamster.create({chores: [ + {name: 'cook', done: true}, + {name: 'clean', done: true}, + {name: 'write more unit tests', done: false} + ]}); + hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] + ``` + + @method computed.filterBy + @for Ember + @param {String} dependentKey + @param {String} propertyKey + @param {*} value + @return {Ember.ComputedProperty} the filtered array + */ + function filterBy (dependentKey, propertyKey, value) { + var callback; + + if (arguments.length === 2) { + callback = function(item) { + return get(item, propertyKey); + }; + } else { + callback = function(item) { + return get(item, propertyKey) === value; + }; + } + + return filter(dependentKey + '.@each.' + propertyKey, callback); + }; + + /** + @method computed.filterProperty + @for Ember + @param dependentKey + @param propertyKey + @param value + @deprecated Use `Ember.computed.filterBy` instead + */ + var filterProperty = filterBy; + + /** + A computed property which returns a new array with all the unique + elements from one or more dependent arrays. + + Example + + ```javascript + App.Hamster = Ember.Object.extend({ + uniqueFruits: Ember.computed.uniq('fruits') + }); + + var hamster = App.Hamster.create({fruits: [ + 'banana', + 'grape', + 'kale', + 'banana' + ]}); + hamster.get('uniqueFruits'); // ['banana', 'grape', 'kale'] + ``` + + @method computed.uniq + @for Ember + @param {String} propertyKey* + @return {Ember.ComputedProperty} computes a new array with all the + unique elements from the dependent array + */ + function uniq() { + var args = a_slice.call(arguments); + args.push({ + initialize: function(array, changeMeta, instanceMeta) { + instanceMeta.itemCounts = {}; + }, + + addedItem: function(array, item, changeMeta, instanceMeta) { + var guid = guidFor(item); + + if (!instanceMeta.itemCounts[guid]) { + instanceMeta.itemCounts[guid] = 1; } 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 + ++instanceMeta.itemCounts[guid]; + } + array.addObject(item); + return array; + }, + removedItem: function(array, item, _, instanceMeta) { + var guid = guidFor(item), + itemCounts = instanceMeta.itemCounts; + + if (--itemCounts[guid] === 0) { + array.removeObject(item); + } + return array; + } + }); + return arrayComputed.apply(null, args); + }; + + /** + Alias for [Ember.computed.uniq](/api/#method_computed_uniq). + + @method computed.union + @for Ember + @param {String} propertyKey* + @return {Ember.ComputedProperty} computes a new array with all the + unique elements from the dependent array + */ + var union = uniq; + + /** + A computed property which returns a new array with all the duplicated + elements from two or more dependent arrays. + + Example + + ```javascript + var obj = Ember.Object.createWithMixins({ + adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'], + charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'], + friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends') + }); + + obj.get('friendsInCommon'); // ['William King', 'Mary Somerville'] + ``` + + @method computed.intersect + @for Ember + @param {String} propertyKey* + @return {Ember.ComputedProperty} computes a new array with all the + duplicated elements from the dependent arrays + */ + function intersect() { + var getDependentKeyGuids = function (changeMeta) { + return EnumerableUtils.map(changeMeta.property._dependentKeys, function (dependentKey) { + return guidFor(dependentKey); + }); + }; + + var args = a_slice.call(arguments); + args.push({ + initialize: function (array, changeMeta, instanceMeta) { + instanceMeta.itemCounts = {}; + }, + + addedItem: function(array, item, changeMeta, instanceMeta) { + var itemGuid = guidFor(item), + dependentGuids = getDependentKeyGuids(changeMeta), + dependentGuid = guidFor(changeMeta.arrayChanged), + numberOfDependentArrays = changeMeta.property._dependentKeys.length, + itemCounts = instanceMeta.itemCounts; + + if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; } + if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } + + if (++itemCounts[itemGuid][dependentGuid] === 1 && + numberOfDependentArrays === keys(itemCounts[itemGuid]).length) { + + array.addObject(item); + } + return array; + }, + removedItem: function(array, item, changeMeta, instanceMeta) { + var itemGuid = guidFor(item), + dependentGuids = getDependentKeyGuids(changeMeta), + dependentGuid = guidFor(changeMeta.arrayChanged), + numberOfDependentArrays = changeMeta.property._dependentKeys.length, + numberOfArraysItemAppearsIn, + itemCounts = instanceMeta.itemCounts; + + if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } + if (--itemCounts[itemGuid][dependentGuid] === 0) { + delete itemCounts[itemGuid][dependentGuid]; + numberOfArraysItemAppearsIn = keys(itemCounts[itemGuid]).length; + + if (numberOfArraysItemAppearsIn === 0) { + delete itemCounts[itemGuid]; + } + array.removeObject(item); + } + return array; + } + }); + return arrayComputed.apply(null, args); + }; + + /** + A computed property which returns a new array with all the + properties from the first dependent array that are not in the second + dependent array. + + Example + + ```javascript + App.Hamster = Ember.Object.extend({ + likes: ['banana', 'grape', 'kale'], + wants: Ember.computed.setDiff('likes', 'fruits') + }); + + var hamster = App.Hamster.create({fruits: [ + 'grape', + 'kale', + ]}); + hamster.get('wants'); // ['banana'] + ``` + + @method computed.setDiff + @for Ember + @param {String} setAProperty + @param {String} setBProperty + @return {Ember.ComputedProperty} computes a new array with all the + items from the first dependent array that are not in the second + dependent array + */ + function setDiff(setAProperty, setBProperty) { + if (arguments.length !== 2) { + throw new EmberError("setDiff requires exactly two dependent arrays."); + } + return arrayComputed(setAProperty, setBProperty, { + addedItem: function (array, item, changeMeta, instanceMeta) { + var setA = get(this, setAProperty), + setB = get(this, setBProperty); + + if (changeMeta.arrayChanged === setA) { + if (!setB.contains(item)) { + array.addObject(item); + } + } else { + array.removeObject(item); + } + return array; + }, + + removedItem: function (array, item, changeMeta, instanceMeta) { + var setA = get(this, setAProperty), + setB = get(this, setBProperty); + + if (changeMeta.arrayChanged === setB) { + if (setA.contains(item)) { + array.addObject(item); + } + } else { + array.removeObject(item); + } + return array; + } + }); + }; + + function binarySearch(array, item, low, high) { + var mid, midItem, res, guidMid, guidItem; + + if (arguments.length < 4) { high = get(array, 'length'); } + if (arguments.length < 3) { low = 0; } + + if (low === high) { + return low; + } + + mid = low + Math.floor((high - low) / 2); + midItem = array.objectAt(mid); + + guidMid = _guidFor(midItem); + guidItem = _guidFor(item); + + if (guidMid === guidItem) { + return mid; + } + + res = this.order(midItem, item); + if (res === 0) { + res = guidMid < guidItem ? -1 : 1; + } + + + if (res < 0) { + return this.binarySearch(array, item, mid+1, high); + } else if (res > 0) { + return this.binarySearch(array, item, low, mid); + } + + return mid; + + function _guidFor(item) { + if (SearchProxy.detectInstance(item)) { + return guidFor(get(item, 'content')); + } + return guidFor(item); + } + } + + + var SearchProxy = ObjectProxy.extend(); + + /** + A computed property which returns a new array with all the + properties from the first dependent array sorted based on a property + or sort function. + + The callback method you provide should have the following signature: + + ```javascript + function(itemA, itemB); + ``` + + - `itemA` the first item to compare. + - `itemB` the second item to compare. + + This function should return negative number (e.g. `-1`) when `itemA` should come before + `itemB`. It should return positive number (e.g. `1`) when `itemA` should come after + `itemB`. If the `itemA` and `itemB` are equal this function should return `0`. + + Therefore, if this function is comparing some numeric values, simple `itemA - itemB` or + `itemA.get( 'foo' ) - itemB.get( 'foo' )` can be used instead of series of `if`. + + Example + + ```javascript + var ToDoList = Ember.Object.extend({ + // using standard ascending sort + todosSorting: ['name'], + sortedTodos: Ember.computed.sort('todos', 'todosSorting'), + + // using descending sort + todosSortingDesc: ['name:desc'], + sortedTodosDesc: Ember.computed.sort('todos', 'todosSortingDesc'), + + // using a custom sort function + priorityTodos: Ember.computed.sort('todos', function(a, b){ + if (a.priority > b.priority) { + return 1; + } else if (a.priority < b.priority) { + return -1; + } + return 0; + }), + }); + var todoList = ToDoList.create({todos: [ + {name: 'Unit Test', priority: 2}, + {name: 'Documentation', priority: 3}, + {name: 'Release', priority: 1} + ]}); + + todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}] + todoList.get('sortedTodosDesc'); // [{name:'Unit Test', priority:2}, {name:'Release', priority:1}, {name:'Documentation', priority:3}] + todoList.get('priorityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}] + ``` + + @method computed.sort + @for Ember + @param {String} dependentKey + @param {String or Function} sortDefinition a dependent key to an + array of sort properties (add `:desc` to the arrays sort properties to sort descending) or a function to use when sorting + @return {Ember.ComputedProperty} computes a new sorted array based + on the sort property array or callback function + */ + function sort(itemsKey, sortDefinition) { + Ember.assert("Ember.computed.sort requires two arguments: an array key to sort and either a sort properties key or sort function", arguments.length === 2); + + var initFn, sortPropertiesKey; + + if (typeof sortDefinition === 'function') { + initFn = function (array, changeMeta, instanceMeta) { + instanceMeta.order = sortDefinition; + instanceMeta.binarySearch = binarySearch; + }; + } else { + sortPropertiesKey = sortDefinition; + initFn = function (array, changeMeta, instanceMeta) { + function setupSortProperties() { + var sortPropertyDefinitions = get(this, sortPropertiesKey), + sortProperty, + sortProperties = instanceMeta.sortProperties = [], + sortPropertyAscending = instanceMeta.sortPropertyAscending = {}, + idx, + asc; + + Ember.assert("Cannot sort: '" + sortPropertiesKey + "' is not an array.", isArray(sortPropertyDefinitions)); + + changeMeta.property.clearItemPropertyKeys(itemsKey); + + forEach(sortPropertyDefinitions, function (sortPropertyDefinition) { + if ((idx = sortPropertyDefinition.indexOf(':')) !== -1) { + sortProperty = sortPropertyDefinition.substring(0, idx); + asc = sortPropertyDefinition.substring(idx+1).toLowerCase() !== 'desc'; + } else { + sortProperty = sortPropertyDefinition; + asc = true; + } + + sortProperties.push(sortProperty); + sortPropertyAscending[sortProperty] = asc; + changeMeta.property.itemPropertyKey(itemsKey, sortProperty); + }); + + sortPropertyDefinitions.addObserver('@each', this, updateSortPropertiesOnce); + } + + function updateSortPropertiesOnce() { + run.once(this, updateSortProperties, changeMeta.propertyName); + } + + function updateSortProperties(propertyName) { + setupSortProperties.call(this); + changeMeta.property.recomputeOnce.call(this, propertyName); + } + + addObserver(this, sortPropertiesKey, updateSortPropertiesOnce); + + setupSortProperties.call(this); + + + instanceMeta.order = function (itemA, itemB) { + var isProxy = itemB instanceof SearchProxy, + sortProperty, result, asc; + + for (var i = 0; i < this.sortProperties.length; ++i) { + sortProperty = this.sortProperties[i]; + result = compare(get(itemA, sortProperty), isProxy ? itemB[sortProperty] : get(itemB, sortProperty)); + + if (result !== 0) { + asc = this.sortPropertyAscending[sortProperty]; + return asc ? result : (-1 * result); + } + } + + return 0; + }; + + instanceMeta.binarySearch = binarySearch; + }; + } + + return arrayComputed(itemsKey, { + initialize: initFn, + + addedItem: function (array, item, changeMeta, instanceMeta) { + var index = instanceMeta.binarySearch(array, item); + array.insertAt(index, item); + return array; + }, + + removedItem: function (array, item, changeMeta, instanceMeta) { + var proxyProperties, index, searchItem; + + if (changeMeta.previousValues) { + proxyProperties = merge({ content: item }, changeMeta.previousValues); + + searchItem = SearchProxy.create(proxyProperties); + } else { + searchItem = item; + } + + index = instanceMeta.binarySearch(array, searchItem); + array.removeAt(index); + return array; + } + }); + }; + + + __exports__.sum = sum; + __exports__.min = min; + __exports__.max = max; + __exports__.map = map; + __exports__.sort = sort; + __exports__.setDiff = setDiff; + __exports__.mapBy = mapBy; + __exports__.mapProperty = mapProperty; + __exports__.filter = filter; + __exports__.filterBy = filterBy; + __exports__.filterProperty = filterProperty; + __exports__.uniq = uniq; + __exports__.union = union; + __exports__.intersect = intersect; + }); +define("ember-runtime/controllers/array_controller", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/enumerable_utils","ember-runtime/system/array_proxy","ember-runtime/mixins/sortable","ember-runtime/controllers/controller","ember-metal/computed","ember-metal/error","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + var Ember = __dependency1__["default"]; + var get = __dependency2__.get; + var set = __dependency3__.set; + var EnumerableUtils = __dependency4__["default"]; + var ArrayProxy = __dependency5__["default"]; + var SortableMixin = __dependency6__["default"]; + var ControllerMixin = __dependency7__.ControllerMixin; + var computed = __dependency8__.computed; + var EmberError = __dependency9__["default"]; + + var forEach = EnumerableUtils.forEach, + replace = 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 + } + } + }); + ``` + + The itemController instances will have a `parentController` property set to + the `ArrayController` instance. + + @class ArrayController + @namespace Ember + @extends Ember.ArrayProxy + @uses Ember.SortableMixin + @uses Ember.ControllerMixin + */ + + var ArrayController = ArrayProxy.extend(ControllerMixin, 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 { - this[keyName] = value; + return "regular"; // use App.RegularController + } + } + }); + ``` + + @method lookupItemController + @param {Object} object + @return {String} + */ + 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(); + + this.set('_subControllers', Ember.A()); + }, + + content: computed(function () { + return Ember.A(); + }), + + /** + * Flag to mark as being "virtual". Used to keep this instance + * from participating in the parentController hierarchy. + * + * @private + * @type Boolean + */ + _isVirtual: false, + + controllerAt: function(idx, object, controllerClass) { + var container = get(this, 'container'), + subControllers = get(this, '_subControllers'), + subController = subControllers[idx], + fullName; + + if (subController) { return subController; } + + fullName = "controller:" + controllerClass; + + if (!container.has(fullName)) { + throw new EmberError('Could not resolve itemController: "' + controllerClass + '"'); + } + var parentController; + if (this._isVirtual) { + parentController = get(this, 'parentController'); + } + parentController = parentController || this; + subController = container.lookupFactory(fullName).create({ + target: this, + parentController: parentController, + content: object + }); + + subControllers[idx] = subController; + + return subController; + }, + + _subControllers: null, + + _resetSubControllers: function() { + var subControllers = get(this, '_subControllers'); + if (subControllers) { + forEach(subControllers, function(subController) { + if (subController) { subController.destroy(); } + }); + } + + this.set('_subControllers', Ember.A()); + } + }); + + __exports__["default"] = ArrayController; + }); +define("ember-runtime/controllers/controller", + ["ember-metal/core","ember-metal/property_get","ember-runtime/system/object","ember-metal/mixin","ember-metal/computed","ember-runtime/mixins/action_handler","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.assert, Ember.deprecate + var get = __dependency2__.get; + var EmberObject = __dependency3__["default"]; + var Mixin = __dependency4__.Mixin; + var computed = __dependency5__.computed; + var ActionHandler = __dependency6__["default"]; + + /** + @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`. + + @class ControllerMixin + @namespace Ember + @uses Ember.ActionHandler + */ + var ControllerMixin = Mixin.create(ActionHandler, { + /* ducktype as a controller */ + isController: true, + + /** + The object to which actions from the view should be sent. + + For example, when a Handlebars template uses the `{{action}}` helper, + it will attempt to send the action 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, + + parentController: null, + + store: null, + + model: computed.alias('content'), + + deprecatedSendHandles: function(actionName) { + return !!this[actionName]; + }, + + deprecatedSend: function(actionName) { + var args = [].slice.call(arguments, 1); + Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); + Ember.deprecate('Action handlers implemented directly on controllers are deprecated in favor of action handlers on an `actions` object ( action: `' + actionName + '` on ' + this + ')', false); + this[actionName].apply(this, args); + return; + } + }); + + /** + @class Controller + @namespace Ember + @extends Ember.Object + @uses Ember.ControllerMixin + */ + var Controller = EmberObject.extend(ControllerMixin); + + __exports__.Controller = Controller; + __exports__.ControllerMixin = ControllerMixin; + }); +define("ember-runtime/controllers/object_controller", + ["ember-runtime/controllers/controller","ember-runtime/system/object_proxy","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var ControllerMixin = __dependency1__.ControllerMixin; + var ObjectProxy = __dependency2__["default"]; + + /** + @module ember + @submodule ember-runtime + */ + + /** + `Ember.ObjectController` is part of Ember's Controller layer. It is intended + to wrap a single object, proxying unhandled attempts to `get` and `set` to the underlying + content object, and to forward unhandled action attempts to its `target`. + + `Ember.ObjectController` derives this functionality from its superclass + `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin. + + @class ObjectController + @namespace Ember + @extends Ember.ObjectProxy + @uses Ember.ControllerMixin + **/ + var ObjectController = ObjectProxy.extend(ControllerMixin); + __exports__["default"] = ObjectController; + }); +define("ember-runtime/copy", + ["ember-metal/enumerable_utils","ember-metal/utils","ember-runtime/system/object","ember-runtime/mixins/copyable","ember-metal/platform","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { + "use strict"; + var EnumerableUtils = __dependency1__["default"]; + var typeOf = __dependency2__.typeOf; + var EmberObject = __dependency3__["default"]; + var Copyable = __dependency4__["default"]; + var create = __dependency5__.create; + + var indexOf = EnumerableUtils.indexOf; + + 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 EmberObject) || (Copyable && Copyable.detect(obj))); + + // IMPORTANT: this specific test will detect a native array only. Any other + // object will need to implement Copyable. + if (typeOf(obj) === 'array') { + ret = obj.slice(); + if (deep) { + loc = ret.length; + while(--loc>=0) ret[loc] = _copy(ret[loc], deep, seen, copies); + } + } else if (Copyable && Copyable.detect(obj)) { + ret = obj.copy(deep, seen, copies); + } else if (obj instanceof Date) { + ret = new Date(obj.getTime()); + } 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 + */ + function copy(obj, deep) { + // fast paths + if ('object' !== typeof obj || obj===null) return obj; // can't copy primitives + if (Copyable && Copyable.detect(obj)) return obj.copy(deep); + return _copy(obj, deep, deep ? [] : null, deep ? [] : null); + }; + + __exports__["default"] = copy; + }); +define("ember-runtime/core", + ["exports"], + function(__exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + /** + 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} + */ + function isEqual(a, b) { + if (a && 'function'===typeof a.isEqual) return a.isEqual(b); + return a === b; + }; + + __exports__.isEqual = isEqual; + }); +define("ember-runtime/ext/function", + ["ember-metal/core","ember-metal/expand_properties","ember-metal/computed"], + function(__dependency1__, __dependency2__, __dependency3__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + var Ember = __dependency1__["default"]; + // Ember.EXTEND_PROTOTYPES, Ember.assert + var expandProperties = __dependency2__["default"]; + var computed = __dependency3__.computed; + + var a_slice = Array.prototype.slice; + var FunctionPrototype = Function.prototype; + + if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) { + + /** + The `property` extension of Javascript's Function prototype is available + when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is + `true`, which is the default. + + Computed properties allow you to treat a function like a property: + + ```javascript + MyApp.President = Ember.Object.extend({ + firstName: '', + lastName: '', + + fullName: function() { + return this.get('firstName') + ' ' + this.get('lastName'); + + // Call this flag to mark the function as a property + }.property() + }); + + var president = MyApp.President.create({ + firstName: "Barack", + lastName: "Obama" + }); + + president.get('fullName'); // "Barack Obama" + ``` + + Treating a function like a property is useful because they can work with + bindings, just like any other property. + + Many computed properties have dependencies on other properties. For + example, in the above example, the `fullName` property depends on + `firstName` and `lastName` to determine its value. You can tell Ember + about these dependencies like this: + + ```javascript + MyApp.President = Ember.Object.extend({ + firstName: '', + lastName: '', + + fullName: function() { + return this.get('firstName') + ' ' + this.get('lastName'); + + // Tell Ember.js that this computed property depends on firstName + // and lastName + }.property('firstName', 'lastName') + }); + ``` + + Make sure you list these dependencies so Ember knows when to update + bindings that connect to a computed property. Changing a dependency + will not immediately trigger an update of the computed property, but + will instead clear the cache so that it is updated when the next `get` + is called on the property. + + See [Ember.ComputedProperty](/api/classes/Ember.ComputedProperty.html), [Ember.computed](/api/#method_computed). + + @method property + @for Function + */ + FunctionPrototype.property = function() { + var ret = computed(this); + // ComputedProperty.prototype.property expands properties; no need for us to + // do so here. + return ret.property.apply(ret, arguments); + }; + + /** + The `observes` extension of Javascript's Function prototype is available + when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is + true, which is the default. + + You can 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.extend({ + valueObserver: function() { + // Executes whenever the "value" property changes + }.observes('value') + }); + ``` + + In the future this method may become asynchronous. If you want to ensure + synchronous behavior, use `observesImmediately`. + + See `Ember.observer`. + + @method observes + @for Function + */ + FunctionPrototype.observes = function() { + var addWatchedProperty = function (obs) { watched.push(obs); }; + var watched = []; + + for (var i=0; i= 0) return; + if (typeof obj.hasOwnProperty === 'function' && !obj.hasOwnProperty(key)) return; + + array.push(key); + }; + + keys = function keys(obj) { + var ret = [], key; + for (key in obj) { + pushPropertyName(obj, ret, key); + } + + // IE8 doesn't enumerate property that named the same as prototype properties. + for (var i = 0, l = prototypeProperties.length; i < l; i++) { + key = prototypeProperties[i]; + + pushPropertyName(obj, ret, key); + } + + return ret; + }; + } + + __exports__["default"] = keys; + }); +define("ember-runtime", + ["ember-metal","ember-runtime/core","ember-runtime/keys","ember-runtime/compare","ember-runtime/copy","ember-runtime/system/namespace","ember-runtime/system/object","ember-runtime/system/tracked_array","ember-runtime/system/subarray","ember-runtime/system/container","ember-runtime/system/application","ember-runtime/system/array_proxy","ember-runtime/system/object_proxy","ember-runtime/system/core_object","ember-runtime/system/each_proxy","ember-runtime/system/native_array","ember-runtime/system/set","ember-runtime/system/string","ember-runtime/system/deferred","ember-runtime/system/lazy_load","ember-runtime/mixins/array","ember-runtime/mixins/comparable","ember-runtime/mixins/copyable","ember-runtime/mixins/enumerable","ember-runtime/mixins/freezable","ember-runtime/mixins/observable","ember-runtime/mixins/action_handler","ember-runtime/mixins/deferred","ember-runtime/mixins/mutable_enumerable","ember-runtime/mixins/mutable_array","ember-runtime/mixins/target_action_support","ember-runtime/mixins/evented","ember-runtime/mixins/promise_proxy","ember-runtime/mixins/sortable","ember-runtime/computed/array_computed","ember-runtime/computed/reduce_computed","ember-runtime/computed/reduce_computed_macros","ember-runtime/controllers/array_controller","ember-runtime/controllers/object_controller","ember-runtime/controllers/controller","ember-runtime/ext/rsvp","ember-runtime/ext/string","ember-runtime/ext/function","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __dependency21__, __dependency22__, __dependency23__, __dependency24__, __dependency25__, __dependency26__, __dependency27__, __dependency28__, __dependency29__, __dependency30__, __dependency31__, __dependency32__, __dependency33__, __dependency34__, __dependency35__, __dependency36__, __dependency37__, __dependency38__, __dependency39__, __dependency40__, __dependency41__, __dependency42__, __dependency43__, __exports__) { + "use strict"; + /** + Ember Runtime + + @module ember + @submodule ember-runtime + @requires ember-metal + */ + + + // BEGIN EXPORTS + Ember.compare = __dependency4__["default"]; + Ember.copy = __dependency5__["default"]; + Ember.isEqual = __dependency2__.isEqual; + Ember.keys = __dependency3__["default"]; + + Ember.Array = __dependency21__["default"]; + + Ember.Comparable = __dependency22__["default"]; + Ember.Copyable = __dependency23__["default"]; + + Ember.SortableMixin = __dependency34__["default"]; + + Ember.Freezable = __dependency25__.Freezable; + Ember.FROZEN_ERROR = __dependency25__.FROZEN_ERROR; + + Ember.DeferredMixin = __dependency28__["default"]; + + Ember.MutableEnumerable = __dependency29__["default"]; + Ember.MutableArray = __dependency30__["default"]; + + Ember.TargetActionSupport = __dependency31__["default"]; + Ember.Evented = __dependency32__["default"]; + + Ember.PromiseProxyMixin = __dependency33__["default"]; + + Ember.Observable = __dependency26__["default"]; + + Ember.arrayComputed = __dependency35__.arrayComputed; + Ember.ArrayComputedProperty = __dependency35__.ArrayComputedProperty; + Ember.reduceComputed = __dependency36__.reduceComputed; + Ember.ReduceComputedProperty = __dependency36__.ReduceComputedProperty; + + // ES6TODO: this seems a less than ideal way/place to add properties to Ember.computed + var EmComputed = Ember.computed; + + EmComputed.sum = __dependency37__.sum; + EmComputed.min = __dependency37__.min; + EmComputed.max = __dependency37__.max; + EmComputed.map = __dependency37__.map; + EmComputed.sort = __dependency37__.sort; + EmComputed.setDiff = __dependency37__.setDiff; + EmComputed.mapBy = __dependency37__.mapBy; + EmComputed.mapProperty = __dependency37__.mapProperty; + EmComputed.filter = __dependency37__.filter; + EmComputed.filterBy = __dependency37__.filterBy; + EmComputed.filterProperty = __dependency37__.filterProperty; + EmComputed.uniq = __dependency37__.uniq; + EmComputed.union = __dependency37__.union; + EmComputed.intersect = __dependency37__.intersect; + + Ember.String = __dependency18__["default"]; + Ember.Object = __dependency7__["default"]; + Ember.TrackedArray = __dependency8__["default"]; + Ember.SubArray = __dependency9__["default"]; + Ember.Container = __dependency10__["default"]; + Ember.Namespace = __dependency6__["default"]; + Ember.Application = __dependency11__["default"]; + Ember.Enumerable = __dependency24__["default"]; + Ember.ArrayProxy = __dependency12__["default"]; + Ember.ObjectProxy = __dependency13__["default"]; + Ember.ActionHandler = __dependency27__["default"]; + Ember.CoreObject = __dependency14__["default"]; + Ember.EachArray = __dependency15__.EachArray; + Ember.EachProxy = __dependency15__.EachProxy; + Ember.NativeArray = __dependency16__["default"]; + // ES6TODO: Currently we must rely on the global from ember-metal/core to avoid circular deps + // Ember.A = A; + Ember.Set = __dependency17__["default"]; + Ember.Deferred = __dependency19__["default"]; + Ember.onLoad = __dependency20__.onLoad; + Ember.runLoadHooks = __dependency20__.runLoadHooks; + + Ember.ArrayController = __dependency38__["default"]; + Ember.ObjectController = __dependency39__["default"]; + Ember.Controller = __dependency40__.Controller; + Ember.ControllerMixin = __dependency40__.ControllerMixin; + // END EXPORTS + + __exports__["default"] = Ember; + }); +define("ember-runtime/mixins/action_handler", + ["ember-metal/merge","ember-metal/mixin","ember-metal/property_get","ember-metal/utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + var merge = __dependency1__["default"]; + var Mixin = __dependency2__.Mixin; + var get = __dependency3__.get; + var typeOf = __dependency4__.typeOf; + + /** + The `Ember.ActionHandler` mixin implements support for moving an `actions` + property to an `_actions` property at extend time, and adding `_actions` + to the object's mergedProperties list. + + `Ember.ActionHandler` is used internally by Ember in `Ember.View`, + `Ember.Controller`, and `Ember.Route`. + + @class ActionHandler + @namespace Ember + */ + var ActionHandler = Mixin.create({ + mergedProperties: ['_actions'], + + /** + The collection of functions, keyed by name, available on this + `ActionHandler` as action targets. + + These functions will be invoked when a matching `{{action}}` is triggered + from within a template and the application's current route is this route. + + Actions can also be invoked from other parts of your application + via `ActionHandler#send`. + + The `actions` hash will inherit action handlers from + the `actions` hash defined on extended parent classes + or mixins rather than just replace the entire hash, e.g.: + + ```js + App.CanDisplayBanner = Ember.Mixin.create({ + actions: { + displayBanner: function(msg) { + // ... + } + } + }); + + App.WelcomeRoute = Ember.Route.extend(App.CanDisplayBanner, { + actions: { + playMusic: function() { + // ... + } + } + }); + + // `WelcomeRoute`, when active, will be able to respond + // to both actions, since the actions hash is merged rather + // then replaced when extending mixins / parent classes. + this.send('displayBanner'); + this.send('playMusic'); + ``` + + Within a Controller, Route, View or Component's action handler, + the value of the `this` context is the Controller, Route, View or + Component object: + + ```js + App.SongRoute = Ember.Route.extend({ + actions: { + myAction: function() { + this.controllerFor("song"); + this.transitionTo("other.route"); + ... + } + } + }); + ``` + + It is also possible to call `this._super()` from within an + action handler if it overrides a handler defined on a parent + class or mixin: + + Take for example the following routes: + + ```js + App.DebugRoute = Ember.Mixin.create({ + actions: { + debugRouteInformation: function() { + console.debug("trololo"); + } + } + }); + + App.AnnoyingDebugRoute = Ember.Route.extend(App.DebugRoute, { + actions: { + debugRouteInformation: function() { + // also call the debugRouteInformation of mixed in App.DebugRoute + this._super(); + + // show additional annoyance + window.alert(...); + } + } + }); + ``` + + ## Bubbling + + By default, an action will stop bubbling once a handler defined + on the `actions` hash handles it. To continue bubbling the action, + you must return `true` from the handler: + + ```js + App.Router.map(function() { + this.resource("album", function() { + this.route("song"); + }); + }); + + App.AlbumRoute = Ember.Route.extend({ + actions: { + startPlaying: function() { + } + } + }); + + App.AlbumSongRoute = Ember.Route.extend({ + actions: { + startPlaying: function() { + // ... + + if (actionShouldAlsoBeTriggeredOnParentRoute) { + return true; + } + } + } + }); + ``` + + @property actions + @type Hash + @default null + */ + + /** + Moves `actions` to `_actions` at extend time. Note that this currently + modifies the mixin themselves, which is technically dubious but + is practically of little consequence. This may change in the future. + + @private + @method willMergeMixin + */ + willMergeMixin: function(props) { + var hashName; + + if (!props._actions) { + Ember.assert("'actions' should not be a function", typeof(props.actions) !== 'function'); + + if (typeOf(props.actions) === 'object') { + hashName = 'actions'; + } else if (typeOf(props.events) === 'object') { + Ember.deprecate('Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object', false); + hashName = 'events'; + } + + if (hashName) { + props._actions = merge(props._actions || {}, props[hashName]); + } + + delete props[hashName]; + } + }, + + /** + Triggers a named action on the `ActionHandler`. Any parameters + supplied after the `actionName` string will be passed as arguments + to the action target function. + + If the `ActionHandler` has its `target` property set, actions may + bubble to the `target`. Bubbling happens when an `actionName` can + not be found in the `ActionHandler`'s `actions` hash or if the + action target function returns `true`. + + Example + + ```js + App.WelcomeRoute = Ember.Route.extend({ + actions: { + playTheme: function() { + this.send('playMusic', 'theme.mp3'); + }, + playMusic: function(track) { + // ... + } + } + }); + ``` + + @method send + @param {String} actionName The action to trigger + @param {*} context a context to send with the action + */ + send: function(actionName) { + var args = [].slice.call(arguments, 1), target; + + if (this._actions && this._actions[actionName]) { + if (this._actions[actionName].apply(this, args) === true) { + // handler returned true, so this action will bubble + } else { + return; + } + } else if (!Ember.FEATURES.isEnabled('ember-routing-drop-deprecated-action-style') && this.deprecatedSend && this.deprecatedSendHandles && this.deprecatedSendHandles(actionName)) { + Ember.warn("The current default is deprecated but will prefer to handle actions directly on the controller instead of a similarly named action in the actions hash. To turn off this deprecated feature set: Ember.FEATURES['ember-routing-drop-deprecated-action-style'] = true"); + if (this.deprecatedSend.apply(this, [].slice.call(arguments)) === true) { + // handler return true, so this action will bubble + } else { + return; + } + } + + if (target = get(this, 'target')) { + Ember.assert("The `target` for " + this + " (" + target + ") does not have a `send` method", typeof target.send === 'function'); + target.send.apply(target, arguments); + } + } + }); + + __exports__["default"] = ActionHandler; + }); +define("ember-runtime/mixins/array", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/computed","ember-metal/is_none","ember-runtime/mixins/enumerable","ember-metal/enumerable_utils","ember-metal/mixin","ember-metal/property_events","ember-metal/events","ember-metal/watching","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + // .......................................................... + // HELPERS + // + var Ember = __dependency1__["default"]; + // ES6TODO: Ember.A + + var get = __dependency2__.get; + var set = __dependency3__.set; + var computed = __dependency4__.computed; + var cacheFor = __dependency4__.cacheFor; + var isNone = __dependency5__.isNone; + var none = __dependency5__.none; + var Enumerable = __dependency6__["default"]; + var EnumerableUtils = __dependency7__["default"]; + var Mixin = __dependency8__.Mixin; + var required = __dependency8__.required; + var propertyWillChange = __dependency9__.propertyWillChange; + var propertyDidChange = __dependency9__.propertyDidChange; + var addListener = __dependency10__.addListener; + var removeListener = __dependency10__.removeListener; + var sendEvent = __dependency10__.sendEvent; + var hasListeners = __dependency10__.hasListeners; + var isWatching = __dependency11__.isWatching; + + var map = EnumerableUtils.map; + + // .......................................................... + // ARRAY + // + /** + This mixin implements Observer-friendly Array-like behavior. It is not a + concrete implementation, but it can be used up by other classes that want + to appear like arrays. + + For example, ArrayProxy and ArrayController are both concrete classes that can + be instantiated to implement array-like behavior. Both of these classes use + the Array Mixin by way of the MutableArray mixin, which allows observable + changes to be made to the underlying array. + + 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 of 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 + @uses Ember.Enumerable + @since Ember 0.9.0 + */ + var EmberArray = Mixin.create(Enumerable, { + + /** + Your array must support the `length` property. Your replace methods should + set this property whenever it changes. + + @property {Number} length + */ + length: 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 {*} 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 + */ + '[]': computed(function(key, value) { + if (value !== undefined) this.replace(0, get(this, 'length'), value) ; + return this ; + }), + + firstObject: computed(function() { + return this.objectAt(0); + }), + + lastObject: 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 (but not included). + @return {Array} New array with specified slice + */ + slice: function(beginIndex, endIndex) { + var ret = Ember.A(); + var length = get(this, 'length') ; + if (isNone(beginIndex)) beginIndex = 0 ; + if (isNone(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(observedObj, start, removeCount, addCount)` - This method will be + called just before the array is modified. + * `arrayDidChange(observedObj, start, removeCount, addCount)` - This method will be + called just after the array is modified. + + Both callbacks will be passed the observed object, 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) propertyWillChange(this, 'hasArrayObservers'); + addListener(this, '@array:before', target, willChange); + addListener(this, '@array:change', target, didChange); + if (!hasObservers) 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) propertyWillChange(this, 'hasArrayObservers'); + removeListener(this, '@array:before', target, willChange); + removeListener(this, '@array:change', target, didChange); + if (hasObservers) propertyDidChange(this, 'hasArrayObservers'); + return this; + }, + + /** + Becomes true whenever the array currently has observers watching changes + on the array. + + @property {Boolean} hasArrayObservers + */ + hasArrayObservers: computed(function() { + return hasListeners(this, '@array:change') || 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 (isWatching(this, '@each')) { get(this, '@each'); } + + 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: required(Function) + + }); + + __exports__["default"] = Comparable; + }); +define("ember-runtime/mixins/copyable", + ["ember-metal/property_get","ember-metal/property_set","ember-metal/mixin","ember-runtime/mixins/freezable","ember-runtime/system/string","ember-metal/error","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + + var get = __dependency1__.get; + var set = __dependency2__.set; + var required = __dependency3__.required; + var Freezable = __dependency4__.Freezable; + var Mixin = __dependency3__.Mixin; + var EmberStringUtils = __dependency5__["default"]; + var EmberError = __dependency6__["default"]; + + + /** + 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 + @since Ember 0.9 + */ + var Copyable = Mixin.create({ + + /** + 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: 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 (Freezable && Freezable.detect(this)) { + return get(this, 'isFrozen') ? this : this.copy().freeze(); + } else { + throw new EmberError(EmberStringUtils.fmt("%@ does not support freezing", [this])); + } + } + }); + + __exports__["default"] = Copyable; + }); +define("ember-runtime/mixins/deferred", + ["ember-metal/core","ember-metal/property_get","ember-metal/mixin","ember-metal/computed","ember-metal/run_loop","ember-runtime/ext/rsvp","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.FEATURES, Ember.Test + var get = __dependency2__.get; + var Mixin = __dependency3__.Mixin; + var computed = __dependency4__.computed; + var run = __dependency5__["default"]; + var RSVP = __dependency6__["default"]; + + if (Ember.FEATURES['ember-runtime-test-friendly-promises']) { + + var asyncStart = function() { + if (Ember.Test && Ember.Test.adapter) { + Ember.Test.adapter.asyncStart(); + } + }; + + var asyncEnd = function() { + if (Ember.Test && Ember.Test.adapter) { + Ember.Test.adapter.asyncEnd(); + } + }; + + RSVP.configure('async', function(callback, promise) { + var async = !run.currentRunLoop; + + if (Ember.testing && async) { asyncStart(); } + + run.backburner.schedule('actions', function(){ + if (Ember.testing && async) { asyncEnd(); } + callback(promise); + }); + }); + } else { + RSVP.configure('async', function(callback, promise) { + run.backburner.schedule('actions', function(){ + callback(promise); + }); + }); + } + + RSVP.Promise.prototype.fail = function(callback, label){ + Ember.deprecate('RSVP.Promise.fail has been renamed as RSVP.Promise.catch'); + return this['catch'](callback, label); + }; + + /** + @module ember + @submodule ember-runtime + */ + + + /** + @class Deferred + @namespace Ember + */ + var DeferredMixin = Mixin.create({ + /** + Add handlers to be called when the Deferred object is resolved or rejected. + + @method then + @param {Function} resolve a callback function to be called when done + @param {Function} reject a callback function to be called when failed + */ + then: function(resolve, reject, label) { + var deferred, promise, entity; + + entity = this; + deferred = get(this, '_deferred'); + promise = deferred.promise; + + function fulfillmentHandler(fulfillment) { + if (fulfillment === promise) { + return resolve(entity); + } else { + return resolve(fulfillment); + } + } + + return promise.then(resolve && fulfillmentHandler, reject, label); + }, + + /** + Resolve a Deferred object and call any `doneCallbacks` with the given args. + + @method resolve + */ + resolve: function(value) { + var deferred, promise; + + deferred = get(this, '_deferred'); + promise = deferred.promise; + + if (value === this) { + deferred.resolve(promise); + } else { + deferred.resolve(value); + } + }, + + /** + Reject a Deferred object and call any `failCallbacks` with the given args. + + @method reject + */ + reject: function(value) { + get(this, '_deferred').reject(value); + }, + + _deferred: computed(function() { + return RSVP.defer('Ember: DeferredMixin - ' + this); + }) + }); + + __exports__["default"] = DeferredMixin; + }); +define("ember-runtime/mixins/enumerable", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/mixin","ember-metal/enumerable_utils","ember-metal/computed","ember-metal/property_events","ember-metal/events","ember-runtime/compare","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + // .......................................................... + // HELPERS + // + + var Ember = __dependency1__["default"]; + var get = __dependency2__.get; + var set = __dependency3__.set; + var apply = __dependency4__.apply; + var Mixin = __dependency5__.Mixin; + var required = __dependency5__.required; + var aliasMethod = __dependency5__.aliasMethod; + var EnumerableUtils = __dependency6__["default"]; + var computed = __dependency7__.computed; + var propertyWillChange = __dependency8__.propertyWillChange; + var propertyDidChange = __dependency8__.propertyDidChange; + var addListener = __dependency9__.addListener; + var removeListener = __dependency9__.removeListener; + var sendEvent = __dependency9__.sendEvent; + var hasListeners = __dependency9__.hasListeners; + var compare = __dependency10__["default"]; + + var a_slice = Array.prototype.slice; + var a_indexOf = EnumerableUtils.indexOf; + + var contexts = []; + + function popCtx() { + return contexts.length===0 ? {} : contexts.pop(); + } + + function pushCtx(ctx) { + contexts.push(ctx); + return null; + } + + function iter(key, value) { + var valueProvided = arguments.length === 2; + + function i(item) { + var cur = get(item, key); + return valueProvided ? value===cur : !!cur; + } + return i ; + } + + /** + This mixin defines the common interface implemented by enumerable objects + in Ember. Most of these methods follow the standard Array iteration + API defined up to JavaScript 1.8 (excluding language-specific features that + cannot be emulated in older versions of JavaScript). + + This mixin is applied automatically to the Array class on page load, so you + can use any of these methods on simple arrays. If Array already implements + one of these methods, the mixin will not override them. + + ## Writing Your Own Enumerable + + To make your own custom class enumerable, you need two items: + + 1. You must have a length property. This property should change whenever + the number of items in your enumerable object changes. If you use this + with an `Ember.Object` subclass, you should be sure to change the length + property using `set().` + + 2. You must implement `nextObject().` See documentation. + + Once you have these two methods implemented, apply the `Ember.Enumerable` mixin + to your class and you will be able to enumerate the contents of your object + like any other collection. + + ## Using Ember Enumeration with Other Libraries + + Many other libraries provide some kind of iterator or enumeration like + facility. This is often where the most common API conflicts occur. + Ember's API is designed to be as friendly as possible with other + libraries by implementing only methods that mostly correspond to the + JavaScript 1.8 API. + + @class Enumerable + @namespace Ember + @since Ember 0.9 + */ + var Enumerable = Mixin.create({ + + /** + Implement this method to make your class enumerable. + + This method will be call repeatedly during enumeration. The index value + will always begin with 0 and increment monotonically. You don't have to + rely on the index value to determine what object to return, but you should + always check the value and start from the beginning when you see the + requested index is 0. + + The `previousObject` is the object that was returned from the last call + to `nextObject` for the current iteration. This is a useful way to + manage iteration if you are tracing a linked list, for example. + + Finally the context parameter will always contain a hash you can use as + a "scratchpad" to maintain any other state you need in order to iterate + properly. The context object is reused and is not reset between + iterations so make sure you setup the context with a fresh state whenever + the index parameter is 0. + + Generally iterators will continue to call `nextObject` until the index + reaches the your current length-1. If you run out of data before this + time for some reason, you should simply return undefined. + + The default implementation of this method simply looks up the index. + This works great on any Array-like objects. + + @method nextObject + @param {Number} index the current index of the iteration + @param {Object} previousObject the value returned by the last call to + `nextObject`. + @param {Object} context a context object you can use to maintain state. + @return {Object} the next object in the iteration or undefined + */ + nextObject: required(Function), + + /** + Helper method returns the first object from a collection. This is usually + used by bindings and other parts of the framework to extract a single + object if the enumerable contains only one item. + + If you override this method, you should implement it so that it will + always return the same value each time it is called. If your enumerable + contains only one object, this method should always return that object. + If your enumerable is empty, this method should return `undefined`. + + ```javascript + var arr = ["a", "b", "c"]; + arr.get('firstObject'); // "a" + + var arr = []; + arr.get('firstObject'); // undefined + ``` + + @property firstObject + @return {Object} the object or undefined + */ + firstObject: computed(function() { + if (get(this, 'length')===0) return undefined ; + + // handle generic enumerables + var context = popCtx(), ret; + ret = this.nextObject(0, null, context); + pushCtx(context); + return ret ; + }).property('[]'), + + /** + Helper method returns the last object from a collection. If your enumerable + contains only one object, this method should always return that object. + If your enumerable is empty, this method should return `undefined`. + + ```javascript + var arr = ["a", "b", "c"]; + arr.get('lastObject'); // "c" + + var arr = []; + arr.get('lastObject'); // undefined + ``` + + @property lastObject + @return {Object} the last object or undefined + */ + lastObject: computed(function() { + var len = get(this, 'length'); + if (len===0) return undefined ; + var context = popCtx(), idx=0, cur, last = null; + do { + last = cur; + cur = this.nextObject(idx++, last, context); + } while (cur !== undefined); + pushCtx(context); + return last; + }).property('[]'), + + /** + Returns `true` if the passed object can be found in the receiver. The + default version will iterate through the enumerable until the object + is found. You may want to override this with a more efficient version. + + ```javascript + var arr = ["a", "b", "c"]; + arr.contains("a"); // true + arr.contains("z"); // false + ``` + + @method contains + @param {Object} obj The object to search for. + @return {Boolean} `true` if object is found in enumerable. + */ + contains: function(obj) { + return this.find(function(item) { return item===obj; }) !== undefined; + }, + + /** + Iterates through the enumerable, calling the passed function on each + item. This method corresponds to the `forEach()` method defined in + JavaScript 1.6. + + The callback method you provide should have the following signature (all + parameters are optional): + + ```javascript + function(item, index, enumerable); + ``` + + - `item` is the current item in the iteration. + - `index` is the current index in the iteration. + - `enumerable` is the enumerable object itself. + + Note that in addition to a callback, you can also pass an optional target + object that will be set as `this` on the context. This is a good way + to give your iterator function access to the current object. + + @method forEach + @param {Function} callback The callback to execute + @param {Object} [target] The target object to use + @return {Object} receiver + */ + forEach: function(callback, target) { + if (typeof callback !== "function") throw new TypeError() ; + var len = get(this, 'length'), last = null, context = popCtx(); + + if (target === undefined) target = null; + + for(var idx=0;idx1) args = a_slice.call(arguments, 1); + + this.forEach(function(x, idx) { + var method = x && x[methodName]; + if ('function' === typeof method) { + ret[idx] = args ? apply(x, method, args) : x[methodName](); + } + }, 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. `Array` overrides + this method. + + @property [] + @type Array + @return this + */ + '[]': 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) propertyWillChange(this, 'hasEnumerableObservers'); + addListener(this, '@enumerable:before', target, willChange); + addListener(this, '@enumerable:change', target, didChange); + if (!hasObservers) 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) propertyWillChange(this, 'hasEnumerableObservers'); + removeListener(this, '@enumerable:before', target, willChange); + removeListener(this, '@enumerable:change', target, didChange); + if (hasObservers) propertyDidChange(this, 'hasEnumerableObservers'); + return this; + }, + + /** + Becomes true whenever the array currently has observers watching changes + on the array. + + @property hasEnumerableObservers + @type Boolean + */ + hasEnumerableObservers: computed(function() { + return hasListeners(this, '@enumerable:change') || 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; + + propertyWillChange(this, '[]'); + if (hasDelta) propertyWillChange(this, 'length'); + 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 {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; + + sendEvent(this, '@enumerable:change', [this, removing, adding]); + if (hasDelta) propertyDidChange(this, 'length'); + propertyDidChange(this, '[]'); + + return this ; + }, + + /** + Converts the enumerable into an array and sorts by the keys + specified in the argument. + + You may provide multiple arguments to sort by multiple properties. + + @method sortBy + @param {String} property name(s) to sort on + @return {Array} The sorted array. + */ + sortBy: function() { + var sortKeys = arguments; + return this.toArray().sort(function(a, b){ + for(var i = 0; i < sortKeys.length; i++) { + var key = sortKeys[i], + propA = get(a, key), + propB = get(b, key); + // return 1 or -1 else continue to the next sortKey + var compareValue = compare(propA, propB); + if (compareValue) { return compareValue; } + } + return 0; + }); + } + }); + + __exports__["default"] = Enumerable; + }); +define("ember-runtime/mixins/evented", + ["ember-metal/mixin","ember-metal/events","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var Mixin = __dependency1__.Mixin; + var addListener = __dependency2__.addListener; + var removeListener = __dependency2__.removeListener; + var hasListeners = __dependency2__.hasListeners; + var sendEvent = __dependency2__.sendEvent; + + /** + @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 + */ + var Evented = 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) { + 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; + } + + 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]); + } + sendEvent(this, name, args); + }, + + /** + Cancels subscription for given 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) { + 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 hasListeners(this, name); + } + }); + + __exports__["default"] = Evented; + }); +define("ember-runtime/mixins/freezable", + ["ember-metal/mixin","ember-metal/property_get","ember-metal/property_set","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + var Mixin = __dependency1__.Mixin; + var get = __dependency2__.get; + var set = __dependency3__.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 = Contact.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 + @since Ember 0.9 + */ + var Freezable = Mixin.create({ + + /** + 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; + } + + }); + + var FROZEN_ERROR = "Frozen object cannot be modified."; + + __exports__.Freezable = Freezable; + __exports__.FROZEN_ERROR = FROZEN_ERROR; + }); +define("ember-runtime/mixins/mutable_array", + ["ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/error","ember-metal/mixin","ember-runtime/mixins/array","ember-runtime/mixins/mutable_enumerable","ember-runtime/mixins/enumerable","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + + // require('ember-runtime/mixins/array'); + // require('ember-runtime/mixins/mutable_enumerable'); + + // .......................................................... + // CONSTANTS + // + + var OUT_OF_RANGE_EXCEPTION = "Index out of range" ; + var EMPTY = []; + + // .......................................................... + // HELPERS + // + + var get = __dependency1__.get; + var set = __dependency2__.set; + var isArray = __dependency3__.isArray; + var EmberError = __dependency4__["default"]; + var Mixin = __dependency5__.Mixin; + var required = __dependency5__.required; + var EmberArray = __dependency6__["default"]; + var MutableEnumerable = __dependency7__["default"]; + var Enumerable = __dependency8__["default"]; + /** + 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. + It builds upon the Array mixin and adds methods to modify the array. + Concrete implementations of this class include ArrayProxy and ArrayController. + + It is important to use the methods in this class to modify arrays so that + changes are observable. This allows the binding system in Ember to function + correctly. + + + 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 + @uses Ember.Array + @uses Ember.MutableEnumerable + */ + var MutableArray = Mixin.create(EmberArray, MutableEnumerable, { + + /** + __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: 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 EmberError(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 `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 EmberError(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"]; + colors.pushObject("black"); // ["red", "green", "black"] + colors.pushObject(["yellow"]); // ["red", "green", ["yellow"]] + ``` + + @method pushObject + @param {*} obj object to push + @return 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"]; + colors.pushObjects(["yellow", "orange"]); // ["red", "yellow", "orange"] + ``` + + @method pushObjects + @param {Ember.Enumerable} objects the objects to add + @return {Ember.Array} receiver + */ + pushObjects: function(objects) { + if (!(Enumerable.detect(objects) || isArray(objects))) { + throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects"); + } + 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"]; + colors.unshiftObject("yellow"); // ["yellow", "red"] + colors.unshiftObject(["black"]); // [["black"], "yellow", "red"] + ``` + + @method unshiftObject + @param {*} obj object to unshift + @return 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"]; + colors.unshiftObjects(["black", "white"]); // ["black", "white", "red"] + 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 ; + } + + }); + + __exports__["default"] = MutableArray; + }); +define("ember-runtime/mixins/mutable_enumerable", + ["ember-metal/enumerable_utils","ember-runtime/mixins/enumerable","ember-metal/mixin","ember-metal/property_events","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + var EnumerableUtils = __dependency1__["default"]; + var Enumerable = __dependency2__["default"]; + var Mixin = __dependency3__.Mixin; + var required = __dependency3__.required; + var beginPropertyChanges = __dependency4__.beginPropertyChanges; + var endPropertyChanges = __dependency4__.endPropertyChanges; + + /** + @module ember + @submodule ember-runtime + */ + + var forEach = 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 is of a type supported by the enumerable. + + ```javascript + set.addObject(contact); + ``` + + ## Removing Objects + + To remove an object from an enumerable, use the `removeObject()` method. This + will only remove the object if it is present 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 + @uses Ember.Enumerable + */ + var MutableEnumerable = Mixin.create(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: 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) { + beginPropertyChanges(this); + forEach(objects, function(obj) { this.addObject(obj); }, this); + 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 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: required(Function), + + + /** + Removes each object in the passed enumerable from the receiver. + + @method removeObjects + @param {Ember.Enumerable} objects the objects to remove + @return {Object} receiver + */ + removeObjects: function(objects) { + beginPropertyChanges(this); + forEach(objects, function(obj) { this.removeObject(obj); }, this); + endPropertyChanges(this); + return this; + } + }); + + __exports__["default"] = MutableEnumerable; + }); +define("ember-runtime/mixins/observable", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/get_properties","ember-metal/set_properties","ember-metal/mixin","ember-metal/events","ember-metal/property_events","ember-metal/observer","ember-metal/computed","ember-metal/is_none","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + var Ember = __dependency1__["default"]; + // Ember.assert + + var get = __dependency2__.get; + var getWithDefault = __dependency2__.getWithDefault; + var set = __dependency3__.set; + var apply = __dependency4__.apply; + var getProperties = __dependency5__["default"]; + var setProperties = __dependency6__["default"]; + var Mixin = __dependency7__.Mixin; + var hasListeners = __dependency8__.hasListeners; + var beginPropertyChanges = __dependency9__.beginPropertyChanges; + var propertyWillChange = __dependency9__.propertyWillChange; + var propertyDidChange = __dependency9__.propertyDidChange; + var endPropertyChanges = __dependency9__.endPropertyChanges; + var addObserver = __dependency10__.addObserver; + var addBeforeObserver = __dependency10__.addBeforeObserver; + var removeObserver = __dependency10__.removeObserver; + var observersFor = __dependency10__.observersFor; + var cacheFor = __dependency11__.cacheFor; + var isNone = __dependency12__.isNone; + + + var slice = Array.prototype.slice; + /** + ## 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.extend({ + 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` 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 + */ + var Observable = Mixin.create({ + + /** + 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.get('firstName') + ' ' + this.get('lastName'); + }.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() { + return apply(null, getProperties, [this].concat(slice.call(arguments))); + }, + + /** + 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 `setUnknownProperty()` 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 `setUnknownProperty()` handler will be called instead. This + gives you an opportunity to implement complex "virtual" properties that + are not predefined on the object. If `setUnknownProperty()` 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; + }, + + + /** + Sets a list of properties at once. These properties are set inside + a single `beginPropertyChanges` and `endPropertyChanges` batch, so + observers will be buffered. + + ```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 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() { + 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() { + 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) { + 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) { + 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) { + 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, any time the key's value is set, your observer + will be notified. Note that the observers are triggered any time 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) { + 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) { + 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 hasListeners(this, key+':change'); + }, + + /** + 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 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 {Number} increment The amount to increment by. Defaults to 1 + @return {Number} The new property value + */ + incrementProperty: function(keyName, increment) { + if (isNone(increment)) { increment = 1; } + Ember.assert("Must pass a numeric value to incrementProperty", (!isNaN(parseFloat(increment)) && isFinite(increment))); + 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 {Number} decrement The amount to decrement by. Defaults to 1 + @return {Number} The new property value + */ + decrementProperty: function(keyName, decrement) { + if (isNone(decrement)) { decrement = 1; } + Ember.assert("Must pass a numeric value to decrementProperty", (!isNaN(parseFloat(decrement)) && isFinite(decrement))); + set(this, keyName, (get(this, keyName) || 0) - decrement); + return get(this, keyName); + }, + + /** + Set the value of a boolean property to the opposite of it's + current value. + + ```javascript + starship.toggleProperty('warpDriveEngaged'); + ``` + + @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 cacheFor(this, keyName); + }, + + // intended for debugging purposes + observersForKey: function(keyName) { + return observersFor(this, keyName); + } + }); + + __exports__["default"] = Observable; + }); +define("ember-runtime/mixins/promise_proxy", + ["ember-metal/property_get","ember-metal/property_set","ember-metal/computed","ember-metal/mixin","ember-metal/error","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { + "use strict"; + var get = __dependency1__.get; + var set = __dependency2__.set; + var computed = __dependency3__.computed; + var Mixin = __dependency4__.Mixin; + var EmberError = __dependency5__["default"]; + + var not = computed.not, or = computed.or; + + /** + @module ember + @submodule ember-runtime + */ + + function tap(proxy, promise) { + set(proxy, 'isFulfilled', false); + set(proxy, 'isRejected', false); + + return promise.then(function(value) { + set(proxy, 'isFulfilled', true); + set(proxy, 'content', value); + return value; + }, function(reason) { + set(proxy, 'isRejected', true); + set(proxy, 'reason', reason); + throw reason; + }, "Ember: PromiseProxy"); + } + + /** + A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware. + + ```javascript + var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin); + + var controller = ObjectPromiseController.create({ + promise: $.getJSON('/some/remote/data.json') + }); + + controller.then(function(json){ + // the json + }, function(reason) { + // the reason why you have no json + }); + ``` + + the controller has bindable attributes which + track the promises life cycle + + ```javascript + controller.get('isPending') //=> true + controller.get('isSettled') //=> false + controller.get('isRejected') //=> false + controller.get('isFulfilled') //=> false + ``` + + When the the $.getJSON completes, and the promise is fulfilled + with json, the life cycle attributes will update accordingly. + + ```javascript + controller.get('isPending') //=> false + controller.get('isSettled') //=> true + controller.get('isRejected') //=> false + controller.get('isFulfilled') //=> true + ``` + + As the controller is an ObjectController, and the json now its content, + all the json properties will be available directly from the controller. + + ```javascript + // Assuming the following json: + { + firstName: 'Stefan', + lastName: 'Penner' + } + + // both properties will accessible on the controller + controller.get('firstName') //=> 'Stefan' + controller.get('lastName') //=> 'Penner' + ``` + + If the controller is backing a template, the attributes are + bindable from within that template + + ```handlebars + {{#if isPending}} + loading... + {{else}} + firstName: {{firstName}} + lastName: {{lastName}} + {{/if}} + ``` + @class Ember.PromiseProxyMixin + */ + var PromiseProxyMixin = Mixin.create({ + /** + If the proxied promise is rejected this will contain the reason + provided. + + @property reason + @default null + */ + reason: null, + + /** + Once the proxied promise has settled this will become `false`. + + @property isPending + @default true + */ + isPending: not('isSettled').readOnly(), + + /** + Once the proxied promise has settled this will become `true`. + + @property isSettled + @default false + */ + isSettled: or('isRejected', 'isFulfilled').readOnly(), + + /** + Will become `true` if the proxied promise is rejected. + + @property isRejected + @default false + */ + isRejected: false, + + /** + Will become `true` if the proxied promise is fulfilled. + + @property isFullfilled + @default false + */ + isFulfilled: false, + + /** + The promise whose fulfillment value is being proxied by this object. + + This property must be specified upon creation, and should not be + changed once created. + + Example: + + ```javascript + Ember.ObjectController.extend(Ember.PromiseProxyMixin).create({ + promise: + }); + ``` + + @property promise + */ + promise: computed(function(key, promise) { + if (arguments.length === 2) { + return tap(this, promise); + } else { + throw new EmberError("PromiseProxy's promise must be set"); + } + }), + + /** + An alias to the proxied promise's `then`. + + See RSVP.Promise.then. + + @method then + @param {Function} callback + @return {RSVP.Promise} + */ + then: promiseAlias('then'), + + /** + An alias to the proxied promise's `catch`. + + See RSVP.Promise.catch. + + @method catch + @param {Function} callback + @return {RSVP.Promise} + */ + 'catch': promiseAlias('catch'), + + /** + An alias to the proxied promise's `finally`. + + See RSVP.Promise.finally. + + @method finally + @param {Function} callback + @return {RSVP.Promise} + */ + 'finally': promiseAlias('finally') + + }); + + function promiseAlias(name) { + return function () { + var promise = get(this, 'promise'); + return promise[name].apply(promise, arguments); + }; + } + + __exports__["default"] = PromiseProxyMixin; + }); +define("ember-runtime/mixins/sortable", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/enumerable_utils","ember-metal/mixin","ember-runtime/mixins/mutable_enumerable","ember-runtime/compare","ember-metal/observer","ember-metal/computed","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + var Ember = __dependency1__["default"]; + // Ember.assert, Ember.A + + var get = __dependency2__.get; + var set = __dependency3__.set; + var EnumerableUtils = __dependency4__["default"]; + var Mixin = __dependency5__.Mixin; + var MutableEnumerable = __dependency6__["default"]; + var compare = __dependency7__["default"]; + var addObserver = __dependency8__.addObserver; + var removeObserver = __dependency8__.removeObserver; + var computed = __dependency9__.computed; + var beforeObserver = __dependency5__.beforeObserver; + var observer = __dependency5__.observer; + //ES6TODO: should we access these directly from their package or from how thier exposed in ember-metal? + + var forEach = 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'} + ``` + + If you add or remove the properties to sort by or change the sort direction the content + sort order will be automatically updated. + + ```javascript + songsController.set('sortProperties', ['title']); + songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'} + + songsController.toggleProperty('sortAscending'); + songsController.get('firstObject'); // {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'} + ``` + + SortableMixin works by sorting the arrangedContent array, which is the array that + arrayProxy displays. Due to the fact that the underlying 'content' array is not changed, that + array will not display the sorted list: + + ```javascript + songsController.get('content').get('firstObject'); // Returns the unsorted original content + songsController.get('firstObject'); // Returns the sorted content. + ``` + + Although the sorted content can also be accessed through the arrangedContent property, + it is preferable to use the proxied class and not the arrangedContent array directly. + + @class SortableMixin + @namespace Ember + @uses Ember.MutableEnumerable + */ + var SortableMixin = Mixin.create(MutableEnumerable, { + + /** + Specifies which properties dictate the arrangedContent's sort order. + + When specifying multiple properties the sorting will use properties + from the `sortProperties` array prioritized from first to last. + + @property {Array} sortProperties + */ + sortProperties: null, + + /** + Specifies the arrangedContent's sort direction. + Sorts the content in ascending order by default. Set to `false` to + use descending order. + + @property {Boolean} sortAscending + @default true + */ + sortAscending: true, + + /** + The function used to compare two values. You can override this if you + want to do custom comparisons. Functions must be of the type expected by + Array#sort, i.e. + return 0 if the two parameters are equal, + return a negative value if the first parameter is smaller than the second or + return a positive value otherwise: + + ```javascript + function(x,y) { // These are assumed to be integers + if (x === y) + return 0; + return x < y ? -1 : 1; + } + ``` + + @property sortFunction + @type {Function} + @default Ember.compare + */ + sortFunction: compare, + + orderBy: function(item1, item2) { + var result = 0, + sortProperties = get(this, 'sortProperties'), + sortAscending = get(this, 'sortAscending'), + sortFunction = get(this, 'sortFunction'); + + Ember.assert("you need to define `sortProperties`", !!sortProperties); + + forEach(sortProperties, function(propertyName) { + if (result === 0) { + result = sortFunction(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) { + removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange'); + }, this); + }, this); + } + + return this._super(); + }, + + isSorted: computed.bool('sortProperties'), + + /** + Overrides the default arrangedContent from arrayProxy in order to sort by sortFunction. + Also sets up observers for each sortProperty on each item in the content Array. + + @property arrangedContent + */ + + arrangedContent: 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) { + addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange'); + }, this); + }, this); + return Ember.A(content); + } + + return content; + }), + + _contentWillChange: beforeObserver('content', function() { + var content = get(this, 'content'), + sortProperties = get(this, 'sortProperties'); + + if (content && sortProperties) { + forEach(content, function(item) { + forEach(sortProperties, function(sortProperty) { + removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange'); + }, this); + }, this); + } + + this._super(); + }), + + sortAscendingWillChange: beforeObserver('sortAscending', function() { + this._lastSortAscending = get(this, 'sortAscending'); + }), + + sortAscendingDidChange: observer('sortAscending', function() { + if (get(this, 'sortAscending') !== this._lastSortAscending) { + var arrangedContent = get(this, 'arrangedContent'); + arrangedContent.reverseObjects(); + } + }), + + 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) { + 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) { + 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; + } + }); + + __exports__["default"] = SortableMixin; + }); +define("ember-runtime/mixins/target_action_support", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/mixin","ember-metal/computed","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + var Ember = __dependency1__["default"]; + // Ember.lookup, Ember.assert + + var get = __dependency2__.get; + var set = __dependency3__.set; + var typeOf = __dependency4__.typeOf; + var Mixin = __dependency5__.Mixin; + var computed = __dependency6__.computed; + + /** + `Ember.TargetActionSupport` is a mixin that can be included in a class + to add a `triggerAction` method with semantics similar to the Handlebars + `{{action}}` helper. In normal Ember usage, the `{{action}}` helper is + usually the best choice. This mixin is most often useful when you are + doing more complex event handling in View objects. + + See also `Ember.ViewTargetActionSupport`, which has + view-aware defaults for target and actionContext. + + @class TargetActionSupport + @namespace Ember + @extends Ember.Mixin + */ + var TargetActionSupport = Mixin.create({ + target: null, + action: null, + actionContext: null, + + targetObject: computed(function() { + var target = get(this, 'target'); + + if (typeOf(target) === "string") { + var value = get(this, target); + if (value === undefined) { value = get(Ember.lookup, target); } + return value; + } else { + return target; + } + }).property('target'), + + actionContextObject: computed(function() { + var actionContext = get(this, 'actionContext'); + + if (typeOf(actionContext) === "string") { + var value = get(this, actionContext); + if (value === undefined) { value = get(Ember.lookup, actionContext); } + return value; + } else { + return actionContext; + } + }).property('actionContext'), + + /** + Send an `action` with an `actionContext` to a `target`. The action, actionContext + and target will be retrieved from properties of the object. For example: + + ```javascript + App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, { + target: Ember.computed.alias('controller'), + action: 'save', + actionContext: Ember.computed.alias('context'), + click: function() { + this.triggerAction(); // Sends the `save` action, along with the current context + // to the current controller + } + }); + ``` + + The `target`, `action`, and `actionContext` can be provided as properties of + an optional object argument to `triggerAction` as well. + + ```javascript + App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, { + click: function() { + this.triggerAction({ + action: 'save', + target: this.get('controller'), + actionContext: this.get('context'), + }); // Sends the `save` action, along with the current context + // to the current controller + } + }); + ``` + + The `actionContext` defaults to the object you mixing `TargetActionSupport` into. + But `target` and `action` must be specified either as properties or with the argument + to `triggerAction`, or a combination: + + ```javascript + App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, { + target: Ember.computed.alias('controller'), + click: function() { + this.triggerAction({ + action: 'save' + }); // Sends the `save` action, along with a reference to `this`, + // to the current controller + } + }); + ``` + + @method triggerAction + @param opts {Hash} (optional, with the optional keys action, target and/or actionContext) + @return {Boolean} true if the action was sent successfully and did not return false + */ + triggerAction: function(opts) { + opts = opts || {}; + var action = opts.action || get(this, 'action'), + target = opts.target || get(this, 'targetObject'), + actionContext = opts.actionContext; + + function args(options, actionName) { + var ret = []; + if (actionName) { ret.push(actionName); } + + return ret.concat(options); + } + + if (typeof actionContext === 'undefined') { + actionContext = get(this, 'actionContextObject') || this; + } + + if (target && action) { + var ret; + + if (target.send) { + ret = target.send.apply(target, args(actionContext, action)); + } else { + Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function'); + ret = target[action].apply(target, args(actionContext)); + } + + if (ret !== false) ret = true; + + return ret; + } else { + return false; + } + } + }); + + __exports__["default"] = TargetActionSupport; + }); +define("ember-runtime/system/application", + ["ember-runtime/system/namespace","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Namespace = __dependency1__["default"]; + + var Application = Namespace.extend(); + __exports__["default"] = Application; + }); +define("ember-runtime/system/array_proxy", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/computed","ember-metal/mixin","ember-metal/property_events","ember-metal/error","ember-runtime/system/object","ember-runtime/mixins/mutable_array","ember-runtime/mixins/enumerable","ember-runtime/system/string","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.K, Ember.assert + var get = __dependency2__.get; + var set = __dependency3__.set; + var isArray = __dependency4__.isArray; + var apply = __dependency4__.apply; + var computed = __dependency5__.computed; + var beforeObserver = __dependency6__.beforeObserver; + var observer = __dependency6__.observer; + var beginPropertyChanges = __dependency7__.beginPropertyChanges; + var endPropertyChanges = __dependency7__.endPropertyChanges; + var EmberError = __dependency8__["default"]; + var EmberObject = __dependency9__["default"];var MutableArray = __dependency10__["default"];var Enumerable = __dependency11__["default"]; + var EmberStringUtils = __dependency12__["default"]; + + /** + @module ember + @submodule ember-runtime + */ + + var OUT_OF_RANGE_EXCEPTION = "Index out of range"; + var EMPTY = []; + var alias = computed.alias; + var K = Ember.K; + + /** + An ArrayProxy wraps any other object that implements `Ember.Array` and/or + `Ember.MutableArray,` forwarding all requests. This makes it very useful for + a number of binding use cases or other cases where being able to swap + out the underlying array is useful. + + A simple example of usage: + + ```javascript + var pets = ['dog', 'cat', 'fish']; + var ap = Ember.ArrayProxy.create({ content: Ember.A(pets) }); + + ap.get('firstObject'); // 'dog' + ap.set('content', ['amoeba', 'paramecium']); + ap.get('firstObject'); // 'amoeba' + ``` + + This class can also be useful as a layer to transform the contents of + an array, as they are accessed. This can be done by overriding + `objectAtContent`: + + ```javascript + var pets = ['dog', 'cat', 'fish']; + var ap = Ember.ArrayProxy.create({ + content: Ember.A(pets), + objectAtContent: function(idx) { + return this.get('content').objectAt(idx).toUpperCase(); + } + }); + + ap.get('firstObject'); // . 'DOG' + ``` + + @class ArrayProxy + @namespace Ember + @extends Ember.Object + @uses Ember.MutableArray + */ + var ArrayProxy = EmberObject.extend(MutableArray, { + + /** + The content array. Must be an object that implements `Ember.Array` and/or + `Ember.MutableArray.` + + @property content + @type Ember.Array + */ + content: null, + + /** + The array that the proxy pretends to be. In the default `ArrayProxy` + implementation, this and `content` are the same. Subclasses of `ArrayProxy` + can override this property to provide things like sorting and filtering. + + @property arrangedContent + */ + arrangedContent: alias('content'), + + /** + Should actually retrieve the object at the specified index from the + content. You can override this method in subclasses to transform the + content item to something new. + + This method will only be called if content is non-`null`. + + @method objectAtContent + @param {Number} idx The index to retrieve. + @return {Object} the value or undefined if none found + */ + objectAtContent: function(idx) { + return get(this, 'arrangedContent').objectAt(idx); + }, + + /** + Should actually replace the specified objects on the content array. + You can override this method in subclasses to transform the content item + into something new. + + This method will only be called if content is non-`null`. + + @method replaceContent + @param {Number} idx The starting index + @param {Number} amt The number of items to remove from the content. + @param {Array} objects Optional array of objects to insert or null if no + objects. + @return {void} + */ + replaceContent: function(idx, amt, objects) { + get(this, 'content').replace(idx, amt, objects); + }, + + /** + Invoked when the content property is about to change. Notifies observers that the + entire array content will change. + + @private + @method _contentWillChange + */ + _contentWillChange: beforeObserver('content', function() { + this._teardownContent(); + }), + + _teardownContent: function() { + var content = get(this, 'content'); + + if (content) { + content.removeArrayObserver(this, { + willChange: 'contentArrayWillChange', + didChange: 'contentArrayDidChange' + }); + } + }, + + contentArrayWillChange: K, + contentArrayDidChange: K, + + /** + Invoked when the content property changes. Notifies observers that the + entire array content has changed. + + @private + @method _contentDidChange + */ + _contentDidChange: observer('content', function() { + var content = get(this, 'content'); + + Ember.assert("Can't set ArrayProxy's content to itself", content !== this); + + this._setupContent(); + }), + + _setupContent: function() { + var content = get(this, 'content'); + + if (content) { + Ember.assert(EmberStringUtils.fmt('ArrayProxy expects an Array or ' + + 'Ember.ArrayProxy, but you passed %@', [typeof content]), + isArray(content) || content.isDestroyed); + + content.addArrayObserver(this, { + willChange: 'contentArrayWillChange', + didChange: 'contentArrayDidChange' + }); + } + }, + + _arrangedContentWillChange: beforeObserver('arrangedContent', function() { + var arrangedContent = get(this, 'arrangedContent'), + len = arrangedContent ? get(arrangedContent, 'length') : 0; + + this.arrangedContentArrayWillChange(this, 0, len, undefined); + this.arrangedContentWillChange(this); + + this._teardownArrangedContent(arrangedContent); + }), + + _arrangedContentDidChange: observer('arrangedContent', function() { + var arrangedContent = get(this, 'arrangedContent'), + len = arrangedContent ? get(arrangedContent, 'length') : 0; + + Ember.assert("Can't set ArrayProxy's content to itself", arrangedContent !== this); + + this._setupArrangedContent(); + + this.arrangedContentDidChange(this); + this.arrangedContentArrayDidChange(this, 0, undefined, len); + }), + + _setupArrangedContent: function() { + var arrangedContent = get(this, 'arrangedContent'); + + if (arrangedContent) { + Ember.assert(EmberStringUtils.fmt('ArrayProxy expects an Array or ' + + 'Ember.ArrayProxy, but you passed %@', [typeof arrangedContent]), + isArray(arrangedContent) || arrangedContent.isDestroyed); + + arrangedContent.addArrayObserver(this, { + willChange: 'arrangedContentArrayWillChange', + didChange: 'arrangedContentArrayDidChange' + }); + } + }, + + _teardownArrangedContent: function() { + var arrangedContent = get(this, 'arrangedContent'); + + if (arrangedContent) { + arrangedContent.removeArrayObserver(this, { + willChange: 'arrangedContentArrayWillChange', + didChange: 'arrangedContentArrayDidChange' + }); + } + }, + + arrangedContentWillChange: K, + arrangedContentDidChange: K, + + objectAt: function(idx) { + return get(this, 'content') && this.objectAtContent(idx); + }, + + length: computed(function() { + var arrangedContent = get(this, 'arrangedContent'); + return arrangedContent ? get(arrangedContent, 'length') : 0; + // No dependencies since Enumerable notifies length of change + }), + + _replace: function(idx, amt, objects) { + var content = get(this, 'content'); + Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', content); + if (content) this.replaceContent(idx, amt, objects); + return this; + }, + + replace: function() { + if (get(this, 'arrangedContent') === get(this, 'content')) { + apply(this, this._replace, arguments); + } else { + throw new EmberError("Using replace on an arranged ArrayProxy is not allowed."); + } + }, + + _insertAt: function(idx, object) { + if (idx > get(this, 'content.length')) throw new EmberError(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 EmberError("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 EmberError(OUT_OF_RANGE_EXCEPTION); + } + + if (len === undefined) len = 1; + + // Get a list of indices in original content to remove + for (i=start; i= 0) { + var baseValue = this[keyName]; + + if (baseValue) { + if ('function' === typeof baseValue.concat) { + value = baseValue.concat(value); + } else { + value = makeArray(baseValue).concat(value); + } + } else { + value = 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) { + defineProperty(this, keyName, null, value); // setup mandatory setter + } else { + this[keyName] = value; + } + } } } } - } - } - finishPartial(this, m); - this.init.apply(this, arguments); - m.proto = proto; - finishChains(this); - sendEvent(this, "init"); - }; + finishPartial(this, m); + apply(this, this.init, arguments); + m.proto = proto; + finishChains(this); + sendEvent(this, "init"); + }; + + 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; - 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 CoreObject + @namespace Ember + */ + var CoreObject = makeCtor(); + CoreObject.toString = function() { return "Ember.CoreObject"; }; - Class.proto = function() { - var superclass = Class.superclass; - if (superclass) { superclass.proto(); } + CoreObject.PrototypeMixin = Mixin.create({ + reopen: function() { + applyMixin(this, arguments, true); + return this; + }, - if (!wasApplied) { - wasApplied = true; - Class.PrototypeMixin.applyPartial(Class.prototype); - rewatch(Class.prototype); - } + /** + An overridable method called when objects are instantiated. By default, + does nothing unless it is overridden during class definition. - return this.prototype; - }; + Example: - return Class; + ```javascript + App.Person = Ember.Object.extend({ + init: function() { + alert('Name is ' + this.get('name')); + } + }); -} + var steve = App.Person.create({ + name: "Steve" + }); -/** - @class CoreObject - @namespace Ember -*/ -var CoreObject = makeCtor(); -CoreObject.toString = function() { return "Ember.CoreObject"; }; + // alerts 'Name is Steve'. + ``` -CoreObject.PrototypeMixin = Mixin.create({ - reopen: function() { - applyMixin(this, arguments, true); - return this; - }, + 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. - /** - An overridable method called when objects are instantiated. By default, - does nothing unless it is overridden during class definition. + @method init + */ + init: function() {}, - Example: + /** + Defines the properties that will be concatenated from the superclass + (instead of overridden). - ```javascript - App.Person = Ember.Object.extend({ - init: function() { - alert('Name is ' + this.get('name')); - } - }); + 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`. - var steve = App.Person.create({ - name: "Steve" - }); + Here is some sample code showing the difference between a concatenated + property and a normal one: - // alerts 'Name is Steve'. - ``` + ```javascript + App.BarView = Ember.View.extend({ + someNonConcatenatedProperty: ['bar'], + classNames: ['bar'] + }); - 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. + App.FooBarView = App.BarView.extend({ + someNonConcatenatedProperty: ['foo'], + classNames: ['foo'], + }); - @method init - */ - init: function() {}, + var fooBarView = App.FooBarView.create(); + fooBarView.get('someNonConcatenatedProperty'); // ['foo'] + fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo'] + ``` - /** - Defines the properties that will be concatenated from the superclass - (instead of overridden). + This behavior extends to object creation as well. Continuing the + above example: - 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`. + ```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: - Here is some sample code showing the difference between a concatenated - property and a normal one: + ```javascript + var view = App.FooBarView.create({ + classNames: 'baz' + }) + view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz'] + ``` - ```javascript - App.BarView = Ember.View.extend({ - someNonConcatenatedProperty: ['bar'], - classNames: ['bar'] - }); + Using the `concatenatedProperties` property, we can tell to Ember that mix + the content of the properties. - App.FooBarView = App.BarView.extend({ - someNonConcatenatedProperty: ['foo'], - classNames: ['foo'], - }); + In `Ember.View` the `classNameBindings` and `attributeBindings` properties + are also concatenated, in addition to `classNames`. - var fooBarView = App.FooBarView.create(); - fooBarView.get('someNonConcatenatedProperty'); // ['foo'] - fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo'] - ``` + This feature is available for you to use throughout the Ember object model, + although typical app developers are likely to use it infrequently. Since + it changes expectations about behavior of properties, you should properly + document its usage in each individual concatenated property (to not + mislead your users to think they can override the property in a subclass). - This behavior extends to object creation as well. Continuing the - above example: + @property concatenatedProperties + @type Array + @default null + */ + concatenatedProperties: null, - ```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: + /** + Destroyed object property flag. - ```javascript - var view = App.FooBarView.create({ - classNames: 'baz' - }) - view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz'] - ``` + if this property is `true` the observers and bindings were already + removed by the effect of calling the `destroy()` method. - Using the `concatenatedProperties` property, we can tell to Ember that mix - the content of the properties. + @property isDestroyed + @default false + */ + isDestroyed: false, - In `Ember.View` the `classNameBindings` and `attributeBindings` properties - are also concatenated, in addition to `classNames`. + /** + Destruction scheduled flag. The `destroy()` method has been called. - This feature is available for you to use throughout the Ember object model, - although typical app developers are likely to use it infrequently. Since - it changes expectations about behavior of properties, you should properly - document its usage in each individual concatenated property (to not - mislead your users to think they can override the property in a subclass). + The object stays intact until the end of the run loop at which point + the `isDestroyed` flag is set. - @property concatenatedProperties - @type Array - @default null - */ - concatenatedProperties: null, + @property isDestroying + @default false + */ + isDestroying: false, - /** - Destroyed object property flag. + /** + Destroys an object by setting the `isDestroyed` flag and removing its + metadata, which effectively destroys observers and bindings. - if this property is `true` the observers and bindings were already - removed by the effect of calling the `destroy()` method. + If you try to set a property on a destroyed object, an exception will be + raised. - @property isDestroyed - @default false - */ - isDestroyed: false, + Note that destruction is scheduled for the end of the run loop and does not + happen immediately. It will set an isDestroying flag immediately. - /** - Destruction scheduled flag. The `destroy()` method has been called. + @method destroy + @return {Ember.Object} receiver + */ + destroy: function() { + if (this.isDestroying) { return; } + this.isDestroying = true; - The object stays intact until the end of the run loop at which point - the `isDestroyed` flag is set. + schedule('actions', this, this.willDestroy); + schedule('destroy', this, this._scheduledDestroy); + return this; + }, - @property isDestroying - @default false - */ - isDestroying: false, + /** + Override to implement teardown. - /** - Destroys an object by setting the `isDestroyed` flag and removing its - metadata, which effectively destroys observers and bindings. + @method willDestroy + */ + willDestroy: K, - If you try to set a property on a destroyed object, an exception will be - raised. + /** + Invoked by the run loop to actually destroy the object. This is + scheduled for execution by the `destroy` method. - Note that destruction is scheduled for the end of the run loop and does not - happen immediately. It will set an isDestroying flag immediately. + @private + @method _scheduledDestroy + */ + _scheduledDestroy: function() { + if (this.isDestroyed) { return; } + destroy(this); + this.isDestroyed = true; + }, - @method destroy - @return {Ember.Object} receiver - */ - destroy: function() { - if (this.isDestroying) { return; } - this.isDestroying = true; + bind: function(to, from) { + if (!(from instanceof Binding)) { from = Binding.from(from); } + from.to(to).connect(this); + return from; + }, - schedule('actions', this, this.willDestroy); - schedule('destroy', this, this._scheduledDestroy); - return this; - }, - - /** - Override to implement teardown. - - @method willDestroy - */ - willDestroy: Ember.K, - - /** - @private - - Invoked by the run loop to actually destroy the object. This is - scheduled for execution by the `destroy` method. - - @method _scheduledDestroy - */ - _scheduledDestroy: function() { - if (this.isDestroyed) { return; } - destroy(this); - this.isDestroyed = true; - }, - - 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. + /** + Returns a string representation which attempts to provide more information + than Javascript's `toString` typically does, in a generic way for all Ember + objects. + ```javascript 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: + If the object's class is not defined on an Ember namespace, it will + indicate it is a subclass of the registered superclass: + ```javascript 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. + If the method `toStringExtension` is defined, its return value will be + included in the output. + ```javascript App.Teacher = App.Person.extend({ toStringExtension: function() { return this.get('fullName'); @@ -10917,12762 +19624,7967 @@ CoreObject.PrototypeMixin = Mixin.create({ }); 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, - - /** - Creates a new subclass. - - ```javascript - App.Person = Ember.Object.extend({ - say: function(thing) { - alert(thing); - } - }); - ``` - - This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`. - - You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class: - - ```javascript - App.PersonView = Ember.View.extend({ - tagName: 'li', - classNameBindings: ['isAdministrator'] - }); - ``` - - When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method: - - ```javascript - App.Person = Ember.Object.extend({ - say: function(thing) { - var name = this.get('name'); - alert(name + ' says: ' + thing); + @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; } }); - App.Soldier = App.Person.extend({ - say: function(thing) { - this._super(thing + ", sir!"); + 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: required(), + + PrototypeMixin: required(), + + isClass: true, + + isMethod: false, + + /** + Creates a new subclass. + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + alert(thing); + } + }); + ``` + + This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`. + + You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class: + + ```javascript + App.PersonView = Ember.View.extend({ + tagName: 'li', + classNameBindings: ['isAdministrator'] + }); + ``` + + When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method: + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + var name = this.get('name'); + alert(name + ' says: ' + thing); + } + }); + + App.Soldier = App.Person.extend({ + say: function(thing) { + this._super(thing + ", sir!"); + }, + march: function(numberOfHours) { + alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.') + } + }); + + var yehuda = App.Soldier.create({ + name: "Yehuda Katz" + }); + + yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!" + ``` + + The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method. + + You can also pass `Mixin` classes to add additional properties to the subclass. + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + alert(this.get('name') + ' says: ' + thing); + } + }); + + App.SingingMixin = Mixin.create({ + sing: function(thing){ + alert(this.get('name') + ' sings: la la la ' + thing); + } + }); + + App.BroadwayStar = App.Person.extend(App.SingingMixin, { + dance: function() { + alert(this.get('name') + ' dances: tap tap tap tap '); + } + }); + ``` + + The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`. + + @method extend + @static + + @param {Mixin} [mixins]* One or more Mixin classes + @param {Object} [arguments]* Object containing values to use within the new class + */ + 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); + meta(proto).proto = proto; // this will disable observers on prototype + + Class.ClassMixin.apply(Class); + return Class; }, - march: function(numberOfHours) { - alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.') - } - }); - var yehuda = App.Soldier.create({ - name: "Yehuda Katz" - }); + /** + Equivalent to doing `extend(arguments).create()`. + If possible use the normal `create` method instead. - yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!" - ``` + @method createWithMixins + @static + @param [arguments]* + */ + createWithMixins: function() { + var C = this; + if (arguments.length>0) { this._initMixins(arguments); } + return new C(); + }, - The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method. + /** + Creates an instance of a class. Accepts either no arguments, or an object + containing values to initialize the newly instantiated object with. - You can also pass `Ember.Mixin` classes to add additional properties to the subclass. - - ```javascript - App.Person = Ember.Object.extend({ - say: function(thing) { - alert(this.get('name') + ' says: ' + thing); - } - }); - - App.SingingMixin = Ember.Mixin.create({ - sing: function(thing){ - alert(this.get('name') + ' sings: la la la ' + thing); - } - }); - - App.BroadwayStar = App.Person.extend(App.SingingMixin, { - dance: function() { - alert(this.get('name') + ' dances: tap tap tap tap '); - } - }); - ``` - - The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`. - - @method extend - @static - - @param {Ember.Mixin} [mixins]* One or more Ember.Mixin classes - @param {Object} [arguments]* Object containing values to use within the new class - */ - 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); - meta(proto).proto = proto; // this will disable observers on prototype - - Class.ClassMixin.apply(Class); - return Class; - }, - - /** - Equivalent to doing `extend(arguments).create()`. - If possible use the normal `create` method instead. - - @method createWithMixins - @static - @param [arguments]* - */ - createWithMixins: function() { - var C = this; - if (arguments.length>0) { this._initMixins(arguments); } - return new C(); - }, - - /** - Creates an instance of a class. Accepts either no arguments, or an object - containing values to initialize the newly instantiated object with. - - ```javascript - App.Person = Ember.Object.extend({ - helloWorld: function() { - alert("Hi, my name is " + this.get('name')); - } - }); - - var tom = App.Person.create({ - name: 'Tom Dale' - }); - - tom.helloWorld(); // alerts "Hi, my name is Tom Dale". - ``` - - `create` will call the `init` function if defined during - `Ember.AnyObject.extend` - - If no arguments are passed to `create`, it will not set values to the new - instance during initialization: - - ```javascript - var noName = App.Person.create(); - noName.helloWorld(); // alerts undefined - ``` - - NOTE: For performance reasons, you cannot declare methods or computed - properties during `create`. You should instead declare methods and computed - properties when using `extend` or use the `createWithMixins` shorthand. - - @method create - @static - @param [arguments]* - */ - create: function() { - var C = this; - if (arguments.length>0) { this._initProperties(arguments); } - return new C(); - }, - - /** - - Augments a constructor's prototype with additional - properties and functions: - - ```javascript - MyObject = Ember.Object.extend({ - name: 'an object' - }); - - o = MyObject.create(); - o.get('name'); // 'an object' - - MyObject.reopen({ - say: function(msg){ - console.log(msg); - } - }) - - o2 = MyObject.create(); - o2.say("hello"); // logs "hello" - - o.say("goodbye"); // logs "goodbye" - ``` - - To add functions and properties to the constructor itself, - see `reopenClass` - - @method reopen - */ - reopen: function() { - this.willReopen(); - reopen.apply(this.PrototypeMixin, arguments); - return this; - }, - - /** - Augments a constructor's own properties and functions: - - ```javascript - MyObject = Ember.Object.extend({ - name: 'an object' - }); - - - MyObject.reopenClass({ - canBuild: false - }); - - MyObject.canBuild; // false - o = MyObject.create(); - ``` - - In other words, this creates static properties and functions for the class. These are only available on the class - and not on any instance of that class. - - ```javascript - App.Person = Ember.Object.extend({ - name : "", - sayHello : function(){ - alert("Hello. My name is " + this.get('name')); - } - }); - - App.Person.reopenClass({ - species : "Homo sapiens", - createPerson: function(newPersonsName){ - return App.Person.create({ - name:newPersonsName + ```javascript + App.Person = Ember.Object.extend({ + helloWorld: function() { + alert("Hi, my name is " + this.get('name')); + } }); - } - }); - var tom = App.Person.create({ - name : "Tom Dale" - }); - var yehuda = App.Person.createPerson("Yehuda Katz"); - - tom.sayHello(); // "Hello. My name is Tom Dale" - yehuda.sayHello(); // "Hello. My name is Yehuda Katz" - alert(App.Person.species); // "Homo sapiens" - ``` - - Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda` - variables. They are only valid on `App.Person`. - - To add functions and properties to instances of - a constructor by extending the constructor's prototype - see `reopen` - - @method reopenClass - */ - 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); - -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" || prop === "webkitStorageInfo") { 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 if (this._toString) { - ret = this._toString; - } 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; 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 ; - } - -}); - -Ember.Enumerable.reopen({ - /** - Converts the enumerable into an array and sorts by the keys - specified in the argument. - - You may provide multiple arguments to sort by multiple properties. - - @method sortBy - @param {String} property name(s) to sort on - @return {Array} The sorted array. - */ - sortBy: function() { - var sortKeys = arguments; - return this.toArray().sort(function(a, b){ - for(var i = 0; i < sortKeys.length; i++) { - var key = sortKeys[i], - propA = get(a, key), - propB = get(b, key); - // return 1 or -1 else continue to the next sortKey - var compareValue = Ember.compare(propA, propB); - if (compareValue) { return compareValue; } - } - return 0; - }); - } -}); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -// .......................................................... -// HELPERS -// - -var get = Ember.get, set = Ember.set, isNone = Ember.isNone, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor; - -// .......................................................... -// 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 of 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 - @uses Ember.Enumerable - @since Ember 0.9.0 -*/ -Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.prototype */ { - - /** - 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 {*} 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 (isNone(beginIndex)) beginIndex = 0 ; - if (isNone(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(observedObj, start, removeCount, addCount)` - This method will be - called just before the array is modified. - * `arrayDidChange(observedObj, start, removeCount, addCount)` - This method will be - called just after the array is modified. - - Both callbacks will be passed the observed object, 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 Ember.TrackedArray instances. We use - // this to lazily recompute indexes for item property observers. - this.trackedArraysByGuid = {}; - - // We suspend observers to ignore replacements from `reset` when totally - // recomputing. Unfortunately we cannot properly suspend the observers - // because we only have the key; instead we make the observers no-ops - this.suspended = false; - - // This is used to coalesce item changes from property observers. - this.changedItems = {}; -} - -function ItemPropertyObserverContext (dependentArray, index, trackedArray) { - Ember.assert("Internal error: trackedArray is null or undefined", trackedArray); - - this.dependentArray = dependentArray; - this.index = index; - this.item = dependentArray.objectAt(index); - this.trackedArray = trackedArray; - this.beforeObserver = null; - this.observer = null; - - this.destroyed = false; -} - -DependentArraysObserver.prototype = { - setValue: function (newValue) { - this.instanceMeta.setValue(newValue, true); - }, - getValue: function () { - return this.instanceMeta.getValue(); - }, - - setupObservers: function (dependentArray, dependentKey) { - Ember.assert("dependent array must be an `Ember.Array`", Ember.Array.detect(dependentArray)); - - this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey; - - dependentArray.addArrayObserver(this, { - willChange: 'dependentArrayWillChange', - didChange: 'dependentArrayDidChange' - }); - - if (this.cp._itemPropertyKeys[dependentKey]) { - this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]); - } - }, - - teardownObservers: function (dependentArray, dependentKey) { - var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || []; - - delete this.dependentKeysByGuid[guidFor(dependentArray)]; - - this.teardownPropertyObservers(dependentKey, itemPropertyKeys); - - dependentArray.removeArrayObserver(this, { - willChange: 'dependentArrayWillChange', - didChange: 'dependentArrayDidChange' - }); - }, - - suspendArrayObservers: function (callback, binding) { - var oldSuspended = this.suspended; - this.suspended = true; - callback.call(binding); - this.suspended = oldSuspended; - }, - - setupPropertyObservers: function (dependentKey, itemPropertyKeys) { - var dependentArray = get(this.instanceMeta.context, dependentKey), - length = get(dependentArray, 'length'), - observerContexts = new Array(length); - - this.resetTransformations(dependentKey, observerContexts); - - forEach(dependentArray, function (item, index) { - var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]); - observerContexts[index] = observerContext; - - forEach(itemPropertyKeys, function (propertyKey) { - addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); - addObserver(item, propertyKey, this, observerContext.observer); - }, this); - }, this); - }, - - teardownPropertyObservers: function (dependentKey, itemPropertyKeys) { - var dependentArrayObserver = this, - trackedArray = this.trackedArraysByGuid[dependentKey], - beforeObserver, - observer, - item; - - if (!trackedArray) { return; } - - trackedArray.apply(function (observerContexts, offset, operation) { - if (operation === Ember.TrackedArray.DELETE) { return; } - - forEach(observerContexts, function (observerContext) { - observerContext.destroyed = true; - beforeObserver = observerContext.beforeObserver; - observer = observerContext.observer; - item = observerContext.item; - - forEach(itemPropertyKeys, function (propertyKey) { - removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver); - removeObserver(item, propertyKey, dependentArrayObserver, observer); + var tom = App.Person.create({ + name: 'Tom Dale' }); + + tom.helloWorld(); // alerts "Hi, my name is Tom Dale". + ``` + + `create` will call the `init` function if defined during + `Ember.AnyObject.extend` + + If no arguments are passed to `create`, it will not set values to the new + instance during initialization: + + ```javascript + var noName = App.Person.create(); + noName.helloWorld(); // alerts undefined + ``` + + NOTE: For performance reasons, you cannot declare methods or computed + properties during `create`. You should instead declare methods and computed + properties when using `extend` or use the `createWithMixins` shorthand. + + @method create + @static + @param [arguments]* + */ + create: function() { + var C = this; + if (arguments.length>0) { this._initProperties(arguments); } + return new C(); + }, + + /** + Augments a constructor's prototype with additional + properties and functions: + + ```javascript + MyObject = Ember.Object.extend({ + name: 'an object' + }); + + o = MyObject.create(); + o.get('name'); // 'an object' + + MyObject.reopen({ + say: function(msg){ + console.log(msg); + } + }) + + o2 = MyObject.create(); + o2.say("hello"); // logs "hello" + + o.say("goodbye"); // logs "goodbye" + ``` + + To add functions and properties to the constructor itself, + see `reopenClass` + + @method reopen + */ + reopen: function() { + this.willReopen(); + apply(this.PrototypeMixin, reopen, arguments); + return this; + }, + + /** + Augments a constructor's own properties and functions: + + ```javascript + MyObject = Ember.Object.extend({ + name: 'an object' + }); + + MyObject.reopenClass({ + canBuild: false + }); + + MyObject.canBuild; // false + o = MyObject.create(); + ``` + + In other words, this creates static properties and functions for the class. These are only available on the class + and not on any instance of that class. + + ```javascript + App.Person = Ember.Object.extend({ + name : "", + sayHello : function(){ + alert("Hello. My name is " + this.get('name')); + } + }); + + App.Person.reopenClass({ + species : "Homo sapiens", + createPerson: function(newPersonsName){ + return App.Person.create({ + name:newPersonsName + }); + } + }); + + var tom = App.Person.create({ + name : "Tom Dale" + }); + var yehuda = App.Person.createPerson("Yehuda Katz"); + + tom.sayHello(); // "Hello. My name is Tom Dale" + yehuda.sayHello(); // "Hello. My name is Yehuda Katz" + alert(App.Person.species); // "Homo sapiens" + ``` + + Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda` + variables. They are only valid on `App.Person`. + + To add functions and properties to instances of + a constructor by extending the constructor's prototype + see `reopen` + + @method reopenClass + */ + reopenClass: function() { + apply(this.ClassMixin, reopen, 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 meta = this.proto()[META_KEY], + desc = meta && meta.descs[key]; + + Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof 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 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); + + __exports__["default"] = CoreObject; + }); +define("ember-runtime/system/deferred", + ["ember-runtime/mixins/deferred","ember-metal/property_get","ember-runtime/system/object","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var DeferredMixin = __dependency1__["default"]; + var get = __dependency2__.get; + var EmberObject = __dependency3__["default"]; + + var Deferred = EmberObject.extend(DeferredMixin); + + Deferred.reopenClass({ + promise: function(callback, binding) { + var deferred = Deferred.create(); + callback.call(binding, deferred); + return deferred; + } + }); + + __exports__["default"] = Deferred; + }); +define("ember-runtime/system/each_proxy", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/enumerable_utils","ember-metal/array","ember-runtime/mixins/array","ember-runtime/system/object","ember-metal/computed","ember-metal/observer","ember-metal/events","ember-metal/properties","ember-metal/property_events","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + var Ember = __dependency1__["default"]; + // Ember.assert + + var get = __dependency2__.get; + var set = __dependency3__.set; + var guidFor = __dependency4__.guidFor; + var EnumerableUtils = __dependency5__["default"]; + var indexOf = __dependency6__.indexOf; + var EmberArray = __dependency7__["default"]; + // ES6TODO: WAT? Circular dep? + var EmberObject = __dependency8__["default"]; + var computed = __dependency9__.computed; + var addObserver = __dependency10__.addObserver; + var addBeforeObserver = __dependency10__.addBeforeObserver; + var removeBeforeObserver = __dependency10__.removeBeforeObserver; + var removeObserver = __dependency10__.removeObserver; + var typeOf = __dependency4__.typeOf; + var watchedEvents = __dependency11__.watchedEvents; + var defineProperty = __dependency12__.defineProperty; + var beginPropertyChanges = __dependency13__.beginPropertyChanges; + var propertyDidChange = __dependency13__.propertyDidChange; + var propertyWillChange = __dependency13__.propertyWillChange; + var endPropertyChanges = __dependency13__.endPropertyChanges; + var changeProperties = __dependency13__.changeProperties; + + var forEach = EnumerableUtils.forEach; + + var EachArray = EmberObject.extend(EmberArray, { + + init: function(content, keyName, owner) { + this._super(); + this._keyName = keyName; + this._owner = owner; + this._content = content; + }, + + objectAt: function(idx) { + var item = this._content.objectAt(idx); + return item && get(item, this._keyName); + }, + + length: computed(function() { + var content = this._content; + return content ? get(content, 'length') : 0; + }) + + }); + + var IS_OBSERVER = /^.+:(before|change)$/; + + function addObserverForContentKey(content, keyName, proxy, idx, loc) { + var objects = proxy._objects, guid; + if (!objects) objects = proxy._objects = {}; + + while(--loc>=idx) { + var item = content.objectAt(loc); + if (item) { + Ember.assert('When using @each to observe the array ' + content + ', the array must return an object', typeOf(item) === 'instance' || typeOf(item) === 'object'); + addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange'); + 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) { + removeBeforeObserver(item, keyName, proxy, 'contentKeyWillChange'); + removeObserver(item, keyName, proxy, 'contentKeyDidChange'); + + guid = guidFor(item); + indicies = objects[guid]; + indicies[indexOf.call(indicies, 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 + */ + var EachProxy = EmberObject.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(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 {*} + */ + unknownProperty: function(keyName, value) { + var ret; + ret = new EachArray(this._content, keyName, this); + 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; + beginPropertyChanges(this); + + for(key in keys) { + if (!keys.hasOwnProperty(key)) { continue; } + + if (lim>0) { removeObserverForContentKey(content, key, this, idx, lim); } + + propertyWillChange(this, key); + } + + propertyWillChange(this._content, '@each'); + endPropertyChanges(this); + }, + + arrayDidChange: function(content, idx, removedCnt, addedCnt) { + var keys = this._keys, lim; + + lim = addedCnt>0 ? idx+addedCnt : -1; + changeProperties(function() { + for(var key in keys) { + if (!keys.hasOwnProperty(key)) { continue; } + + if (lim>0) { addObserverForContentKey(content, key, this, idx, lim); } + + propertyDidChange(this, key); + } + + propertyDidChange(this._content, '@each'); + }, 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) { + propertyWillChange(this, keyName); + }, + + contentKeyDidChange: function(obj, keyName) { + propertyDidChange(this, keyName); + } + + }); + + __exports__.EachArray = EachArray; + __exports__.EachProxy = EachProxy; + }); +define("ember-runtime/system/lazy_load", + ["ember-metal/core","ember-metal/array","ember-runtime/system/native_array","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + /*globals CustomEvent */ + + var Ember = __dependency1__["default"]; + // Ember.ENV.EMBER_LOAD_HOOKS + var forEach = __dependency2__.forEach; + // make sure Ember.A is setup. + + /** + @module ember + @submodule ember-runtime + */ + + var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {}; + var loaded = {}; + + /** + Detects when a specific package of Ember (e.g. 'Ember.Handlebars') + has fully loaded and is available for extension. + + The provided `callback` will be called with the `name` passed + resolved from a string into the object: + + ``` javascript + Ember.onLoad('Ember.Handlebars' function(hbars){ + hbars.registerHelper(...); }); + ``` + + @method onLoad + @for Ember + @param name {String} name of hook + @param callback {Function} callback to be called + */ + function onLoad(name, callback) { + var object; + + loadHooks[name] = loadHooks[name] || Ember.A(); + loadHooks[name].pushObject(callback); + + if (object = loaded[name]) { + callback(object); + } + }; + + /** + Called when an Ember.js package (e.g Ember.Handlebars) has finished + loading. Triggers any callbacks registered for this event. + + @method runLoadHooks + @for Ember + @param name {String} name of hook + @param object {Object} object to pass to callbacks + */ + function runLoadHooks(name, object) { + loaded[name] = object; + + if (typeof window === 'object' && typeof window.dispatchEvent === 'function' && typeof CustomEvent === "function") { + var event = new CustomEvent(name, {detail: object, name: name}); + window.dispatchEvent(event); + } + + if (loadHooks[name]) { + forEach.call(loadHooks[name], function(callback) { + callback(object); + }); + } + }; + + __exports__.onLoad = onLoad; + __exports__.runLoadHooks = runLoadHooks; + }); +define("ember-runtime/system/namespace", + ["ember-metal/core","ember-metal/property_get","ember-metal/array","ember-metal/utils","ember-metal/mixin","ember-runtime/system/object","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + // Ember.lookup, Ember.BOOTED, Ember.deprecate, Ember.NAME_KEY, Ember.anyUnprocessedMixins + var Ember = __dependency1__["default"]; + var get = __dependency2__.get; + var indexOf = __dependency3__.indexOf; + var GUID_KEY = __dependency4__.GUID_KEY; + var guidFor = __dependency4__.guidFor; + var Mixin = __dependency5__.Mixin; + + var EmberObject = __dependency6__["default"]; + + /** + 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 = EmberObject.extend({ + isNamespace: true, + + init: function() { + Namespace.NAMESPACES.push(this); + Namespace.PROCESSED = false; + }, + + toString: function() { + var name = get(this, 'name'); + if (name) { return name; } + + findNamespaces(); + return this[GUID_KEY+'_name']; + }, + + nameClasses: function() { + processNamespace([this.toString()], this, {}); + }, + + destroy: function() { + var namespaces = Namespace.NAMESPACES, + toString = this.toString(); + + if (toString) { + Ember.lookup[toString] = undefined; + delete Namespace.NAMESPACES_BY_ID[toString]; + } + namespaces.splice(indexOf.call(namespaces, this), 1); + this._super(); + } }); - }, - createPropertyObserverContext: function (dependentArray, index, trackedArray) { - var observerContext = new ItemPropertyObserverContext(dependentArray, index, trackedArray); + Namespace.reopenClass({ + NAMESPACES: [Ember], + NAMESPACES_BY_ID: {}, + PROCESSED: false, + processAll: processAllNamespaces, + byName: function(name) { + if (!Ember.BOOTED) { + processAllNamespaces(); + } - this.createPropertyObserver(observerContext); + return NAMESPACES_BY_ID[name]; + } + }); - return observerContext; - }, + var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID; - createPropertyObserver: function (observerContext) { - var dependentArrayObserver = this; + var hasOwnProp = ({}).hasOwnProperty; - observerContext.beforeObserver = function (obj, keyName) { - return dependentArrayObserver.itemPropertyWillChange(obj, keyName, observerContext.dependentArray, observerContext); - }; - observerContext.observer = function (obj, keyName) { - return dependentArrayObserver.itemPropertyDidChange(obj, keyName, observerContext.dependentArray, observerContext); - }; - }, + function processNamespace(paths, root, seen) { + var idx = paths.length; - resetTransformations: function (dependentKey, observerContexts) { - this.trackedArraysByGuid[dependentKey] = new Ember.TrackedArray(observerContexts); - }, + NAMESPACES_BY_ID[paths.join('.')] = root; - trackAdd: function (dependentKey, index, newItems) { - var trackedArray = this.trackedArraysByGuid[dependentKey]; - if (trackedArray) { - trackedArray.addItems(index, newItems); - } - }, + // 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]; - trackRemove: function (dependentKey, index, removedCount) { - var trackedArray = this.trackedArraysByGuid[dependentKey]; + // 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 (trackedArray) { - return trackedArray.removeItems(index, removedCount); + // 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 } - return []; - }, + function findNamespaces() { + var lookup = Ember.lookup, obj, isNamespace; - updateIndexes: function (trackedArray, array) { - var length = get(array, 'length'); - // OPTIMIZE: we could stop updating once we hit the object whose observer - // fired; ie partially apply the transformations - trackedArray.apply(function (observerContexts, offset, operation) { - // we don't even have observer contexts for removed items, even if we did, - // they no longer have any index in the array - if (operation === Ember.TrackedArray.DELETE) { return; } - if (operation === Ember.TrackedArray.RETAIN && observerContexts.length === length && offset === 0) { - // If we update many items we don't want to walk the array each time: we - // only need to update the indexes at most once per run loop. + 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" || prop === "webkitStorageInfo") { 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 = 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; } - - forEach(observerContexts, function (context, index) { - context.index = index + offset; - }); - }); - }, - - dependentArrayWillChange: function (dependentArray, index, removedCount, addedCount) { - if (this.suspended) { return; } - - var removedItem = this.callbacks.removedItem, - changeMeta, - guid = guidFor(dependentArray), - dependentKey = this.dependentKeysByGuid[guid], - itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [], - length = get(dependentArray, 'length'), - normalizedIndex = normalizeIndex(index, length, 0), - normalizedRemoveCount = normalizeRemoveCount(normalizedIndex, length, removedCount), - item, - itemIndex, - sliceIndex, - observerContexts; - - observerContexts = this.trackRemove(dependentKey, normalizedIndex, normalizedRemoveCount); - - function removeObservers(propertyKey) { - observerContexts[sliceIndex].destroyed = true; - removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver); - removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer); } - for (sliceIndex = normalizedRemoveCount - 1; sliceIndex >= 0; --sliceIndex) { - itemIndex = normalizedIndex + sliceIndex; - if (itemIndex >= length) { break; } - - item = dependentArray.objectAt(itemIndex); - - forEach(itemPropertyKeys, removeObservers, this); - - changeMeta = createChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp); - this.setValue( removedItem.call( - this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); - } - }, - - dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) { - if (this.suspended) { return; } - - var addedItem = this.callbacks.addedItem, - guid = guidFor(dependentArray), - dependentKey = this.dependentKeysByGuid[guid], - observerContexts = new Array(addedCount), - itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey], - length = get(dependentArray, 'length'), - normalizedIndex = normalizeIndex(index, length, addedCount), - changeMeta, - observerContext; - - forEach(dependentArray.slice(normalizedIndex, normalizedIndex + addedCount), function (item, sliceIndex) { - if (itemPropertyKeys) { - observerContext = - observerContexts[sliceIndex] = - this.createPropertyObserverContext(dependentArray, normalizedIndex + sliceIndex, this.trackedArraysByGuid[dependentKey]); - forEach(itemPropertyKeys, function (propertyKey) { - addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); - addObserver(item, propertyKey, this, observerContext.observer); - }, this); + function classToString() { + if (!Ember.BOOTED && !this[NAME_KEY]) { + processAllNamespaces(); } - changeMeta = createChangeMeta(dependentArray, item, normalizedIndex + sliceIndex, this.instanceMeta.propertyName, this.cp); - this.setValue( addedItem.call( - this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); - }, this); - - this.trackAdd(dependentKey, normalizedIndex, observerContexts); - }, - - itemPropertyWillChange: function (obj, keyName, array, observerContext) { - var guid = guidFor(obj); - - if (!this.changedItems[guid]) { - this.changedItems[guid] = { - array: array, - observerContext: observerContext, - obj: obj, - previousValues: {} - }; - } - - this.changedItems[guid].previousValues[keyName] = get(obj, keyName); - }, - - itemPropertyDidChange: function(obj, keyName, array, observerContext) { - this.flushChanges(); - }, - - flushChanges: function() { - var changedItems = this.changedItems, key, c, changeMeta; - - for (key in changedItems) { - c = changedItems[key]; - if (c.observerContext.destroyed) { continue; } - - this.updateIndexes(c.observerContext.trackedArray, c.observerContext.dependentArray); - - changeMeta = createChangeMeta(c.array, c.obj, c.observerContext.index, this.instanceMeta.propertyName, this.cp, c.previousValues); - this.setValue( - this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta)); - this.setValue( - this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta)); - } - this.changedItems = {}; - } -}; - -function normalizeIndex(index, length, newItemsOffset) { - if (index < 0) { - return Math.max(0, length + index); - } else if (index < length) { - return index; - } else /* index > length */ { - return Math.min(length - newItemsOffset, index); - } -} - -function normalizeRemoveCount(index, length, removedCount) { - return Math.min(removedCount, length - index); -} - -function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) { - var meta = { - arrayChanged: dependentArray, - index: index, - item: item, - propertyName: propertyName, - property: property - }; - - if (previousValues) { - // previous values only available for item property changes - meta.previousValues = previousValues; - } - - return meta; -} - -function addItems (dependentArray, callbacks, cp, propertyName, meta) { - forEach(dependentArray, function (item, index) { - meta.setValue( callbacks.addedItem.call( - this, meta.getValue(), item, createChangeMeta(dependentArray, item, index, propertyName, cp), meta.sugarMeta)); - }, this); -} - -function reset(cp, propertyName) { - var callbacks = cp._callbacks(), - meta; - - if (cp._hasInstanceMeta(this, propertyName)) { - meta = cp._instanceMeta(this, propertyName); - meta.setValue(cp.resetValue(meta.getValue())); - } else { - meta = cp._instanceMeta(this, propertyName); - } - - if (cp.options.initialize) { - cp.options.initialize.call(this, meta.getValue(), { property: cp, propertyName: propertyName }, meta.sugarMeta); - } -} - -function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) { - this.context = context; - this.propertyName = propertyName; - this.cache = metaFor(context).cache; - - this.dependentArrays = {}; - this.sugarMeta = {}; - - this.initialValue = initialValue; -} - -ReduceComputedPropertyInstanceMeta.prototype = { - getValue: function () { - if (this.propertyName in this.cache) { - return this.cache[this.propertyName]; - } else { - return this.initialValue; - } - }, - - setValue: function(newValue, triggerObservers) { - // This lets sugars force a recomputation, handy for very simple - // implementations of eg max. - if (newValue !== undefined) { - var fireObservers = triggerObservers && (newValue !== this.cache[this.propertyName]); - - if (fireObservers) { - propertyWillChange(this.context, this.propertyName); - } - - this.cache[this.propertyName] = newValue; - - if (fireObservers) { - propertyDidChange(this.context, this.propertyName); - } - } else { - delete this.cache[this.propertyName]; - } - } -}; - -/** - A computed property whose dependent keys are arrays and which is updated with - "one at a time" semantics. - - @class ReduceComputedProperty - @namespace Ember - @extends Ember.ComputedProperty - @constructor -*/ -function ReduceComputedProperty(options) { - var cp = this; - - this.options = options; - this._instanceMetas = {}; - - this._dependentKeys = null; - // A map of dependentKey -> [itemProperty, ...] that tracks what properties of - // items in the array we must track to update this property. - this._itemPropertyKeys = {}; - this._previousItemPropertyKeys = {}; - - this.readOnly(); - this.cacheable(); - - this.recomputeOnce = function(propertyName) { - // What we really want to do is coalesce by . - // We need a form of `scheduleOnce` that accepts an arbitrary token to - // coalesce by, in addition to the target and method. - Ember.run.once(this, recompute, propertyName); - }; - var recompute = function(propertyName) { - var dependentKeys = cp._dependentKeys, - meta = cp._instanceMeta(this, propertyName), - callbacks = cp._callbacks(); - - reset.call(this, cp, propertyName); - - meta.dependentArraysObserver.suspendArrayObservers(function () { - forEach(cp._dependentKeys, function (dependentKey) { - var dependentArray = get(this, dependentKey), - previousDependentArray = meta.dependentArrays[dependentKey]; - - if (dependentArray === previousDependentArray) { - // The array may be the same, but our item property keys may have - // changed, so we set them up again. We can't easily tell if they've - // changed: the array may be the same object, but with different - // contents. - if (cp._previousItemPropertyKeys[dependentKey]) { - delete cp._previousItemPropertyKeys[dependentKey]; - meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]); - } - } else { - meta.dependentArrays[dependentKey] = dependentArray; - - if (previousDependentArray) { - meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey); - } - - if (dependentArray) { - meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey); - } - } - }, this); - }, this); - - forEach(cp._dependentKeys, function(dependentKey) { - var dependentArray = get(this, dependentKey); - if (dependentArray) { - addItems.call(this, dependentArray, callbacks, cp, propertyName, meta); - } - }, this); - }; - - this.func = function (propertyName) { - Ember.assert("Computed reduce values require at least one dependent key", cp._dependentKeys); - - recompute.call(this, propertyName); - - return cp._instanceMeta(this, propertyName).getValue(); - }; -} - -Ember.ReduceComputedProperty = ReduceComputedProperty; -ReduceComputedProperty.prototype = o_create(ComputedProperty.prototype); - -function defaultCallback(computedValue) { - return computedValue; -} - -ReduceComputedProperty.prototype._callbacks = function () { - if (!this.callbacks) { - var options = this.options; - this.callbacks = { - removedItem: options.removedItem || defaultCallback, - addedItem: options.addedItem || defaultCallback - }; - } - return this.callbacks; -}; - -ReduceComputedProperty.prototype._hasInstanceMeta = function (context, propertyName) { - var guid = guidFor(context), - key = guid + ':' + propertyName; - - return !!this._instanceMetas[key]; -}; - -ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) { - var guid = guidFor(context), - key = guid + ':' + propertyName, - meta = this._instanceMetas[key]; - - if (!meta) { - meta = this._instanceMetas[key] = new ReduceComputedPropertyInstanceMeta(context, propertyName, this.initialValue()); - meta.dependentArraysObserver = new DependentArraysObserver(this._callbacks(), this, meta, context, propertyName, meta.sugarMeta); - } - - return meta; -}; - -ReduceComputedProperty.prototype.initialValue = function () { - if (typeof this.options.initialValue === 'function') { - return this.options.initialValue(); - } - else { - return this.options.initialValue; - } -}; - -ReduceComputedProperty.prototype.resetValue = function (value) { - return this.initialValue(); -}; - -ReduceComputedProperty.prototype.itemPropertyKey = function (dependentArrayKey, itemPropertyKey) { - this._itemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey] || []; - this._itemPropertyKeys[dependentArrayKey].push(itemPropertyKey); -}; - -ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArrayKey) { - if (this._itemPropertyKeys[dependentArrayKey]) { - this._previousItemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey]; - this._itemPropertyKeys[dependentArrayKey] = []; - } -}; - -ReduceComputedProperty.prototype.property = function () { - var cp = this, - args = a_slice.call(arguments), - propertyArgs = new Ember.Set(), - match, - dependentArrayKey, - itemPropertyKey; - - forEach(a_slice.call(arguments), function (dependentKey) { - if (doubleEachPropertyPattern.test(dependentKey)) { - throw new Ember.Error("Nested @each properties not supported: " + dependentKey); - } else if (match = eachPropertyPattern.exec(dependentKey)) { - dependentArrayKey = match[1]; - itemPropertyKey = match[2]; - cp.itemPropertyKey(dependentArrayKey, itemPropertyKey); - propertyArgs.add(dependentArrayKey); - } else { - propertyArgs.add(dependentKey); - } - }); - - return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray()); -}; - -/** - Creates a computed property which operates on dependent arrays and - is updated with "one at a time" semantics. When items are added or - removed from the dependent array(s) a reduce computed only operates - on the change instead of re-evaluating the entire array. - - If there are more than one arguments the first arguments are - considered to be dependent property keys. The last argument is - required to be an options object. The options object can have the - following four properties: - - `initialValue` - A value or function that will be used as the initial - value for the computed. If this property is a function the result of calling - the function will be used as the initial value. This property is required. - - `initialize` - An optional initialize function. Typically this will be used - to set up state on the instanceMeta object. - - `removedItem` - A function that is called each time an element is removed - from the array. - - `addedItem` - A function that is called each time an element is added to - the array. - - - The `initialize` function has the following signature: - - ```javascript - function (initialValue, changeMeta, instanceMeta) - ``` - - `initialValue` - The value of the `initialValue` property from the - options object. - - `changeMeta` - An object which contains meta information about the - computed. It contains the following properties: - - - `property` the computed property - - `propertyName` the name of the property on the object - - `instanceMeta` - An object that can be used to store meta - information needed for calculating your computed. For example a - unique computed might use this to store the number of times a given - element is found in the dependent array. - - - The `removedItem` and `addedItem` functions both have the following signature: - - ```javascript - function (accumulatedValue, item, changeMeta, instanceMeta) - ``` - - `accumulatedValue` - The value returned from the last time - `removedItem` or `addedItem` was called or `initialValue`. - - `item` - the element added or removed from the array - - `changeMeta` - An object which contains meta information about the - change. It contains the following properties: - - - `property` the computed property - - `propertyName` the name of the property on the object - - `index` the index of the added or removed item - - `item` the added or removed item: this is exactly the same as - the second arg - - `arrayChanged` the array that triggered the change. Can be - useful when depending on multiple arrays. - - For property changes triggered on an item property change (when - depKey is something like `someArray.@each.someProperty`), - `changeMeta` will also contain the following property: - - - `previousValues` an object whose keys are the properties that changed on - the item, and whose values are the item's previous values. - - `previousValues` is important Ember coalesces item property changes via - Ember.run.once. This means that by the time removedItem gets called, item has - the new values, but you may need the previous value (eg for sorting & - filtering). - - `instanceMeta` - An object that can be used to store meta - information needed for calculating your computed. For example a - unique computed might use this to store the number of times a given - element is found in the dependent array. - - The `removedItem` and `addedItem` functions should return the accumulated - value. It is acceptable to not return anything (ie return undefined) - to invalidate the computation. This is generally not a good idea for - arrayComputed but it's used in eg max and min. - - Note that observers will be fired if either of these functions return a value - that differs from the accumulated value. When returning an object that - mutates in response to array changes, for example an array that maps - everything from some other array (see `Ember.computed.map`), it is usually - important that the *same* array be returned to avoid accidentally triggering observers. - - Example - - ```javascript - Ember.computed.max = function (dependentKey) { - return Ember.reduceComputed.call(null, dependentKey, { - initialValue: -Infinity, - - addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - return Math.max(accumulatedValue, item); - }, - - removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - if (item < accumulatedValue) { - return accumulatedValue; - } - } - }); - }; - ``` - - Dependent keys may refer to `@this` to observe changes to the object itself, - which must be array-like, rather than a property of the object. This is - mostly useful for array proxies, to ensure objects are retrieved via - `objectAtContent`. This is how you could sort items by properties defined on an item controller. - - Example - - ```javascript - App.PeopleController = Ember.ArrayController.extend({ - itemController: 'person', - - sortedPeople: Ember.computed.sort('@this.@each.reversedName', function(personA, personB) { - // `reversedName` isn't defined on Person, but we have access to it via - // the item controller App.PersonController. If we'd used - // `content.@each.reversedName` above, we would be getting the objects - // directly and not have access to `reversedName`. - // - var reversedNameA = get(personA, 'reversedName'), - reversedNameB = get(personB, 'reversedName'); - - return Ember.compare(reversedNameA, reversedNameB); - }) - }); - - App.PersonController = Ember.ObjectController.extend({ - reversedName: function () { - return reverse(get(this, 'name')); - }.property('name') - }) - ``` - - @method reduceComputed - @for Ember - @param {String} [dependentKeys*] - @param {Object} options - @return {Ember.ComputedProperty} -*/ -Ember.reduceComputed = function (options) { - var args; - - if (arguments.length > 1) { - args = a_slice.call(arguments, 0, -1); - options = a_slice.call(arguments, -1)[0]; - } - - if (typeof options !== "object") { - throw new Ember.Error("Reduce Computed Property declared without an options hash"); - } - - if (!('initialValue' in options)) { - throw new Ember.Error("Reduce Computed Property declared without an initial value"); - } - - var cp = new ReduceComputedProperty(options); - - if (args) { - cp.property.apply(cp, args); - } - - return cp; -}; - -})(); - - - -(function() { -var ReduceComputedProperty = Ember.ReduceComputedProperty, - a_slice = [].slice, - o_create = Ember.create, - forEach = Ember.EnumerableUtils.forEach; - -function ArrayComputedProperty() { - var cp = this; - - ReduceComputedProperty.apply(this, arguments); - - this.func = (function(reduceFunc) { - return function (propertyName) { - if (!cp._hasInstanceMeta(this, propertyName)) { - // When we recompute an array computed property, we need already - // retrieved arrays to be updated; we can't simply empty the cache and - // hope the array is re-retrieved. - forEach(cp._dependentKeys, function(dependentKey) { - Ember.addObserver(this, dependentKey, function() { - cp.recomputeOnce.call(this, propertyName); - }); - }, this); - } - - return reduceFunc.apply(this, arguments); - }; - })(this.func); - - return this; -} -Ember.ArrayComputedProperty = ArrayComputedProperty; -ArrayComputedProperty.prototype = o_create(ReduceComputedProperty.prototype); -ArrayComputedProperty.prototype.initialValue = function () { - return Ember.A(); -}; -ArrayComputedProperty.prototype.resetValue = function (array) { - array.clear(); - return array; -}; - -/** - Creates a computed property which operates on dependent arrays and - is updated with "one at a time" semantics. When items are added or - removed from the dependent array(s) an array computed only operates - on the change instead of re-evaluating the entire array. This should - return an array, if you'd like to use "one at a time" semantics and - compute some value other then an array look at - `Ember.reduceComputed`. - - If there are more than one arguments the first arguments are - considered to be dependent property keys. The last argument is - required to be an options object. The options object can have the - following three properties. - - `initialize` - An optional initialize function. Typically this will be used - to set up state on the instanceMeta object. - - `removedItem` - A function that is called each time an element is - removed from the array. - - `addedItem` - A function that is called each time an element is - added to the array. - - - The `initialize` function has the following signature: - - ```javascript - function (array, changeMeta, instanceMeta) - ``` - - `array` - The initial value of the arrayComputed, an empty array. - - `changeMeta` - An object which contains meta information about the - computed. It contains the following properties: - - - `property` the computed property - - `propertyName` the name of the property on the object - - `instanceMeta` - An object that can be used to store meta - information needed for calculating your computed. For example a - unique computed might use this to store the number of times a given - element is found in the dependent array. - - - The `removedItem` and `addedItem` functions both have the following signature: - - ```javascript - function (accumulatedValue, item, changeMeta, instanceMeta) - ``` - - `accumulatedValue` - The value returned from the last time - `removedItem` or `addedItem` was called or an empty array. - - `item` - the element added or removed from the array - - `changeMeta` - An object which contains meta information about the - change. It contains the following properties: - - - `property` the computed property - - `propertyName` the name of the property on the object - - `index` the index of the added or removed item - - `item` the added or removed item: this is exactly the same as - the second arg - - `arrayChanged` the array that triggered the change. Can be - useful when depending on multiple arrays. - - For property changes triggered on an item property change (when - depKey is something like `someArray.@each.someProperty`), - `changeMeta` will also contain the following property: - - - `previousValues` an object whose keys are the properties that changed on - the item, and whose values are the item's previous values. - - `previousValues` is important Ember coalesces item property changes via - Ember.run.once. This means that by the time removedItem gets called, item has - the new values, but you may need the previous value (eg for sorting & - filtering). - - `instanceMeta` - An object that can be used to store meta - information needed for calculating your computed. For example a - unique computed might use this to store the number of times a given - element is found in the dependent array. - - The `removedItem` and `addedItem` functions should return the accumulated - value. It is acceptable to not return anything (ie return undefined) - to invalidate the computation. This is generally not a good idea for - arrayComputed but it's used in eg max and min. - - Example - - ```javascript - Ember.computed.map = function(dependentKey, callback) { - var options = { - addedItem: function(array, item, changeMeta, instanceMeta) { - var mapped = callback(item); - array.insertAt(changeMeta.index, mapped); - return array; - }, - removedItem: function(array, item, changeMeta, instanceMeta) { - array.removeAt(changeMeta.index, 1); - return array; - } - }; - - return Ember.arrayComputed(dependentKey, options); - }; - ``` - - @method arrayComputed - @for Ember - @param {String} [dependentKeys*] - @param {Object} options - @return {Ember.ComputedProperty} -*/ -Ember.arrayComputed = function (options) { - var args; - - if (arguments.length > 1) { - args = a_slice.call(arguments, 0, -1); - options = a_slice.call(arguments, -1)[0]; - } - - if (typeof options !== "object") { - throw new Ember.Error("Array Computed Property declared without an options hash"); - } - - var cp = new ArrayComputedProperty(options); - - if (args) { - cp.property.apply(cp, args); - } - - return cp; -}; - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var get = Ember.get, - set = Ember.set, - guidFor = Ember.guidFor, - merge = Ember.merge, - a_slice = [].slice, - forEach = Ember.EnumerableUtils.forEach, - map = Ember.EnumerableUtils.map, - SearchProxy; - -/** - A computed property that calculates the maximum value in the - dependent array. This will return `-Infinity` when the dependent - array is empty. - - Example - - ```javascript - App.Person = Ember.Object.extend({ - childAges: Ember.computed.mapBy('children', 'age'), - maxChildAge: Ember.computed.max('childAges') - }); - - var lordByron = App.Person.create({children: []}); - lordByron.get('maxChildAge'); // -Infinity - lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); - lordByron.get('maxChildAge'); // 7 - lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); - lordByron.get('maxChildAge'); // 8 - ``` - - @method computed.max - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array -*/ -Ember.computed.max = function (dependentKey) { - return Ember.reduceComputed.call(null, dependentKey, { - initialValue: -Infinity, - - addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - return Math.max(accumulatedValue, item); - }, - - removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - if (item < accumulatedValue) { - return accumulatedValue; - } - } - }); -}; - -/** - A computed property that calculates the minimum value in the - dependent array. This will return `Infinity` when the dependent - array is empty. - - Example - - ```javascript - App.Person = Ember.Object.extend({ - childAges: Ember.computed.mapBy('children', 'age'), - minChildAge: Ember.computed.min('childAges') - }); - - var lordByron = App.Person.create({children: []}); - lordByron.get('minChildAge'); // Infinity - lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); - lordByron.get('minChildAge'); // 7 - lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); - lordByron.get('minChildAge'); // 5 - ``` - - @method computed.min - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array -*/ -Ember.computed.min = function (dependentKey) { - return Ember.reduceComputed.call(null, dependentKey, { - initialValue: Infinity, - - addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - return Math.min(accumulatedValue, item); - }, - - removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { - if (item > accumulatedValue) { - return accumulatedValue; - } - } - }); -}; - -/** - Returns an array mapped via the callback - - The callback method you provide should have the following signature: - - ```javascript - function(item); - ``` - - - `item` is the current item in the iteration. - - Example - - ```javascript - App.Hamster = Ember.Object.extend({ - excitingChores: Ember.computed.map('chores', function(chore) { - return chore.toUpperCase() + '!'; - }) - }); - - var hamster = App.Hamster.create({chores: ['cook', 'clean', 'write more unit tests']}); - hamster.get('excitingChores'); // ['COOK!', 'CLEAN!', 'WRITE MORE UNIT TESTS!'] - ``` - - @method computed.map - @for Ember - @param {String} dependentKey - @param {Function} callback - @return {Ember.ComputedProperty} an array mapped via the callback -*/ -Ember.computed.map = function(dependentKey, callback) { - var options = { - addedItem: function(array, item, changeMeta, instanceMeta) { - var mapped = callback.call(this, item); - array.insertAt(changeMeta.index, mapped); - return array; - }, - removedItem: function(array, item, changeMeta, instanceMeta) { - array.removeAt(changeMeta.index, 1); - return array; - } - }; - - return Ember.arrayComputed(dependentKey, options); -}; - -/** - Returns an array mapped to the specified key. - - Example - - ```javascript - App.Person = Ember.Object.extend({ - childAges: Ember.computed.mapBy('children', 'age') - }); - - var lordByron = App.Person.create({children: []}); - lordByron.get('childAges'); // [] - lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); - lordByron.get('childAges'); // [7] - lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); - lordByron.get('childAges'); // [7, 5, 8] - ``` - - @method computed.mapBy - @for Ember - @param {String} dependentKey - @param {String} propertyKey - @return {Ember.ComputedProperty} an array mapped to the specified key -*/ -Ember.computed.mapBy = function(dependentKey, propertyKey) { - var callback = function(item) { return get(item, propertyKey); }; - return Ember.computed.map(dependentKey + '.@each.' + propertyKey, callback); -}; - -/** - @method computed.mapProperty - @for Ember - @deprecated Use `Ember.computed.mapBy` instead - @param dependentKey - @param propertyKey -*/ -Ember.computed.mapProperty = Ember.computed.mapBy; - -/** - Filters the array by the callback. - - The callback method you provide should have the following signature: - - ```javascript - function(item); - ``` - - - `item` is the current item in the iteration. - - Example - - ```javascript - App.Hamster = Ember.Object.extend({ - remainingChores: Ember.computed.filter('chores', function(chore) { - return !chore.done; - }) - }); - - var hamster = App.Hamster.create({chores: [ - {name: 'cook', done: true}, - {name: 'clean', done: true}, - {name: 'write more unit tests', done: false} - ]}); - hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] - ``` - - @method computed.filter - @for Ember - @param {String} dependentKey - @param {Function} callback - @return {Ember.ComputedProperty} the filtered array -*/ -Ember.computed.filter = function(dependentKey, callback) { - var options = { - initialize: function (array, changeMeta, instanceMeta) { - instanceMeta.filteredArrayIndexes = new Ember.SubArray(); - }, - - addedItem: function(array, item, changeMeta, instanceMeta) { - var match = !!callback.call(this, item), - filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match); - - if (match) { - array.insertAt(filterIndex, item); - } - - return array; - }, - - removedItem: function(array, item, changeMeta, instanceMeta) { - var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index); - - if (filterIndex > -1) { - array.removeAt(filterIndex); - } - - return array; - } - }; - - return Ember.arrayComputed(dependentKey, options); -}; - -/** - Filters the array by the property and value - - Example - - ```javascript - App.Hamster = Ember.Object.extend({ - remainingChores: Ember.computed.filterBy('chores', 'done', false) - }); - - var hamster = App.Hamster.create({chores: [ - {name: 'cook', done: true}, - {name: 'clean', done: true}, - {name: 'write more unit tests', done: false} - ]}); - hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] - ``` - - @method computed.filterBy - @for Ember - @param {String} dependentKey - @param {String} propertyKey - @param {String} value - @return {Ember.ComputedProperty} the filtered array -*/ -Ember.computed.filterBy = function(dependentKey, propertyKey, value) { - var callback; - - if (arguments.length === 2) { - callback = function(item) { - return get(item, propertyKey); - }; - } else { - callback = function(item) { - return get(item, propertyKey) === value; - }; - } - - return Ember.computed.filter(dependentKey + '.@each.' + propertyKey, callback); -}; - -/** - @method computed.filterProperty - @for Ember - @param dependentKey - @param propertyKey - @param value - @deprecated Use `Ember.computed.filterBy` instead -*/ -Ember.computed.filterProperty = Ember.computed.filterBy; - -/** - A computed property which returns a new array with all the unique - elements from one or more dependent arrays. - - Example - - ```javascript - App.Hamster = Ember.Object.extend({ - uniqueFruits: Ember.computed.uniq('fruits') - }); - - var hamster = App.Hamster.create({fruits: [ - 'banana', - 'grape', - 'kale', - 'banana' - ]}); - hamster.get('uniqueFruits'); // ['banana', 'grape', 'kale'] - ``` - - @method computed.uniq - @for Ember - @param {String} propertyKey* - @return {Ember.ComputedProperty} computes a new array with all the - unique elements from the dependent array -*/ -Ember.computed.uniq = function() { - var args = a_slice.call(arguments); - args.push({ - initialize: function(array, changeMeta, instanceMeta) { - instanceMeta.itemCounts = {}; - }, - - addedItem: function(array, item, changeMeta, instanceMeta) { - var guid = guidFor(item); - - if (!instanceMeta.itemCounts[guid]) { - instanceMeta.itemCounts[guid] = 1; - } else { - ++instanceMeta.itemCounts[guid]; - } - array.addObject(item); - return array; - }, - removedItem: function(array, item, _, instanceMeta) { - var guid = guidFor(item), - itemCounts = instanceMeta.itemCounts; - - if (--itemCounts[guid] === 0) { - array.removeObject(item); - } - return array; - } - }); - return Ember.arrayComputed.apply(null, args); -}; - -/** - Alias for [Ember.computed.uniq](/api/#method_computed_uniq). - - @method computed.union - @for Ember - @param {String} propertyKey* - @return {Ember.ComputedProperty} computes a new array with all the - unique elements from the dependent array -*/ -Ember.computed.union = Ember.computed.uniq; - -/** - A computed property which returns a new array with all the duplicated - elements from two or more dependeny arrays. - - Example - - ```javascript - var obj = Ember.Object.createWithMixins({ - adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'], - charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'], - friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends') - }); - - obj.get('friendsInCommon'); // ['William King', 'Mary Somerville'] - ``` - - @method computed.intersect - @for Ember - @param {String} propertyKey* - @return {Ember.ComputedProperty} computes a new array with all the - duplicated elements from the dependent arrays -*/ -Ember.computed.intersect = function () { - var getDependentKeyGuids = function (changeMeta) { - return map(changeMeta.property._dependentKeys, function (dependentKey) { - return guidFor(dependentKey); - }); - }; - - var args = a_slice.call(arguments); - args.push({ - initialize: function (array, changeMeta, instanceMeta) { - instanceMeta.itemCounts = {}; - }, - - addedItem: function(array, item, changeMeta, instanceMeta) { - var itemGuid = guidFor(item), - dependentGuids = getDependentKeyGuids(changeMeta), - dependentGuid = guidFor(changeMeta.arrayChanged), - numberOfDependentArrays = changeMeta.property._dependentKeys.length, - itemCounts = instanceMeta.itemCounts; - - if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; } - if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } - - if (++itemCounts[itemGuid][dependentGuid] === 1 && - numberOfDependentArrays === Ember.keys(itemCounts[itemGuid]).length) { - - array.addObject(item); - } - return array; - }, - removedItem: function(array, item, changeMeta, instanceMeta) { - var itemGuid = guidFor(item), - dependentGuids = getDependentKeyGuids(changeMeta), - dependentGuid = guidFor(changeMeta.arrayChanged), - numberOfDependentArrays = changeMeta.property._dependentKeys.length, - numberOfArraysItemAppearsIn, - itemCounts = instanceMeta.itemCounts; - - if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } - if (--itemCounts[itemGuid][dependentGuid] === 0) { - delete itemCounts[itemGuid][dependentGuid]; - numberOfArraysItemAppearsIn = Ember.keys(itemCounts[itemGuid]).length; - - if (numberOfArraysItemAppearsIn === 0) { - delete itemCounts[itemGuid]; - } - array.removeObject(item); - } - return array; - } - }); - return Ember.arrayComputed.apply(null, args); -}; - -/** - A computed property which returns a new array with all the - properties from the first dependent array that are not in the second - dependent array. - - Example - - ```javascript - App.Hamster = Ember.Object.extend({ - likes: ['banana', 'grape', 'kale'], - wants: Ember.computed.setDiff('likes', 'fruits') - }); - - var hamster = App.Hamster.create({fruits: [ - 'grape', - 'kale', - ]}); - hamster.get('wants'); // ['banana'] - ``` - - @method computed.setDiff - @for Ember - @param {String} setAProperty - @param {String} setBProperty - @return {Ember.ComputedProperty} computes a new array with all the - items from the first dependent array that are not in the second - dependent array -*/ -Ember.computed.setDiff = function (setAProperty, setBProperty) { - if (arguments.length !== 2) { - throw new Ember.Error("setDiff requires exactly two dependent arrays."); - } - return Ember.arrayComputed.call(null, setAProperty, setBProperty, { - addedItem: function (array, item, changeMeta, instanceMeta) { - var setA = get(this, setAProperty), - setB = get(this, setBProperty); - - if (changeMeta.arrayChanged === setA) { - if (!setB.contains(item)) { - array.addObject(item); - } - } else { - array.removeObject(item); - } - return array; - }, - - removedItem: function (array, item, changeMeta, instanceMeta) { - var setA = get(this, setAProperty), - setB = get(this, setBProperty); - - if (changeMeta.arrayChanged === setB) { - if (setA.contains(item)) { - array.addObject(item); - } - } else { - array.removeObject(item); - } - return array; - } - }); -}; - -function binarySearch(array, item, low, high) { - var mid, midItem, res, guidMid, guidItem; - - if (arguments.length < 4) { high = get(array, 'length'); } - if (arguments.length < 3) { low = 0; } - - if (low === high) { - return low; - } - - mid = low + Math.floor((high - low) / 2); - midItem = array.objectAt(mid); - - guidMid = _guidFor(midItem); - guidItem = _guidFor(item); - - if (guidMid === guidItem) { - return mid; - } - - res = this.order(midItem, item); - if (res === 0) { - res = guidMid < guidItem ? -1 : 1; - } - - - if (res < 0) { - return this.binarySearch(array, item, mid+1, high); - } else if (res > 0) { - return this.binarySearch(array, item, low, mid); - } - - return mid; - - function _guidFor(item) { - if (SearchProxy.detectInstance(item)) { - return guidFor(get(item, 'content')); - } - return guidFor(item); - } -} - - -SearchProxy = Ember.ObjectProxy.extend(); - -/** - A computed property which returns a new array with all the - properties from the first dependent array sorted based on a property - or sort function. - - The callback method you provide should have the following signature: - - ```javascript - function(itemA, itemB); - ``` - - - `itemA` the first item to compare. - - `itemB` the second item to compare. - - This function should return `-1` when `itemA` should come before - `itemB`. It should return `1` when `itemA` should come after - `itemB`. If the `itemA` and `itemB` are equal this function should return `0`. - - Example - - ```javascript - var ToDoList = Ember.Object.extend({ - todosSorting: ['name'], - sortedTodos: Ember.computed.sort('todos', 'todosSorting'), - priorityTodos: Ember.computed.sort('todos', function(a, b){ - if (a.priority > b.priority) { - return 1; - } else if (a.priority < b.priority) { - return -1; - } - return 0; - }), - }); - var todoList = ToDoList.create({todos: [ - {name: 'Unit Test', priority: 2}, - {name: 'Documentation', priority: 3}, - {name: 'Release', priority: 1} - ]}); - - todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}] - todoList.get('priorityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}] - ``` - - @method computed.sort - @for Ember - @param {String} dependentKey - @param {String or Function} sortDefinition a dependent key to an - array of sort properties or a function to use when sorting - @return {Ember.ComputedProperty} computes a new sorted array based - on the sort property array or callback function -*/ -Ember.computed.sort = function (itemsKey, sortDefinition) { - Ember.assert("Ember.computed.sort requires two arguments: an array key to sort and either a sort properties key or sort function", arguments.length === 2); - - var initFn, sortPropertiesKey; - - if (typeof sortDefinition === 'function') { - initFn = function (array, changeMeta, instanceMeta) { - instanceMeta.order = sortDefinition; - instanceMeta.binarySearch = binarySearch; - }; - } else { - sortPropertiesKey = sortDefinition; - initFn = function (array, changeMeta, instanceMeta) { - function setupSortProperties() { - var sortPropertyDefinitions = get(this, sortPropertiesKey), - sortProperty, - sortProperties = instanceMeta.sortProperties = [], - sortPropertyAscending = instanceMeta.sortPropertyAscending = {}, - idx, - asc; - - Ember.assert("Cannot sort: '" + sortPropertiesKey + "' is not an array.", Ember.isArray(sortPropertyDefinitions)); - - changeMeta.property.clearItemPropertyKeys(itemsKey); - - forEach(sortPropertyDefinitions, function (sortPropertyDefinition) { - if ((idx = sortPropertyDefinition.indexOf(':')) !== -1) { - sortProperty = sortPropertyDefinition.substring(0, idx); - asc = sortPropertyDefinition.substring(idx+1).toLowerCase() !== 'desc'; - } else { - sortProperty = sortPropertyDefinition; - asc = true; - } - - sortProperties.push(sortProperty); - sortPropertyAscending[sortProperty] = asc; - changeMeta.property.itemPropertyKey(itemsKey, sortProperty); - }); - - sortPropertyDefinitions.addObserver('@each', this, updateSortPropertiesOnce); - } - - function updateSortPropertiesOnce() { - Ember.run.once(this, updateSortProperties, changeMeta.propertyName); - } - - function updateSortProperties(propertyName) { - setupSortProperties.call(this); - changeMeta.property.recomputeOnce.call(this, propertyName); - } - - Ember.addObserver(this, sortPropertiesKey, updateSortPropertiesOnce); - - setupSortProperties.call(this); - - - instanceMeta.order = function (itemA, itemB) { - var sortProperty, result, asc; - for (var i = 0; i < this.sortProperties.length; ++i) { - sortProperty = this.sortProperties[i]; - result = Ember.compare(get(itemA, sortProperty), get(itemB, sortProperty)); - - if (result !== 0) { - asc = this.sortPropertyAscending[sortProperty]; - return asc ? result : (-1 * result); - } - } - - return 0; - }; - - instanceMeta.binarySearch = binarySearch; - }; - } - - return Ember.arrayComputed.call(null, itemsKey, { - initialize: initFn, - - addedItem: function (array, item, changeMeta, instanceMeta) { - var index = instanceMeta.binarySearch(array, item); - array.insertAt(index, item); - return array; - }, - - removedItem: function (array, item, changeMeta, instanceMeta) { - var proxyProperties, index, searchItem; - - if (changeMeta.previousValues) { - proxyProperties = merge({ content: item }, changeMeta.previousValues); - - searchItem = SearchProxy.create(proxyProperties); - } else { - searchItem = item; - } - - index = instanceMeta.binarySearch(array, searchItem); - array.removeAt(index); - return array; - } - }); -}; - -})(); - - - -(function() { -/** - Expose RSVP implementation - - Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md - - @class RSVP - @namespace Ember - @constructor -*/ -Ember.RSVP = requireModule('rsvp'); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var a_slice = Array.prototype.slice; - -if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) { - - /** - The `property` extension of Javascript's Function prototype is available - when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is - `true`, which is the default. - - Computed properties allow you to treat a function like a property: - - ```javascript - MyApp.President = Ember.Object.extend({ - firstName: '', - lastName: '', - - fullName: function() { - return this.get('firstName') + ' ' + this.get('lastName'); - - // Call this flag to mark the function as a property - }.property() - }); - - var president = MyApp.President.create({ - firstName: "Barack", - lastName: "Obama" - }); - - president.get('fullName'); // "Barack Obama" - ``` - - Treating a function like a property is useful because they can work with - bindings, just like any other property. - - Many computed properties have dependencies on other properties. For - example, in the above example, the `fullName` property depends on - `firstName` and `lastName` to determine its value. You can tell Ember - about these dependencies like this: - - ```javascript - MyApp.President = Ember.Object.extend({ - firstName: '', - lastName: '', - - fullName: function() { - return this.get('firstName') + ' ' + this.get('lastName'); - - // Tell Ember.js that this computed property depends on firstName - // and lastName - }.property('firstName', 'lastName') - }); - ``` - - Make sure you list these dependencies so Ember knows when to update - bindings that connect to a computed property. Changing a dependency - will not immediately trigger an update of the computed property, but - will instead clear the cache so that it is updated when the next `get` - is called on the property. - - See [Ember.ComputedProperty](/api/classes/Ember.ComputedProperty.html), [Ember.computed](/api/#method_computed). - - @method property - @for Function - */ - Function.prototype.property = function() { - var ret = Ember.computed(this); - return ret.property.apply(ret, arguments); - }; - - /** - The `observes` extension of Javascript's Function prototype is available - when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is - true, which is the default. - - You can 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.extend({ - valueObserver: function() { - // Executes whenever the "value" property changes - }.observes('value') - }); - ``` - - In the future this method may become asynchronous. If you want to ensure - synchronous behavior, use `observesImmediately`. - - See `Ember.observer`. - - @method observes - @for Function - */ - Function.prototype.observes = function() { - this.__ember_observes__ = a_slice.call(arguments); - return this; - }; - - /** - The `observesImmediately` extension of Javascript's Function prototype is - available when `Ember.EXTEND_PROTOTYPES` or - `Ember.EXTEND_PROTOTYPES.Function` is true, which is the default. - - You can observe property changes simply by adding the `observesImmediately` - call to the end of your method declarations in classes that you write. - For example: - - ```javascript - Ember.Object.extend({ - valueObserver: function() { - // Executes immediately after the "value" property changes - }.observesImmediately('value') - }); - ``` - - In the future, `observes` may become asynchronous. In this event, - `observesImmediately` will maintain the synchronous behavior. - - See `Ember.immediateObserver`. - - @method observesImmediately - @for Function - */ - Function.prototype.observesImmediately = function() { - for (var i=0, l=arguments.length; i 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 - @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 Ember.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 = Contact.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 - @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 is of a type supported by the enumerable. - - ```javascript - set.addObject(contact); - ``` - - ## Removing Objects - - To remove an object from an enumerable, use the `removeObject()` method. This - will only remove the object if it is present 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 - @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 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 object 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 - @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 Ember.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 `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 Ember.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 {*} obj object to push - @return {*} 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) { - if (!(Ember.Enumerable.detect(objects) || Ember.isArray(objects))) { - throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects"); - } - 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 {*} obj object to unshift - @return {*} 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; - -/** -`Ember.TargetActionSupport` is a mixin that can be included in a class -to add a `triggerAction` method with semantics similar to the Handlebars -`{{action}}` helper. In normal Ember usage, the `{{action}}` helper is -usually the best choice. This mixin is most often useful when you are -doing more complex event handling in View objects. - -See also `Ember.ViewTargetActionSupport`, which has -view-aware defaults for target and actionContext. - -@class TargetActionSupport -@namespace Ember -@extends Ember.Mixin -*/ -Ember.TargetActionSupport = Ember.Mixin.create({ - target: null, - action: null, - actionContext: 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'), - - actionContextObject: Ember.computed(function() { - var actionContext = get(this, 'actionContext'); - - if (Ember.typeOf(actionContext) === "string") { - var value = get(this, actionContext); - if (value === undefined) { value = get(Ember.lookup, actionContext); } - return value; - } else { - return actionContext; - } - }).property('actionContext'), - - /** - Send an "action" with an "actionContext" to a "target". The action, actionContext - and target will be retrieved from properties of the object. For example: - - ```javascript - App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, { - target: Ember.computed.alias('controller'), - action: 'save', - actionContext: Ember.computed.alias('context'), - click: function() { - this.triggerAction(); // Sends the `save` action, along with the current context - // to the current controller - } - }); - ``` - - The `target`, `action`, and `actionContext` can be provided as properties of - an optional object argument to `triggerAction` as well. - - ```javascript - App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, { - click: function() { - this.triggerAction({ - action: 'save', - target: this.get('controller'), - actionContext: this.get('context'), - }); // Sends the `save` action, along with the current context - // to the current controller - } - }); - ``` - - The `actionContext` defaults to the object you mixing `TargetActionSupport` into. - But `target` and `action` must be specified either as properties or with the argument - to `triggerAction`, or a combination: - - ```javascript - App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, { - target: Ember.computed.alias('controller'), - click: function() { - this.triggerAction({ - action: 'save' - }); // Sends the `save` action, along with a reference to `this`, - // to the current controller - } - }); - ``` - - @method triggerAction - @param opts {Hash} (optional, with the optional keys action, target and/or actionContext) - @return {Boolean} true if the action was sent successfully and did not return false - */ - triggerAction: function(opts) { - opts = opts || {}; - var action = opts.action || get(this, 'action'), - target = opts.target || get(this, 'targetObject'), - actionContext = opts.actionContext; - - function args(options, actionName) { - var ret = []; - if (actionName) { ret.push(actionName); } - - return ret.concat(options); - } - - if (typeof actionContext === 'undefined') { - actionContext = get(this, 'actionContextObject') || this; - } - - if (target && action) { var ret; - if (target.send) { - ret = target.send.apply(target, args(actionContext, action)); + if (this[NAME_KEY]) { + ret = this[NAME_KEY]; + } else if (this._toString) { + ret = this._toString; } else { - Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function'); - ret = target[action].apply(target, args(actionContext)); + var str = superClassString(this); + if (str) { + ret = "(subclass of " + str + ")"; + } else { + ret = "(unknown mixin)"; + } + this.toString = makeToString(ret); } - 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 - */ -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; - }, + function processAllNamespaces() { + var unprocessedNamespaces = !Namespace.PROCESSED, + unprocessedMixins = Ember.anyUnprocessedMixins; - /** - 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); - }, - - /** - Cancels subscription for given 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.configure('async', function(callback, promise) { - Ember.run.schedule('actions', promise, callback, promise); -}); - -/** -@module ember -@submodule ember-runtime -*/ - -var get = Ember.get; - -/** - @class Deferred - @namespace Ember - */ -Ember.DeferredMixin = Ember.Mixin.create({ - /** - Add handlers to be called when the Deferred object is resolved or rejected. - - @method then - @param {Function} resolve a callback function to be called when done - @param {Function} reject a callback function to be called when failed - */ - then: function(resolve, reject) { - var deferred, promise, entity; - - entity = this; - deferred = get(this, '_deferred'); - promise = deferred.promise; - - function fulfillmentHandler(fulfillment) { - if (fulfillment === promise) { - return resolve(entity); - } else { - return resolve(fulfillment); - } - } - - return promise.then(resolve && fulfillmentHandler, reject); - }, - - /** - Resolve a Deferred object and call any `doneCallbacks` with the given args. - - @method resolve - */ - resolve: function(value) { - var deferred, promise; - - deferred = get(this, '_deferred'); - promise = deferred.promise; - - if (value === this) { - deferred.resolve(promise); - } else { - deferred.resolve(value); - } - }, - - /** - Reject a Deferred object and call any `failCallbacks` with the given args. - - @method reject - */ - reject: function(value) { - get(this, '_deferred').reject(value); - }, - - _deferred: Ember.computed(function() { - return RSVP.defer(); - }) -}); - - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var get = Ember.get, typeOf = Ember.typeOf; - -/** - The `Ember.ActionHandler` mixin implements support for moving an `actions` - property to an `_actions` property at extend time, and adding `_actions` - to the object's mergedProperties list. - - `Ember.ActionHandler` is used internally by Ember in `Ember.View`, - `Ember.Controller`, and `Ember.Route`. - - @class ActionHandler - @namespace Ember -*/ -Ember.ActionHandler = Ember.Mixin.create({ - mergedProperties: ['_actions'], - - /** - @private - - Moves `actions` to `_actions` at extend time. Note that this currently - modifies the mixin themselves, which is technically dubious but - is practically of little consequence. This may change in the future. - - @method willMergeMixin - */ - willMergeMixin: function(props) { - var hashName; - - if (!props._actions) { - if (typeOf(props.actions) === 'object') { - hashName = 'actions'; - } else if (typeOf(props.events) === 'object') { - Ember.deprecate('Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object', false); - hashName = 'events'; + if (unprocessedNamespaces) { + findNamespaces(); + Namespace.PROCESSED = true; } - if (hashName) { - props._actions = Ember.merge(props._actions || {}, props[hashName]); - } - - delete props[hashName]; - } - }, - - send: function(actionName) { - var args = [].slice.call(arguments, 1), target; - - if (this._actions && this._actions[actionName]) { - if (this._actions[actionName].apply(this, args) === true) { - // handler returned true, so this action will bubble - } else { - return; - } - } else if (this.deprecatedSend && this.deprecatedSendHandles && this.deprecatedSendHandles(actionName)) { - if (this.deprecatedSend.apply(this, [].slice.call(arguments)) === true) { - // handler return true, so this action will bubble - } else { - return; - } - } - - if (target = get(this, 'target')) { - Ember.assert("The `target` for " + this + " (" + target + ") does not have a `send` method", typeof target.send === 'function'); - target.send.apply(target, arguments); - } - } - -}); - -})(); - - - -(function() { -var set = Ember.set, get = Ember.get, - resolve = Ember.RSVP.resolve, - rethrow = Ember.RSVP.rethrow, - not = Ember.computed.not, - or = Ember.computed.or; - -/** - @module ember - @submodule ember-runtime - */ - -function observePromise(proxy, promise) { - promise.then(function(value) { - set(proxy, 'isFulfilled', true); - set(proxy, 'content', value); - }, function(reason) { - set(proxy, 'isRejected', true); - set(proxy, 'reason', reason); - // don't re-throw, as we are merely observing - }); -} - -/** - A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware. - - ```javascript - var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin); - - var controller = ObjectPromiseController.create({ - promise: $.getJSON('/some/remote/data.json') - }); - - controller.then(function(json){ - // the json - }, function(reason) { - // the reason why you have no json - }); - ``` - - the controller has bindable attributes which - track the promises life cycle - - ```javascript - controller.get('isPending') //=> true - controller.get('isSettled') //=> false - controller.get('isRejected') //=> false - controller.get('isFulfilled') //=> false - ``` - - When the the $.getJSON completes, and the promise is fulfilled - with json, the life cycle attributes will update accordingly. - - ```javascript - controller.get('isPending') //=> false - controller.get('isSettled') //=> true - controller.get('isRejected') //=> false - controller.get('isFulfilled') //=> true - ``` - - As the controller is an ObjectController, and the json now its content, - all the json properties will be available directly from the controller. - - ```javascript - // Assuming the following json: - { - firstName: 'Stefan', - lastName: 'Penner' - } - - // both properties will accessible on the controller - controller.get('firstName') //=> 'Stefan' - controller.get('lastName') //=> 'Penner' - ``` - - If the controller is backing a template, the attributes are - bindable from within that template - - ```handlebars - {{#if isPending}} - loading... - {{else}} - firstName: {{firstName}} - lastName: {{lastName}} - {{/if}} - ``` - @class Ember.PromiseProxyMixin -*/ -Ember.PromiseProxyMixin = Ember.Mixin.create({ - reason: null, - isPending: not('isSettled').readOnly(), - isSettled: or('isRejected', 'isFulfilled').readOnly(), - isRejected: false, - isFulfilled: false, - - promise: Ember.computed(function(key, promise) { - if (arguments.length === 2) { - promise = resolve(promise); - observePromise(this, promise); - return promise.then(); // fork the promise. - } else { - throw new Ember.Error("PromiseProxy's promise must be set"); - } - }), - - then: function(fulfill, reject) { - return get(this, 'promise').then(fulfill, reject); - } -}); - - - -})(); - - - -(function() { - -})(); - - - -(function() { -var get = Ember.get, - forEach = Ember.EnumerableUtils.forEach, - RETAIN = 'r', - INSERT = 'i', - DELETE = 'd'; - -/** - An `Ember.TrackedArray` tracks array operations. It's useful when you want to - lazily compute the indexes of items in an array after they've been shifted by - subsequent operations. - - @class TrackedArray - @namespace Ember - @param {array} [items=[]] The array to be tracked. This is used just to get - the initial items for the starting state of retain:n. -*/ -Ember.TrackedArray = function (items) { - if (arguments.length < 1) { items = []; } - - var length = get(items, 'length'); - - if (length) { - this._operations = [new ArrayOperation(RETAIN, length, items)]; - } else { - this._operations = []; - } -}; - -Ember.TrackedArray.RETAIN = RETAIN; -Ember.TrackedArray.INSERT = INSERT; -Ember.TrackedArray.DELETE = DELETE; - -Ember.TrackedArray.prototype = { - - /** - Track that `newItems` were added to the tracked array at `index`. - - @method addItems - @param index - @param newItems - */ - addItems: function (index, newItems) { - var count = get(newItems, 'length'); - if (count < 1) { return; } - - var match = this._findArrayOperation(index), - arrayOperation = match.operation, - arrayOperationIndex = match.index, - arrayOperationRangeStart = match.rangeStart, - composeIndex, - splitIndex, - splitItems, - splitArrayOperation, - newArrayOperation; - - newArrayOperation = new ArrayOperation(INSERT, count, newItems); - - if (arrayOperation) { - if (!match.split) { - // insert left of arrayOperation - this._operations.splice(arrayOperationIndex, 0, newArrayOperation); - composeIndex = arrayOperationIndex; - } else { - this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); - composeIndex = arrayOperationIndex + 1; - } - } else { - // insert at end - this._operations.push(newArrayOperation); - composeIndex = arrayOperationIndex; - } - - this._composeInsert(composeIndex); - }, - - /** - Track that `count` items were removed at `index`. - - @method removeItems - @param index - @param count - */ - removeItems: function (index, count) { - if (count < 1) { return; } - - var match = this._findArrayOperation(index), - arrayOperation = match.operation, - arrayOperationIndex = match.index, - arrayOperationRangeStart = match.rangeStart, - newArrayOperation, - composeIndex; - - newArrayOperation = new ArrayOperation(DELETE, count); - if (!match.split) { - // insert left of arrayOperation - this._operations.splice(arrayOperationIndex, 0, newArrayOperation); - composeIndex = arrayOperationIndex; - } else { - this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); - composeIndex = arrayOperationIndex + 1; - } - - return this._composeDelete(composeIndex); - }, - - /** - Apply all operations, reducing them to retain:n, for `n`, the number of - items in the array. - - `callback` will be called for each operation and will be passed the following arguments: - * {array} items The items for the given operation - * {number} offset The computed offset of the items, ie the index in the - array of the first item for this operation. - * {string} operation The type of the operation. One of - `Ember.TrackedArray.{RETAIN, DELETE, INSERT}` - - @method apply - @param {function} callback - */ - apply: function (callback) { - var items = [], - offset = 0; - - forEach(this._operations, function (arrayOperation) { - callback(arrayOperation.items, offset, arrayOperation.type); - - if (arrayOperation.type !== DELETE) { - offset += arrayOperation.count; - items = items.concat(arrayOperation.items); - } - }); - - this._operations = [new ArrayOperation(RETAIN, items.length, items)]; - }, - - /** - Return an ArrayOperationMatch for the operation that contains the item at `index`. - - @method _findArrayOperation - - @param {number} index the index of the item whose operation information - should be returned. - @private - */ - _findArrayOperation: function (index) { - var arrayOperationIndex, - len, - split = false, - arrayOperation, - arrayOperationRangeStart, - arrayOperationRangeEnd; - - // OPTIMIZE: we could search these faster if we kept a balanced tree. - // find leftmost arrayOperation to the right of `index` - for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) { - arrayOperation = this._operations[arrayOperationIndex]; - - if (arrayOperation.type === DELETE) { continue; } - - arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1; - - if (index === arrayOperationRangeStart) { - break; - } else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) { - split = true; - break; - } else { - arrayOperationRangeStart = arrayOperationRangeEnd + 1; - } - } - - return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart); - }, - - _split: function (arrayOperationIndex, splitIndex, newArrayOperation) { - var arrayOperation = this._operations[arrayOperationIndex], - splitItems = arrayOperation.items.slice(splitIndex), - splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems); - - // truncate LHS - arrayOperation.count = splitIndex; - arrayOperation.items = arrayOperation.items.slice(0, splitIndex); - - this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation); - }, - - // see SubArray for a better implementation. - _composeInsert: function (index) { - var newArrayOperation = this._operations[index], - leftArrayOperation = this._operations[index-1], // may be undefined - rightArrayOperation = this._operations[index+1], // may be undefined - leftOp = leftArrayOperation && leftArrayOperation.type, - rightOp = rightArrayOperation && rightArrayOperation.type; - - if (leftOp === INSERT) { - // merge left - leftArrayOperation.count += newArrayOperation.count; - leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items); - - if (rightOp === INSERT) { - // also merge right (we have split an insert with an insert) - leftArrayOperation.count += rightArrayOperation.count; - leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items); - this._operations.splice(index, 2); - } else { - // only merge left - this._operations.splice(index, 1); - } - } else if (rightOp === INSERT) { - // merge right - newArrayOperation.count += rightArrayOperation.count; - newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items); - this._operations.splice(index + 1, 1); - } - }, - - _composeDelete: function (index) { - var arrayOperation = this._operations[index], - deletesToGo = arrayOperation.count, - leftArrayOperation = this._operations[index-1], // may be undefined - leftOp = leftArrayOperation && leftArrayOperation.type, - nextArrayOperation, - nextOp, - nextCount, - removeNewAndNextOp = false, - removedItems = []; - - if (leftOp === DELETE) { - arrayOperation = leftArrayOperation; - index -= 1; - } - - for (var i = index + 1; deletesToGo > 0; ++i) { - nextArrayOperation = this._operations[i]; - nextOp = nextArrayOperation.type; - nextCount = nextArrayOperation.count; - - if (nextOp === DELETE) { - arrayOperation.count += nextCount; - continue; - } - - if (nextCount > deletesToGo) { - // d:2 {r,i}:5 we reduce the retain or insert, but it stays - removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo)); - nextArrayOperation.count -= deletesToGo; - - // In the case where we truncate the last arrayOperation, we don't need to - // remove it; also the deletesToGo reduction is not the entirety of - // nextCount - i -= 1; - nextCount = deletesToGo; - - deletesToGo = 0; - } else { - if (nextCount === deletesToGo) { - // Handle edge case of d:2 i:2 in which case both operations go away - // during composition. - removeNewAndNextOp = true; + if (unprocessedNamespaces || unprocessedMixins) { + var namespaces = Namespace.NAMESPACES, namespace; + for (var i=0, l=namespaces.length; i 0) { - // compose our new delete with possibly several operations to the right of - // disparate types - this._operations.splice(index+1, i-1-index); - } else { - // The delete operation can go away; it has merely reduced some other - // operation, as in d:3 i:4; it may also have eliminated that operation, - // as in d:3 i:3. - this._operations.splice(index, removeNewAndNextOp ? 2 : 1); + function makeToString(ret) { + return function() { return ret; }; } - return removedItems; - }, + Mixin.prototype.toString = classToString; // ES6TODO: altering imported objects. SBB. - toString: function () { - var str = ""; - forEach(this._operations, function (operation) { - str += " " + operation.type + ":" + operation.count; - }); - return str.substring(1); - } -}; + __exports__["default"] = Namespace; + }); +define("ember-runtime/system/native_array", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/enumerable_utils","ember-metal/mixin","ember-runtime/mixins/array","ember-runtime/mixins/mutable_array","ember-runtime/mixins/observable","ember-runtime/mixins/copyable","ember-runtime/mixins/freezable","ember-runtime/copy","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ -/** - Internal data structure to represent an array operation. + var Ember = __dependency1__["default"]; + // Ember.EXTEND_PROTOTYPES - @method ArrayOperation - @private - @property {string} type The type of the operation. One of - `Ember.TrackedArray.{RETAIN, INSERT, DELETE}` - @property {number} count The number of items in this operation. - @property {array} items The items of the operation, if included. RETAIN and - INSERT include their items, DELETE does not. -*/ -function ArrayOperation (operation, count, items) { - this.type = operation; // RETAIN | INSERT | DELETE - this.count = count; - this.items = items; -} + var get = __dependency2__.get; + var set = __dependency3__.set; + var EnumerableUtils = __dependency4__["default"]; + var Mixin = __dependency5__.Mixin; + var EmberArray = __dependency6__["default"]; + var MutableArray = __dependency7__["default"]; + var Observable = __dependency8__["default"]; + var Copyable = __dependency9__["default"]; + var FROZEN_ERROR = __dependency10__.FROZEN_ERROR; + var copy = __dependency11__["default"]; -/** - Internal data structure used to include information when looking up operations - by item index. + var replace = EnumerableUtils._replace, + forEach = EnumerableUtils.forEach; - @method ArrayOperationMatch - @private - @property {ArrayOperation} operation - @property {number} index The index of `operation` in the array of operations. - @property {boolean} split Whether or not the item index searched for would - require a split for a new operation type. - @property {number} rangeStart The index of the first item in the operation, - with respect to the tracked array. The index of the last item can be computed - from `rangeStart` and `operation.count`. -*/ -function ArrayOperationMatch(operation, index, split, rangeStart) { - this.operation = operation; - this.index = index; - this.split = split; - this.rangeStart = rangeStart; -} + // 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. -})(); + /** + 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 + @uses Ember.MutableArray + @uses Ember.Observable + @uses Ember.Copyable + */ + var NativeArray = Mixin.create(MutableArray, Observable, 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); + }, -(function() { -var get = Ember.get, - forEach = Ember.EnumerableUtils.forEach, - RETAIN = 'r', - FILTER = 'f'; + objectAt: function(idx) { + return this[idx]; + }, -function Operation (type, count) { - this.type = type; - this.count = count; -} + // primitive for array support. + replace: function(idx, amt, objects) { -/** - An `Ember.SubArray` tracks an array in a way similar to, but more specialized - than, `Ember.TrackedArray`. It is useful for keeping track of the indexes of - items within a filtered array. + if (this.isFrozen) throw FROZEN_ERROR; - @class SubArray - @namespace Ember -*/ -Ember.SubArray = function (length) { - if (arguments.length < 1) { length = 0; } + // 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 (length > 0) { - this._operations = [new Operation(RETAIN, length)]; - } else { - this._operations = []; - } -}; - -Ember.SubArray.prototype = { - /** - Track that an item was added to the tracked array. - - @method addItem - - @param {number} index The index of the item in the tracked array. - @param {boolean} match `true` iff the item is included in the subarray. - - @return {number} The index of the item in the subarray. - */ - addItem: function(index, match) { - var returnValue = -1, - itemType = match ? RETAIN : FILTER, - self = this; - - this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { - var newOperation, splitOperation; - - if (itemType === operation.type) { - ++operation.count; - } else if (index === rangeStart) { - // insert to the left of `operation` - self._operations.splice(operationIndex, 0, new Operation(itemType, 1)); - } else { - newOperation = new Operation(itemType, 1); - splitOperation = new Operation(operation.type, rangeEnd - index + 1); - operation.count = index - rangeStart; - - self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation); - } - - if (match) { - if (operation.type === RETAIN) { - returnValue = seenInSubArray + (index - rangeStart); + if (len === 0) { + this.splice(idx, amt); } else { - returnValue = seenInSubArray; + replace(this, idx, amt, objects); } + + 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 copy(item, true); }); + } + + return this.slice(); } - - self._composeAt(operationIndex); - }, function(seenInSubArray) { - self._operations.push(new Operation(itemType, 1)); - - if (match) { - returnValue = seenInSubArray; - } - - self._composeAt(self._operations.length-1); }); - return returnValue; - }, + // Remove any methods implemented natively so we don't override them + var ignore = ['length']; + forEach(NativeArray.keys(), function(methodName) { + if (Array.prototype[methodName]) ignore.push(methodName); + }); - /** - Track that an item was removed from the tracked array. + if (ignore.length>0) { + NativeArray = NativeArray.without.apply(NativeArray, ignore); + } - @method removeItem + /** + Creates an `Ember.NativeArray` from an Array like object. + Does not modify the original object. Ember.A is not needed if + `Ember.EXTEND_PROTOTYPES` is `true` (the default value). However, + it is recommended that you use Ember.A when creating addons for + ember or when you can not guarantee that `Ember.EXTEND_PROTOTYPES` + will be `true`. - @param {number} index The index of the item in the tracked array. + Example - @return {number} The index of the item in the subarray, or `-1` if the item - was not in the subarray. - */ - removeItem: function(index) { - var returnValue = -1, - self = this; + ```js + var Pagination = Ember.CollectionView.extend({ + tagName: 'ul', + classNames: ['pagination'], + init: function() { + this._super(); + if (!this.get('content')) { + this.set('content', Ember.A([])); + } + } + }); + ``` - this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { - if (operation.type === RETAIN) { - returnValue = seenInSubArray + (index - rangeStart); + @method A + @for Ember + @return {Ember.NativeArray} + */ + var A = function(arr) { + if (arr === undefined) { arr = []; } + return EmberArray.detect(arr) ? arr : NativeArray.apply(arr); + }; + + /** + Activates the mixin on the Array.prototype if not already applied. Calling + this method more than once is safe. This will be called when ember is loaded + unless you have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` + set to `false`. + + Example + + ```js + if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) { + Ember.NativeArray.activate(); + } + ``` + + @method activate + @for Ember.NativeArray + @static + @return {void} + */ + NativeArray.activate = function() { + NativeArray.apply(Array.prototype); + + A = function(arr) { return arr || []; }; + }; + + if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) { + NativeArray.activate(); + } + + Ember.A = A; // ES6TODO: Setting A onto the object returned by ember-metal/core to avoid circles + __exports__.A = A; + __exports__.NativeArray = NativeArray;__exports__["default"] = NativeArray; + }); +define("ember-runtime/system/object", + ["ember-runtime/system/core_object","ember-runtime/mixins/observable","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + + var CoreObject = __dependency1__["default"]; + var Observable = __dependency2__["default"]; + + /** + `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 + */ + var EmberObject = CoreObject.extend(Observable); + EmberObject.toString = function() { return "Ember.Object"; }; + + __exports__["default"] = EmberObject; + }); +define("ember-runtime/system/object_proxy", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/observer","ember-metal/property_events","ember-metal/computed","ember-metal/properties","ember-metal/mixin","ember-runtime/system/string","ember-runtime/system/object","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + var Ember = __dependency1__["default"]; + // Ember.assert + var get = __dependency2__.get; + var set = __dependency3__.set; + var meta = __dependency4__.meta; + var addObserver = __dependency5__.addObserver; + var removeObserver = __dependency5__.removeObserver; + var addBeforeObserver = __dependency5__.addBeforeObserver; + var removeBeforeObserver = __dependency5__.removeBeforeObserver; + var propertyWillChange = __dependency6__.propertyWillChange; + var propertyDidChange = __dependency6__.propertyDidChange; + var computed = __dependency7__.computed; + var defineProperty = __dependency8__.defineProperty; + var observer = __dependency9__.observer; + var EmberStringUtils = __dependency10__["default"]; + var EmberObject = __dependency11__["default"]; + + function contentPropertyWillChange(content, contentKey) { + var key = contentKey.slice(8); // remove "content." + if (key in this) { return; } // if shadowed in proxy + propertyWillChange(this, key); + } + + function contentPropertyDidChange(content, contentKey) { + var key = contentKey.slice(8); // remove "content." + if (key in this) { return; } // if shadowed in proxy + propertyDidChange(this, key); + } + + /** + `Ember.ObjectProxy` forwards all properties not defined by the proxy itself + to a proxied `content` object. + + ```javascript + object = Ember.Object.create({ + name: 'Foo' + }); + + proxy = Ember.ObjectProxy.create({ + content: object + }); + + // Access and change existing properties + proxy.get('name') // 'Foo' + proxy.set('name', 'Bar'); + object.get('name') // 'Bar' + + // Create new 'description' property on `object` + proxy.set('description', 'Foo is a whizboo baz'); + object.get('description') // 'Foo is a whizboo baz' + ``` + + While `content` is unset, setting a property to be delegated will throw an + Error. + + ```javascript + proxy = Ember.ObjectProxy.create({ + content: null, + flag: null + }); + proxy.set('flag', true); + proxy.get('flag'); // true + proxy.get('foo'); // undefined + proxy.set('foo', 'data'); // throws Error + ``` + + Delegated properties can be bound to and will change when content is updated. + + Computed properties on the proxy itself can depend on delegated properties. + + ```javascript + ProxyWithComputedProperty = Ember.ObjectProxy.extend({ + fullName: function () { + var firstName = this.get('firstName'), + lastName = this.get('lastName'); + if (firstName && lastName) { + return firstName + ' ' + lastName; + } + return firstName || lastName; + }.property('firstName', 'lastName') + }); + + proxy = ProxyWithComputedProperty.create(); + + proxy.get('fullName'); // undefined + proxy.set('content', { + firstName: 'Tom', lastName: 'Dale' + }); // triggers property change for fullName on proxy + + proxy.get('fullName'); // 'Tom Dale' + ``` + + @class ObjectProxy + @namespace Ember + @extends Ember.Object + */ + var ObjectProxy = EmberObject.extend({ + /** + The object whose properties will be forwarded. + + @property content + @type Ember.Object + @default null + */ + content: null, + _contentDidChange: observer('content', function() { + Ember.assert("Can't set ObjectProxy's content to itself", get(this, 'content') !== this); + }), + + isTruthy: computed.bool('content'), + + _debugContainerKey: null, + + willWatchProperty: function (key) { + var contentKey = 'content.' + key; + addBeforeObserver(this, contentKey, null, contentPropertyWillChange); + addObserver(this, contentKey, null, contentPropertyDidChange); + }, + + didUnwatchProperty: function (key) { + var contentKey = 'content.' + key; + removeBeforeObserver(this, contentKey, null, contentPropertyWillChange); + removeObserver(this, contentKey, null, contentPropertyDidChange); + }, + + unknownProperty: function (key) { + var content = get(this, 'content'); + if (content) { + return get(content, key); + } + }, + + setUnknownProperty: function (key, value) { + var m = meta(this); + if (m.proto === this) { + // if marked as prototype then just defineProperty + // rather than delegate + defineProperty(this, key, null, value); + return value; + } + + var content = get(this, 'content'); + Ember.assert(EmberStringUtils.fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content); + return set(content, key, value); } - if (operation.count > 1) { - --operation.count; + }); + + __exports__["default"] = ObjectProxy; + }); +define("ember-runtime/system/set", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/utils","ember-metal/is_none","ember-runtime/system/string","ember-runtime/system/core_object","ember-runtime/mixins/mutable_enumerable","ember-runtime/mixins/enumerable","ember-runtime/mixins/copyable","ember-runtime/mixins/freezable","ember-metal/error","ember-metal/property_events","ember-metal/mixin","ember-metal/computed","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + var Ember = __dependency1__["default"]; + // Ember.isNone + + var get = __dependency2__.get; + var set = __dependency3__.set; + var guidFor = __dependency4__.guidFor; + var isNone = __dependency5__.isNone; + var EmberStringUtils = __dependency6__["default"]; + var CoreObject = __dependency7__["default"]; + var MutableEnumerable = __dependency8__["default"]; + var Enumerable = __dependency9__["default"]; + var Copyable = __dependency10__["default"]; + var Freezable = __dependency11__.Freezable; + var FROZEN_ERROR = __dependency11__.FROZEN_ERROR; + var EmberError = __dependency12__["default"]; + var propertyWillChange = __dependency13__.propertyWillChange; + var propertyDidChange = __dependency13__.propertyDidChange; + var aliasMethod = __dependency14__.aliasMethod; + var computed = __dependency15__.computed; + + /** + 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](/api/classes/Ember.Enumerable.html) + 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 + */ + var Set = CoreObject.extend(MutableEnumerable, Copyable, Freezable, + { + + // .......................................................... + // 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 EmberError(FROZEN_ERROR); } + + var len = get(this, 'length'); + if (len === 0) { return this; } + + var guid; + + this.enumerableContentWillChange(len, 0); + propertyWillChange(this, 'firstObject'); + propertyWillChange(this, 'lastObject'); + + for (var i=0; i < len; i++) { + guid = guidFor(this[i]); + delete this[guid]; + delete this[i]; + } + + set(this, 'length', 0); + + propertyDidChange(this, 'firstObject'); + 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 (!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: 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: 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 EmberError(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: 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: 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: 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: 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: 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: computed(function() { + return this.length > 0 ? this[0] : undefined; + }), + + // more optimized version + lastObject: computed(function() { + return this.length > 0 ? this[this.length-1] : undefined; + }), + + // implements Ember.MutableEnumerable + addObject: function(obj) { + if (get(this, 'isFrozen')) throw new EmberError(FROZEN_ERROR); + if (isNone(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 EmberStringUtils.fmt("Ember.Set<%@>", [array.join(',')]); + } + + }); + + + __exports__["default"] = Set; + }); +define("ember-runtime/system/string", + ["ember-metal/core","ember-metal/utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-runtime + */ + var Ember = __dependency1__["default"]; + // Ember.STRINGS, Ember.FEATURES + var EmberInspect = __dependency2__.inspect; + + + var STRING_DASHERIZE_REGEXP = (/[ _]/g); + var STRING_DASHERIZE_CACHE = {}; + var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([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 + */ + var EmberStringUtils = { + + /** + 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 {String} str The string to format + @param {Array} formats An array of parameters to interpolate into string. + @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, 10) - 1 : idx++; + s = formats[argIndex]; + return (s === null) ? '(null)' : (s === undefined) ? '' : EmberInspect(s); + }) ; + }, + + /** + 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 EmberStringUtils.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, spaces, or camelCase 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 = EmberStringUtils.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; i 0) { + this._operations = [new Operation(RETAIN, length)]; } else { - self._operations.splice(operationIndex, 1); - self._composeAt(operationIndex); + this._operations = []; } - }, function() { - throw new Ember.Error("Can't remove an item that has never been added."); - }); + }; - return returnValue; - }, + SubArray.prototype = { + /** + Track that an item was added to the tracked array. + + @method addItem + + @param {number} index The index of the item in the tracked array. + @param {boolean} match `true` iff the item is included in the subarray. + + @return {number} The index of the item in the subarray. + */ + addItem: function(index, match) { + var returnValue = -1, + itemType = match ? RETAIN : FILTER, + self = this; + + this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { + var newOperation, splitOperation; + + if (itemType === operation.type) { + ++operation.count; + } else if (index === rangeStart) { + // insert to the left of `operation` + self._operations.splice(operationIndex, 0, new Operation(itemType, 1)); + } else { + newOperation = new Operation(itemType, 1); + splitOperation = new Operation(operation.type, rangeEnd - index + 1); + operation.count = index - rangeStart; + + self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation); + } + + if (match) { + if (operation.type === RETAIN) { + returnValue = seenInSubArray + (index - rangeStart); + } else { + returnValue = seenInSubArray; + } + } + + self._composeAt(operationIndex); + }, function(seenInSubArray) { + self._operations.push(new Operation(itemType, 1)); + + if (match) { + returnValue = seenInSubArray; + } + + self._composeAt(self._operations.length-1); + }); + + return returnValue; + }, + + /** + Track that an item was removed from the tracked array. + + @method removeItem + + @param {number} index The index of the item in the tracked array. + + @return {number} The index of the item in the subarray, or `-1` if the item + was not in the subarray. + */ + removeItem: function(index) { + var returnValue = -1, + self = this; + + this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { + if (operation.type === RETAIN) { + returnValue = seenInSubArray + (index - rangeStart); + } + + if (operation.count > 1) { + --operation.count; + } else { + self._operations.splice(operationIndex, 1); + self._composeAt(operationIndex); + } + }, function() { + throw new EmberError("Can't remove an item that has never been added."); + }); + + return returnValue; + }, - _findOperation: function (index, foundCallback, notFoundCallback) { - var operationIndex, - len, - operation, - rangeStart, - rangeEnd, - seenInSubArray = 0; + _findOperation: function (index, foundCallback, notFoundCallback) { + var operationIndex, + len, + operation, + rangeStart, + rangeEnd, + seenInSubArray = 0; - // OPTIMIZE: change to balanced tree - // find leftmost operation to the right of `index` - for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) { - operation = this._operations[operationIndex]; - rangeEnd = rangeStart + operation.count - 1; + // OPTIMIZE: change to balanced tree + // find leftmost operation to the right of `index` + for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) { + operation = this._operations[operationIndex]; + rangeEnd = rangeStart + operation.count - 1; - if (index >= rangeStart && index <= rangeEnd) { - foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray); - return; - } else if (operation.type === RETAIN) { - seenInSubArray += operation.count; + if (index >= rangeStart && index <= rangeEnd) { + foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray); + return; + } else if (operation.type === RETAIN) { + seenInSubArray += operation.count; + } + } + + notFoundCallback(seenInSubArray); + }, + + _composeAt: function(index) { + var op = this._operations[index], + otherOp; + + if (!op) { + // Composing out of bounds is a no-op, as when removing the last operation + // in the list. + return; + } + + if (index > 0) { + otherOp = this._operations[index-1]; + if (otherOp.type === op.type) { + op.count += otherOp.count; + this._operations.splice(index-1, 1); + --index; + } + } + + if (index < this._operations.length-1) { + otherOp = this._operations[index+1]; + if (otherOp.type === op.type) { + op.count += otherOp.count; + this._operations.splice(index+1, 1); + } + } + }, + + toString: function () { + var str = ""; + forEach(this._operations, function (operation) { + str += " " + operation.type + ":" + operation.count; + }); + return str.substring(1); } - } + }; - notFoundCallback(seenInSubArray); - }, - - _composeAt: function(index) { - var op = this._operations[index], - otherOp; - - if (!op) { - // Composing out of bounds is a no-op, as when removing the last operation - // in the list. - return; - } - - if (index > 0) { - otherOp = this._operations[index-1]; - if (otherOp.type === op.type) { - op.count += otherOp.count; - this._operations.splice(index-1, 1); - --index; - } - } - - if (index < this._operations.length-1) { - otherOp = this._operations[index+1]; - if (otherOp.type === op.type) { - op.count += otherOp.count; - this._operations.splice(index+1, 1); - } - } - }, - - toString: function () { - var str = ""; - forEach(this._operations, function (operation) { - str += " " + operation.type + ":" + operation.count; - }); - return str.substring(1); - } -}; - -})(); - - - -(function() { -Ember.Container = requireModule('container'); -Ember.Container.set = Ember.set; - -})(); - - - -(function() { -Ember.Application = Ember.Namespace.extend(); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var OUT_OF_RANGE_EXCEPTION = "Index out of range"; -var EMPTY = []; - -var get = Ember.get, set = Ember.set; - -/** - An ArrayProxy wraps any other object that implements `Ember.Array` and/or - `Ember.MutableArray,` forwarding all requests. This makes it very useful for - a number of binding use cases or other cases where being able to swap - out the underlying array is useful. - - A simple example of usage: - - ```javascript - var pets = ['dog', 'cat', 'fish']; - var ap = Ember.ArrayProxy.create({ content: Ember.A(pets) }); - - ap.get('firstObject'); // 'dog' - ap.set('content', ['amoeba', 'paramecium']); - ap.get('firstObject'); // 'amoeba' - ``` - - This class can also be useful as a layer to transform the contents of - an array, as they are accessed. This can be done by overriding - `objectAtContent`: - - ```javascript - var pets = ['dog', 'cat', 'fish']; - var ap = Ember.ArrayProxy.create({ - content: Ember.A(pets), - objectAtContent: function(idx) { - return this.get('content').objectAt(idx).toUpperCase(); - } + __exports__["default"] = SubArray; }); +define("ember-runtime/system/tracked_array", + ["ember-metal/property_get","ember-metal/enumerable_utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var get = __dependency1__.get; + var EnumerableUtils = __dependency2__["default"]; - ap.get('firstObject'); // . 'DOG' - ``` + var forEach = EnumerableUtils.forEach, + RETAIN = 'r', + INSERT = 'i', + DELETE = 'd'; - @class ArrayProxy - @namespace Ember - @extends Ember.Object - @uses Ember.MutableArray -*/ -Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.ArrayProxy.prototype */ { - /** - The content array. Must be an object that implements `Ember.Array` and/or - `Ember.MutableArray.` + /** + An `Ember.TrackedArray` tracks array operations. It's useful when you want to + lazily compute the indexes of items in an array after they've been shifted by + subsequent operations. - @property content - @type Ember.Array - */ - content: null, + @class TrackedArray + @namespace Ember + @param {array} [items=[]] The array to be tracked. This is used just to get + the initial items for the starting state of retain:n. + */ + function TrackedArray(items) { + if (arguments.length < 1) { items = []; } - /** - The array that the proxy pretends to be. In the default `ArrayProxy` - implementation, this and `content` are the same. Subclasses of `ArrayProxy` - can override this property to provide things like sorting and filtering. + var length = get(items, 'length'); - @property arrangedContent - */ - arrangedContent: Ember.computed.alias('content'), - - /** - Should actually retrieve the object at the specified index from the - content. You can override this method in subclasses to transform the - content item to something new. - - This method will only be called if content is non-`null`. - - @method objectAtContent - @param {Number} idx The index to retrieve. - @return {Object} the value or undefined if none found - */ - objectAtContent: function(idx) { - return get(this, 'arrangedContent').objectAt(idx); - }, - - /** - Should actually replace the specified objects on the content array. - You can override this method in subclasses to transform the content item - into something new. - - This method will only be called if content is non-`null`. - - @method replaceContent - @param {Number} idx The starting index - @param {Number} amt The number of items to remove from the content. - @param {Array} objects Optional array of objects to insert or null if no - objects. - @return {void} - */ - replaceContent: function(idx, amt, objects) { - get(this, 'content').replace(idx, amt, objects); - }, - - /** - @private - - Invoked when the content property is about to change. Notifies observers that the - entire array content will change. - - @method _contentWillChange - */ - _contentWillChange: Ember.beforeObserver('content', function() { - this._teardownContent(); - }), - - _teardownContent: function() { - var content = get(this, 'content'); - - if (content) { - content.removeArrayObserver(this, { - willChange: 'contentArrayWillChange', - didChange: 'contentArrayDidChange' - }); - } - }, - - contentArrayWillChange: Ember.K, - contentArrayDidChange: Ember.K, - - /** - @private - - Invoked when the content property changes. Notifies observers that the - entire array content has changed. - - @method _contentDidChange - */ - _contentDidChange: Ember.observer('content', function() { - var content = get(this, 'content'); - - Ember.assert("Can't set ArrayProxy's content to itself", content !== this); - - this._setupContent(); - }), - - _setupContent: function() { - var content = get(this, 'content'); - - if (content) { - content.addArrayObserver(this, { - willChange: 'contentArrayWillChange', - didChange: 'contentArrayDidChange' - }); - } - }, - - _arrangedContentWillChange: Ember.beforeObserver('arrangedContent', function() { - var arrangedContent = get(this, 'arrangedContent'), - len = arrangedContent ? get(arrangedContent, 'length') : 0; - - this.arrangedContentArrayWillChange(this, 0, len, undefined); - this.arrangedContentWillChange(this); - - this._teardownArrangedContent(arrangedContent); - }), - - _arrangedContentDidChange: Ember.observer('arrangedContent', function() { - var arrangedContent = get(this, 'arrangedContent'), - len = arrangedContent ? get(arrangedContent, 'length') : 0; - - Ember.assert("Can't set ArrayProxy's content to itself", arrangedContent !== this); - - this._setupArrangedContent(); - - this.arrangedContentDidChange(this); - this.arrangedContentArrayDidChange(this, 0, undefined, len); - }), - - _setupArrangedContent: function() { - var arrangedContent = get(this, 'arrangedContent'); - - if (arrangedContent) { - arrangedContent.addArrayObserver(this, { - willChange: 'arrangedContentArrayWillChange', - didChange: 'arrangedContentArrayDidChange' - }); - } - }, - - _teardownArrangedContent: function() { - var arrangedContent = get(this, 'arrangedContent'); - - if (arrangedContent) { - arrangedContent.removeArrayObserver(this, { - willChange: 'arrangedContentArrayWillChange', - didChange: 'arrangedContentArrayDidChange' - }); - } - }, - - arrangedContentWillChange: Ember.K, - arrangedContentDidChange: Ember.K, - - objectAt: function(idx) { - return get(this, 'content') && this.objectAtContent(idx); - }, - - length: Ember.computed(function() { - var arrangedContent = get(this, 'arrangedContent'); - return arrangedContent ? get(arrangedContent, 'length') : 0; - // No dependencies since Enumerable notifies length of change - }), - - _replace: function(idx, amt, objects) { - var content = get(this, 'content'); - Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', content); - if (content) this.replaceContent(idx, amt, objects); - return this; - }, - - replace: function() { - if (get(this, 'arrangedContent') === get(this, 'content')) { - this._replace.apply(this, arguments); - } else { - throw new Ember.Error("Using replace on an arranged ArrayProxy is not allowed."); - } - }, - - _insertAt: function(idx, object) { - if (idx > get(this, 'content.length')) throw new Ember.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 Ember.Error(OUT_OF_RANGE_EXCEPTION); + if (length) { + this._operations = [new ArrayOperation(RETAIN, length, items)]; + } else { + this._operations = []; } + }; - if (len === undefined) len = 1; + TrackedArray.RETAIN = RETAIN; + TrackedArray.INSERT = INSERT; + TrackedArray.DELETE = DELETE; - // Get a list of indices in original content to remove - for (i=start; i arrayOperationRangeStart && index <= arrayOperationRangeEnd) { + split = true; + break; + } else { + arrayOperationRangeStart = arrayOperationRangeEnd + 1; + } + } + + return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart); + }, + + _split: function (arrayOperationIndex, splitIndex, newArrayOperation) { + var arrayOperation = this._operations[arrayOperationIndex], + splitItems = arrayOperation.items.slice(splitIndex), + splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems); + + // truncate LHS + arrayOperation.count = splitIndex; + arrayOperation.items = arrayOperation.items.slice(0, splitIndex); + + this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation); + }, + + // see SubArray for a better implementation. + _composeInsert: function (index) { + var newArrayOperation = this._operations[index], + leftArrayOperation = this._operations[index-1], // may be undefined + rightArrayOperation = this._operations[index+1], // may be undefined + leftOp = leftArrayOperation && leftArrayOperation.type, + rightOp = rightArrayOperation && rightArrayOperation.type; + + if (leftOp === INSERT) { + // merge left + leftArrayOperation.count += newArrayOperation.count; + leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items); + + if (rightOp === INSERT) { + // also merge right (we have split an insert with an insert) + leftArrayOperation.count += rightArrayOperation.count; + leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items); + this._operations.splice(index, 2); + } else { + // only merge left + this._operations.splice(index, 1); + } + } else if (rightOp === INSERT) { + // merge right + newArrayOperation.count += rightArrayOperation.count; + newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items); + this._operations.splice(index + 1, 1); + } + }, + + _composeDelete: function (index) { + var arrayOperation = this._operations[index], + deletesToGo = arrayOperation.count, + leftArrayOperation = this._operations[index-1], // may be undefined + leftOp = leftArrayOperation && leftArrayOperation.type, + nextArrayOperation, + nextOp, + nextCount, + removeNewAndNextOp = false, + removedItems = []; + + if (leftOp === DELETE) { + arrayOperation = leftArrayOperation; + index -= 1; + } + + for (var i = index + 1; deletesToGo > 0; ++i) { + nextArrayOperation = this._operations[i]; + nextOp = nextArrayOperation.type; + nextCount = nextArrayOperation.count; + + if (nextOp === DELETE) { + arrayOperation.count += nextCount; + continue; + } + + if (nextCount > deletesToGo) { + // d:2 {r,i}:5 we reduce the retain or insert, but it stays + removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo)); + nextArrayOperation.count -= deletesToGo; + + // In the case where we truncate the last arrayOperation, we don't need to + // remove it; also the deletesToGo reduction is not the entirety of + // nextCount + i -= 1; + nextCount = deletesToGo; + + deletesToGo = 0; + } else { + if (nextCount === deletesToGo) { + // Handle edge case of d:2 i:2 in which case both operations go away + // during composition. + removeNewAndNextOp = true; + } + removedItems = removedItems.concat(nextArrayOperation.items); + deletesToGo -= nextCount; + } + + if (nextOp === INSERT) { + // d:2 i:3 will result in delete going away + arrayOperation.count -= nextCount; + } + } + + if (arrayOperation.count > 0) { + // compose our new delete with possibly several operations to the right of + // disparate types + this._operations.splice(index+1, i-1-index); + } else { + // The delete operation can go away; it has merely reduced some other + // operation, as in d:3 i:4; it may also have eliminated that operation, + // as in d:3 i:3. + this._operations.splice(index, removeNewAndNextOp ? 2 : 1); + } + + return removedItems; + }, + + toString: function () { + var str = ""; + forEach(this._operations, function (operation) { + str += " " + operation.type + ":" + operation.count; + }); + return str.substring(1); } + }; - // Replace in reverse order since indices will change - indices.sort(function(a,b) { return b - a; }); + /** + Internal data structure to represent an array operation. - Ember.beginPropertyChanges(); - for (i=0; i=idx) { - var item = content.objectAt(loc); - if (item) { - Ember.assert('When using @each to observe the array ' + content + ', the array must return an object', Ember.typeOf(item) === 'instance' || Ember.typeOf(item) === 'object'); - 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[indexOf.call(indicies, 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 {*} - */ - 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); + @method ArrayOperationMatch + @private + @param {ArrayOperation} operation + @param {number} index The index of `operation` in the array of operations. + @param {boolean} split Whether or not the item index searched for would + require a split for a new operation type. + @param {number} rangeStart The index of the first item in the operation, + with respect to the tracked array. The index of the last item can be computed + from `rangeStart` and `operation.count`. + */ + function ArrayOperationMatch(operation, index, split, rangeStart) { + this.operation = operation; + this.index = index; + this.split = split; + this.rangeStart = rangeStart; } - Ember.propertyWillChange(this._content, '@each'); - Ember.endPropertyChanges(this); - }, - - arrayDidChange: function(content, idx, removedCnt, addedCnt) { - var keys = this._keys, lim; - - lim = addedCnt>0 ? idx+addedCnt : -1; - Ember.changeProperties(function() { - for(var 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'); - }, 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, replace = Ember.EnumerableUtils._replace; - -// 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 (len === 0) { - this.splice(idx, amt); - } else { - replace(this, idx, amt, objects); - } - - 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 - @uses Ember.MutableArray - @uses Ember.Observable - @uses Ember.Copyable -*/ -Ember.NativeArray = NativeArray; - -/** - Creates an `Ember.NativeArray` from an Array like object. - Does not modify the original object. Ember.A is not needed if - `Ember.EXTEND_PROTOTYPES` is `true` (the default value). However, - it is recommended that you use Ember.A when creating addons for - ember or when you can not guarantee that `Ember.EXTEND_PROTOTYPES` - will be `true`. - - Example - - ```js - var Pagination = Ember.CollectionView.extend({ - tagName: 'ul', - classNames: ['pagination'], - init: function() { - this._super(); - if (!this.get('content')) { - this.set('content', Ember.A([])); - } - } + __exports__["default"] = TrackedArray; }); - ``` - - @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. This will be called when ember is loaded - unless you have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` - set to `false`. - - Example - - ```js - if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) { - Ember.NativeArray.activate(); - } - ``` - - @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, isNone = 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](/api/classes/Ember.Enumerable.html) - 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 Ember.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 Ember.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 Ember.Error(Ember.FROZEN_ERROR); - if (isNone(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 deferred; - } -}); - -Ember.Deferred = Deferred; - -})(); - - - -(function() { -var forEach = Ember.ArrayPolyfills.forEach; - -/** -@module ember -@submodule ember-runtime -*/ - -var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {}; -var loaded = {}; - -/** - -Detects when a specific package of Ember (e.g. 'Ember.Handlebars') -has fully loaded and is available for extension. - -The provided `callback` will be called with the `name` passed -resolved from a string into the object: - -```javascript -Ember.onLoad('Ember.Handlebars' function(hbars){ - hbars.registerHelper(...); -}); -``` - - -@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); - } -}; - -/** - -Called when an Ember.js package (e.g Ember.Handlebars) has finished -loading. Triggers any callbacks registered for this event. - -@method runLoadHooks -@for Ember -@param name {String} name of hook -@param object {Object} object to pass to callbacks -*/ -Ember.runLoadHooks = function(name, object) { - loaded[name] = object; - - if (loadHooks[name]) { - forEach.call(loadHooks[name], 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`. - - @class ControllerMixin - @namespace Ember -*/ -Ember.ControllerMixin = Ember.Mixin.create(Ember.ActionHandler, { - /* ducktype as a controller */ - isController: true, - - /** - The object to which actions from the view should be sent. - - For example, when a Handlebars template uses the `{{action}}` helper, - it will attempt to send the action 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, - - parentController: null, - - store: null, - - model: Ember.computed.alias('content'), - - deprecatedSendHandles: function(actionName) { - return !!this[actionName]; - }, - - deprecatedSend: function(actionName) { - var args = [].slice.call(arguments, 1); - Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); - Ember.deprecate('Action handlers implemented directly on controllers are deprecated in favor of action handlers on an `actions` object (' + actionName + ' on ' + this + ')', false); - this[actionName].apply(this, args); - return; - } -}); - -/** - @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 +define("ember-views", + ["ember-runtime","ember-views/system/jquery","ember-views/system/utils","ember-views/system/render_buffer","ember-views/system/ext","ember-views/views/states","ember-views/views/view","ember-views/views/container_view","ember-views/views/collection_view","ember-views/views/component","ember-views/system/event_dispatcher","ember-views/mixins/view_target_action_support","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) { + "use strict"; + /** + Ember Views + + @module ember + @submodule ember-views + @requires ember-runtime + @main ember-views + */ + + // BEGIN EXPORTS + Ember.$ = __dependency2__["default"]; + + Ember.ViewTargetActionSupport = __dependency12__["default"]; + Ember.RenderBuffer = __dependency4__["default"]; + + var ViewUtils = Ember.ViewUtils = {}; + ViewUtils.setInnerHTML = __dependency3__.setInnerHTML; + ViewUtils.isSimpleClick = __dependency3__.isSimpleClick; + + Ember.CoreView = __dependency7__.CoreView; + Ember.View = __dependency7__.View; + Ember.View.states = __dependency6__.states; + Ember.View.cloneStates = __dependency6__.cloneStates; + + Ember._ViewCollection = __dependency7__.ViewCollection; + Ember.ContainerView = __dependency8__["default"]; + Ember.CollectionView = __dependency9__["default"]; + Ember.Component = __dependency10__["default"]; + Ember.EventDispatcher = __dependency11__["default"]; + // END EXPORTS + + __exports__["default"] = Ember; }); +define("ember-views/mixins/component_template_deprecation", + ["ember-metal/core","ember-metal/property_get","ember-metal/mixin","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.deprecate + var get = __dependency2__.get; + var Mixin = __dependency3__.Mixin; - songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'} + /** + The ComponentTemplateDeprecation mixin is used to provide a useful + deprecation warning when using either `template` or `templateName` with + a component. The `template` and `templateName` properties specified at + extend time are moved to `layout` and `layoutName` respectively. - songsController.addObject({trackNumber: 1, title: 'Dear Prudence'}); - songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'} - ``` + `Ember.ComponentTemplateDeprecation` is used internally by Ember in + `Ember.Component`. - If you add or remove the properties to sort by or change the sort direction the content - sort order will be automatically updated. + @class ComponentTemplateDeprecation + @namespace Ember + */ + var ComponentTemplateDeprecation = Mixin.create({ + /** + @private - ```javascript - songsController.set('sortProperties', ['title']); - songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'} + Moves `templateName` to `layoutName` and `template` to `layout` at extend + time if a layout is not also specified. - songsController.toggleProperty('sortAscending'); - songsController.get('firstObject'); // {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'} - ``` + Note that this currently modifies the mixin themselves, which is technically + dubious but is practically of little consequence. This may change in the + future. - SortableMixin works by sorting the arrangedContent array, which is the array that - arrayProxy displays. Due to the fact that the underlying 'content' array is not changed, that - array will not display the sorted list: + @method willMergeMixin + */ + willMergeMixin: function(props) { + // must call _super here to ensure that the ActionHandler + // mixin is setup properly (moves actions -> _actions) + // + // Calling super is only OK here since we KNOW that + // there is another Mixin loaded first. + this._super.apply(this, arguments); - ```javascript - songsController.get('content').get('firstObject'); // Returns the unsorted original content - songsController.get('firstObject'); // Returns the sorted content. - ``` - - Although the sorted content can also be accessed through the arrangedContent property, - it is preferable to use the proxied class and not the arrangedContent array directly. + var deprecatedProperty, replacementProperty, + layoutSpecified = (props.layoutName || props.layout || get(this, 'layoutName')); - @class SortableMixin - @namespace Ember - @uses Ember.MutableEnumerable -*/ -Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { + if (props.templateName && !layoutSpecified) { + deprecatedProperty = 'templateName'; + replacementProperty = 'layoutName'; - /** - Specifies which properties dictate the arrangedContent's sort order. + props.layoutName = props.templateName; + delete props['templateName']; + } - When specifying multiple properties the sorting will use properties - from the `sortProperties` array prioritized from first to last. + if (props.template && !layoutSpecified) { + deprecatedProperty = 'template'; + replacementProperty = 'layout'; - @property {Array} sortProperties - */ - sortProperties: null, + props.layout = props.template; + delete props['template']; + } - /** - Specifies the arrangedContent's sort direction - - @property {Boolean} sortAscending - */ - sortAscending: true, - - /** - The function used to compare two values. You can override this if you - want to do custom comparisons. Functions must be of the type expected by - Array#sort, i.e. - return 0 if the two parameters are equal, - return a negative value if the first parameter is smaller than the second or - return a positive value otherwise: - - ```javascript - function(x,y) { // These are assumed to be integers - if (x === y) - return 0; - return x < y ? -1 : 1; - } - ``` - - @property sortFunction - @type {Function} - @default Ember.compare - */ - sortFunction: Ember.compare, - - orderBy: function(item1, item2) { - var result = 0, - sortProperties = get(this, 'sortProperties'), - sortAscending = get(this, 'sortAscending'), - sortFunction = get(this, 'sortFunction'); - - Ember.assert("you need to define `sortProperties`", !!sortProperties); - - forEach(sortProperties, function(propertyName) { - if (result === 0) { - result = sortFunction(get(item1, propertyName), get(item2, propertyName)); - if ((result !== 0) && !sortAscending) { - result = (-1) * result; + if (deprecatedProperty) { + Ember.deprecate('Do not specify ' + deprecatedProperty + ' on a Component, use ' + replacementProperty + ' instead.', false); } } }); - 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'), - - /** - Overrides the default arrangedContent from arrayProxy in order to sort by sortFunction. - Also sets up observers for each sortProperty on each item in the content Array. - - @property arrangedContent - */ - - 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('content', 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(); - }), - - sortAscendingWillChange: Ember.beforeObserver('sortAscending', function() { - this._lastSortAscending = get(this, 'sortAscending'); - }), - - sortAscendingDidChange: Ember.observer('sortAscending', function() { - if (get(this, 'sortAscending') !== this._lastSortAscending) { - var arrangedContent = get(this, 'arrangedContent'); - arrangedContent.reverseObjects(); - } - }), - - 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); + __exports__["default"] = ComponentTemplateDeprecation; }); - ``` +define("ember-views/mixins/view_target_action_support", + ["ember-metal/mixin","ember-runtime/mixins/target_action_support","ember-metal/computed","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var Mixin = __dependency1__.Mixin; + var TargetActionSupport = __dependency2__["default"]; - Then, create a view that binds to your new controller: + // ES6TODO: computed should have its own export path so you can do import {defaultTo} from computed + var computed = __dependency3__.computed; + var alias = computed.alias; - ```handlebars - {{#each MyApp.listController}} - {{firstName}} {{lastName}} - {{/each}} - ``` + /** + `Ember.ViewTargetActionSupport` is a mixin that can be included in a + view class to add a `triggerAction` method with semantics similar to + the Handlebars `{{action}}` helper. It provides intelligent defaults + for the action's target: the view's controller; and the context that is + sent with the action: the view's context. - 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 - } - } - }); - ``` - - The itemController instances will have a `parentController` property set to - either the the `parentController` property of the `ArrayController` - or to the `ArrayController` instance itself. - - @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. + Note: In normal Ember usage, the `{{action}}` helper is usually the best + choice. This mixin is most often useful when you are doing more complex + event handling in custom View subclasses. 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 - } + App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, { + action: 'save', + click: function() { + this.triggerAction(); // Sends the `save` action, along with the current context + // to the current controller } }); ``` - @method lookupItemController - @param {Object} object - @return {String} - */ - 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(); - - this.set('_subControllers', Ember.A()); - }, - - content: Ember.computed(function () { - return Ember.A(); - }), - - controllerAt: function(idx, object, controllerClass) { - var container = get(this, 'container'), - subControllers = get(this, '_subControllers'), - subController = subControllers[idx], - factory, fullName; - - if (subController) { return subController; } - - fullName = "controller:" + controllerClass; - - if (!container.has(fullName)) { - throw new Ember.Error('Could not resolve itemController: "' + controllerClass + '"'); - } - - subController = container.lookupFactory(fullName).create({ - target: this, - parentController: get(this, 'parentController') || this, - content: object - }); - - subControllers[idx] = subController; - - return subController; - }, - - _subControllers: null, - - _resetSubControllers: function() { - var subControllers = get(this, '_subControllers'); - if (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. It is intended - to wrap a single object, proxying unhandled attempts to `get` and `set` to the underlying - content object, and to forward unhandled action attempts to its `target`. - - `Ember.ObjectController` derives this 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 = this.jQuery || (Ember.imports && Ember.imports.jQuery); -if (!jQuery && typeof require === 'function') { - jQuery = require('jquery'); -} - -Ember.assert("Ember Views require jQuery 1.7, 1.8, 1.9, 1.10, or 2.0", jQuery && (jQuery().jquery.match(/^((1\.(7|8|9|10))|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 = "
    "; - testEl.firstChild.innerHTML = ""; - return testEl.firstChild.innerHTML === ''; -})(); - -// IE 8 (and likely earlier) likes to move whitespace preceeding -// a script tag to appear after it. This means that we can -// accidentally remove whitespace when updating a morph. -var movesWhitespace = this.document && (function() { - var testEl = document.createElement('div'); - testEl.innerHTML = "Test: Value"; - return testEl.childNodes[0].nodeValue === 'Test:' && - testEl.childNodes[2].nodeValue === ' Value'; -})(); - -// Use this to find children by ID instead of using jQuery -var findChildById = function(element, id) { - if (element.getAttribute('id') === id) { return element; } - - var len = element.childNodes.length, idx, node, found; - for (idx=0; idx 0) { - var len = matches.length, idx; - for (idx=0; idxTest'); - canSet = el.options.length === 1; - } - - innerHTMLTags[tagName] = canSet; - - return canSet; -}; - -var setInnerHTML = function(element, html) { - var tagName = element.tagName; - - if (canSetInnerHTML(tagName)) { - setInnerHTMLWithoutFix(element, html); - } else { - // Firefox versions < 11 do not have support for element.outerHTML. - var outerHTML = element.outerHTML || new XMLSerializer().serializeToString(element); - Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", outerHTML); - - var startTag = outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0], - endTag = ''; - - var wrapper = document.createElement('div'); - setInnerHTMLWithoutFix(wrapper, startTag + html + endTag); - element = wrapper.firstChild; - while (element.tagName !== tagName) { - element = element.nextSibling; - } - } - - return element; -}; - -function isSimpleClick(event) { - var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey, - secondaryClick = event.which > 1; // IE9 may return undefined - - return !modifier && !secondaryClick; -} - -Ember.ViewUtils = { - setInnerHTML: setInnerHTML, - isSimpleClick: isSimpleClick -}; - -})(); - - - -(function() { -/** -@module ember -@submodule ember-views -*/ - -var get = Ember.get, set = Ember.set; - -var ClassSet = function() { - this.seen = {}; - this.list = []; -}; - -ClassSet.prototype = { - add: function(string) { - if (string in this.seen) { return; } - this.seen[string] = true; - - this.list.push(string); - }, - - toDOM: function() { - return this.list.join(" "); - } -}; - -var BAD_TAG_NAME_TEST_REGEXP = /[^a-zA-Z0-9\-]/; -var BAD_TAG_NAME_REPLACE_REGEXP = /[^a-zA-Z0-9\-]/g; - -function stripTagName(tagName) { - if (!tagName) { - return tagName; - } - - if (!BAD_TAG_NAME_TEST_REGEXP.test(tagName)) { - return tagName; - } - - return tagName.replace(BAD_TAG_NAME_REPLACE_REGEXP, ''); -} - -var BAD_CHARS_REGEXP = /&(?!\w+;)|[<>"'`]/g; -var POSSIBLE_CHARS_REGEXP = /[&<>"'`]/; - -function escapeAttribute(value) { - // Stolen shamelessly from Handlebars - - var escape = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }; - - var escapeChar = function(chr) { - return escape[chr] || "&"; - }; - - var string = value.toString(); - - if(!POSSIBLE_CHARS_REGEXP.test(string)) { return string; } - return string.replace(BAD_CHARS_REGEXP, escapeChar); -} - -// IE 6/7 have bugs arond setting names on inputs during creation. -// From http://msdn.microsoft.com/en-us/library/ie/ms536389(v=vs.85).aspx: -// "To include the NAME attribute at run time on objects created with the createElement method, use the eTag." -var canSetNameOnInputs = (function() { - var div = document.createElement('div'), - el = document.createElement('input'); - - el.setAttribute('name', 'foo'); - div.appendChild(el); - - return !!div.innerHTML.match('foo'); -})(); - -/** - `Ember.RenderBuffer` gathers information regarding the a view and generates the - final representation. `Ember.RenderBuffer` will generate HTML which can be pushed - to the DOM. - - ```javascript - var buffer = Ember.RenderBuffer('div'); - ``` - - @class RenderBuffer - @namespace Ember - @constructor - @param {String} tagName tag name (such as 'div' or 'p') used for the buffer -*/ -Ember.RenderBuffer = function(tagName) { - return new Ember._RenderBuffer(tagName); -}; - -Ember._RenderBuffer = function(tagName) { - this.tagNames = [tagName || null]; - this.buffer = ""; -}; - -Ember._RenderBuffer.prototype = -/** @scope Ember.RenderBuffer.prototype */ { - - // The root view's element - _element: null, - - _hasElement: true, - - /** - @private - - An internal set used to de-dupe class names when `addClass()` is - used. After each call to `addClass()`, the `classes` property - will be updated. - - @property elementClasses - @type Array - @default [] - */ - elementClasses: null, - - /** - Array of class names which will be applied in the class attribute. - - You can use `setClasses()` to set this property directly. If you - use `addClass()`, it will be maintained for you. - - @property classes - @type Array - @default [] - */ - classes: null, - - /** - The id in of the element, to be applied in the id attribute. - - You should not set this property yourself, rather, you should use - the `id()` method of `Ember.RenderBuffer`. - - @property elementId - @type String - @default null - */ - elementId: null, - - /** - A hash keyed on the name of the attribute and whose value will be - applied to that attribute. For example, if you wanted to apply a - `data-view="Foo.bar"` property to an element, you would set the - elementAttributes hash to `{'data-view':'Foo.bar'}`. - - You should not maintain this hash yourself, rather, you should use - the `attr()` method of `Ember.RenderBuffer`. - - @property elementAttributes - @type Hash - @default {} - */ - elementAttributes: null, - - /** - A hash keyed on the name of the properties and whose value will be - applied to that property. For example, if you wanted to apply a - `checked=true` property to an element, you would set the - elementProperties hash to `{'checked':true}`. - - You should not maintain this hash yourself, rather, you should use - the `prop()` method of `Ember.RenderBuffer`. - - @property elementProperties - @type Hash - @default {} - */ - elementProperties: null, - - /** - The tagname of the element an instance of `Ember.RenderBuffer` represents. - - Usually, this gets set as the first parameter to `Ember.RenderBuffer`. For - example, if you wanted to create a `p` tag, then you would call + The `action` can be provided as properties of an optional object argument + to `triggerAction` as well. ```javascript - Ember.RenderBuffer('p') + App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, { + click: function() { + this.triggerAction({ + action: 'save' + }); // Sends the `save` action, along with the current context + // to the current controller + } + }); ``` - @property elementTag - @type String - @default null - */ - elementTag: null, + @class ViewTargetActionSupport + @namespace Ember + @extends Ember.TargetActionSupport + */ + var ViewTargetActionSupport = Mixin.create(TargetActionSupport, { + /** + @property target + */ + target: alias('controller'), + /** + @property actionContext + */ + actionContext: alias('context') + }); - /** - A hash keyed on the name of the style attribute and whose value will - be applied to that attribute. For example, if you wanted to apply a - `background-color:black;` style to an element, you would set the - elementStyle hash to `{'background-color':'black'}`. + __exports__["default"] = ViewTargetActionSupport; + }); +define("ember-views/system/event_dispatcher", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/is_none","ember-metal/run_loop","ember-metal/utils","ember-runtime/system/string","ember-runtime/system/object","ember-views/system/jquery","ember-views/views/view","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-views + */ + var Ember = __dependency1__["default"]; + // Ember.assert - You should not maintain this hash yourself, rather, you should use - the `style()` method of `Ember.RenderBuffer`. + var get = __dependency2__.get; + var set = __dependency3__.set; + var isNone = __dependency4__.isNone; + var run = __dependency5__["default"]; + var typeOf = __dependency6__.typeOf; - @property elementStyle - @type Hash - @default {} - */ - elementStyle: null, + // ES6TODO functions on EmberStringUtils should have their own export + var EmberStringUtils = __dependency7__["default"]; + var fmt = EmberStringUtils.fmt; - /** - Nested `RenderBuffers` will set this to their parent `RenderBuffer` - instance. + var EmberObject = __dependency8__["default"]; + var jQuery = __dependency9__["default"]; + var View = __dependency10__.View; - @property parentBuffer - @type Ember._RenderBuffer - */ - parentBuffer: null, + var ActionHelper; - /** - Adds a string of HTML to the `RenderBuffer`. + //ES6TODO: + // find a better way to do Ember.View.views without global state - @method push - @param {String} string HTML to push into the buffer - @chainable - */ - push: function(string) { - this.buffer += string; - return this; - }, + /** + `Ember.EventDispatcher` handles delegating browser events to their + corresponding `Ember.Views.` For example, when you click on a view, + `Ember.EventDispatcher` ensures that that view's `mouseDown` method gets + called. - /** - Adds a class to the buffer, which will be rendered to the class attribute. + @class EventDispatcher + @namespace Ember + @private + @extends Ember.Object + */ + var EventDispatcher = EmberObject.extend({ - @method addClass - @param {String} className Class name to add to the buffer - @chainable - */ - addClass: function(className) { - // lazily create elementClasses - this.elementClasses = (this.elementClasses || new ClassSet()); - this.elementClasses.add(className); - this.classes = this.elementClasses.list; + /** + The set of events names (and associated handler function names) to be setup + and dispatched by the `EventDispatcher`. Custom events can added to this list at setup + time, generally via the `Ember.Application.customEvents` hash. Only override this + default set to prevent the EventDispatcher from listening on some events all together. - return this; - }, + This set will be modified by `setup` to also include any events added at that time. - setClasses: function(classNames) { - this.classes = classNames; - }, + @property events + @type Object + */ + events: { + touchstart : 'touchStart', + touchmove : 'touchMove', + touchend : 'touchEnd', + touchcancel : 'touchCancel', + keydown : 'keyDown', + keyup : 'keyUp', + keypress : 'keyPress', + mousedown : 'mouseDown', + mouseup : 'mouseUp', + contextmenu : 'contextMenu', + click : 'click', + dblclick : 'doubleClick', + mousemove : 'mouseMove', + focusin : 'focusIn', + focusout : 'focusOut', + mouseenter : 'mouseEnter', + mouseleave : 'mouseLeave', + submit : 'submit', + input : 'input', + change : 'change', + dragstart : 'dragStart', + drag : 'drag', + dragenter : 'dragEnter', + dragleave : 'dragLeave', + dragover : 'dragOver', + drop : 'drop', + dragend : 'dragEnd' + }, - /** - Sets the elementID to be used for the element. + /** + The root DOM element to which event listeners should be attached. Event + listeners will be attached to the document unless this is overridden. - @method id - @param {String} id - @chainable - */ - id: function(id) { - this.elementId = id; - return this; - }, + Can be specified as a DOMElement or a selector string. - // duck type attribute functionality like jQuery so a render buffer - // can be used like a jQuery object in attribute binding scenarios. + The default body is a string since this may be evaluated before document.body + exists in the DOM. - /** - Adds an attribute which will be rendered to the element. + @private + @property rootElement + @type DOMElement + @default 'body' + */ + rootElement: 'body', - @method attr - @param {String} name The name of the attribute - @param {String} value The value to add to the attribute - @chainable - @return {Ember.RenderBuffer|String} this or the current attribute value - */ - attr: function(name, value) { - var attributes = this.elementAttributes = (this.elementAttributes || {}); + /** + Sets up event listeners for standard browser events. - if (arguments.length === 1) { - return attributes[name]; - } else { - attributes[name] = value; - } + This will be called after the browser sends a `DOMContentReady` event. By + default, it will set up all of the listeners on the document body. If you + would like to register the listeners on a different element, set the event + dispatcher's `root` property. - return this; - }, + @private + @method setup + @param addedEvents {Hash} + */ + setup: function(addedEvents, rootElement) { + var event, events = get(this, 'events'); - /** - Remove an attribute from the list of attributes to render. + jQuery.extend(events, addedEvents || {}); - @method removeAttr - @param {String} name The name of the attribute - @chainable - */ - removeAttr: function(name) { - var attributes = this.elementAttributes; - if (attributes) { delete attributes[name]; } - return this; - }, - - /** - Adds an property which will be rendered to the element. - - @method prop - @param {String} name The name of the property - @param {String} value The value to add to the property - @chainable - @return {Ember.RenderBuffer|String} this or the current property value - */ - prop: function(name, value) { - var properties = this.elementProperties = (this.elementProperties || {}); - - if (arguments.length === 1) { - return properties[name]; - } else { - properties[name] = value; - } - - return this; - }, - - /** - Remove an property from the list of properties to render. - - @method removeProp - @param {String} name The name of the property - @chainable - */ - removeProp: function(name) { - var properties = this.elementProperties; - if (properties) { delete properties[name]; } - - return this; - }, - - /** - Adds a style to the style attribute which will be rendered to the element. - - @method style - @param {String} name Name of the style - @param {String} value - @chainable - */ - style: function(name, value) { - this.elementStyle = (this.elementStyle || {}); - - this.elementStyle[name] = value; - return this; - }, - - begin: function(tagName) { - this.tagNames.push(tagName || null); - return this; - }, - - pushOpeningTag: function() { - var tagName = this.currentTagName(); - if (!tagName) { return; } - - if (this._hasElement && !this._element && this.buffer.length === 0) { - this._element = this.generateElement(); - return; - } - - var buffer = this.buffer, - id = this.elementId, - classes = this.classes, - attrs = this.elementAttributes, - props = this.elementProperties, - style = this.elementStyle, - attr, prop; - - buffer += '<' + stripTagName(tagName); - - if (id) { - buffer += ' id="' + escapeAttribute(id) + '"'; - this.elementId = null; - } - if (classes) { - buffer += ' class="' + escapeAttribute(classes.join(' ')) + '"'; - this.classes = null; - } - - if (style) { - buffer += ' style="'; - - for (prop in style) { - if (style.hasOwnProperty(prop)) { - buffer += prop + ':' + escapeAttribute(style[prop]) + ';'; + if (!isNone(rootElement)) { + set(this, 'rootElement', rootElement); } - } - buffer += '"'; + rootElement = jQuery(get(this, 'rootElement')); - this.elementStyle = null; - } + Ember.assert(fmt('You cannot use the same root element (%@) multiple times in an Ember.Application', [rootElement.selector || rootElement[0].tagName]), !rootElement.is('.ember-application')); + Ember.assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', !rootElement.closest('.ember-application').length); + Ember.assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.find('.ember-application').length); - if (attrs) { - for (attr in attrs) { - if (attrs.hasOwnProperty(attr)) { - buffer += ' ' + attr + '="' + escapeAttribute(attrs[attr]) + '"'; - } - } + rootElement.addClass('ember-application'); - this.elementAttributes = null; - } + Ember.assert('Unable to add "ember-application" class to rootElement. Make sure you set rootElement to the body or an element in the body.', rootElement.is('.ember-application')); - if (props) { - for (prop in props) { - if (props.hasOwnProperty(prop)) { - var value = props[prop]; - if (value || typeof(value) === 'number') { - if (value === true) { - buffer += ' ' + prop + '="' + prop + '"'; - } else { - buffer += ' ' + prop + '="' + escapeAttribute(props[prop]) + '"'; - } + for (event in events) { + if (events.hasOwnProperty(event)) { + this.setupHandler(rootElement, event, events[event]); } } - } + }, - this.elementProperties = null; - } + /** + Registers an event listener on the document. If the given event is + triggered, the provided event handler will be triggered on the target view. - buffer += '>'; - this.buffer = buffer; - }, + If the target view does not implement the event handler, or if the handler + returns `false`, the parent view will be called. The event will continue to + bubble to each successive parent view until it reaches the top. - pushClosingTag: function() { - var tagName = this.tagNames.pop(); - if (tagName) { this.buffer += ''; } - }, + For example, to have the `mouseDown` method called on the target view when + a `mousedown` event is received from the browser, do the following: - currentTagName: function() { - return this.tagNames[this.tagNames.length-1]; - }, + ```javascript + setupHandler('mousedown', 'mouseDown'); + ``` - generateElement: function() { - var tagName = this.tagNames.pop(), // pop since we don't need to close - id = this.elementId, - classes = this.classes, - attrs = this.elementAttributes, - props = this.elementProperties, - style = this.elementStyle, - styleBuffer = '', attr, prop, tagString; + @private + @method setupHandler + @param {Element} rootElement + @param {String} event the browser-originated event to listen to + @param {String} eventName the name of the method to call on the view + */ + setupHandler: function(rootElement, event, eventName) { + var self = this; - if (attrs && attrs.name && !canSetNameOnInputs) { - // IE allows passing a tag to createElement. See note on `canSetNameOnInputs` above as well. - tagString = '<'+stripTagName(tagName)+' name="'+escapeAttribute(attrs.name)+'">'; - } else { - tagString = tagName; - } + rootElement.on(event + '.ember', '.ember-view', function(evt, triggeringManager) { + var view = View.views[this.id], + result = true, manager = null; - var element = document.createElement(tagString), - $element = Ember.$(element); + manager = self._findNearestEventManager(view, eventName); - if (id) { - $element.attr('id', id); - this.elementId = null; - } - if (classes) { - $element.attr('class', classes.join(' ')); - this.classes = null; - } + if (manager && manager !== triggeringManager) { + result = self._dispatchEvent(manager, evt, eventName, view); + } else if (view) { + result = self._bubbleEvent(view, evt, eventName); + } else { + evt.stopPropagation(); + } - if (style) { - for (prop in style) { - if (style.hasOwnProperty(prop)) { - styleBuffer += (prop + ':' + style[prop] + ';'); + return result; + }); + + rootElement.on(event + '.ember', '[data-ember-action]', function(evt) { + //ES6TODO: Needed for ActionHelper (generally not available in ember-views test suite) + if (!ActionHelper) { ActionHelper = requireModule("ember-routing/helpers/action")["ActionHelper"]; }; + + var actionId = jQuery(evt.currentTarget).attr('data-ember-action'), + action = ActionHelper.registeredActions[actionId]; + + // We have to check for action here since in some cases, jQuery will trigger + // an event on `removeChild` (i.e. focusout) after we've already torn down the + // action handlers for the view. + if (action && action.eventName === eventName) { + return action.handler(evt); + } + }); + }, + + _findNearestEventManager: function(view, eventName) { + var manager = null; + + while (view) { + manager = get(view, 'eventManager'); + if (manager && manager[eventName]) { break; } + + view = get(view, 'parentView'); } - } - $element.attr('style', styleBuffer); + return manager; + }, - this.elementStyle = null; - } + _dispatchEvent: function(object, evt, eventName, view) { + var result = true; - if (attrs) { - for (attr in attrs) { - if (attrs.hasOwnProperty(attr)) { - $element.attr(attr, attrs[attr]); - } - } - - this.elementAttributes = null; - } - - if (props) { - for (prop in props) { - if (props.hasOwnProperty(prop)) { - $element.prop(prop, props[prop]); - } - } - - this.elementProperties = null; - } - - return element; - }, - - /** - @method element - @return {DOMElement} The element corresponding to the generated HTML - of this buffer - */ - element: function() { - var html = this.innerString(); - - if (html) { - this._element = Ember.ViewUtils.setInnerHTML(this._element, html); - } - - return this._element; - }, - - /** - Generates the HTML content for this buffer. - - @method string - @return {String} The generated HTML - */ - string: function() { - if (this._hasElement && this._element) { - // Firefox versions < 11 do not have support for element.outerHTML. - var thisElement = this.element(), outerHTML = thisElement.outerHTML; - if (typeof outerHTML === 'undefined') { - return Ember.$('
    ').append(thisElement).html(); - } - return outerHTML; - } else { - return this.innerString(); - } - }, - - innerString: function() { - return this.buffer; - } -}; - -})(); - - - -(function() { -/** -@module ember -@submodule ember-views -*/ - -var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt; - -/** - `Ember.EventDispatcher` handles delegating browser events to their - corresponding `Ember.Views.` For example, when you click on a view, - `Ember.EventDispatcher` ensures that that view's `mouseDown` method gets - called. - - @class EventDispatcher - @namespace Ember - @private - @extends Ember.Object -*/ -Ember.EventDispatcher = Ember.Object.extend(/** @scope Ember.EventDispatcher.prototype */{ - - /** - The set of events names (and associated handler function names) to be setup - and dispatched by the `EventDispatcher`. Custom events can added to this list at setup - time, generally via the `Ember.Application.customEvents` hash. Only override this - default set to prevent the EventDispatcher from listening on some events all together. - - This set will be modified by `setup` to also include any events added at that time. - - @property events - @type Object - */ - events: { - touchstart : 'touchStart', - touchmove : 'touchMove', - touchend : 'touchEnd', - touchcancel : 'touchCancel', - keydown : 'keyDown', - keyup : 'keyUp', - keypress : 'keyPress', - mousedown : 'mouseDown', - mouseup : 'mouseUp', - contextmenu : 'contextMenu', - click : 'click', - dblclick : 'doubleClick', - mousemove : 'mouseMove', - focusin : 'focusIn', - focusout : 'focusOut', - mouseenter : 'mouseEnter', - mouseleave : 'mouseLeave', - submit : 'submit', - input : 'input', - change : 'change', - dragstart : 'dragStart', - drag : 'drag', - dragenter : 'dragEnter', - dragleave : 'dragLeave', - dragover : 'dragOver', - drop : 'drop', - dragend : 'dragEnd' - }, - - /** - @private - - The root DOM element to which event listeners should be attached. Event - listeners will be attached to the document unless this is overridden. - - Can be specified as a DOMElement or a selector string. - - The default body is a string since this may be evaluated before document.body - exists in the DOM. - - @property rootElement - @type DOMElement - @default 'body' - */ - rootElement: 'body', - - /** - @private - - Sets up event listeners for standard browser events. - - This will be called after the browser sends a `DOMContentReady` event. By - default, it will set up all of the listeners on the document body. If you - would like to register the listeners on a different element, set the event - dispatcher's `root` property. - - @method setup - @param addedEvents {Hash} - */ - setup: function(addedEvents, rootElement) { - var event, events = get(this, 'events'); - - Ember.$.extend(events, addedEvents || {}); - - - if (!Ember.isNone(rootElement)) { - set(this, 'rootElement', rootElement); - } - - rootElement = Ember.$(get(this, 'rootElement')); - - Ember.assert(fmt('You cannot use the same root element (%@) multiple times in an Ember.Application', [rootElement.selector || rootElement[0].tagName]), !rootElement.is('.ember-application')); - Ember.assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', !rootElement.closest('.ember-application').length); - Ember.assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.find('.ember-application').length); - - rootElement.addClass('ember-application'); - - Ember.assert('Unable to add "ember-application" class to rootElement. Make sure you set rootElement to the body or an element in the body.', rootElement.is('.ember-application')); - - for (event in events) { - if (events.hasOwnProperty(event)) { - this.setupHandler(rootElement, event, events[event]); - } - } - }, - - /** - @private - - Registers an event listener on the document. If the given event is - triggered, the provided event handler will be triggered on the target view. - - If the target view does not implement the event handler, or if the handler - returns `false`, the parent view will be called. The event will continue to - bubble to each successive parent view until it reaches the top. - - For example, to have the `mouseDown` method called on the target view when - a `mousedown` event is received from the browser, do the following: - - ```javascript - setupHandler('mousedown', 'mouseDown'); - ``` - - @method setupHandler - @param {Element} rootElement - @param {String} event the browser-originated event to listen to - @param {String} eventName the name of the method to call on the view - */ - setupHandler: function(rootElement, event, eventName) { - var self = this; - - rootElement.on(event + '.ember', '.ember-view', function(evt, triggeringManager) { - return Ember.handleErrors(function() { - var view = Ember.View.views[this.id], - result = true, manager = null; - - manager = self._findNearestEventManager(view,eventName); - - if (manager && manager !== triggeringManager) { - result = self._dispatchEvent(manager, evt, eventName, view); - } else if (view) { - result = self._bubbleEvent(view,evt,eventName); - } else { + var handler = object[eventName]; + if (typeOf(handler) === 'function') { + result = run(object, handler, evt, view); + // Do not preventDefault in eventManagers. evt.stopPropagation(); } + else { + result = this._bubbleEvent(view, evt, eventName); + } return result; - }, this); + }, + + _bubbleEvent: function(view, evt, eventName) { + return run(view, view.handleEvent, eventName, evt); + }, + + destroy: function() { + var rootElement = get(this, 'rootElement'); + jQuery(rootElement).off('.ember', '**').removeClass('ember-application'); + return this._super(); + } }); - rootElement.on(event + '.ember', '[data-ember-action]', function(evt) { - return Ember.handleErrors(function() { - var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'), - action = Ember.Handlebars.ActionHelper.registeredActions[actionId]; + __exports__["default"] = EventDispatcher; + }); +define("ember-views/system/ext", + ["ember-metal/run_loop"], + function(__dependency1__) { + "use strict"; + /** + @module ember + @submodule ember-views + */ - // We have to check for action here since in some cases, jQuery will trigger - // an event on `removeChild` (i.e. focusout) after we've already torn down the - // action handlers for the view. - if (action && action.eventName === eventName) { - return action.handler(evt); - } - }, this); - }); - }, + var run = __dependency1__["default"]; - _findNearestEventManager: function(view, eventName) { - var manager = null; + // Add a new named queue for rendering views that happens + // after bindings have synced, and a queue for scheduling actions + // that that should occur after view rendering. + var queues = run.queues; + run._addQueue('render', 'actions'); + run._addQueue('afterRender', 'render'); + }); +define("ember-views/system/jquery", + ["ember-metal/core","ember-runtime/system/string","ember-metal/enumerable_utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.assert - while (view) { - manager = get(view, 'eventManager'); - if (manager && manager[eventName]) { break; } + // ES6TODO: the functions on EmberStringUtils need their own exports + var EmberStringUtils = __dependency2__["default"]; + var w = EmberStringUtils.w; - view = get(view, 'parentView'); + // ES6TODO: the functions on EnumerableUtils need their own exports + var EnumerableUtils = __dependency3__["default"]; + var forEach = EnumerableUtils.forEach; + + /** + Ember Views + + @module ember + @submodule ember-views + @requires ember-runtime + @main ember-views + */ + + var jQuery = (Ember.imports && Ember.imports.jQuery) || (this && this.jQuery); + if (!jQuery && typeof require === 'function') { + jQuery = require('jquery'); } - return manager; - }, + Ember.assert("Ember Views require jQuery between 1.7 and 2.1", jQuery && (jQuery().jquery.match(/^((1\.(7|8|9|10|11))|(2\.(0|1)))(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY)); - _dispatchEvent: function(object, evt, eventName, view) { - var result = true; + /** + @module ember + @submodule ember-views + */ + if (jQuery) { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents + var dragEvents = w('dragstart drag dragenter dragleave dragover drop dragend'); - var handler = object[eventName]; - if (Ember.typeOf(handler) === 'function') { - result = Ember.run(function() { - return handler.call(object, evt, view); + // Copies the `dataTransfer` property from a browser event object onto the + // jQuery event object for the specified events + forEach(dragEvents, function(eventName) { + jQuery.event.fixHooks[eventName] = { props: ['dataTransfer'] }; }); - // Do not preventDefault in eventManagers. - evt.stopPropagation(); - } - else { - result = this._bubbleEvent(view, evt, eventName); } - return result; - }, + __exports__["default"] = jQuery; + }); +define("ember-views/system/render_buffer", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-views/system/utils","ember-views/system/jquery","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-views + */ - _bubbleEvent: function(view, evt, eventName) { - return Ember.run(function() { - return view.handleEvent(eventName, evt); - }); - }, + var Ember = __dependency1__["default"]; + // jQuery - destroy: function() { - var rootElement = get(this, 'rootElement'); - Ember.$(rootElement).off('.ember', '**').removeClass('ember-application'); - return this._super(); - } -}); + var get = __dependency2__.get; + var set = __dependency3__.set; + var setInnerHTML = __dependency4__.setInnerHTML; + var jQuery = __dependency5__["default"]; -})(); + function ClassSet() { + this.seen = {}; + this.list = []; + }; + ClassSet.prototype = { + add: function(string) { + if (string in this.seen) { return; } + this.seen[string] = true; -(function() { -/** -@module ember -@submodule ember-views -*/ + this.list.push(string); + }, -// Add a new named queue for rendering views that happens -// after bindings have synced, and a queue for scheduling actions -// that that should occur after view rendering. -var queues = Ember.run.queues, - indexOf = Ember.ArrayPolyfills.indexOf; -queues.splice(indexOf.call(queues, 'actions')+1, 0, 'render', 'afterRender'); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-views -*/ - -var get = Ember.get, set = Ember.set; - -// Original class declaration and documentation in runtime/lib/controllers/controller.js -// NOTE: It may be possible with YUIDoc to combine docs in two locations - -/** -Additional methods for the ControllerMixin - -@class ControllerMixin -@namespace Ember -*/ -Ember.ControllerMixin.reopen({ - target: null, - namespace: null, - view: null, - container: null, - _childContainers: null, - - init: function() { - this._super(); - set(this, '_childContainers', {}); - }, - - _modelDidChange: Ember.observer('model', function() { - var containers = get(this, '_childContainers'); - - for (var prop in containers) { - if (!containers.hasOwnProperty(prop)) { continue; } - containers[prop].destroy(); - } - - set(this, '_childContainers', {}); - }) -}); - -})(); - - - -(function() { - -})(); - - - -(function() { -var states = {}; - -/** -@module ember -@submodule ember-views -*/ - -var get = Ember.get, set = Ember.set; -var guidFor = Ember.guidFor; -var a_forEach = Ember.EnumerableUtils.forEach; -var a_addObject = Ember.EnumerableUtils.addObject; -var meta = Ember.meta; - -var childViewsProperty = Ember.computed(function() { - var childViews = this._childViews, ret = Ember.A(), view = this; - - a_forEach(childViews, function(view) { - var currentChildViews; - if (view.isVirtual) { - if (currentChildViews = get(view, 'childViews')) { - ret.pushObjects(currentChildViews); + toDOM: function() { + return this.list.join(" "); } - } else { - ret.push(view); - } - }); + }; - ret.replace = function (idx, removedCount, addedViews) { - if (view instanceof Ember.ContainerView) { - Ember.deprecate("Manipulating an Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray."); - return view.replace(idx, removedCount, addedViews); - } - throw new Ember.Error("childViews is immutable"); - }; + var BAD_TAG_NAME_TEST_REGEXP = /[^a-zA-Z0-9\-]/; + var BAD_TAG_NAME_REPLACE_REGEXP = /[^a-zA-Z0-9\-]/g; - return ret; -}); - -Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionality can no longer be disabled.", Ember.ENV.VIEW_PRESERVES_CONTEXT !== false); - -/** - Global hash of shared templates. This will automatically be populated - by the build tools so that you can store your Handlebars templates in - separate files that get loaded into JavaScript at buildtime. - - @property TEMPLATES - @for Ember - @type Hash -*/ -Ember.TEMPLATES = {}; - -/** - `Ember.CoreView` is an abstract class that exists to give view-like behavior - to both Ember's main view class `Ember.View` and other classes like - `Ember._SimpleMetamorphView` that don't need the fully functionaltiy of - `Ember.View`. - - Unless you have specific needs for `CoreView`, you will use `Ember.View` - in your applications. - - @class CoreView - @namespace Ember - @extends Ember.Object - @uses Ember.Evented -*/ - -Ember.CoreView = Ember.Object.extend(Ember.Evented, Ember.ActionHandler, { - isView: true, - - states: states, - - init: function() { - this._super(); - this.transitionTo('preRender'); - }, - - /** - If the view is currently inserted into the DOM of a parent view, this - property will point to the parent of the view. - - @property parentView - @type Ember.View - @default null - */ - parentView: Ember.computed(function() { - var parent = this._parentView; - - if (parent && parent.isVirtual) { - return get(parent, 'parentView'); - } else { - return parent; - } - }).property('_parentView'), - - state: null, - - _parentView: null, - - // return the current view, not including virtual views - concreteView: Ember.computed(function() { - if (!this.isVirtual) { return this; } - else { return get(this, 'parentView'); } - }).property('parentView'), - - instrumentName: 'core_view', - - instrumentDetails: function(hash) { - hash.object = this.toString(); - }, - - /** - @private - - Invoked by the view system when this view needs to produce an HTML - representation. This method will create a new render buffer, if needed, - then apply any default attributes, such as class names and visibility. - Finally, the `render()` method is invoked, which is responsible for - doing the bulk of the rendering. - - You should not need to override this method; instead, implement the - `template` property, or if you need more control, override the `render` - method. - - @method renderToBuffer - @param {Ember.RenderBuffer} buffer the render buffer. If no buffer is - passed, a default buffer, using the current view's `tagName`, will - be used. - */ - renderToBuffer: function(parentBuffer, bufferOperation) { - var name = 'render.' + this.instrumentName, - details = {}; - - this.instrumentDetails(details); - - return Ember.instrument(name, details, function() { - return this._renderToBuffer(parentBuffer, bufferOperation); - }, this); - }, - - _renderToBuffer: function(parentBuffer, bufferOperation) { - // If this is the top-most view, start a new buffer. Otherwise, - // create a new buffer relative to the original using the - // provided buffer operation (for example, `insertAfter` will - // insert a new buffer after the "parent buffer"). - var tagName = this.tagName; - - if (tagName === null || tagName === undefined) { - tagName = 'div'; - } - - var buffer = this.buffer = parentBuffer && parentBuffer.begin(tagName) || Ember.RenderBuffer(tagName); - this.transitionTo('inBuffer', false); - - this.beforeRender(buffer); - this.render(buffer); - this.afterRender(buffer); - - return buffer; - }, - - /** - @private - - Override the default event firing from `Ember.Evented` to - also call methods with the given name. - - @method trigger - @param name {String} - */ - trigger: function(name) { - this._super.apply(this, arguments); - var method = this[name]; - if (method) { - var args = [], i, l; - for (i = 1, l = arguments.length; i < l; i++) { - args.push(arguments[i]); + function stripTagName(tagName) { + if (!tagName) { + return tagName; } - return method.apply(this, args); - } - }, - deprecatedSendHandles: function(actionName) { - return !!this[actionName]; - }, - - deprecatedSend: function(actionName) { - var args = [].slice.call(arguments, 1); - Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); - Ember.deprecate('Action handlers implemented directly on views are deprecated in favor of action handlers on an `actions` object (' + actionName + ' on ' + this + ')', false); - this[actionName].apply(this, args); - return; - }, - - has: function(name) { - return Ember.typeOf(this[name]) === 'function' || this._super(name); - }, - - destroy: function() { - var parent = this._parentView; - - if (!this._super()) { return; } - - // destroy the element -- this will avoid each child view destroying - // the element over and over again... - if (!this.removedFromDOM) { this.destroyElement(); } - - // remove from parent if found. Don't call removeFromParent, - // as removeFromParent will try to remove the element from - // the DOM again. - if (parent) { parent.removeChild(this); } - - this.transitionTo('destroying', false); - - return this; - }, - - clearRenderedChildren: Ember.K, - triggerRecursively: Ember.K, - invokeRecursively: Ember.K, - transitionTo: Ember.K, - destroyElement: Ember.K -}); - -var ViewCollection = Ember._ViewCollection = function(initialViews) { - var views = this.views = initialViews || []; - this.length = views.length; -}; - -ViewCollection.prototype = { - length: 0, - - trigger: function(eventName) { - var views = this.views, view; - for (var i = 0, l = views.length; i < l; i++) { - view = views[i]; - if (view.trigger) { view.trigger(eventName); } - } - }, - - triggerRecursively: function(eventName) { - var views = this.views; - for (var i = 0, l = views.length; i < l; i++) { - views[i].triggerRecursively(eventName); - } - }, - - invokeRecursively: function(fn) { - var views = this.views, view; - - for (var i = 0, l = views.length; i < l; i++) { - view = views[i]; - fn(view); - } - }, - - transitionTo: function(state, children) { - var views = this.views; - for (var i = 0, l = views.length; i < l; i++) { - views[i].transitionTo(state, children); - } - }, - - push: function() { - this.length += arguments.length; - var views = this.views; - return views.push.apply(views, arguments); - }, - - objectAt: function(idx) { - return this.views[idx]; - }, - - forEach: function(callback) { - var views = this.views; - return a_forEach(views, callback); - }, - - clear: function() { - this.length = 0; - this.views.length = 0; - } -}; - -var EMPTY_ARRAY = []; - -/** - `Ember.View` is the class in Ember responsible for encapsulating templates of - HTML content, combining templates with data to render as sections of a page's - DOM, and registering and responding to user-initiated events. - - ## HTML Tag - - The default HTML tag name used for a view's DOM representation is `div`. This - can be customized by setting the `tagName` property. The following view - class: - - ```javascript - ParagraphView = Ember.View.extend({ - tagName: 'em' - }); - ``` - - Would result in instances with the following HTML: - - ```html - - ``` - - ## HTML `class` Attribute - - The HTML `class` attribute of a view's tag can be set by providing a - `classNames` property that is set to an array of strings: - - ```javascript - MyView = Ember.View.extend({ - classNames: ['my-class', 'my-other-class'] - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html -
    - ``` - - `class` attribute values can also be set by providing a `classNameBindings` - property set to an array of properties names for the view. The return value - of these properties will be added as part of the value for the view's `class` - attribute. These properties can be computed properties: - - ```javascript - MyView = Ember.View.extend({ - classNameBindings: ['propertyA', 'propertyB'], - propertyA: 'from-a', - propertyB: function() { - if (someLogic) { return 'from-b'; } - }.property() - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html -
    - ``` - - If the value of a class name binding returns a boolean the property name - itself will be used as the class name if the property is true. The class name - will not be added if the value is `false` or `undefined`. - - ```javascript - MyView = Ember.View.extend({ - classNameBindings: ['hovered'], - hovered: true - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html -
    - ``` - - When using boolean class name bindings you can supply a string value other - than the property name for use as the `class` HTML attribute by appending the - preferred value after a ":" character when defining the binding: - - ```javascript - MyView = Ember.View.extend({ - classNameBindings: ['awesome:so-very-cool'], - awesome: true - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html -
    - ``` - - Boolean value class name bindings whose property names are in a - camelCase-style format will be converted to a dasherized format: - - ```javascript - MyView = Ember.View.extend({ - classNameBindings: ['isUrgent'], - isUrgent: true - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html -
    - ``` - - Class name bindings can also refer to object values that are found by - traversing a path relative to the view itself: - - ```javascript - MyView = Ember.View.extend({ - classNameBindings: ['messages.empty'] - messages: Ember.Object.create({ - empty: true - }) - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html -
    - ``` - - If you want to add a class name for a property which evaluates to true and - and a different class name if it evaluates to false, you can pass a binding - like this: - - ```javascript - // Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false - Ember.View.extend({ - classNameBindings: ['isEnabled:enabled:disabled'] - isEnabled: true - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html -
    - ``` - - When isEnabled is `false`, the resulting HTML reprensentation looks like - this: - - ```html -
    - ``` - - This syntax offers the convenience to add a class if a property is `false`: - - ```javascript - // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false - Ember.View.extend({ - classNameBindings: ['isEnabled::disabled'] - isEnabled: true - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html -
    - ``` - - When the `isEnabled` property on the view is set to `false`, it will result - in view instances with an HTML representation of: - - ```html -
    - ``` - - Updates to the the value of a class name binding will result in automatic - update of the HTML `class` attribute in the view's rendered HTML - representation. If the value becomes `false` or `undefined` the class name - will be removed. - - Both `classNames` and `classNameBindings` are concatenated properties. See - [Ember.Object](/api/classes/Ember.Object.html) documentation for more - information about concatenated properties. - - ## HTML Attributes - - The HTML attribute section of a view's tag can be set by providing an - `attributeBindings` property set to an array of property names on the view. - The return value of these properties will be used as the value of the view's - HTML associated attribute: - - ```javascript - AnchorView = Ember.View.extend({ - tagName: 'a', - attributeBindings: ['href'], - href: 'http://google.com' - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html - - ``` - - If the return value of an `attributeBindings` monitored property is a boolean - the property will follow HTML's pattern of repeating the attribute's name as - its value: - - ```javascript - MyTextInput = Ember.View.extend({ - tagName: 'input', - attributeBindings: ['disabled'], - disabled: true - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html - - ``` - - `attributeBindings` can refer to computed properties: - - ```javascript - MyTextInput = Ember.View.extend({ - tagName: 'input', - attributeBindings: ['disabled'], - disabled: function() { - if (someLogic) { - return true; - } else { - return false; + if (!BAD_TAG_NAME_TEST_REGEXP.test(tagName)) { + return tagName; } - }.property() - }); - ``` - Updates to the the property of an attribute binding will result in automatic - update of the HTML attribute in the view's rendered HTML representation. - - `attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html) - documentation for more information about concatenated properties. - - ## Templates - - The HTML contents of a view's rendered representation are determined by its - template. Templates can be any function that accepts an optional context - parameter and returns a string of HTML that will be inserted within the - view's tag. Most typically in Ember this function will be a compiled - `Ember.Handlebars` template. - - ```javascript - AView = Ember.View.extend({ - template: Ember.Handlebars.compile('I am the template') - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html -
    I am the template
    - ``` - - Within an Ember application is more common to define a Handlebars templates as - part of a page: - - ```html - - ``` - - And associate it by name using a view's `templateName` property: - - ```javascript - AView = Ember.View.extend({ - templateName: 'some-template' - }); - ``` - - Using a value for `templateName` that does not have a Handlebars template - with a matching `data-template-name` attribute will throw an error. - - For views classes that may have a template later defined (e.g. as the block - portion of a `{{view}}` Handlebars helper call in another template or in - a subclass), you can provide a `defaultTemplate` property set to compiled - template function. If a template is not later provided for the view instance - the `defaultTemplate` value will be used: - - ```javascript - AView = Ember.View.extend({ - defaultTemplate: Ember.Handlebars.compile('I was the default'), - template: null, - templateName: null - }); - ``` - - Will result in instances with an HTML representation of: - - ```html -
    I was the default
    - ``` - - If a `template` or `templateName` is provided it will take precedence over - `defaultTemplate`: - - ```javascript - AView = Ember.View.extend({ - defaultTemplate: Ember.Handlebars.compile('I was the default') - }); - - aView = AView.create({ - template: Ember.Handlebars.compile('I was the template, not default') - }); - ``` - - Will result in the following HTML representation when rendered: - - ```html -
    I was the template, not default
    - ``` - - ## View Context - - The default context of the compiled template is the view's controller: - - ```javascript - AView = Ember.View.extend({ - template: Ember.Handlebars.compile('Hello {{excitedGreeting}}') - }); - - aController = Ember.Object.create({ - firstName: 'Barry', - excitedGreeting: function() { - return this.get("content.firstName") + "!!!" - }.property() - }); - - aView = AView.create({ - controller: aController, - }); - ``` - - Will result in an HTML representation of: - - ```html -
    Hello Barry!!!
    - ``` - - A context can also be explicitly supplied through the view's `context` - property. If the view has neither `context` nor `controller` properties, the - `parentView`'s context will be used. - - ## Layouts - - Views can have a secondary template that wraps their main template. Like - primary templates, layouts can be any function that accepts an optional - context parameter and returns a string of HTML that will be inserted inside - view's tag. Views whose HTML element is self closing (e.g. ``) - cannot have a layout and this property will be ignored. - - Most typically in Ember a layout will be a compiled `Ember.Handlebars` - template. - - A view's layout can be set directly with the `layout` property or reference - an existing Handlebars template by name with the `layoutName` property. - - A template used as a layout must contain a single use of the Handlebars - `{{yield}}` helper. The HTML contents of a view's rendered `template` will be - inserted at this location: - - ```javascript - AViewWithLayout = Ember.View.extend({ - layout: Ember.Handlebars.compile("
    {{yield}}
    ") - template: Ember.Handlebars.compile("I got wrapped"), - }); - ``` - - Will result in view instances with an HTML representation of: - - ```html -
    -
    - I got wrapped -
    -
    - ``` - - See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield) - for more information. - - ## Responding to Browser Events - - Views can respond to user-initiated events in one of three ways: method - implementation, through an event manager, and through `{{action}}` helper use - in their template or layout. - - ### Method Implementation - - Views can respond to user-initiated events by implementing a method that - matches the event name. A `jQuery.Event` object will be passed as the - argument to this method. - - ```javascript - AView = Ember.View.extend({ - click: function(event) { - // will be called when when an instance's - // rendered element is clicked - } - }); - ``` - - ### Event Managers - - Views can define an object as their `eventManager` property. This object can - then implement methods that match the desired event names. Matching events - that occur on the view's rendered HTML or the rendered HTML of any of its DOM - descendants will trigger this method. A `jQuery.Event` object will be passed - as the first argument to the method and an `Ember.View` object as the - second. The `Ember.View` will be the view whose rendered HTML was interacted - with. This may be the view with the `eventManager` property or one of its - descendent views. - - ```javascript - AView = Ember.View.extend({ - eventManager: Ember.Object.create({ - doubleClick: function(event, view) { - // will be called when when an instance's - // rendered element or any rendering - // of this views's descendent - // elements is clicked - } - }) - }); - ``` - - An event defined for an event manager takes precedence over events of the - same name handled through methods on the view. - - ```javascript - AView = Ember.View.extend({ - mouseEnter: function(event) { - // will never trigger. - }, - eventManager: Ember.Object.create({ - mouseEnter: function(event, view) { - // takes precedence over AView#mouseEnter - } - }) - }); - ``` - - Similarly a view's event manager will take precedence for events of any views - rendered as a descendent. A method name that matches an event name will not - be called if the view instance was rendered inside the HTML representation of - a view that has an `eventManager` property defined that handles events of the - name. Events not handled by the event manager will still trigger method calls - on the descendent. - - ```javascript - OuterView = Ember.View.extend({ - template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"), - eventManager: Ember.Object.create({ - mouseEnter: function(event, view) { - // view might be instance of either - // OuterView or InnerView depending on - // where on the page the user interaction occured - } - }) - }); - - InnerView = Ember.View.extend({ - click: function(event) { - // will be called if rendered inside - // an OuterView because OuterView's - // eventManager doesn't handle click events - }, - mouseEnter: function(event) { - // will never be called if rendered inside - // an OuterView. - } - }); - ``` - - ### Handlebars `{{action}}` Helper - - See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.helpers.html#method_action). - - ### Event Names - - All of the event handling approaches described above respond to the same set - of events. The names of the built-in events are listed below. (The hash of - built-in events exists in `Ember.EventDispatcher`.) Additional, custom events - can be registered by using `Ember.Application.customEvents`. - - Touch events: - - * `touchStart` - * `touchMove` - * `touchEnd` - * `touchCancel` - - Keyboard events - - * `keyDown` - * `keyUp` - * `keyPress` - - Mouse events - - * `mouseDown` - * `mouseUp` - * `contextMenu` - * `click` - * `doubleClick` - * `mouseMove` - * `focusIn` - * `focusOut` - * `mouseEnter` - * `mouseLeave` - - Form events: - - * `submit` - * `change` - * `focusIn` - * `focusOut` - * `input` - - HTML5 drag and drop events: - - * `dragStart` - * `drag` - * `dragEnter` - * `dragLeave` - * `drop` - * `dragEnd` - - ## Handlebars `{{view}}` Helper - - Other `Ember.View` instances can be included as part of a view's template by - using the `{{view}}` Handlebars helper. See [Ember.Handlebars.helpers.view](/api/classes/Ember.Handlebars.helpers.html#method_view) - for additional information. - - @class View - @namespace Ember - @extends Ember.CoreView -*/ -Ember.View = Ember.CoreView.extend( -/** @scope Ember.View.prototype */ { - - concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'], - - /** - @property isView - @type Boolean - @default true - @final - */ - isView: true, - - // .......................................................... - // TEMPLATE SUPPORT - // - - /** - The name of the template to lookup if no template is provided. - - By default `Ember.View` will lookup a template with this name in - `Ember.TEMPLATES` (a shared global object). - - @property templateName - @type String - @default null - */ - templateName: null, - - /** - The name of the layout to lookup if no layout is provided. - - By default `Ember.View` will lookup a template with this name in - `Ember.TEMPLATES` (a shared global object). - - @property layoutName - @type String - @default null - */ - layoutName: null, - - /** - The template used to render the view. This should be a function that - accepts an optional context parameter and returns a string of HTML that - will be inserted into the DOM relative to its parent view. - - In general, you should set the `templateName` property instead of setting - the template yourself. - - @property template - @type Function - */ - template: Ember.computed(function(key, value) { - if (value !== undefined) { return value; } - - var templateName = get(this, 'templateName'), - template = this.templateForName(templateName, 'template'); - - Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template); - - return template || get(this, 'defaultTemplate'); - }).property('templateName'), - - /** - The controller managing this view. If this property is set, it will be - made available for use by the template. - - @property controller - @type Object - */ - controller: Ember.computed(function(key) { - var parentView = get(this, '_parentView'); - return parentView ? get(parentView, 'controller') : null; - }).property('_parentView'), - - /** - A view may contain a layout. A layout is a regular template but - supersedes the `template` property during rendering. It is the - responsibility of the layout template to retrieve the `template` - property from the view (or alternatively, call `Handlebars.helpers.yield`, - `{{yield}}`) to render it in the correct location. - - This is useful for a view that has a shared wrapper, but which delegates - the rendering of the contents of the wrapper to the `template` property - on a subclass. - - @property layout - @type Function - */ - layout: Ember.computed(function(key) { - var layoutName = get(this, 'layoutName'), - layout = this.templateForName(layoutName, 'layout'); - - Ember.assert("You specified the layoutName " + layoutName + " for " + this + ", but it did not exist.", !layoutName || layout); - - return layout || get(this, 'defaultLayout'); - }).property('layoutName'), - - _yield: function(context, options) { - var template = get(this, 'template'); - if (template) { template(context, options); } - }, - - templateForName: function(name, type) { - if (!name) { return; } - Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1); - - // the defaultContainer is deprecated - var container = this.container || (Ember.Container && Ember.Container.defaultContainer); - return container && container.lookup('template:' + name); - }, - - /** - The object from which templates should access properties. - - This object will be passed to the template function each time the render - method is called, but it is up to the individual function to decide what - to do with it. - - By default, this will be the view's controller. - - @property context - @type Object - */ - context: Ember.computed(function(key, value) { - if (arguments.length === 2) { - set(this, '_context', value); - return value; - } else { - return get(this, '_context'); - } - }).volatile(), - - /** - @private - - Private copy of the view's template context. This can be set directly - by Handlebars without triggering the observer that causes the view - to be re-rendered. - - The context of a view is looked up as follows: - - 1. Supplied context (usually by Handlebars) - 2. Specified controller - 3. `parentView`'s context (for a child of a ContainerView) - - The code in Handlebars that overrides the `_context` property first - checks to see whether the view has a specified controller. This is - something of a hack and should be revisited. - - @property _context - */ - _context: Ember.computed(function(key) { - var parentView, controller; - - if (controller = get(this, 'controller')) { - return controller; + return tagName.replace(BAD_TAG_NAME_REPLACE_REGEXP, ''); } - parentView = this._parentView; - if (parentView) { - return get(parentView, '_context'); - } + var BAD_CHARS_REGEXP = /&(?!\w+;)|[<>"'`]/g; + var POSSIBLE_CHARS_REGEXP = /[&<>"'`]/; - return null; - }), + function escapeAttribute(value) { + // Stolen shamelessly from Handlebars - /** - @private - - If a value that affects template rendering changes, the view should be - re-rendered to reflect the new value. - - @method _contextDidChange - */ - _contextDidChange: Ember.observer('context', function() { - this.rerender(); - }), - - /** - If `false`, the view will appear hidden in DOM. - - @property isVisible - @type Boolean - @default null - */ - isVisible: true, - - /** - @private - - Array of child views. You should never edit this array directly. - Instead, use `appendChild` and `removeFromParent`. - - @property childViews - @type Array - @default [] - */ - childViews: childViewsProperty, - - _childViews: EMPTY_ARRAY, - - // When it's a virtual view, we need to notify the parent that their - // childViews will change. - _childViewsWillChange: Ember.beforeObserver('childViews', function() { - if (this.isVirtual) { - var parentView = get(this, 'parentView'); - if (parentView) { Ember.propertyWillChange(parentView, 'childViews'); } - } - }), - - // When it's a virtual view, we need to notify the parent that their - // childViews did change. - _childViewsDidChange: Ember.observer('childViews', function() { - if (this.isVirtual) { - var parentView = get(this, 'parentView'); - if (parentView) { Ember.propertyDidChange(parentView, 'childViews'); } - } - }), - - /** - Return the nearest ancestor that is an instance of the provided - class. - - @property nearestInstanceOf - @param {Class} klass Subclass of Ember.View (or Ember.View itself) - @return Ember.View - @deprecated - */ - nearestInstanceOf: function(klass) { - Ember.deprecate("nearestInstanceOf is deprecated and will be removed from future releases. Use nearestOfType."); - var view = get(this, 'parentView'); - - while (view) { - if (view instanceof klass) { return view; } - view = get(view, 'parentView'); - } - }, - - /** - Return the nearest ancestor that is an instance of the provided - class or mixin. - - @property nearestOfType - @param {Class,Mixin} klass Subclass of Ember.View (or Ember.View itself), - or an instance of Ember.Mixin. - @return Ember.View - */ - nearestOfType: function(klass) { - var view = get(this, 'parentView'), - isOfType = klass instanceof Ember.Mixin ? - function(view) { return klass.detect(view); } : - function(view) { return klass.detect(view.constructor); }; - - while (view) { - if (isOfType(view)) { return view; } - view = get(view, 'parentView'); - } - }, - - /** - Return the nearest ancestor that has a given property. - - @property nearestWithProperty - @param {String} property A property name - @return Ember.View - */ - nearestWithProperty: function(property) { - var view = get(this, 'parentView'); - - while (view) { - if (property in view) { return view; } - view = get(view, 'parentView'); - } - }, - - /** - Return the nearest ancestor whose parent is an instance of - `klass`. - - @property nearestChildOf - @param {Class} klass Subclass of Ember.View (or Ember.View itself) - @return Ember.View - */ - nearestChildOf: function(klass) { - var view = get(this, 'parentView'); - - while (view) { - if (get(view, 'parentView') instanceof klass) { return view; } - view = get(view, 'parentView'); - } - }, - - /** - @private - - When the parent view changes, recursively invalidate `controller` - - @method _parentViewDidChange - */ - _parentViewDidChange: Ember.observer('_parentView', function() { - if (this.isDestroying) { return; } - - this.trigger('parentViewDidChange'); - - if (get(this, 'parentView.controller') && !get(this, 'controller')) { - this.notifyPropertyChange('controller'); - } - }), - - _controllerDidChange: Ember.observer('controller', function() { - if (this.isDestroying) { return; } - - this.rerender(); - - this.forEachChildView(function(view) { - view.propertyDidChange('controller'); - }); - }), - - cloneKeywords: function() { - var templateData = get(this, 'templateData'); - - var keywords = templateData ? Ember.copy(templateData.keywords) : {}; - set(keywords, 'view', get(this, 'concreteView')); - set(keywords, '_view', this); - set(keywords, 'controller', get(this, 'controller')); - - return keywords; - }, - - /** - Called on your view when it should push strings of HTML into a - `Ember.RenderBuffer`. Most users will want to override the `template` - or `templateName` properties instead of this method. - - By default, `Ember.View` will look for a function in the `template` - property and invoke it with the value of `context`. The value of - `context` will be the view's controller unless you override it. - - @method render - @param {Ember.RenderBuffer} buffer The render buffer - */ - render: function(buffer) { - // If this view has a layout, it is the responsibility of the - // the layout to render the view's template. Otherwise, render the template - // directly. - var template = get(this, 'layout') || get(this, 'template'); - - if (template) { - var context = get(this, 'context'); - var keywords = this.cloneKeywords(); - var output; - - var data = { - view: this, - buffer: buffer, - isRenderData: true, - keywords: keywords, - insideGroup: get(this, 'templateData.insideGroup') + var escape = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" }; - // Invoke the template with the provided template context, which - // is the view's controller by default. A hash of data is also passed that provides - // the template with access to the view and render buffer. + var escapeChar = function(chr) { + return escape[chr] || "&"; + }; - Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function'); - // The template should write directly to the render buffer instead - // of returning a string. - output = template(context, { data: data }); + var string = value.toString(); - // If the template returned a string instead of writing to the buffer, - // push the string onto the buffer. - if (output !== undefined) { buffer.push(output); } + if(!POSSIBLE_CHARS_REGEXP.test(string)) { return string; } + return string.replace(BAD_CHARS_REGEXP, escapeChar); } - }, - /** - Renders the view again. This will work regardless of whether the - view is already in the DOM or not. If the view is in the DOM, the - rendering process will be deferred to give bindings a chance - to synchronize. + // IE 6/7 have bugs around setting names on inputs during creation. + // From http://msdn.microsoft.com/en-us/library/ie/ms536389(v=vs.85).aspx: + // "To include the NAME attribute at run time on objects created with the createElement method, use the eTag." + var canSetNameOnInputs = (function() { + var div = document.createElement('div'), + el = document.createElement('input'); - If children were added during the rendering process using `appendChild`, - `rerender` will remove them, because they will be added again - if needed by the next `render`. + el.setAttribute('name', 'foo'); + div.appendChild(el); - In general, if the display of your view changes, you should modify - the DOM element directly instead of manually calling `rerender`, which can - be slow. + return !!div.innerHTML.match('foo'); + })(); - @method rerender - */ - rerender: function() { - return this.currentState.rerender(this); - }, + /** + `Ember.RenderBuffer` gathers information regarding the a view and generates the + final representation. `Ember.RenderBuffer` will generate HTML which can be pushed + to the DOM. - clearRenderedChildren: function() { - var lengthBefore = this.lengthBeforeRender, - lengthAfter = this.lengthAfterRender; + ```javascript + var buffer = Ember.RenderBuffer('div'); + ``` - // If there were child views created during the last call to render(), - // remove them under the assumption that they will be re-created when - // we re-render. + @class RenderBuffer + @namespace Ember + @constructor + @param {String} tagName tag name (such as 'div' or 'p') used for the buffer + */ + var RenderBuffer = function(tagName) { + return new _RenderBuffer(tagName); + }; - // VIEW-TODO: Unit test this path. - var childViews = this._childViews; - for (var i=lengthAfter-1; i>=lengthBefore; i--) { - if (childViews[i]) { childViews[i].destroy(); } - } - }, + var _RenderBuffer = function(tagName) { + this.tagNames = [tagName || null]; + this.buffer = ""; + }; - /** - @private + _RenderBuffer.prototype = { - Iterates over the view's `classNameBindings` array, inserts the value - of the specified property into the `classNames` array, then creates an - observer to update the view's element if the bound property ever changes - in the future. + // The root view's element + _element: null, - @method _applyClassNameBindings - */ - _applyClassNameBindings: function(classBindings) { - var classNames = this.classNames, - elem, newClass, dasherizedClass; + _hasElement: true, - // Loop through all of the configured bindings. These will be either - // property names ('isUrgent') or property paths relative to the view - // ('content.isUrgent') - a_forEach(classBindings, function(binding) { + /** + An internal set used to de-dupe class names when `addClass()` is + used. After each call to `addClass()`, the `classes` property + will be updated. - // Variable in which the old class value is saved. The observer function - // closes over this variable, so it knows which string to remove when - // the property changes. - var oldClass; - // Extract just the property name from bindings like 'foo:bar' - var parsedPath = Ember.View._parsePropertyPath(binding); + @private + @property elementClasses + @type Array + @default [] + */ + elementClasses: null, - // Set up an observer on the context. If the property changes, toggle the - // class name. - var observer = function() { - // Get the current value of the property - newClass = this._classStringForProperty(binding); - elem = this.$(); + /** + Array of class names which will be applied in the class attribute. - // If we had previously added a class to the element, remove it. - if (oldClass) { - elem.removeClass(oldClass); - // Also remove from classNames so that if the view gets rerendered, - // the class doesn't get added back to the DOM. - classNames.removeObject(oldClass); + You can use `setClasses()` to set this property directly. If you + use `addClass()`, it will be maintained for you. + + @property classes + @type Array + @default [] + */ + classes: null, + + /** + The id in of the element, to be applied in the id attribute. + + You should not set this property yourself, rather, you should use + the `id()` method of `Ember.RenderBuffer`. + + @property elementId + @type String + @default null + */ + elementId: null, + + /** + A hash keyed on the name of the attribute and whose value will be + applied to that attribute. For example, if you wanted to apply a + `data-view="Foo.bar"` property to an element, you would set the + elementAttributes hash to `{'data-view':'Foo.bar'}`. + + You should not maintain this hash yourself, rather, you should use + the `attr()` method of `Ember.RenderBuffer`. + + @property elementAttributes + @type Hash + @default {} + */ + elementAttributes: null, + + /** + A hash keyed on the name of the properties and whose value will be + applied to that property. For example, if you wanted to apply a + `checked=true` property to an element, you would set the + elementProperties hash to `{'checked':true}`. + + You should not maintain this hash yourself, rather, you should use + the `prop()` method of `Ember.RenderBuffer`. + + @property elementProperties + @type Hash + @default {} + */ + elementProperties: null, + + /** + The tagname of the element an instance of `Ember.RenderBuffer` represents. + + Usually, this gets set as the first parameter to `Ember.RenderBuffer`. For + example, if you wanted to create a `p` tag, then you would call + + ```javascript + Ember.RenderBuffer('p') + ``` + + @property elementTag + @type String + @default null + */ + elementTag: null, + + /** + A hash keyed on the name of the style attribute and whose value will + be applied to that attribute. For example, if you wanted to apply a + `background-color:black;` style to an element, you would set the + elementStyle hash to `{'background-color':'black'}`. + + You should not maintain this hash yourself, rather, you should use + the `style()` method of `Ember.RenderBuffer`. + + @property elementStyle + @type Hash + @default {} + */ + elementStyle: null, + + /** + Nested `RenderBuffers` will set this to their parent `RenderBuffer` + instance. + + @property parentBuffer + @type Ember._RenderBuffer + */ + parentBuffer: null, + + /** + Adds a string of HTML to the `RenderBuffer`. + + @method push + @param {String} string HTML to push into the buffer + @chainable + */ + push: function(string) { + this.buffer += string; + return this; + }, + + /** + Adds a class to the buffer, which will be rendered to the class attribute. + + @method addClass + @param {String} className Class name to add to the buffer + @chainable + */ + addClass: function(className) { + // lazily create elementClasses + this.elementClasses = (this.elementClasses || new ClassSet()); + this.elementClasses.add(className); + this.classes = this.elementClasses.list; + + return this; + }, + + setClasses: function(classNames) { + this.elementClasses = null; + var len = classNames.length, i; + for (i = 0; i < len; i++) { + this.addClass(classNames[i]); } + }, - // If necessary, add a new class. Make sure we keep track of it so - // it can be removed in the future. - if (newClass) { - elem.addClass(newClass); - oldClass = newClass; + /** + Sets the elementID to be used for the element. + + @method id + @param {String} id + @chainable + */ + id: function(id) { + this.elementId = id; + return this; + }, + + // duck type attribute functionality like jQuery so a render buffer + // can be used like a jQuery object in attribute binding scenarios. + + /** + Adds an attribute which will be rendered to the element. + + @method attr + @param {String} name The name of the attribute + @param {String} value The value to add to the attribute + @chainable + @return {Ember.RenderBuffer|String} this or the current attribute value + */ + attr: function(name, value) { + var attributes = this.elementAttributes = (this.elementAttributes || {}); + + if (arguments.length === 1) { + return attributes[name]; } else { - oldClass = null; + attributes[name] = value; } - }; - // Get the class name for the property at its current value - dasherizedClass = this._classStringForProperty(binding); + return this; + }, - if (dasherizedClass) { - // Ensure that it gets into the classNames array - // so it is displayed when we render. - a_addObject(classNames, dasherizedClass); + /** + Remove an attribute from the list of attributes to render. - // Save a reference to the class name so we can remove it - // if the observer fires. Remember that this variable has - // been closed over by the observer. - oldClass = dasherizedClass; + @method removeAttr + @param {String} name The name of the attribute + @chainable + */ + removeAttr: function(name) { + var attributes = this.elementAttributes; + if (attributes) { delete attributes[name]; } + + return this; + }, + + /** + Adds a property which will be rendered to the element. + + @method prop + @param {String} name The name of the property + @param {String} value The value to add to the property + @chainable + @return {Ember.RenderBuffer|String} this or the current property value + */ + prop: function(name, value) { + var properties = this.elementProperties = (this.elementProperties || {}); + + if (arguments.length === 1) { + return properties[name]; + } else { + properties[name] = value; + } + + return this; + }, + + /** + Remove an property from the list of properties to render. + + @method removeProp + @param {String} name The name of the property + @chainable + */ + removeProp: function(name) { + var properties = this.elementProperties; + if (properties) { delete properties[name]; } + + return this; + }, + + /** + Adds a style to the style attribute which will be rendered to the element. + + @method style + @param {String} name Name of the style + @param {String} value + @chainable + */ + style: function(name, value) { + this.elementStyle = (this.elementStyle || {}); + + this.elementStyle[name] = value; + return this; + }, + + begin: function(tagName) { + this.tagNames.push(tagName || null); + return this; + }, + + pushOpeningTag: function() { + var tagName = this.currentTagName(); + if (!tagName) { return; } + + if (this._hasElement && !this._element && this.buffer.length === 0) { + this._element = this.generateElement(); + return; + } + + var buffer = this.buffer, + id = this.elementId, + classes = this.classes, + attrs = this.elementAttributes, + props = this.elementProperties, + style = this.elementStyle, + attr, prop; + + buffer += '<' + stripTagName(tagName); + + if (id) { + buffer += ' id="' + escapeAttribute(id) + '"'; + this.elementId = null; + } + if (classes) { + buffer += ' class="' + escapeAttribute(classes.join(' ')) + '"'; + this.classes = null; + this.elementClasses = null; + } + + if (style) { + buffer += ' style="'; + + for (prop in style) { + if (style.hasOwnProperty(prop)) { + buffer += prop + ':' + escapeAttribute(style[prop]) + ';'; + } + } + + buffer += '"'; + + this.elementStyle = null; + } + + if (attrs) { + for (attr in attrs) { + if (attrs.hasOwnProperty(attr)) { + buffer += ' ' + attr + '="' + escapeAttribute(attrs[attr]) + '"'; + } + } + + this.elementAttributes = null; + } + + if (props) { + for (prop in props) { + if (props.hasOwnProperty(prop)) { + var value = props[prop]; + if (value || typeof(value) === 'number') { + if (value === true) { + buffer += ' ' + prop + '="' + prop + '"'; + } else { + buffer += ' ' + prop + '="' + escapeAttribute(props[prop]) + '"'; + } + } + } + } + + this.elementProperties = null; + } + + buffer += '>'; + this.buffer = buffer; + }, + + pushClosingTag: function() { + var tagName = this.tagNames.pop(); + if (tagName) { this.buffer += ''; } + }, + + currentTagName: function() { + return this.tagNames[this.tagNames.length-1]; + }, + + generateElement: function() { + var tagName = this.tagNames.pop(), // pop since we don't need to close + id = this.elementId, + classes = this.classes, + attrs = this.elementAttributes, + props = this.elementProperties, + style = this.elementStyle, + styleBuffer = '', attr, prop, tagString; + + if (attrs && attrs.name && !canSetNameOnInputs) { + // IE allows passing a tag to createElement. See note on `canSetNameOnInputs` above as well. + tagString = '<'+stripTagName(tagName)+' name="'+escapeAttribute(attrs.name)+'">'; + } else { + tagString = tagName; + } + + var element = document.createElement(tagString), + $element = jQuery(element); + + if (id) { + $element.attr('id', id); + this.elementId = null; + } + if (classes) { + $element.attr('class', classes.join(' ')); + this.classes = null; + this.elementClasses = null; + } + + if (style) { + for (prop in style) { + if (style.hasOwnProperty(prop)) { + styleBuffer += (prop + ':' + style[prop] + ';'); + } + } + + $element.attr('style', styleBuffer); + + this.elementStyle = null; + } + + if (attrs) { + for (attr in attrs) { + if (attrs.hasOwnProperty(attr)) { + $element.attr(attr, attrs[attr]); + } + } + + this.elementAttributes = null; + } + + if (props) { + for (prop in props) { + if (props.hasOwnProperty(prop)) { + $element.prop(prop, props[prop]); + } + } + + this.elementProperties = null; + } + + return element; + }, + + /** + @method element + @return {DOMElement} The element corresponding to the generated HTML + of this buffer + */ + element: function() { + var html = this.innerString(); + + if (html) { + this._element = setInnerHTML(this._element, html); + } + + return this._element; + }, + + /** + Generates the HTML content for this buffer. + + @method string + @return {String} The generated HTML + */ + string: function() { + if (this._hasElement && this._element) { + // Firefox versions < 11 do not have support for element.outerHTML. + var thisElement = this.element(), outerHTML = thisElement.outerHTML; + if (typeof outerHTML === 'undefined') { + return jQuery('
    ').append(thisElement).html(); + } + return outerHTML; + } else { + return this.innerString(); + } + }, + + innerString: function() { + return this.buffer; + } + }; + + __exports__["default"] = RenderBuffer; + }); +define("ember-views/system/utils", + ["ember-metal/core","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.assert + + /** + @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 = typeof document !== 'undefined' && (function() { + var testEl = document.createElement('div'); + testEl.innerHTML = "
    "; + testEl.firstChild.innerHTML = ""; + return testEl.firstChild.innerHTML === ''; + })(); + + // IE 8 (and likely earlier) likes to move whitespace preceeding + // a script tag to appear after it. This means that we can + // accidentally remove whitespace when updating a morph. + var movesWhitespace = typeof document !== 'undefined' && (function() { + var testEl = document.createElement('div'); + testEl.innerHTML = "Test: Value"; + return testEl.childNodes[0].nodeValue === 'Test:' && + testEl.childNodes[2].nodeValue === ' Value'; + })(); + + // Use this to find children by ID instead of using jQuery + var findChildById = function(element, id) { + if (element.getAttribute('id') === id) { return element; } + + var len = element.childNodes.length, idx, node, found; + for (idx=0; idx 0) { + var len = matches.length, idx; + for (idx=0; idxTest'); + canSet = el.options.length === 1; + } + + innerHTMLTags[tagName] = canSet; + + return canSet; + }; + + var setInnerHTML = function(element, html) { + var tagName = element.tagName; + + if (canSetInnerHTML(tagName)) { + setInnerHTMLWithoutFix(element, html); + } else { + // Firefox versions < 11 do not have support for element.outerHTML. + var outerHTML = element.outerHTML || new XMLSerializer().serializeToString(element); + Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", outerHTML); + + var startTag = outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0], + endTag = ''; + + var wrapper = document.createElement('div'); + setInnerHTMLWithoutFix(wrapper, startTag + html + endTag); + element = wrapper.firstChild; + while (element.tagName !== tagName) { + element = element.nextSibling; + } + } + + return element; + }; + + function isSimpleClick(event) { + var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey, + secondaryClick = event.which > 1; // IE9 may return undefined + + return !modifier && !secondaryClick; + } + + __exports__.setInnerHTML = setInnerHTML; + __exports__.isSimpleClick = isSimpleClick; + }); +define("ember-views/views/collection_view", + ["ember-metal/core","ember-metal/platform","ember-metal/merge","ember-metal/property_get","ember-metal/property_set","ember-runtime/system/string","ember-views/views/container_view","ember-views/views/view","ember-metal/mixin","ember-runtime/mixins/array","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { + "use strict"; + + /** + @module ember + @submodule ember-views + */ + + var Ember = __dependency1__["default"]; + // Ember.assert + var create = __dependency2__.create; + var merge = __dependency3__["default"]; + var get = __dependency4__.get; + var set = __dependency5__.set; + + var EmberStringUtils = __dependency6__["default"]; + var fmt = EmberStringUtils.fmt; + + var ContainerView = __dependency7__["default"]; + var CoreView = __dependency8__.CoreView; + var View = __dependency8__.View; + var observer = __dependency9__.observer; + var beforeObserver = __dependency9__.beforeObserver; + var EmberArray = __dependency10__["default"]; + + /** + `Ember.CollectionView` is an `Ember.View` descendent responsible for managing + a collection (an array or array-like object) by maintaining a child view object + and associated DOM representation for each item in the array and ensuring + that child views and their associated rendered HTML are updated when items in + the array are added, removed, or replaced. + + ## Setting content + + The managed collection of objects is referenced as the `Ember.CollectionView` + instance's `content` property. + + ```javascript + someItemsView = Ember.CollectionView.create({ + content: ['A', 'B','C'] + }) + ``` + + The view for each item in the collection will have its `content` property set + to the item. + + ## Specifying itemViewClass + + By default the view class for each item in the managed collection will be an + instance of `Ember.View`. You can supply a different class by setting the + `CollectionView`'s `itemViewClass` property. + + Given an empty `` and the following code: + + ```javascript + someItemsView = Ember.CollectionView.create({ + classNames: ['a-collection'], + content: ['A','B','C'], + itemViewClass: Ember.View.extend({ + template: Ember.Handlebars.compile("the letter: {{view.content}}") + }) }); - }, this); - }, - - /** - @private - - Iterates through the view's attribute bindings, sets up observers for each, - then applies the current value of the attributes to the passed render buffer. - - @method _applyAttributeBindings - @param {Ember.RenderBuffer} buffer - */ - _applyAttributeBindings: function(buffer, attributeBindings) { - var attributeValue, elem; - - a_forEach(attributeBindings, function(binding) { - var split = binding.split(':'), - property = split[0], - attributeName = split[1] || property; - - // Create an observer to add/remove/change the attribute if the - // JavaScript property changes. - var observer = function() { - elem = this.$(); - - attributeValue = get(this, property); - - Ember.View.applyAttributeBindings(elem, attributeName, attributeValue); - }; - - this.registerObserver(this, property, observer); - - // Determine the current value and add it to the render buffer - // if necessary. - attributeValue = get(this, property); - Ember.View.applyAttributeBindings(buffer, attributeName, attributeValue); - }, this); - }, - - /** - @private - - Given a property name, returns a dasherized version of that - property name if the property evaluates to a non-falsy value. - - For example, if the view has property `isUrgent` that evaluates to true, - passing `isUrgent` to this method will return `"is-urgent"`. - - @method _classStringForProperty - @param property - */ - _classStringForProperty: function(property) { - var parsedPath = Ember.View._parsePropertyPath(property); - var path = parsedPath.path; - - var val = get(this, path); - if (val === undefined && Ember.isGlobalPath(path)) { - val = get(Ember.lookup, path); - } - - return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName); - }, - - // .......................................................... - // ELEMENT SUPPORT - // - - /** - Returns the current DOM element for the view. - - @property element - @type DOMElement - */ - element: Ember.computed(function(key, value) { - if (value !== undefined) { - return this.currentState.setElement(this, value); - } else { - return this.currentState.getElement(this); - } - }).property('_parentView'), - - /** - Returns a jQuery object for this view's element. If you pass in a selector - string, this method will return a jQuery object, using the current element - as its buffer. - - For example, calling `view.$('li')` will return a jQuery object containing - all of the `li` elements inside the DOM element of this view. - - @method $ - @param {String} [selector] a jQuery-compatible selector string - @return {jQuery} the jQuery object for the DOM node - */ - $: function(sel) { - return this.currentState.$(this, sel); - }, - - mutateChildViews: function(callback) { - var childViews = this._childViews, - idx = childViews.length, - view; - - while(--idx >= 0) { - view = childViews[idx]; - callback(this, view, idx); - } - - return this; - }, - - forEachChildView: function(callback) { - var childViews = this._childViews; - - if (!childViews) { return this; } - - var len = childViews.length, - view, idx; - - for (idx = 0; idx < len; idx++) { - view = childViews[idx]; - callback(view); - } - - return this; - }, - - /** - Appends the view's element to the specified parent element. - - If the view does not have an HTML representation yet, `createElement()` - will be called automatically. - - Note that this method just schedules the view to be appended; the DOM - element will not be appended to the given element until all bindings have - finished synchronizing. - - This is not typically a function that you will need to call directly when - building your application. You might consider using `Ember.ContainerView` - instead. If you do need to use `appendTo`, be sure that the target element - you are providing is associated with an `Ember.Application` and does not - have an ancestor element that is associated with an Ember view. - - @method appendTo - @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object - @return {Ember.View} receiver - */ - appendTo: function(target) { - // Schedule the DOM element to be created and appended to the given - // element after bindings have synchronized. - this._insertElementLater(function() { - Ember.assert("You tried to append to (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0); - Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view')); - this.$().appendTo(target); - }); - - return this; - }, - - /** - Replaces the content of the specified parent element with this view's - element. If the view does not have an HTML representation yet, - `createElement()` will be called automatically. - - Note that this method just schedules the view to be appended; the DOM - element will not be appended to the given element until all bindings have - finished synchronizing - - @method replaceIn - @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object - @return {Ember.View} received - */ - replaceIn: function(target) { - Ember.assert("You tried to replace in (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0); - Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view')); - - this._insertElementLater(function() { - Ember.$(target).empty(); - this.$().appendTo(target); - }); - - return this; - }, - - /** - @private - - Schedules a DOM operation to occur during the next render phase. This - ensures that all bindings have finished synchronizing before the view is - rendered. - - To use, pass a function that performs a DOM operation. - - Before your function is called, this view and all child views will receive - the `willInsertElement` event. After your function is invoked, this view - and all of its child views will receive the `didInsertElement` event. - - ```javascript - view._insertElementLater(function() { - this.createElement(); - this.$().appendTo('body'); - }); - ``` - - @method _insertElementLater - @param {Function} fn the function that inserts the element into the DOM - */ - _insertElementLater: function(fn) { - this._scheduledInsert = Ember.run.scheduleOnce('render', this, '_insertElement', fn); - }, - - _insertElement: function (fn) { - this._scheduledInsert = null; - this.currentState.insertElement(this, fn); - }, - - /** - Appends the view's element to the document body. If the view does - not have an HTML representation yet, `createElement()` will be called - automatically. - - If your application uses the `rootElement` property, you must append - the view within that element. Rendering views outside of the `rootElement` - is not supported. - - Note that this method just schedules the view to be appended; the DOM - element will not be appended to the document body until all bindings have - finished synchronizing. - - @method append - @return {Ember.View} receiver - */ - append: function() { - return this.appendTo(document.body); - }, - - /** - Removes the view's element from the element to which it is attached. - - @method remove - @return {Ember.View} receiver - */ - remove: function() { - // What we should really do here is wait until the end of the run loop - // to determine if the element has been re-appended to a different - // element. - // In the interim, we will just re-render if that happens. It is more - // important than elements get garbage collected. - if (!this.removedFromDOM) { this.destroyElement(); } - this.invokeRecursively(function(view) { - if (view.clearRenderedChildren) { view.clearRenderedChildren(); } - }); - }, - - elementId: null, - - /** - Attempts to discover the element in the parent element. The default - implementation looks for an element with an ID of `elementId` (or the - view's guid if `elementId` is null). You can override this method to - provide your own form of lookup. For example, if you want to discover your - element using a CSS class name instead of an ID. - - @method findElementInParentElement - @param {DOMElement} parentElement The parent's DOM element - @return {DOMElement} The discovered element - */ - findElementInParentElement: function(parentElem) { - var id = "#" + this.elementId; - return Ember.$(id)[0] || Ember.$(id, parentElem)[0]; - }, - - /** - Creates a DOM representation of the view and all of its - child views by recursively calling the `render()` method. - - After the element has been created, `didInsertElement` will - be called on this view and all of its child views. - - @method createElement - @return {Ember.View} receiver - */ - createElement: function() { - if (get(this, 'element')) { return this; } - - var buffer = this.renderToBuffer(); - set(this, 'element', buffer.element()); - - return this; - }, - - /** - Called when a view is going to insert an element into the DOM. - - @event willInsertElement - */ - willInsertElement: Ember.K, - - /** - Called when the element of the view has been inserted into the DOM - or after the view was re-rendered. Override this function to do any - set up that requires an element in the document body. - - @event didInsertElement - */ - didInsertElement: Ember.K, - - /** - Called when the view is about to rerender, but before anything has - been torn down. This is a good opportunity to tear down any manual - observers you have installed based on the DOM state - - @event willClearRender - */ - willClearRender: Ember.K, - - /** - @private - - Run this callback on the current view (unless includeSelf is false) and recursively on child views. - - @method invokeRecursively - @param fn {Function} - @param includeSelf (optional, default true) - */ - invokeRecursively: function(fn, includeSelf) { - var childViews = (includeSelf === false) ? this._childViews : [this]; - var currentViews, view, currentChildViews; - - while (childViews.length) { - currentViews = childViews.slice(); - childViews = []; - - for (var i=0, l=currentViews.length; i +
    the letter: A
    +
    the letter: B
    +
    the letter: C
    +
    + ``` + + ## Automatic matching of parent/child tagNames + + Setting the `tagName` property of a `CollectionView` to any of + "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result + in the item views receiving an appropriately matched `tagName` property. + + Given an empty `` and the following code: + + ```javascript + anUnorderedListView = Ember.CollectionView.create({ + tagName: 'ul', + content: ['A','B','C'], + itemViewClass: Ember.View.extend({ + template: Ember.Handlebars.compile("the letter: {{view.content}}") + }) + }); + + anUnorderedListView.appendTo('body'); + ``` + + Will result in the following HTML structure + + ```html +
      +
    • the letter: A
    • +
    • the letter: B
    • +
    • the letter: C
    • +
    + ``` + + Additional `tagName` pairs can be provided by adding to + `Ember.CollectionView.CONTAINER_MAP ` + + ```javascript + Ember.CollectionView.CONTAINER_MAP['article'] = 'section' + ``` + + ## Programmatic creation of child views + + For cases where additional customization beyond the use of a single + `itemViewClass` or `tagName` matching is required CollectionView's + `createChildView` method can be overidden: + + ```javascript + CustomCollectionView = Ember.CollectionView.extend({ + createChildView: function(viewClass, attrs) { + if (attrs.content.kind == 'album') { + viewClass = App.AlbumView; + } else { + viewClass = App.SongView; + } + return this._super(viewClass, attrs); } - } - } - }, + }); + ``` - triggerRecursively: function(eventName) { - var childViews = [this], currentViews, view, currentChildViews; + ## Empty View - while (childViews.length) { - currentViews = childViews.slice(); - childViews = []; + You can provide an `Ember.View` subclass to the `Ember.CollectionView` + instance as its `emptyView` property. If the `content` property of a + `CollectionView` is set to `null` or an empty array, an instance of this view + will be the `CollectionView`s only child. - for (var i=0, l=currentViews.length; i +
    + The collection is empty +
    +
    + ``` + + ## Adding and Removing items + + The `childViews` property of a `CollectionView` should not be directly + manipulated. Instead, add, remove, replace items from its `content` property. + This will trigger appropriate changes to its rendered HTML. + + + @class CollectionView + @namespace Ember + @extends Ember.ContainerView + @since Ember 0.9 + */ + var CollectionView = ContainerView.extend({ + + /** + A list of items to be displayed by the `Ember.CollectionView`. + + @property content + @type Ember.Array + @default null + */ + content: null, + + /** + This provides metadata about what kind of empty view class this + collection would like if it is being instantiated from another + system (like Handlebars) + + @private + @property emptyViewClass + */ + emptyViewClass: View, + + /** + An optional view to display if content is set to an empty array. + + @property emptyView + @type Ember.View + @default null + */ + emptyView: null, + + /** + @property itemViewClass + @type Ember.View + @default Ember.View + */ + itemViewClass: View, + + /** + Setup a CollectionView + + @method init + */ + init: function() { + var ret = this._super(); + this._contentDidChange(); + return ret; + }, + + /** + Invoked when the content property is about to change. Notifies observers that the + entire array content will change. + + @private + @method _contentWillChange + */ + _contentWillChange: beforeObserver('content', function() { + var content = this.get('content'); + + if (content) { content.removeArrayObserver(this); } + var len = content ? get(content, 'length') : 0; + this.arrayWillChange(content, 0, len); + }), + + /** + Check to make sure that the content has changed, and if so, + update the children directly. This is always scheduled + asynchronously, to allow the element to be created before + bindings have synchronized and vice versa. + + @private + @method _contentDidChange + */ + _contentDidChange: observer('content', function() { + var content = get(this, 'content'); + + if (content) { + this._assertArrayLike(content); + content.addArrayObserver(this); } - } - } - }, - - viewHierarchyCollection: function() { - var currentView, viewCollection = new ViewCollection([this]); - - for (var i = 0; i < viewCollection.length; i++) { - currentView = viewCollection.objectAt(i); - if (currentView._childViews) { - viewCollection.push.apply(viewCollection, currentView._childViews); - } - } - - return viewCollection; - }, - - /** - Destroys any existing element along with the element for any child views - as well. If the view does not currently have a element, then this method - will do nothing. - - If you implement `willDestroyElement()` on your view, then this method will - be invoked on your view before your element is destroyed to give you a - chance to clean up any event handlers, etc. - - If you write a `willDestroyElement()` handler, you can assume that your - `didInsertElement()` handler was called earlier for the same element. - - You should not call or override this method yourself, but you may - want to implement the above callbacks. - - @method destroyElement - @return {Ember.View} receiver - */ - destroyElement: function() { - return this.currentState.destroyElement(this); - }, - - /** - Called when the element of the view is going to be destroyed. Override - this function to do any teardown that requires an element, like removing - event listeners. - - @event willDestroyElement - */ - willDestroyElement: Ember.K, - - /** - @private - - Triggers the `willDestroyElement` event (which invokes the - `willDestroyElement()` method if it exists) on this view and all child - views. - - Before triggering `willDestroyElement`, it first triggers the - `willClearRender` event recursively. - - @method _notifyWillDestroyElement - */ - _notifyWillDestroyElement: function() { - var viewCollection = this.viewHierarchyCollection(); - viewCollection.trigger('willClearRender'); - viewCollection.trigger('willDestroyElement'); - return viewCollection; - }, - - /** - @private - - If this view's element changes, we need to invalidate the caches of our - child views so that we do not retain references to DOM elements that are - no longer needed. - - @method _elementDidChange - */ - _elementDidChange: Ember.observer('element', function() { - this.forEachChildView(function(view) { - delete meta(view).cache.element; - }); - }), - - /** - Called when the parentView property has changed. - - @event parentViewDidChange - */ - parentViewDidChange: Ember.K, - - instrumentName: 'view', - - instrumentDetails: function(hash) { - hash.template = get(this, 'templateName'); - this._super(hash); - }, - - _renderToBuffer: function(parentBuffer, bufferOperation) { - this.lengthBeforeRender = this._childViews.length; - var buffer = this._super(parentBuffer, bufferOperation); - this.lengthAfterRender = this._childViews.length; - - return buffer; - }, - - renderToBufferIfNeeded: function (buffer) { - return this.currentState.renderToBufferIfNeeded(this, buffer); - }, - - beforeRender: function(buffer) { - this.applyAttributesToBuffer(buffer); - buffer.pushOpeningTag(); - }, - - afterRender: function(buffer) { - buffer.pushClosingTag(); - }, - - applyAttributesToBuffer: function(buffer) { - // Creates observers for all registered class name and attribute bindings, - // then adds them to the element. - var classNameBindings = get(this, 'classNameBindings'); - if (classNameBindings.length) { - this._applyClassNameBindings(classNameBindings); - } - - // Pass the render buffer so the method can apply attributes directly. - // This isn't needed for class name bindings because they use the - // existing classNames infrastructure. - var attributeBindings = get(this, 'attributeBindings'); - if (attributeBindings.length) { - this._applyAttributeBindings(buffer, attributeBindings); - } - - buffer.setClasses(this.classNames); - buffer.id(this.elementId); - - var role = get(this, 'ariaRole'); - if (role) { - buffer.attr('role', role); - } - - if (get(this, 'isVisible') === false) { - buffer.style('display', 'none'); - } - }, - - // .......................................................... - // STANDARD RENDER PROPERTIES - // - - /** - Tag name for the view's outer element. The tag name is only used when an - element is first created. If you change the `tagName` for an element, you - must destroy and recreate the view element. - - By default, the render buffer will use a `
    ` tag for views. - - @property tagName - @type String - @default null - */ - - // We leave this null by default so we can tell the difference between - // the default case and a user-specified tag. - tagName: null, - - /** - The WAI-ARIA role of the control represented by this view. For example, a - button may have a role of type 'button', or a pane may have a role of - type 'alertdialog'. This property is used by assistive software to help - visually challenged users navigate rich web applications. - - The full list of valid WAI-ARIA roles is available at: - [http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization) - - @property ariaRole - @type String - @default null - */ - ariaRole: null, - - /** - Standard CSS class names to apply to the view's outer element. This - property automatically inherits any class names defined by the view's - superclasses as well. - - @property classNames - @type Array - @default ['ember-view'] - */ - classNames: ['ember-view'], - - /** - A list of properties of the view to apply as class names. If the property - is a string value, the value of that string will be applied as a class - name. - - ```javascript - // Applies the 'high' class to the view element - Ember.View.extend({ - classNameBindings: ['priority'] - priority: 'high' - }); - ``` - - If the value of the property is a Boolean, the name of that property is - added as a dasherized class name. - - ```javascript - // Applies the 'is-urgent' class to the view element - Ember.View.extend({ - classNameBindings: ['isUrgent'] - isUrgent: true - }); - ``` - - If you would prefer to use a custom value instead of the dasherized - property name, you can pass a binding like this: - - ```javascript - // Applies the 'urgent' class to the view element - Ember.View.extend({ - classNameBindings: ['isUrgent:urgent'] - isUrgent: true - }); - ``` - - This list of properties is inherited from the view's superclasses as well. - - @property classNameBindings - @type Array - @default [] - */ - classNameBindings: EMPTY_ARRAY, - - /** - A list of properties of the view to apply as attributes. If the property is - a string value, the value of that string will be applied as the attribute. - - ```javascript - // Applies the type attribute to the element - // with the value "button", like
    - Ember.View.extend({ - attributeBindings: ['type'], - type: 'button' - }); - ``` - - If the value of the property is a Boolean, the name of that property is - added as an attribute. - - ```javascript - // Renders something like
    - Ember.View.extend({ - attributeBindings: ['enabled'], - enabled: true - }); - ``` - - @property attributeBindings - */ - attributeBindings: EMPTY_ARRAY, - - // ....................................................... - // CORE DISPLAY METHODS - // - - /** - @private - - Setup a view, but do not finish waking it up. - - configure `childViews` - - register the view with the global views hash, which is used for event - dispatch - - @method init - */ - init: function() { - this.elementId = this.elementId || guidFor(this); - - this._super(); - - // setup child views. be sure to clone the child views array first - this._childViews = this._childViews.slice(); - - Ember.assert("Only arrays are allowed for 'classNameBindings'", Ember.typeOf(this.classNameBindings) === 'array'); - this.classNameBindings = Ember.A(this.classNameBindings.slice()); - - Ember.assert("Only arrays are allowed for 'classNames'", Ember.typeOf(this.classNames) === 'array'); - this.classNames = Ember.A(this.classNames.slice()); - }, - - appendChild: function(view, options) { - return this.currentState.appendChild(this, view, options); - }, - - /** - Removes the child view from the parent view. - - @method removeChild - @param {Ember.View} view - @return {Ember.View} receiver - */ - removeChild: function(view) { - // If we're destroying, the entire subtree will be - // freed, and the DOM will be handled separately, - // so no need to mess with childViews. - if (this.isDestroying) { return; } - - // update parent node - set(view, '_parentView', null); - - // remove view from childViews array. - var childViews = this._childViews; - - Ember.EnumerableUtils.removeObject(childViews, view); - - this.propertyDidChange('childViews'); // HUH?! what happened to will change? - - return this; - }, - - /** - Removes all children from the `parentView`. - - @method removeAllChildren - @return {Ember.View} receiver - */ - removeAllChildren: function() { - return this.mutateChildViews(function(parentView, view) { - parentView.removeChild(view); - }); - }, - - destroyAllChildren: function() { - return this.mutateChildViews(function(parentView, view) { - view.destroy(); - }); - }, - - /** - Removes the view from its `parentView`, if one is found. Otherwise - does nothing. - - @method removeFromParent - @return {Ember.View} receiver - */ - removeFromParent: function() { - var parent = this._parentView; - - // Remove DOM element from parent - this.remove(); - - if (parent) { parent.removeChild(this); } - return this; - }, - - /** - You must call `destroy` on a view to destroy the view (and all of its - child views). This will remove the view from any parent node, then make - sure that the DOM element managed by the view can be released by the - memory manager. - - @method destroy - */ - destroy: function() { - var childViews = this._childViews, - // get parentView before calling super because it'll be destroyed - nonVirtualParentView = get(this, 'parentView'), - viewName = this.viewName, - childLen, i; - - if (!this._super()) { return; } - - childLen = childViews.length; - for (i=childLen-1; i>=0; i--) { - childViews[i].removedFromDOM = true; - } - - // remove from non-virtual parent view if viewName was specified - if (viewName && nonVirtualParentView) { - nonVirtualParentView.set(viewName, null); - } - - childLen = childViews.length; - for (i=childLen-1; i>=0; i--) { - childViews[i].destroy(); - } - - return this; - }, - - /** - Instantiates a view to be added to the childViews array during view - initialization. You generally will not call this method directly unless - you are overriding `createChildViews()`. Note that this method will - automatically configure the correct settings on the new view instance to - act as a child of the parent. - - @method createChildView - @param {Class|String} viewClass - @param {Hash} [attrs] Attributes to add - @return {Ember.View} new instance - */ - createChildView: function(view, attrs) { - if (!view) { - throw new TypeError("createChildViews first argument must exist"); - } - - if (view.isView && view._parentView === this && view.container === this.container) { - return view; - } - - attrs = attrs || {}; - attrs._parentView = this; - - if (Ember.CoreView.detect(view)) { - attrs.templateData = attrs.templateData || get(this, 'templateData'); - - attrs.container = this.container; - view = view.create(attrs); - - // don't set the property on a virtual view, as they are invisible to - // consumers of the view API - if (view.viewName) { - set(get(this, 'concreteView'), view.viewName, view); - } - } else if ('string' === typeof view) { - var fullName = 'view:' + view; - var View = this.container.lookupFactory(fullName); - - Ember.assert("Could not find view: '" + fullName + "'", !!View); - - attrs.templateData = get(this, 'templateData'); - view = View.create(attrs); - } else { - Ember.assert('You must pass instance or subclass of View', view.isView); - attrs.container = this.container; - - if (!get(view, 'templateData')) { - attrs.templateData = get(this, 'templateData'); - } - - Ember.setProperties(view, attrs); - - } - - return view; - }, - - becameVisible: Ember.K, - becameHidden: Ember.K, - - /** - @private - - When the view's `isVisible` property changes, toggle the visibility - element of the actual DOM element. - - @method _isVisibleDidChange - */ - _isVisibleDidChange: Ember.observer('isVisible', function() { - var $el = this.$(); - if (!$el) { return; } - - var isVisible = get(this, 'isVisible'); - - $el.toggle(isVisible); + var len = content ? get(content, 'length') : 0; + this.arrayDidChange(content, 0, null, len); + }), + + /** + Ensure that the content implements Ember.Array + + @private + @method _assertArrayLike + */ + _assertArrayLike: function(content) { + Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), EmberArray.detect(content)); + }, + + /** + Removes the content and content observers. + + @method destroy + */ + destroy: function() { + if (!this._super()) { return; } + + var content = get(this, 'content'); + if (content) { content.removeArrayObserver(this); } + + if (this._createdEmptyView) { + this._createdEmptyView.destroy(); + } + + return this; + }, + + /** + Called when a mutation to the underlying content array will occur. + + This method will remove any views that are no longer in the underlying + content array. + + Invokes whenever the content array itself will change. + + @method arrayWillChange + @param {Array} content the managed collection of objects + @param {Number} start the index at which the changes will occurr + @param {Number} removed number of object to be removed from content + */ + arrayWillChange: function(content, start, removedCount) { + // If the contents were empty before and this template collection has an + // empty view remove it now. + var emptyView = get(this, 'emptyView'); + if (emptyView && emptyView instanceof View) { + emptyView.removeFromParent(); + } + + // Loop through child views that correspond with the removed items. + // Note that we loop from the end of the array to the beginning because + // we are mutating it as we go. + var childViews = this._childViews, childView, idx, len; + + len = this._childViews.length; + + var removingAll = removedCount === len; + + if (removingAll) { + this.currentState.empty(this); + this.invokeRecursively(function(view) { + view.removedFromDOM = true; + }, false); + } + + for (idx = start + removedCount - 1; idx >= start; idx--) { + childView = childViews[idx]; + childView.destroy(); + } + }, + + /** + Called when a mutation to the underlying content array occurs. + + This method will replay that mutation against the views that compose the + `Ember.CollectionView`, ensuring that the view reflects the model. + + This array observer is added in `contentDidChange`. + + @method arrayDidChange + @param {Array} content the managed collection of objects + @param {Number} start the index at which the changes occurred + @param {Number} removed number of object removed from content + @param {Number} added number of object added to content + */ + arrayDidChange: function(content, start, removed, added) { + var addedViews = [], view, item, idx, len, itemViewClass, + emptyView; + + len = content ? get(content, 'length') : 0; + + if (len) { + itemViewClass = get(this, 'itemViewClass'); + + if ('string' === typeof itemViewClass) { + itemViewClass = get(itemViewClass) || itemViewClass; + } + + Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", + [itemViewClass]), + 'string' === typeof itemViewClass || View.detect(itemViewClass)); + + for (idx = start; idx < start+added; idx++) { + item = content.objectAt(idx); + + view = this.createChildView(itemViewClass, { + content: item, + contentIndex: idx + }); + + addedViews.push(view); + } + } else { + emptyView = get(this, 'emptyView'); + + if (!emptyView) { return; } + + if ('string' === typeof emptyView) { + emptyView = get(emptyView) || emptyView; + } + + emptyView = this.createChildView(emptyView); + addedViews.push(emptyView); + set(this, 'emptyView', emptyView); + + if (CoreView.detect(emptyView)) { + this._createdEmptyView = emptyView; + } + } + + this.replace(start, 0, addedViews); + }, + + /** + Instantiates a view to be added to the childViews array during view + initialization. You generally will not call this method directly unless + you are overriding `createChildViews()`. Note that this method will + automatically configure the correct settings on the new view instance to + act as a child of the parent. + + The tag name for the view will be set to the tagName of the viewClass + passed in. + + @method createChildView + @param {Class} viewClass + @param {Hash} [attrs] Attributes to add + @return {Ember.View} new instance + */ + createChildView: function(view, attrs) { + view = this._super(view, attrs); + + var itemTagName = get(view, 'tagName'); - if (this._isAncestorHidden()) { return; } - - if (isVisible) { - this._notifyBecameVisible(); - } else { - this._notifyBecameHidden(); - } - }), - - _notifyBecameVisible: function() { - this.trigger('becameVisible'); - - this.forEachChildView(function(view) { - var isVisible = get(view, 'isVisible'); - - if (isVisible || isVisible === null) { - view._notifyBecameVisible(); + if (itemTagName === null || itemTagName === undefined) { + itemTagName = CollectionView.CONTAINER_MAP[get(this, 'tagName')]; + set(view, 'tagName', itemTagName); + } + + return view; } }); - }, - _notifyBecameHidden: function() { - this.trigger('becameHidden'); - this.forEachChildView(function(view) { - var isVisible = get(view, 'isVisible'); + /** + A map of parent tags to their default child tags. You can add + additional parent tags if you want collection views that use + a particular parent tag to default to a child tag. - if (isVisible || isVisible === null) { - view._notifyBecameHidden(); + @property CONTAINER_MAP + @type Hash + @static + @final + */ + CollectionView.CONTAINER_MAP = { + ul: 'li', + ol: 'li', + table: 'tr', + thead: 'tr', + tbody: 'tr', + tfoot: 'tr', + tr: 'td', + select: 'option' + }; + + __exports__["default"] = CollectionView; + }); +define("ember-views/views/component", + ["ember-metal/core","ember-views/mixins/component_template_deprecation","ember-runtime/mixins/target_action_support","ember-views/views/view","ember-metal/property_get","ember-metal/property_set","ember-metal/is_none","ember-metal/computed","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.assert, Ember.Handlebars + + var ComponentTemplateDeprecation = __dependency2__["default"]; + var TargetActionSupport = __dependency3__["default"]; + var View = __dependency4__.View;var get = __dependency5__.get; + var set = __dependency6__.set; + var isNone = __dependency7__.isNone; + + var computed = __dependency8__.computed; + + var a_slice = Array.prototype.slice; + + /** + @module ember + @submodule ember-views + */ + + /** + An `Ember.Component` is a view that is completely + isolated. Property access in its templates go + to the view object and actions are targeted at + the view object. There is no access to the + surrounding context or outer controller; all + contextual information must be passed in. + + The easiest way to create an `Ember.Component` is via + a template. If you name a template + `components/my-foo`, you will be able to use + `{{my-foo}}` in other templates, which will make + an instance of the isolated component. + + ```handlebars + {{app-profile person=currentUser}} + ``` + + ```handlebars + +

    {{person.title}}

    + +

    {{person.signature}}

    + ``` + + You can use `yield` inside a template to + include the **contents** of any block attached to + the component. The block will be executed in the + context of the surrounding context or outer controller: + + ```handlebars + {{#app-profile person=currentUser}} +

    Admin mode

    + {{! Executed in the controller's context. }} + {{/app-profile}} + ``` + + ```handlebars + +

    {{person.title}}

    + {{! Executed in the components context. }} + {{yield}} {{! block contents }} + ``` + + If you want to customize the component, in order to + handle events or actions, you implement a subclass + of `Ember.Component` named after the name of the + component. Note that `Component` needs to be appended to the name of + your subclass like `AppProfileComponent`. + + For example, you could implement the action + `hello` for the `app-profile` component: + + ```javascript + App.AppProfileComponent = Ember.Component.extend({ + actions: { + hello: function(name) { + console.log("Hello", name); + } + } + }); + ``` + + And then use it in the component's template: + + ```handlebars + + +

    {{person.title}}

    + {{yield}} + + + ``` + + Components must have a `-` in their name to avoid + conflicts with built-in controls that wrap HTML + elements. This is consistent with the same + requirement in web components. + + @class Component + @namespace Ember + @extends Ember.View + */ + var Component = View.extend(TargetActionSupport, ComponentTemplateDeprecation, { + init: function() { + this._super(); + set(this, 'context', this); + set(this, 'controller', this); + }, + + defaultLayout: function(context, options){ + Ember.Handlebars.helpers['yield'].call(context, options); + }, + + /** + A components template property is set by passing a block + during its invocation. It is executed within the parent context. + + Example: + + ```handlebars + {{#my-component}} + // something that is run in the context + // of the parent context + {{/my-component}} + ``` + + Specifying a template directly to a component is deprecated without + also specifying the layout property. + + @deprecated + @property template + */ + template: computed(function(key, value) { + if (value !== undefined) { return value; } + + var templateName = get(this, 'templateName'), + template = this.templateForName(templateName, 'template'); + + Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template); + + return template || get(this, 'defaultTemplate'); + }).property('templateName'), + + /** + Specifying a components `templateName` is deprecated without also + providing the `layout` or `layoutName` properties. + + @deprecated + @property templateName + */ + templateName: null, + + // during render, isolate keywords + cloneKeywords: function() { + return { + view: this, + controller: this + }; + }, + + _yield: function(context, options) { + var view = options.data.view, + parentView = this._parentView, + template = get(this, 'template'); + + if (template) { + Ember.assert("A Component must have a parent view in order to yield.", parentView); + + view.appendChild(View, { + isVirtual: true, + tagName: '', + _contextView: parentView, + template: template, + context: get(parentView, 'context'), + controller: get(parentView, 'controller'), + templateData: { keywords: parentView.cloneKeywords() } + }); + } + }, + + /** + If the component is currently inserted into the DOM of a parent view, this + property will point to the controller of the parent view. + + @property targetObject + @type Ember.Controller + @default null + */ + targetObject: computed(function(key) { + var parentView = get(this, '_parentView'); + return parentView ? get(parentView, 'controller') : null; + }).property('_parentView'), + + /** + Triggers a named action on the controller context where the component is used if + this controller has registered for notifications of the action. + + For example a component for playing or pausing music may translate click events + into action notifications of "play" or "stop" depending on some internal state + of the component: + + + ```javascript + App.PlayButtonComponent = Ember.Component.extend({ + click: function(){ + if (this.get('isPlaying')) { + this.sendAction('play'); + } else { + this.sendAction('stop'); + } + } + }); + ``` + + When used inside a template these component actions are configured to + trigger actions in the outer application context: + + ```handlebars + {{! application.hbs }} + {{play-button play="musicStarted" stop="musicStopped"}} + ``` + + When the component receives a browser `click` event it translate this + interaction into application-specific semantics ("play" or "stop") and + triggers the specified action name on the controller for the template + where the component is used: + + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + actions: { + musicStarted: function(){ + // called when the play button is clicked + // and the music started playing + }, + musicStopped: function(){ + // called when the play button is clicked + // and the music stopped playing + } + } + }); + ``` + + If no action name is passed to `sendAction` a default name of "action" + is assumed. + + ```javascript + App.NextButtonComponent = Ember.Component.extend({ + click: function(){ + this.sendAction(); + } + }); + ``` + + ```handlebars + {{! application.hbs }} + {{next-button action="playNextSongInAlbum"}} + ``` + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + actions: { + playNextSongInAlbum: function(){ + ... + } + } + }); + ``` + + @method sendAction + @param [action] {String} the action to trigger + @param [context] {*} a context to send with the action + */ + sendAction: function(action) { + var actionName, + contexts = a_slice.call(arguments, 1); + + // Send the default action + if (action === undefined) { + actionName = get(this, 'action'); + Ember.assert("The default action was triggered on the component " + this.toString() + + ", but the action name (" + actionName + ") was not a string.", + isNone(actionName) || typeof actionName === 'string'); + } else { + actionName = get(this, action); + Ember.assert("The " + action + " action was triggered on the component " + + this.toString() + ", but the action name (" + actionName + + ") was not a string.", + isNone(actionName) || typeof actionName === 'string'); + } + + // If no action name for that action could be found, just abort. + if (actionName === undefined) { return; } + + this.triggerAction({ + action: actionName, + actionContext: contexts + }); } }); - }, - _isAncestorHidden: function() { - var parent = get(this, 'parentView'); + __exports__["default"] = Component; + }); +define("ember-views/views/container_view", + ["ember-metal/core","ember-metal/merge","ember-runtime/mixins/mutable_array","ember-metal/property_get","ember-metal/property_set","ember-views/views/view","ember-views/views/states","ember-metal/error","ember-metal/enumerable_utils","ember-metal/computed","ember-metal/run_loop","ember-metal/properties","ember-views/system/render_buffer","ember-metal/mixin","ember-runtime/system/native_array","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.assert, Ember.K - while (parent) { - if (get(parent, 'isVisible') === false) { return true; } + var merge = __dependency2__["default"]; + var MutableArray = __dependency3__["default"]; + var get = __dependency4__.get; + var set = __dependency5__.set; - parent = get(parent, 'parentView'); + var View = __dependency6__.View; + var ViewCollection = __dependency6__.ViewCollection; + var cloneStates = __dependency7__.cloneStates; + var EmberViewStates = __dependency7__.states; + + var EmberError = __dependency8__["default"]; + + // ES6TODO: functions on EnumerableUtils should get their own export + var EnumerableUtils = __dependency9__["default"]; + var forEach = EnumerableUtils.forEach; + + var computed = __dependency10__.computed; + var run = __dependency11__["default"]; + var defineProperty = __dependency12__.defineProperty; + var RenderBuffer = __dependency13__["default"]; + var observer = __dependency14__.observer; + var beforeObserver = __dependency14__.beforeObserver; + var A = __dependency15__.A; + + /** + @module ember + @submodule ember-views + */ + + var states = cloneStates(EmberViewStates); + + /** + A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray` + allowing programmatic management of its child views. + + ## Setting Initial Child Views + + The initial array of child views can be set in one of two ways. You can + provide a `childViews` property at creation time that contains instance of + `Ember.View`: + + ```javascript + aContainer = Ember.ContainerView.create({ + childViews: [Ember.View.create(), Ember.View.create()] + }); + ``` + + You can also provide a list of property names whose values are instances of + `Ember.View`: + + ```javascript + aContainer = Ember.ContainerView.create({ + childViews: ['aView', 'bView', 'cView'], + aView: Ember.View.create(), + bView: Ember.View.create(), + cView: Ember.View.create() + }); + ``` + + The two strategies can be combined: + + ```javascript + aContainer = Ember.ContainerView.create({ + childViews: ['aView', Ember.View.create()], + aView: Ember.View.create() + }); + ``` + + Each child view's rendering will be inserted into the container's rendered + HTML in the same order as its position in the `childViews` property. + + ## Adding and Removing Child Views + + The container view implements `Ember.MutableArray` allowing programmatic management of its child views. + + To remove a view, pass that view into a `removeObject` call on the container view. + + Given an empty `` the following code + + ```javascript + aContainer = Ember.ContainerView.create({ + classNames: ['the-container'], + childViews: ['aView', 'bView'], + aView: Ember.View.create({ + template: Ember.Handlebars.compile("A") + }), + bView: Ember.View.create({ + template: Ember.Handlebars.compile("B") + }) + }); + + aContainer.appendTo('body'); + ``` + + Results in the HTML + + ```html +
    +
    A
    +
    B
    +
    + ``` + + Removing a view + + ```javascript + aContainer.toArray(); // [aContainer.aView, aContainer.bView] + aContainer.removeObject(aContainer.get('bView')); + aContainer.toArray(); // [aContainer.aView] + ``` + + Will result in the following HTML + + ```html +
    +
    A
    +
    + ``` + + Similarly, adding a child view is accomplished by adding `Ember.View` instances to the + container view. + + Given an empty `` the following code + + ```javascript + aContainer = Ember.ContainerView.create({ + classNames: ['the-container'], + childViews: ['aView', 'bView'], + aView: Ember.View.create({ + template: Ember.Handlebars.compile("A") + }), + bView: Ember.View.create({ + template: Ember.Handlebars.compile("B") + }) + }); + + aContainer.appendTo('body'); + ``` + + Results in the HTML + + ```html +
    +
    A
    +
    B
    +
    + ``` + + Adding a view + + ```javascript + AnotherViewClass = Ember.View.extend({ + template: Ember.Handlebars.compile("Another view") + }); + + aContainer.toArray(); // [aContainer.aView, aContainer.bView] + aContainer.pushObject(AnotherViewClass.create()); + aContainer.toArray(); // [aContainer.aView, aContainer.bView, ] + ``` + + Will result in the following HTML + + ```html +
    +
    A
    +
    B
    +
    Another view
    +
    + ``` + + ## Templates and Layout + + A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or + `defaultLayout` property on a container view will not result in the template + or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM + representation will only be the rendered HTML of its child views. + + @class ContainerView + @namespace Ember + @extends Ember.View + */ + var ContainerView = View.extend(MutableArray, { + states: states, + + init: function() { + this._super(); + + var childViews = get(this, 'childViews'); + + // redefine view's childViews property that was obliterated + defineProperty(this, 'childViews', View.childViewsProperty); + + var _childViews = this._childViews; + + forEach(childViews, function(viewName, idx) { + var view; + + if ('string' === typeof viewName) { + view = get(this, viewName); + view = this.createChildView(view); + set(this, viewName, view); + } else { + view = this.createChildView(viewName); + } + + _childViews[idx] = view; + }, this); + + var currentView = get(this, 'currentView'); + if (currentView) { + if (!_childViews.length) { _childViews = this._childViews = this._childViews.slice(); } + _childViews.push(this.createChildView(currentView)); + } + }, + + replace: function(idx, removedCount, addedViews) { + var addedCount = addedViews ? get(addedViews, 'length') : 0; + var self = this; + Ember.assert("You can't add a child to a container that is already a child of another view", A(addedViews).every(function(item) { return !get(item, '_parentView') || get(item, '_parentView') === self; })); + + this.arrayContentWillChange(idx, removedCount, addedCount); + this.childViewsWillChange(this._childViews, idx, removedCount); + + if (addedCount === 0) { + this._childViews.splice(idx, removedCount) ; + } else { + var args = [idx, removedCount].concat(addedViews); + if (addedViews.length && !this._childViews.length) { this._childViews = this._childViews.slice(); } + this._childViews.splice.apply(this._childViews, args); + } + + this.arrayContentDidChange(idx, removedCount, addedCount); + this.childViewsDidChange(this._childViews, idx, removedCount, addedCount); + + return this; + }, + + objectAt: function(idx) { + return this._childViews[idx]; + }, + + length: computed(function () { + return this._childViews.length; + }).volatile(), + + /** + Instructs each child view to render to the passed render buffer. + + @private + @method render + @param {Ember.RenderBuffer} buffer the buffer to render to + */ + render: function(buffer) { + this.forEachChildView(function(view) { + view.renderToBuffer(buffer); + }); + }, + + instrumentName: 'container', + + /** + When a child view is removed, destroy its element so that + it is removed from the DOM. + + The array observer that triggers this action is set up in the + `renderToBuffer` method. + + @private + @method childViewsWillChange + @param {Ember.Array} views the child views array before mutation + @param {Number} start the start position of the mutation + @param {Number} removed the number of child views removed + **/ + childViewsWillChange: function(views, start, removed) { + this.propertyWillChange('childViews'); + + if (removed > 0) { + var changedViews = views.slice(start, start+removed); + // transition to preRender before clearing parentView + this.currentState.childViewsWillChange(this, views, start, removed); + this.initializeViews(changedViews, null, null); + } + }, + + removeChild: function(child) { + this.removeObject(child); + return this; + }, + + /** + When a child view is added, make sure the DOM gets updated appropriately. + + If the view has already rendered an element, we tell the child view to + create an element and insert it into the DOM. If the enclosing container + view has already written to a buffer, but not yet converted that buffer + into an element, we insert the string representation of the child into the + appropriate place in the buffer. + + @private + @method childViewsDidChange + @param {Ember.Array} views the array of child views after the mutation has occurred + @param {Number} start the start position of the mutation + @param {Number} removed the number of child views removed + @param {Number} the number of child views added + */ + childViewsDidChange: function(views, start, removed, added) { + if (added > 0) { + var changedViews = views.slice(start, start+added); + this.initializeViews(changedViews, this, get(this, 'templateData')); + this.currentState.childViewsDidChange(this, views, start, added); + } + this.propertyDidChange('childViews'); + }, + + initializeViews: function(views, parentView, templateData) { + forEach(views, function(view) { + set(view, '_parentView', parentView); + + if (!view.container && parentView) { + set(view, 'container', parentView.container); + } + + if (!get(view, 'templateData')) { + set(view, 'templateData', templateData); + } + }); + }, + + currentView: null, + + _currentViewWillChange: beforeObserver('currentView', function() { + var currentView = get(this, 'currentView'); + if (currentView) { + currentView.destroy(); + } + }), + + _currentViewDidChange: observer('currentView', function() { + var currentView = get(this, 'currentView'); + if (currentView) { + Ember.assert("You tried to set a current view that already has a parent. Make sure you don't have multiple outlets in the same view.", !get(currentView, '_parentView')); + this.pushObject(currentView); + } + }), + + _ensureChildrenAreInDOM: function () { + this.currentState.ensureChildrenAreInDOM(this); + } + }); + + merge(states._default, { + childViewsWillChange: Ember.K, + childViewsDidChange: Ember.K, + ensureChildrenAreInDOM: Ember.K + }); + + merge(states.inBuffer, { + childViewsDidChange: function(parentView, views, start, added) { + throw new EmberError('You cannot modify child views while in the inBuffer state'); + } + }); + + merge(states.hasElement, { + childViewsWillChange: function(view, views, start, removed) { + for (var i=start; i + ``` + + ## HTML `class` Attribute + + The HTML `class` attribute of a view's tag can be set by providing a + `classNames` property that is set to an array of strings: + + ```javascript + MyView = Ember.View.extend({ + classNames: ['my-class', 'my-other-class'] + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html +
    + ``` + + `class` attribute values can also be set by providing a `classNameBindings` + property set to an array of properties names for the view. The return value + of these properties will be added as part of the value for the view's `class` + attribute. These properties can be computed properties: + + ```javascript + MyView = Ember.View.extend({ + classNameBindings: ['propertyA', 'propertyB'], + propertyA: 'from-a', + propertyB: function() { + if (someLogic) { return 'from-b'; } + }.property() + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html +
    + ``` + + If the value of a class name binding returns a boolean the property name + itself will be used as the class name if the property is true. The class name + will not be added if the value is `false` or `undefined`. + + ```javascript + MyView = Ember.View.extend({ + classNameBindings: ['hovered'], + hovered: true + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html +
    + ``` + + When using boolean class name bindings you can supply a string value other + than the property name for use as the `class` HTML attribute by appending the + preferred value after a ":" character when defining the binding: + + ```javascript + MyView = Ember.View.extend({ + classNameBindings: ['awesome:so-very-cool'], + awesome: true + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html +
    + ``` + + Boolean value class name bindings whose property names are in a + camelCase-style format will be converted to a dasherized format: + + ```javascript + MyView = Ember.View.extend({ + classNameBindings: ['isUrgent'], + isUrgent: true + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html +
    + ``` + + Class name bindings can also refer to object values that are found by + traversing a path relative to the view itself: + + ```javascript + MyView = Ember.View.extend({ + classNameBindings: ['messages.empty'] + messages: Ember.Object.create({ + empty: true + }) + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html +
    + ``` + + If you want to add a class name for a property which evaluates to true and + and a different class name if it evaluates to false, you can pass a binding + like this: + + ```javascript + // Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false + Ember.View.extend({ + classNameBindings: ['isEnabled:enabled:disabled'] + isEnabled: true + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html +
    + ``` + + When isEnabled is `false`, the resulting HTML reprensentation looks like + this: + + ```html +
    + ``` + + This syntax offers the convenience to add a class if a property is `false`: + + ```javascript + // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false + Ember.View.extend({ + classNameBindings: ['isEnabled::disabled'] + isEnabled: true + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html +
    + ``` + + When the `isEnabled` property on the view is set to `false`, it will result + in view instances with an HTML representation of: + + ```html +
    + ``` + + Updates to the the value of a class name binding will result in automatic + update of the HTML `class` attribute in the view's rendered HTML + representation. If the value becomes `false` or `undefined` the class name + will be removed. + + Both `classNames` and `classNameBindings` are concatenated properties. See + [Ember.Object](/api/classes/Ember.Object.html) documentation for more + information about concatenated properties. + + ## HTML Attributes + + The HTML attribute section of a view's tag can be set by providing an + `attributeBindings` property set to an array of property names on the view. + The return value of these properties will be used as the value of the view's + HTML associated attribute: + + ```javascript + AnchorView = Ember.View.extend({ + tagName: 'a', + attributeBindings: ['href'], + href: 'http://google.com' + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html + + ``` + + If the return value of an `attributeBindings` monitored property is a boolean + the property will follow HTML's pattern of repeating the attribute's name as + its value: + + ```javascript + MyTextInput = Ember.View.extend({ + tagName: 'input', + attributeBindings: ['disabled'], + disabled: true + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html + + ``` + + `attributeBindings` can refer to computed properties: + + ```javascript + MyTextInput = Ember.View.extend({ + tagName: 'input', + attributeBindings: ['disabled'], + disabled: function() { + if (someLogic) { + return true; + } else { + return false; + } + }.property() + }); + ``` + + Updates to the the property of an attribute binding will result in automatic + update of the HTML attribute in the view's rendered HTML representation. + + `attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html) + documentation for more information about concatenated properties. + + ## Templates + + The HTML contents of a view's rendered representation are determined by its + template. Templates can be any function that accepts an optional context + parameter and returns a string of HTML that will be inserted within the + view's tag. Most typically in Ember this function will be a compiled + `Ember.Handlebars` template. + + ```javascript + AView = Ember.View.extend({ + template: Ember.Handlebars.compile('I am the template') + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html +
    I am the template
    + ``` + + Within an Ember application is more common to define a Handlebars templates as + part of a page: + + ```html + + ``` + + And associate it by name using a view's `templateName` property: + + ```javascript + AView = Ember.View.extend({ + templateName: 'some-template' + }); + ``` + + If you have nested resources, your Handlebars template will look like this: + + ```html + + ``` + + And `templateName` property: + + ```javascript + AView = Ember.View.extend({ + templateName: 'posts/new' + }); + ``` + + Using a value for `templateName` that does not have a Handlebars template + with a matching `data-template-name` attribute will throw an error. + + For views classes that may have a template later defined (e.g. as the block + portion of a `{{view}}` Handlebars helper call in another template or in + a subclass), you can provide a `defaultTemplate` property set to compiled + template function. If a template is not later provided for the view instance + the `defaultTemplate` value will be used: + + ```javascript + AView = Ember.View.extend({ + defaultTemplate: Ember.Handlebars.compile('I was the default'), + template: null, + templateName: null + }); + ``` + + Will result in instances with an HTML representation of: + + ```html +
    I was the default
    + ``` + + If a `template` or `templateName` is provided it will take precedence over + `defaultTemplate`: + + ```javascript + AView = Ember.View.extend({ + defaultTemplate: Ember.Handlebars.compile('I was the default') + }); + + aView = AView.create({ + template: Ember.Handlebars.compile('I was the template, not default') + }); + ``` + + Will result in the following HTML representation when rendered: + + ```html +
    I was the template, not default
    + ``` + + ## View Context + + The default context of the compiled template is the view's controller: + + ```javascript + AView = Ember.View.extend({ + template: Ember.Handlebars.compile('Hello {{excitedGreeting}}') + }); + + aController = Ember.Object.create({ + firstName: 'Barry', + excitedGreeting: function() { + return this.get("content.firstName") + "!!!" + }.property() + }); + + aView = AView.create({ + controller: aController, + }); + ``` + + Will result in an HTML representation of: + + ```html +
    Hello Barry!!!
    + ``` + + A context can also be explicitly supplied through the view's `context` + property. If the view has neither `context` nor `controller` properties, the + `parentView`'s context will be used. + + ## Layouts + + Views can have a secondary template that wraps their main template. Like + primary templates, layouts can be any function that accepts an optional + context parameter and returns a string of HTML that will be inserted inside + view's tag. Views whose HTML element is self closing (e.g. ``) + cannot have a layout and this property will be ignored. + + Most typically in Ember a layout will be a compiled `Ember.Handlebars` + template. + + A view's layout can be set directly with the `layout` property or reference + an existing Handlebars template by name with the `layoutName` property. + + A template used as a layout must contain a single use of the Handlebars + `{{yield}}` helper. The HTML contents of a view's rendered `template` will be + inserted at this location: + + ```javascript + AViewWithLayout = Ember.View.extend({ + layout: Ember.Handlebars.compile("
    {{yield}}
    ") + template: Ember.Handlebars.compile("I got wrapped"), + }); + ``` + + Will result in view instances with an HTML representation of: + + ```html +
    +
    + I got wrapped +
    +
    + ``` + + See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield) + for more information. + + ## Responding to Browser Events + + Views can respond to user-initiated events in one of three ways: method + implementation, through an event manager, and through `{{action}}` helper use + in their template or layout. + + ### Method Implementation + + Views can respond to user-initiated events by implementing a method that + matches the event name. A `jQuery.Event` object will be passed as the + argument to this method. + + ```javascript + AView = Ember.View.extend({ + click: function(event) { + // will be called when when an instance's + // rendered element is clicked + } + }); + ``` + + ### Event Managers + + Views can define an object as their `eventManager` property. This object can + then implement methods that match the desired event names. Matching events + that occur on the view's rendered HTML or the rendered HTML of any of its DOM + descendants will trigger this method. A `jQuery.Event` object will be passed + as the first argument to the method and an `Ember.View` object as the + second. The `Ember.View` will be the view whose rendered HTML was interacted + with. This may be the view with the `eventManager` property or one of its + descendent views. + + ```javascript + AView = Ember.View.extend({ + eventManager: Ember.Object.create({ + doubleClick: function(event, view) { + // will be called when when an instance's + // rendered element or any rendering + // of this views's descendent + // elements is clicked + } + }) + }); + ``` + + An event defined for an event manager takes precedence over events of the + same name handled through methods on the view. + + ```javascript + AView = Ember.View.extend({ + mouseEnter: function(event) { + // will never trigger. }, - scheduledObserver = function() { - Ember.run.scheduleOnce('render', this, stateCheckedObserver); + eventManager: Ember.Object.create({ + mouseEnter: function(event, view) { + // takes precedence over AView#mouseEnter + } + }) + }); + ``` + + Similarly a view's event manager will take precedence for events of any views + rendered as a descendent. A method name that matches an event name will not + be called if the view instance was rendered inside the HTML representation of + a view that has an `eventManager` property defined that handles events of the + name. Events not handled by the event manager will still trigger method calls + on the descendent. + + ```javascript + OuterView = Ember.View.extend({ + template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"), + eventManager: Ember.Object.create({ + mouseEnter: function(event, view) { + // view might be instance of either + // OuterView or InnerView depending on + // where on the page the user interaction occured + } + }) + }); + + InnerView = Ember.View.extend({ + click: function(event) { + // will be called if rendered inside + // an OuterView because OuterView's + // eventManager doesn't handle click events + }, + mouseEnter: function(event) { + // will never be called if rendered inside + // an OuterView. + } + }); + ``` + + ### Handlebars `{{action}}` Helper + + See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.helpers.html#method_action). + + ### Event Names + + All of the event handling approaches described above respond to the same set + of events. The names of the built-in events are listed below. (The hash of + built-in events exists in `Ember.EventDispatcher`.) Additional, custom events + can be registered by using `Ember.Application.customEvents`. + + Touch events: + + * `touchStart` + * `touchMove` + * `touchEnd` + * `touchCancel` + + Keyboard events + + * `keyDown` + * `keyUp` + * `keyPress` + + Mouse events + + * `mouseDown` + * `mouseUp` + * `contextMenu` + * `click` + * `doubleClick` + * `mouseMove` + * `focusIn` + * `focusOut` + * `mouseEnter` + * `mouseLeave` + + Form events: + + * `submit` + * `change` + * `focusIn` + * `focusOut` + * `input` + + HTML5 drag and drop events: + + * `dragStart` + * `drag` + * `dragEnter` + * `dragLeave` + * `drop` + * `dragEnd` + + ## Handlebars `{{view}}` Helper + + Other `Ember.View` instances can be included as part of a view's template by + using the `{{view}}` Handlebars helper. See [Ember.Handlebars.helpers.view](/api/classes/Ember.Handlebars.helpers.html#method_view) + for additional information. + + @class View + @namespace Ember + @extends Ember.CoreView + */ + var View = CoreView.extend({ + + concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'], + + /** + @property isView + @type Boolean + @default true + @static + */ + isView: true, + + // .......................................................... + // TEMPLATE SUPPORT + // + + /** + The name of the template to lookup if no template is provided. + + By default `Ember.View` will lookup a template with this name in + `Ember.TEMPLATES` (a shared global object). + + @property templateName + @type String + @default null + */ + templateName: null, + + /** + The name of the layout to lookup if no layout is provided. + + By default `Ember.View` will lookup a template with this name in + `Ember.TEMPLATES` (a shared global object). + + @property layoutName + @type String + @default null + */ + layoutName: null, + + /** + The template used to render the view. This should be a function that + accepts an optional context parameter and returns a string of HTML that + will be inserted into the DOM relative to its parent view. + + In general, you should set the `templateName` property instead of setting + the template yourself. + + @property template + @type Function + */ + template: computed('templateName', function(key, value) { + if (value !== undefined) { return value; } + + var templateName = get(this, 'templateName'), + template = this.templateForName(templateName, 'template'); + + Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template); + + return template || get(this, 'defaultTemplate'); + }), + + /** + The controller managing this view. If this property is set, it will be + made available for use by the template. + + @property controller + @type Object + */ + controller: computed('_parentView', function(key) { + var parentView = get(this, '_parentView'); + return parentView ? get(parentView, 'controller') : null; + }), + + /** + A view may contain a layout. A layout is a regular template but + supersedes the `template` property during rendering. It is the + responsibility of the layout template to retrieve the `template` + property from the view (or alternatively, call `Handlebars.helpers.yield`, + `{{yield}}`) to render it in the correct location. + + This is useful for a view that has a shared wrapper, but which delegates + the rendering of the contents of the wrapper to the `template` property + on a subclass. + + @property layout + @type Function + */ + layout: computed(function(key) { + var layoutName = get(this, 'layoutName'), + layout = this.templateForName(layoutName, 'layout'); + + Ember.assert("You specified the layoutName " + layoutName + " for " + this + ", but it did not exist.", !layoutName || layout); + + return layout || get(this, 'defaultLayout'); + }).property('layoutName'), + + _yield: function(context, options) { + var template = get(this, 'template'); + if (template) { template(context, options); } + }, + + templateForName: function(name, type) { + if (!name) { return; } + Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1); + + // the defaultContainer is deprecated + var container = this.container || (Container && Container.defaultContainer); + return container && container.lookup('template:' + name); + }, + + /** + The object from which templates should access properties. + + This object will be passed to the template function each time the render + method is called, but it is up to the individual function to decide what + to do with it. + + By default, this will be the view's controller. + + @property context + @type Object + */ + context: computed(function(key, value) { + if (arguments.length === 2) { + set(this, '_context', value); + return value; + } else { + return get(this, '_context'); + } + }).volatile(), + + /** + Private copy of the view's template context. This can be set directly + by Handlebars without triggering the observer that causes the view + to be re-rendered. + + The context of a view is looked up as follows: + + 1. Supplied context (usually by Handlebars) + 2. Specified controller + 3. `parentView`'s context (for a child of a ContainerView) + + The code in Handlebars that overrides the `_context` property first + checks to see whether the view has a specified controller. This is + something of a hack and should be revisited. + + @property _context + @private + */ + _context: computed(function(key) { + var parentView, controller; + + if (controller = get(this, 'controller')) { + return controller; + } + + parentView = this._parentView; + if (parentView) { + return get(parentView, '_context'); + } + + return null; + }), + + /** + If a value that affects template rendering changes, the view should be + re-rendered to reflect the new value. + + @method _contextDidChange + @private + */ + _contextDidChange: observer('context', function() { + this.rerender(); + }), + + /** + If `false`, the view will appear hidden in DOM. + + @property isVisible + @type Boolean + @default null + */ + isVisible: true, + + /** + Array of child views. You should never edit this array directly. + Instead, use `appendChild` and `removeFromParent`. + + @property childViews + @type Array + @default [] + @private + */ + childViews: childViewsProperty, + + _childViews: EMPTY_ARRAY, + + // When it's a virtual view, we need to notify the parent that their + // childViews will change. + _childViewsWillChange: beforeObserver('childViews', function() { + if (this.isVirtual) { + var parentView = get(this, 'parentView'); + if (parentView) { propertyWillChange(parentView, 'childViews'); } + } + }), + + // When it's a virtual view, we need to notify the parent that their + // childViews did change. + _childViewsDidChange: observer('childViews', function() { + if (this.isVirtual) { + var parentView = get(this, 'parentView'); + if (parentView) { propertyDidChange(parentView, 'childViews'); } + } + }), + + /** + Return the nearest ancestor that is an instance of the provided + class. + + @method nearestInstanceOf + @param {Class} klass Subclass of Ember.View (or Ember.View itself) + @return Ember.View + @deprecated + */ + nearestInstanceOf: function(klass) { + Ember.deprecate("nearestInstanceOf is deprecated and will be removed from future releases. Use nearestOfType."); + var view = get(this, 'parentView'); + + while (view) { + if (view instanceof klass) { return view; } + view = get(view, 'parentView'); + } + }, + + /** + Return the nearest ancestor that is an instance of the provided + class or mixin. + + @method nearestOfType + @param {Class,Mixin} klass Subclass of Ember.View (or Ember.View itself), + or an instance of Ember.Mixin. + @return Ember.View + */ + nearestOfType: function(klass) { + var view = get(this, 'parentView'), + isOfType = klass instanceof Mixin ? + function(view) { return klass.detect(view); } : + function(view) { return klass.detect(view.constructor); }; + + while (view) { + if (isOfType(view)) { return view; } + view = get(view, 'parentView'); + } + }, + + /** + Return the nearest ancestor that has a given property. + + @function nearestWithProperty + @param {String} property A property name + @return Ember.View + */ + nearestWithProperty: function(property) { + var view = get(this, 'parentView'); + + while (view) { + if (property in view) { return view; } + view = get(view, 'parentView'); + } + }, + + /** + Return the nearest ancestor whose parent is an instance of + `klass`. + + @method nearestChildOf + @param {Class} klass Subclass of Ember.View (or Ember.View itself) + @return Ember.View + */ + nearestChildOf: function(klass) { + var view = get(this, 'parentView'); + + while (view) { + if (get(view, 'parentView') instanceof klass) { return view; } + view = get(view, 'parentView'); + } + }, + + /** + When the parent view changes, recursively invalidate `controller` + + @method _parentViewDidChange + @private + */ + _parentViewDidChange: observer('_parentView', function() { + if (this.isDestroying) { return; } + + this.trigger('parentViewDidChange'); + + if (get(this, 'parentView.controller') && !get(this, 'controller')) { + this.notifyPropertyChange('controller'); + } + }), + + _controllerDidChange: observer('controller', function() { + if (this.isDestroying) { return; } + + this.rerender(); + + this.forEachChildView(function(view) { + view.propertyDidChange('controller'); + }); + }), + + cloneKeywords: function() { + var templateData = get(this, 'templateData'); + + var keywords = templateData ? copy(templateData.keywords) : {}; + set(keywords, 'view', get(this, 'concreteView')); + set(keywords, '_view', this); + set(keywords, 'controller', get(this, 'controller')); + + return keywords; + }, + + /** + Called on your view when it should push strings of HTML into a + `Ember.RenderBuffer`. Most users will want to override the `template` + or `templateName` properties instead of this method. + + By default, `Ember.View` will look for a function in the `template` + property and invoke it with the value of `context`. The value of + `context` will be the view's controller unless you override it. + + @method render + @param {Ember.RenderBuffer} buffer The render buffer + */ + render: function(buffer) { + // If this view has a layout, it is the responsibility of the + // the layout to render the view's template. Otherwise, render the template + // directly. + var template = get(this, 'layout') || get(this, 'template'); + + if (template) { + var context = get(this, 'context'); + var keywords = this.cloneKeywords(); + var output; + + var data = { + view: this, + buffer: buffer, + isRenderData: true, + keywords: keywords, + insideGroup: get(this, 'templateData.insideGroup') + }; + + // Invoke the template with the provided template context, which + // is the view's controller by default. A hash of data is also passed that provides + // the template with access to the view and render buffer. + + Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function'); + // The template should write directly to the render buffer instead + // of returning a string. + output = template(context, { data: data }); + + // If the template returned a string instead of writing to the buffer, + // push the string onto the buffer. + if (output !== undefined) { buffer.push(output); } + } + }, + + /** + Renders the view again. This will work regardless of whether the + view is already in the DOM or not. If the view is in the DOM, the + rendering process will be deferred to give bindings a chance + to synchronize. + + If children were added during the rendering process using `appendChild`, + `rerender` will remove them, because they will be added again + if needed by the next `render`. + + In general, if the display of your view changes, you should modify + the DOM element directly instead of manually calling `rerender`, which can + be slow. + + @method rerender + */ + rerender: function() { + return this.currentState.rerender(this); + }, + + clearRenderedChildren: function() { + var lengthBefore = this.lengthBeforeRender, + lengthAfter = this.lengthAfterRender; + + // If there were child views created during the last call to render(), + // remove them under the assumption that they will be re-created when + // we re-render. + + // VIEW-TODO: Unit test this path. + var childViews = this._childViews; + for (var i=lengthAfter-1; i>=lengthBefore; i--) { + if (childViews[i]) { childViews[i].destroy(); } + } + }, + + /** + Iterates over the view's `classNameBindings` array, inserts the value + of the specified property into the `classNames` array, then creates an + observer to update the view's element if the bound property ever changes + in the future. + + @method _applyClassNameBindings + @private + */ + _applyClassNameBindings: function(classBindings) { + var classNames = this.classNames, + elem, newClass, dasherizedClass; + + // Loop through all of the configured bindings. These will be either + // property names ('isUrgent') or property paths relative to the view + // ('content.isUrgent') + a_forEach(classBindings, function(binding) { + + Ember.assert("classNameBindings must not have spaces in them. Multiple class name bindings can be provided as elements of an array, e.g. ['foo', ':bar']", binding.indexOf(' ') === -1); + + // Variable in which the old class value is saved. The observer function + // closes over this variable, so it knows which string to remove when + // the property changes. + var oldClass; + // Extract just the property name from bindings like 'foo:bar' + var parsedPath = View._parsePropertyPath(binding); + + // Set up an observer on the context. If the property changes, toggle the + // class name. + var observer = function() { + // Get the current value of the property + newClass = this._classStringForProperty(binding); + elem = this.$(); + + // If we had previously added a class to the element, remove it. + if (oldClass) { + elem.removeClass(oldClass); + // Also remove from classNames so that if the view gets rerendered, + // the class doesn't get added back to the DOM. + classNames.removeObject(oldClass); + } + + // If necessary, add a new class. Make sure we keep track of it so + // it can be removed in the future. + if (newClass) { + elem.addClass(newClass); + oldClass = newClass; + } else { + oldClass = null; + } + }; + + // Get the class name for the property at its current value + dasherizedClass = this._classStringForProperty(binding); + + if (dasherizedClass) { + // Ensure that it gets into the classNames array + // so it is displayed when we render. + a_addObject(classNames, dasherizedClass); + + // Save a reference to the class name so we can remove it + // if the observer fires. Remember that this variable has + // been closed over by the observer. + oldClass = dasherizedClass; + } + + this.registerObserver(this, parsedPath.path, observer); + // Remove className so when the view is rerendered, + // the className is added based on binding reevaluation + this.one('willClearRender', function() { + if (oldClass) { + classNames.removeObject(oldClass); + oldClass = null; + } + }); + + }, this); + }, + + _unspecifiedAttributeBindings: null, + + /** + Iterates through the view's attribute bindings, sets up observers for each, + then applies the current value of the attributes to the passed render buffer. + + @method _applyAttributeBindings + @param {Ember.RenderBuffer} buffer + @private + */ + _applyAttributeBindings: function(buffer, attributeBindings) { + var attributeValue, + unspecifiedAttributeBindings = this._unspecifiedAttributeBindings = this._unspecifiedAttributeBindings || {}; + + a_forEach(attributeBindings, function(binding) { + var split = binding.split(':'), + property = split[0], + attributeName = split[1] || property; + + if (property in this) { + this._setupAttributeBindingObservation(property, attributeName); + + // Determine the current value and add it to the render buffer + // if necessary. + attributeValue = get(this, property); + View.applyAttributeBindings(buffer, attributeName, attributeValue); + } else { + unspecifiedAttributeBindings[property] = attributeName; + } + }, this); + + // Lazily setup setUnknownProperty after attributeBindings are initially applied + this.setUnknownProperty = this._setUnknownProperty; + }, + + _setupAttributeBindingObservation: function(property, attributeName) { + var attributeValue, elem; + + // Create an observer to add/remove/change the attribute if the + // JavaScript property changes. + var observer = function() { + elem = this.$(); + + attributeValue = get(this, property); + + View.applyAttributeBindings(elem, attributeName, attributeValue); }; - Ember.addObserver(root, path, target, scheduledObserver); - - this.one('willClearRender', function() { - Ember.removeObserver(root, path, target, scheduledObserver); - }); - } - -}); - -/* - Describe how the specified actions should behave in the various - states that a view can exist in. Possible states: - - * preRender: when a view is first instantiated, and after its - element was destroyed, it is in the preRender state - * inBuffer: once a view has been rendered, but before it has - been inserted into the DOM, it is in the inBuffer state - * inDOM: once a view has been inserted into the DOM it is in - the inDOM state. A view spends the vast majority of its - existence in this state. - * destroyed: once a view has been destroyed (using the destroy - method), it is in this state. No further actions can be invoked - on a destroyed view. -*/ - - // in the destroyed state, everything is illegal - - // before rendering has begun, all legal manipulations are noops. - - // inside the buffer, legal manipulations are done on the buffer - - // once the view has been inserted into the DOM, legal manipulations - // are done on the DOM element. - -function notifyMutationListeners() { - Ember.run.once(Ember.View, 'notifyMutationListeners'); -} - -var DOMManager = { - prepend: function(view, html) { - view.$().prepend(html); - notifyMutationListeners(); - }, - - after: function(view, html) { - view.$().after(html); - notifyMutationListeners(); - }, - - html: function(view, html) { - view.$().html(html); - notifyMutationListeners(); - }, - - replace: function(view) { - var element = get(view, 'element'); - - set(view, 'element', null); - - view._insertElementLater(function() { - Ember.$(element).replaceWith(get(view, 'element')); - notifyMutationListeners(); - }); - }, - - remove: function(view) { - view.$().remove(); - notifyMutationListeners(); - }, - - empty: function(view) { - view.$().empty(); - notifyMutationListeners(); - } -}; - -Ember.View.reopen({ - domManager: DOMManager -}); - -Ember.View.reopenClass({ - - /** - @private - - Parse a path and return an object which holds the parsed properties. - - For example a path like "content.isEnabled:enabled:disabled" will return the - following object: - - ```javascript - { - path: "content.isEnabled", - className: "enabled", - falsyClassName: "disabled", - classNames: ":enabled:disabled" - } - ``` - - @method _parsePropertyPath - @static - */ - _parsePropertyPath: function(path) { - var split = path.split(':'), - propertyPath = split[0], - classNames = "", - className, - falsyClassName; - - // check if the property is defined as prop:class or prop:trueClass:falseClass - if (split.length > 1) { - className = split[1]; - if (split.length === 3) { falsyClassName = split[2]; } - - classNames = ':' + className; - if (falsyClassName) { classNames += ":" + falsyClassName; } - } - - return { - path: propertyPath, - classNames: classNames, - className: (className === '') ? undefined : className, - falsyClassName: falsyClassName - }; - }, - - /** - @private - - Get the class name for a given value, based on the path, optional - `className` and optional `falsyClassName`. - - - if a `className` or `falsyClassName` has been specified: - - if the value is truthy and `className` has been specified, - `className` is returned - - if the value is falsy and `falsyClassName` has been specified, - `falsyClassName` is returned - - otherwise `null` is returned - - if the value is `true`, the dasherized last part of the supplied path - is returned - - if the value is not `false`, `undefined` or `null`, the `value` - is returned - - if none of the above rules apply, `null` is returned - - @method _classStringForValue - @param path - @param val - @param className - @param falsyClassName - @static - */ - _classStringForValue: function(path, val, className, falsyClassName) { - // When using the colon syntax, evaluate the truthiness or falsiness - // of the value to determine which className to return - if (className || falsyClassName) { - if (className && !!val) { - return className; - - } else if (falsyClassName && !val) { - return falsyClassName; - - } else { - return null; - } - - // If value is a Boolean and true, return the dasherized property - // name. - } else if (val === true) { - // Normalize property path to be suitable for use - // as a class name. For exaple, content.foo.barBaz - // becomes bar-baz. - var parts = path.split('.'); - return Ember.String.dasherize(parts[parts.length-1]); - - // If the value is not false, undefined, or null, return the current - // value of the property. - } else if (val !== false && val != null) { - return val; - - // Nothing to display. Return null so that the old class is removed - // but no new class is added. - } else { - return null; - } - } -}); - -var mutation = Ember.Object.extend(Ember.Evented).create(); - -Ember.View.addMutationListener = function(callback) { - mutation.on('change', callback); -}; - -Ember.View.removeMutationListener = function(callback) { - mutation.off('change', callback); -}; - -Ember.View.notifyMutationListeners = function() { - mutation.trigger('change'); -}; - -/** - Global views hash - - @property views - @static - @type Hash -*/ -Ember.View.views = {}; - -// If someone overrides the child views computed property when -// defining their class, we want to be able to process the user's -// supplied childViews and then restore the original computed property -// at view initialization time. This happens in Ember.ContainerView's init -// method. -Ember.View.childViewsProperty = childViewsProperty; - -Ember.View.applyAttributeBindings = function(elem, name, value) { - var type = Ember.typeOf(value); - - // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js - if (name !== 'value' && (type === 'string' || (type === 'number' && !isNaN(value)))) { - if (value !== elem.attr(name)) { - elem.attr(name, value); - } - } else if (name === 'value' || type === 'boolean') { - // We can't set properties to undefined or null - if (Ember.isNone(value)) { value = ''; } - - if (value !== elem.prop(name)) { - // value and booleans should always be properties - elem.prop(name, value); - } - } else if (!value) { - elem.removeAttr(name); - } -}; - -Ember.View.states = states; - -})(); - - - -(function() { -/** -@module ember -@submodule ember-views -*/ - -var get = Ember.get, set = Ember.set; - -Ember.View.states._default = { - // appendChild is only legal while rendering the buffer. - appendChild: function() { - throw "You can't use appendChild outside of the rendering process"; - }, - - $: function() { - return undefined; - }, - - getElement: function() { - return null; - }, - - // Handle events from `Ember.EventDispatcher` - handleEvent: function() { - return true; // continue event propagation - }, - - destroyElement: function(view) { - set(view, 'element', null); - if (view._scheduledInsert) { - Ember.run.cancel(view._scheduledInsert); - view._scheduledInsert = null; - } - return view; - }, - - renderToBufferIfNeeded: function () { - return false; - }, - - rerender: Ember.K, - invokeObserver: Ember.K -}; - -})(); - - - -(function() { -/** -@module ember -@submodule ember-views -*/ - -var preRender = Ember.View.states.preRender = Ember.create(Ember.View.states._default); - -Ember.merge(preRender, { - // a view leaves the preRender state once its element has been - // created (createElement). - insertElement: function(view, fn) { - view.createElement(); - var viewCollection = view.viewHierarchyCollection(); - - viewCollection.trigger('willInsertElement'); - - fn.call(view); - - // We transition to `inDOM` if the element exists in the DOM - var element = view.get('element'); - while (element = element.parentNode) { - if (element === document) { - viewCollection.transitionTo('inDOM', false); - viewCollection.trigger('didInsertElement'); - } - } - - }, - - renderToBufferIfNeeded: function(view, buffer) { - view.renderToBuffer(buffer); - return true; - }, - - empty: Ember.K, - - setElement: function(view, value) { - if (value !== null) { - view.transitionTo('hasElement'); - } - return value; - } -}); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-views -*/ - -var get = Ember.get, set = Ember.set; - -var inBuffer = Ember.View.states.inBuffer = Ember.create(Ember.View.states._default); - -Ember.merge(inBuffer, { - $: function(view, sel) { - // if we don't have an element yet, someone calling this.$() is - // trying to update an element that isn't in the DOM. Instead, - // rerender the view to allow the render method to reflect the - // changes. - view.rerender(); - return Ember.$(); - }, - - // when a view is rendered in a buffer, rerendering it simply - // replaces the existing buffer with a new one - rerender: function(view) { - throw new Ember.Error("Something you did caused a view to re-render after it rendered but before it was inserted into the DOM."); - }, - - // when a view is rendered in a buffer, appending a child - // view will render that view and append the resulting - // buffer into its buffer. - appendChild: function(view, childView, options) { - var buffer = view.buffer, _childViews = view._childViews; - - childView = view.createChildView(childView, options); - if (!_childViews.length) { _childViews = view._childViews = _childViews.slice(); } - _childViews.push(childView); - - childView.renderToBuffer(buffer); - - view.propertyDidChange('childViews'); - - return childView; - }, - - // when a view is rendered in a buffer, destroying the - // element will simply destroy the buffer and put the - // state back into the preRender state. - destroyElement: function(view) { - view.clearBuffer(); - var viewCollection = view._notifyWillDestroyElement(); - viewCollection.transitionTo('preRender', false); - - return view; - }, - - empty: function() { - Ember.assert("Emptying a view in the inBuffer state is not allowed and should not happen under normal circumstances. Most likely there is a bug in your application. This may be due to excessive property change notifications."); - }, - - renderToBufferIfNeeded: function (view, buffer) { - return false; - }, - - // It should be impossible for a rendered view to be scheduled for - // insertion. - insertElement: function() { - throw "You can't insert an element that has already been rendered"; - }, - - setElement: function(view, value) { - if (value === null) { - view.transitionTo('preRender'); - } else { - view.clearBuffer(); - view.transitionTo('hasElement'); - } - - return value; - }, - - invokeObserver: function(target, observer) { - observer.call(target); - } -}); - - -})(); - - - -(function() { -/** -@module ember -@submodule ember-views -*/ - -var get = Ember.get, set = Ember.set; - -var hasElement = Ember.View.states.hasElement = Ember.create(Ember.View.states._default); - -Ember.merge(hasElement, { - $: function(view, sel) { - var elem = get(view, 'element'); - return sel ? Ember.$(sel, elem) : Ember.$(elem); - }, - - getElement: function(view) { - var parent = get(view, 'parentView'); - if (parent) { parent = get(parent, 'element'); } - if (parent) { return view.findElementInParentElement(parent); } - return Ember.$("#" + get(view, 'elementId'))[0]; - }, - - setElement: function(view, value) { - if (value === null) { - view.transitionTo('preRender'); - } else { - throw "You cannot set an element to a non-null value when the element is already in the DOM."; - } - - return value; - }, - - // once the view has been inserted into the DOM, rerendering is - // deferred to allow bindings to synchronize. - rerender: function(view) { - view.triggerRecursively('willClearRender'); - - view.clearRenderedChildren(); - - view.domManager.replace(view); - return view; - }, - - // once the view is already in the DOM, destroying it removes it - // from the DOM, nukes its element, and puts it back into the - // preRender state if inDOM. - - destroyElement: function(view) { - view._notifyWillDestroyElement(); - view.domManager.remove(view); - set(view, 'element', null); - if (view._scheduledInsert) { - Ember.run.cancel(view._scheduledInsert); - view._scheduledInsert = null; - } - return view; - }, - - empty: function(view) { - var _childViews = view._childViews, len, idx; - if (_childViews) { - len = _childViews.length; - for (idx = 0; idx < len; idx++) { - _childViews[idx]._notifyWillDestroyElement(); - } - } - view.domManager.empty(view); - }, - - // Handle events from `Ember.EventDispatcher` - handleEvent: function(view, eventName, evt) { - if (view.has(eventName)) { - // Handler should be able to re-dispatch events, so we don't - // preventDefault or stopPropagation. - return view.trigger(eventName, evt); - } else { - return true; // continue event propagation - } - }, - - invokeObserver: function(target, observer) { - observer.call(target); - } -}); - -var inDOM = Ember.View.states.inDOM = Ember.create(hasElement); - -Ember.merge(inDOM, { - enter: function(view) { - // Register the view for event handling. This hash is used by - // Ember.EventDispatcher to dispatch incoming events. - if (!view.isVirtual) { - Ember.assert("Attempted to register a view with an id already in use: "+view.elementId, !Ember.View.views[view.elementId]); - Ember.View.views[view.elementId] = view; - } - - view.addBeforeObserver('elementId', function() { - throw new Ember.Error("Changing a view's elementId after creation is not allowed"); - }); - }, - - exit: function(view) { - if (!this.isVirtual) delete Ember.View.views[view.elementId]; - }, - - insertElement: function(view, fn) { - throw "You can't insert an element into the DOM that has already been inserted"; - } -}); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-views -*/ - -var destroyingError = "You can't call %@ on a view being destroyed", fmt = Ember.String.fmt; - -var destroying = Ember.View.states.destroying = Ember.create(Ember.View.states._default); - -Ember.merge(destroying, { - appendChild: function() { - throw fmt(destroyingError, ['appendChild']); - }, - rerender: function() { - throw fmt(destroyingError, ['rerender']); - }, - destroyElement: function() { - throw fmt(destroyingError, ['destroyElement']); - }, - empty: function() { - throw fmt(destroyingError, ['empty']); - }, - - setElement: function() { - throw fmt(destroyingError, ["set('element', ...)"]); - }, - - renderToBufferIfNeeded: function() { - return false; - }, - - // Since element insertion is scheduled, don't do anything if - // the view has been destroyed between scheduling and execution - insertElement: Ember.K -}); - - -})(); - - - -(function() { -Ember.View.cloneStates = function(from) { - var into = {}; - - into._default = {}; - into.preRender = Ember.create(into._default); - into.destroying = Ember.create(into._default); - into.inBuffer = Ember.create(into._default); - into.hasElement = Ember.create(into._default); - into.inDOM = Ember.create(into.hasElement); - - for (var stateName in from) { - if (!from.hasOwnProperty(stateName)) { continue; } - Ember.merge(into[stateName], from[stateName]); - } - - return into; -}; - -})(); - - - -(function() { -var states = Ember.View.cloneStates(Ember.View.states); - -/** -@module ember -@submodule ember-views -*/ - -var get = Ember.get, set = Ember.set; -var forEach = Ember.EnumerableUtils.forEach; -var ViewCollection = Ember._ViewCollection; - -/** - A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray` - allowing programatic management of its child views. - - ## Setting Initial Child Views - - The initial array of child views can be set in one of two ways. You can - provide a `childViews` property at creation time that contains instance of - `Ember.View`: - - ```javascript - aContainer = Ember.ContainerView.create({ - childViews: [Ember.View.create(), Ember.View.create()] - }); - ``` - - You can also provide a list of property names whose values are instances of - `Ember.View`: - - ```javascript - aContainer = Ember.ContainerView.create({ - childViews: ['aView', 'bView', 'cView'], - aView: Ember.View.create(), - bView: Ember.View.create(), - cView: Ember.View.create() - }); - ``` - - The two strategies can be combined: - - ```javascript - aContainer = Ember.ContainerView.create({ - childViews: ['aView', Ember.View.create()], - aView: Ember.View.create() - }); - ``` - - Each child view's rendering will be inserted into the container's rendered - HTML in the same order as its position in the `childViews` property. - - ## Adding and Removing Child Views - - The container view implements `Ember.MutableArray` allowing programatic management of its child views. - - To remove a view, pass that view into a `removeObject` call on the container view. - - Given an empty `` the following code - - ```javascript - aContainer = Ember.ContainerView.create({ - classNames: ['the-container'], - childViews: ['aView', 'bView'], - aView: Ember.View.create({ - template: Ember.Handlebars.compile("A") - }), - bView: Ember.View.create({ - template: Ember.Handlebars.compile("B") - }) - }); - - aContainer.appendTo('body'); - ``` - - Results in the HTML - - ```html -
    -
    A
    -
    B
    -
    - ``` - - Removing a view - - ```javascript - aContainer.toArray(); // [aContainer.aView, aContainer.bView] - aContainer.removeObject(aContainer.get('bView')); - aContainer.toArray(); // [aContainer.aView] - ``` - - Will result in the following HTML - - ```html -
    -
    A
    -
    - ``` - - Similarly, adding a child view is accomplished by adding `Ember.View` instances to the - container view. - - Given an empty `` the following code - - ```javascript - aContainer = Ember.ContainerView.create({ - classNames: ['the-container'], - childViews: ['aView', 'bView'], - aView: Ember.View.create({ - template: Ember.Handlebars.compile("A") - }), - bView: Ember.View.create({ - template: Ember.Handlebars.compile("B") - }) - }); - - aContainer.appendTo('body'); - ``` - - Results in the HTML - - ```html -
    -
    A
    -
    B
    -
    - ``` - - Adding a view - - ```javascript - AnotherViewClass = Ember.View.extend({ - template: Ember.Handlebars.compile("Another view") - }); - - aContainer.toArray(); // [aContainer.aView, aContainer.bView] - aContainer.pushObject(AnotherViewClass.create()); - aContainer.toArray(); // [aContainer.aView, aContainer.bView, ] - ``` - - Will result in the following HTML - - ```html -
    -
    A
    -
    B
    -
    Another view
    -
    - ``` - - ## Templates and Layout - - A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or - `defaultLayout` property on a container view will not result in the template - or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM - representation will only be the rendered HTML of its child views. - - @class ContainerView - @namespace Ember - @extends Ember.View -*/ -Ember.ContainerView = Ember.View.extend(Ember.MutableArray, { - states: states, - - init: function() { - this._super(); - - var childViews = get(this, 'childViews'); - - // redefine view's childViews property that was obliterated - Ember.defineProperty(this, 'childViews', Ember.View.childViewsProperty); - - var _childViews = this._childViews; - - forEach(childViews, function(viewName, idx) { - var view; - - if ('string' === typeof viewName) { - view = get(this, viewName); - view = this.createChildView(view); - set(this, viewName, view); - } else { - view = this.createChildView(viewName); - } - - _childViews[idx] = view; - }, this); - - var currentView = get(this, 'currentView'); - if (currentView) { - if (!_childViews.length) { _childViews = this._childViews = this._childViews.slice(); } - _childViews.push(this.createChildView(currentView)); - } - }, - - replace: function(idx, removedCount, addedViews) { - var addedCount = addedViews ? get(addedViews, 'length') : 0; - var self = this; - Ember.assert("You can't add a child to a container that is already a child of another view", Ember.A(addedViews).every(function(item) { return !get(item, '_parentView') || get(item, '_parentView') === self; })); - - this.arrayContentWillChange(idx, removedCount, addedCount); - this.childViewsWillChange(this._childViews, idx, removedCount); - - if (addedCount === 0) { - this._childViews.splice(idx, removedCount) ; - } else { - var args = [idx, removedCount].concat(addedViews); - if (addedViews.length && !this._childViews.length) { this._childViews = this._childViews.slice(); } - this._childViews.splice.apply(this._childViews, args); - } - - this.arrayContentDidChange(idx, removedCount, addedCount); - this.childViewsDidChange(this._childViews, idx, removedCount, addedCount); - - return this; - }, - - objectAt: function(idx) { - return this._childViews[idx]; - }, - - length: Ember.computed(function () { - return this._childViews.length; - }).volatile(), - - /** - @private - - Instructs each child view to render to the passed render buffer. - - @method render - @param {Ember.RenderBuffer} buffer the buffer to render to - */ - render: function(buffer) { - this.forEachChildView(function(view) { - view.renderToBuffer(buffer); - }); - }, - - instrumentName: 'container', - - /** - @private - - When a child view is removed, destroy its element so that - it is removed from the DOM. - - The array observer that triggers this action is set up in the - `renderToBuffer` method. - - @method childViewsWillChange - @param {Ember.Array} views the child views array before mutation - @param {Number} start the start position of the mutation - @param {Number} removed the number of child views removed - **/ - childViewsWillChange: function(views, start, removed) { - this.propertyWillChange('childViews'); - - if (removed > 0) { - var changedViews = views.slice(start, start+removed); - // transition to preRender before clearing parentView - this.currentState.childViewsWillChange(this, views, start, removed); - this.initializeViews(changedViews, null, null); - } - }, - - removeChild: function(child) { - this.removeObject(child); - return this; - }, - - /** - @private - - When a child view is added, make sure the DOM gets updated appropriately. - - If the view has already rendered an element, we tell the child view to - create an element and insert it into the DOM. If the enclosing container - view has already written to a buffer, but not yet converted that buffer - into an element, we insert the string representation of the child into the - appropriate place in the buffer. - - @method childViewsDidChange - @param {Ember.Array} views the array of child views afte the mutation has occurred - @param {Number} start the start position of the mutation - @param {Number} removed the number of child views removed - @param {Number} the number of child views added - */ - childViewsDidChange: function(views, start, removed, added) { - if (added > 0) { - var changedViews = views.slice(start, start+added); - this.initializeViews(changedViews, this, get(this, 'templateData')); - this.currentState.childViewsDidChange(this, views, start, added); - } - this.propertyDidChange('childViews'); - }, - - initializeViews: function(views, parentView, templateData) { - forEach(views, function(view) { - set(view, '_parentView', parentView); - - if (!view.container && parentView) { - set(view, 'container', parentView.container); - } - - if (!get(view, 'templateData')) { - set(view, 'templateData', templateData); - } - }); - }, - - currentView: null, - - _currentViewWillChange: Ember.beforeObserver('currentView', function() { - var currentView = get(this, 'currentView'); - if (currentView) { - currentView.destroy(); - } - }), - - _currentViewDidChange: Ember.observer('currentView', function() { - var currentView = get(this, 'currentView'); - if (currentView) { - Ember.assert("You tried to set a current view that already has a parent. Make sure you don't have multiple outlets in the same view.", !get(currentView, '_parentView')); - this.pushObject(currentView); - } - }), - - _ensureChildrenAreInDOM: function () { - this.currentState.ensureChildrenAreInDOM(this); - } -}); - -Ember.merge(states._default, { - childViewsWillChange: Ember.K, - childViewsDidChange: Ember.K, - ensureChildrenAreInDOM: Ember.K -}); - -Ember.merge(states.inBuffer, { - childViewsDidChange: function(parentView, views, start, added) { - throw new Ember.Error('You cannot modify child views while in the inBuffer state'); - } -}); - -Ember.merge(states.hasElement, { - childViewsWillChange: function(view, views, start, removed) { - for (var i=start; i` and the following code: - - ```javascript - someItemsView = Ember.CollectionView.create({ - classNames: ['a-collection'], - content: ['A','B','C'], - itemViewClass: Ember.View.extend({ - template: Ember.Handlebars.compile("the letter: {{view.content}}") - }) - }); - - someItemsView.appendTo('body'); - ``` - - Will result in the following HTML structure - - ```html -
    -
    the letter: A
    -
    the letter: B
    -
    the letter: C
    -
    - ``` - - ## Automatic matching of parent/child tagNames - - Setting the `tagName` property of a `CollectionView` to any of - "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result - in the item views receiving an appropriately matched `tagName` property. - - Given an empty `` and the following code: - - ```javascript - anUndorderedListView = Ember.CollectionView.create({ - tagName: 'ul', - content: ['A','B','C'], - itemViewClass: Ember.View.extend({ - template: Ember.Handlebars.compile("the letter: {{view.content}}") - }) - }); - - anUndorderedListView.appendTo('body'); - ``` - - Will result in the following HTML structure - - ```html -
      -
    • the letter: A
    • -
    • the letter: B
    • -
    • the letter: C
    • -
    - ``` - - Additional `tagName` pairs can be provided by adding to - `Ember.CollectionView.CONTAINER_MAP ` - - ```javascript - Ember.CollectionView.CONTAINER_MAP['article'] = 'section' - ``` - - ## Programatic creation of child views - - For cases where additional customization beyond the use of a single - `itemViewClass` or `tagName` matching is required CollectionView's - `createChildView` method can be overidden: - - ```javascript - CustomCollectionView = Ember.CollectionView.extend({ - createChildView: function(viewClass, attrs) { - if (attrs.content.kind == 'album') { - viewClass = App.AlbumView; - } else { - viewClass = App.SongView; - } - return this._super(viewClass, attrs); - } - }); - ``` - - ## Empty View - - You can provide an `Ember.View` subclass to the `Ember.CollectionView` - instance as its `emptyView` property. If the `content` property of a - `CollectionView` is set to `null` or an empty array, an instance of this view - will be the `CollectionView`s only child. - - ```javascript - aListWithNothing = Ember.CollectionView.create({ - classNames: ['nothing'] - content: null, - emptyView: Ember.View.extend({ - template: Ember.Handlebars.compile("The collection is empty") - }) - }); - - aListWithNothing.appendTo('body'); - ``` - - Will result in the following HTML structure - - ```html -
    -
    - The collection is empty -
    -
    - ``` - - ## Adding and Removing items - - The `childViews` property of a `CollectionView` should not be directly - manipulated. Instead, add, remove, replace items from its `content` property. - This will trigger appropriate changes to its rendered HTML. - - - @class CollectionView - @namespace Ember - @extends Ember.ContainerView - @since Ember 0.9 -*/ -Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionView.prototype */ { - - /** - A list of items to be displayed by the `Ember.CollectionView`. - - @property content - @type Ember.Array - @default null - */ - content: null, - - /** - @private - - This provides metadata about what kind of empty view class this - collection would like if it is being instantiated from another - system (like Handlebars) - - @property emptyViewClass - */ - emptyViewClass: Ember.View, - - /** - An optional view to display if content is set to an empty array. - - @property emptyView - @type Ember.View - @default null - */ - emptyView: null, - - /** - @property itemViewClass - @type Ember.View - @default Ember.View - */ - itemViewClass: Ember.View, - - /** - Setup a CollectionView - - @method init - */ - init: function() { - var ret = this._super(); - this._contentDidChange(); - return ret; - }, - - /** - @private - - Invoked when the content property is about to change. Notifies observers that the - entire array content will change. - - @method _contentWillChange - */ - _contentWillChange: Ember.beforeObserver('content', function() { - var content = this.get('content'); - - if (content) { content.removeArrayObserver(this); } - var len = content ? get(content, 'length') : 0; - this.arrayWillChange(content, 0, len); - }), - - /** - @private - - Check to make sure that the content has changed, and if so, - update the children directly. This is always scheduled - asynchronously, to allow the element to be created before - bindings have synchronized and vice versa. - - @method _contentDidChange - */ - _contentDidChange: Ember.observer('content', function() { - var content = get(this, 'content'); - - if (content) { - this._assertArrayLike(content); - content.addArrayObserver(this); - } - - var len = content ? get(content, 'length') : 0; - this.arrayDidChange(content, 0, null, len); - }), - - /** - @private - - Ensure that the content implements Ember.Array - - @method _assertArrayLike - */ - _assertArrayLike: function(content) { - Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content)); - }, - - /** - Removes the content and content observers. - - @method destroy - */ - destroy: function() { - if (!this._super()) { return; } - - var content = get(this, 'content'); - if (content) { content.removeArrayObserver(this); } - - if (this._createdEmptyView) { - this._createdEmptyView.destroy(); - } - - return this; - }, - - /** - Called when a mutation to the underlying content array will occur. - - This method will remove any views that are no longer in the underlying - content array. - - Invokes whenever the content array itself will change. - - @method arrayWillChange - @param {Array} content the managed collection of objects - @param {Number} start the index at which the changes will occurr - @param {Number} removed number of object to be removed from content - */ - arrayWillChange: function(content, start, removedCount) { - // If the contents were empty before and this template collection has an - // empty view remove it now. - var emptyView = get(this, 'emptyView'); - if (emptyView && emptyView instanceof Ember.View) { - emptyView.removeFromParent(); - } - - // Loop through child views that correspond with the removed items. - // Note that we loop from the end of the array to the beginning because - // we are mutating it as we go. - var childViews = this._childViews, childView, idx, len; - - len = this._childViews.length; - - var removingAll = removedCount === len; - - if (removingAll) { - this.currentState.empty(this); - this.invokeRecursively(function(view) { - view.removedFromDOM = true; - }, false); - } - - for (idx = start + removedCount - 1; idx >= start; idx--) { - childView = childViews[idx]; - childView.destroy(); - } - }, - - /** - Called when a mutation to the underlying content array occurs. - - This method will replay that mutation against the views that compose the - `Ember.CollectionView`, ensuring that the view reflects the model. - - This array observer is added in `contentDidChange`. - - @method arrayDidChange - @param {Array} content the managed collection of objects - @param {Number} start the index at which the changes occurred - @param {Number} removed number of object removed from content - @param {Number} added number of object added to content - */ - arrayDidChange: function(content, start, removed, added) { - var addedViews = [], view, item, idx, len, itemViewClass, - emptyView; - - len = content ? get(content, 'length') : 0; - - if (len) { - itemViewClass = get(this, 'itemViewClass'); - - if ('string' === typeof itemViewClass) { - itemViewClass = get(itemViewClass) || itemViewClass; - } - - Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", [itemViewClass]), 'string' === typeof itemViewClass || Ember.View.detect(itemViewClass)); - - for (idx = start; idx < start+added; idx++) { - item = content.objectAt(idx); - - view = this.createChildView(itemViewClass, { - content: item, - contentIndex: idx + this.registerObserver(this, property, observer); + }, + + /** + We're using setUnknownProperty as a hook to setup attributeBinding observers for + properties that aren't defined on a view at initialization time. + + Note: setUnknownProperty will only be called once for each property. + + @method setUnknownProperty + @param key + @param value + @private + */ + setUnknownProperty: null, // Gets defined after initialization by _applyAttributeBindings + + _setUnknownProperty: function(key, value) { + var attributeName = this._unspecifiedAttributeBindings && this._unspecifiedAttributeBindings[key]; + if (attributeName) { + this._setupAttributeBindingObservation(key, attributeName); + } + + defineProperty(this, key); + return set(this, key, value); + }, + + /** + Given a property name, returns a dasherized version of that + property name if the property evaluates to a non-falsy value. + + For example, if the view has property `isUrgent` that evaluates to true, + passing `isUrgent` to this method will return `"is-urgent"`. + + @method _classStringForProperty + @param property + @private + */ + _classStringForProperty: function(property) { + var parsedPath = View._parsePropertyPath(property); + var path = parsedPath.path; + + var val = get(this, path); + if (val === undefined && isGlobalPath(path)) { + val = get(Ember.lookup, path); + } + + return View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName); + }, + + // .......................................................... + // ELEMENT SUPPORT + // + + /** + Returns the current DOM element for the view. + + @property element + @type DOMElement + */ + element: computed('_parentView', function(key, value) { + if (value !== undefined) { + return this.currentState.setElement(this, value); + } else { + return this.currentState.getElement(this); + } + }), + + /** + Returns a jQuery object for this view's element. If you pass in a selector + string, this method will return a jQuery object, using the current element + as its buffer. + + For example, calling `view.$('li')` will return a jQuery object containing + all of the `li` elements inside the DOM element of this view. + + @method $ + @param {String} [selector] a jQuery-compatible selector string + @return {jQuery} the jQuery object for the DOM node + */ + $: function(sel) { + return this.currentState.$(this, sel); + }, + + mutateChildViews: function(callback) { + var childViews = this._childViews, + idx = childViews.length, + view; + + while(--idx >= 0) { + view = childViews[idx]; + callback(this, view, idx); + } + + return this; + }, + + forEachChildView: function(callback) { + var childViews = this._childViews; + + if (!childViews) { return this; } + + var len = childViews.length, + view, idx; + + for (idx = 0; idx < len; idx++) { + view = childViews[idx]; + callback(view); + } + + return this; + }, + + /** + Appends the view's element to the specified parent element. + + If the view does not have an HTML representation yet, `createElement()` + will be called automatically. + + Note that this method just schedules the view to be appended; the DOM + element will not be appended to the given element until all bindings have + finished synchronizing. + + This is not typically a function that you will need to call directly when + building your application. You might consider using `Ember.ContainerView` + instead. If you do need to use `appendTo`, be sure that the target element + you are providing is associated with an `Ember.Application` and does not + have an ancestor element that is associated with an Ember view. + + @method appendTo + @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object + @return {Ember.View} receiver + */ + appendTo: function(target) { + // Schedule the DOM element to be created and appended to the given + // element after bindings have synchronized. + this._insertElementLater(function() { + Ember.assert("You tried to append to (" + target + ") but that isn't in the DOM", jQuery(target).length > 0); + Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !jQuery(target).is('.ember-view') && !jQuery(target).parents().is('.ember-view')); + this.$().appendTo(target); }); - addedViews.push(view); - } - } else { - emptyView = get(this, 'emptyView'); + return this; + }, - if (!emptyView) { return; } + /** + Replaces the content of the specified parent element with this view's + element. If the view does not have an HTML representation yet, + `createElement()` will be called automatically. - if ('string' === typeof emptyView) { - emptyView = get(emptyView) || emptyView; - } + Note that this method just schedules the view to be appended; the DOM + element will not be appended to the given element until all bindings have + finished synchronizing - emptyView = this.createChildView(emptyView); - addedViews.push(emptyView); - set(this, 'emptyView', emptyView); + @method replaceIn + @param {String|DOMElement|jQuery} target A selector, element, HTML string, or jQuery object + @return {Ember.View} received + */ + replaceIn: function(target) { + Ember.assert("You tried to replace in (" + target + ") but that isn't in the DOM", jQuery(target).length > 0); + Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !jQuery(target).is('.ember-view') && !jQuery(target).parents().is('.ember-view')); - if (Ember.CoreView.detect(emptyView)) { - this._createdEmptyView = emptyView; - } - } + this._insertElementLater(function() { + jQuery(target).empty(); + this.$().appendTo(target); + }); - this.replace(start, 0, addedViews); - }, + return this; + }, - /** - Instantiates a view to be added to the childViews array during view - initialization. You generally will not call this method directly unless - you are overriding `createChildViews()`. Note that this method will - automatically configure the correct settings on the new view instance to - act as a child of the parent. + /** + Schedules a DOM operation to occur during the next render phase. This + ensures that all bindings have finished synchronizing before the view is + rendered. - The tag name for the view will be set to the tagName of the viewClass - passed in. + To use, pass a function that performs a DOM operation. - @method createChildView - @param {Class} viewClass - @param {Hash} [attrs] Attributes to add - @return {Ember.View} new instance - */ - createChildView: function(view, attrs) { - view = this._super(view, attrs); + Before your function is called, this view and all child views will receive + the `willInsertElement` event. After your function is invoked, this view + and all of its child views will receive the `didInsertElement` event. - var itemTagName = get(view, 'tagName'); + ```javascript + view._insertElementLater(function() { + this.createElement(); + this.$().appendTo('body'); + }); + ``` - if (itemTagName === null || itemTagName === undefined) { - itemTagName = Ember.CollectionView.CONTAINER_MAP[get(this, 'tagName')]; - set(view, 'tagName', itemTagName); - } + @method _insertElementLater + @param {Function} fn the function that inserts the element into the DOM + @private + */ + _insertElementLater: function(fn) { + this._scheduledInsert = run.scheduleOnce('render', this, '_insertElement', fn); + }, - return view; - } -}); + _insertElement: function (fn) { + this._scheduledInsert = null; + this.currentState.insertElement(this, fn); + }, -/** - A map of parent tags to their default child tags. You can add - additional parent tags if you want collection views that use - a particular parent tag to default to a child tag. + /** + Appends the view's element to the document body. If the view does + not have an HTML representation yet, `createElement()` will be called + automatically. - @property CONTAINER_MAP - @type Hash - @static - @final -*/ -Ember.CollectionView.CONTAINER_MAP = { - ul: 'li', - ol: 'li', - table: 'tr', - thead: 'tr', - tbody: 'tr', - tfoot: 'tr', - tr: 'td', - select: 'option' -}; + If your application uses the `rootElement` property, you must append + the view within that element. Rendering views outside of the `rootElement` + is not supported. -})(); + Note that this method just schedules the view to be appended; the DOM + element will not be appended to the document body until all bindings have + finished synchronizing. + @method append + @return {Ember.View} receiver + */ + append: function() { + return this.appendTo(document.body); + }, + /** + Removes the view's element from the element to which it is attached. -(function() { -var get = Ember.get, set = Ember.set, isNone = Ember.isNone, - a_slice = Array.prototype.slice; + @method remove + @return {Ember.View} receiver + */ + remove: function() { + // What we should really do here is wait until the end of the run loop + // to determine if the element has been re-appended to a different + // element. + // In the interim, we will just re-render if that happens. It is more + // important than elements get garbage collected. + if (!this.removedFromDOM) { this.destroyElement(); } + this.invokeRecursively(function(view) { + if (view.clearRenderedChildren) { view.clearRenderedChildren(); } + }); + }, + elementId: null, -/** -@module ember -@submodule ember-views -*/ + /** + Attempts to discover the element in the parent element. The default + implementation looks for an element with an ID of `elementId` (or the + view's guid if `elementId` is null). You can override this method to + provide your own form of lookup. For example, if you want to discover your + element using a CSS class name instead of an ID. -/** - An `Ember.Component` is a view that is completely - isolated. Property access in its templates go - to the view object and actions are targeted at - the view object. There is no access to the - surrounding context or outer controller; all - contextual information is passed in. + @method findElementInParentElement + @param {DOMElement} parentElement The parent's DOM element + @return {DOMElement} The discovered element + */ + findElementInParentElement: function(parentElem) { + var id = "#" + this.elementId; + return jQuery(id)[0] || jQuery(id, parentElem)[0]; + }, - The easiest way to create an `Ember.Component` is via - a template. If you name a template - `components/my-foo`, you will be able to use - `{{my-foo}}` in other templates, which will make - an instance of the isolated component. + /** + Creates a DOM representation of the view and all of its + child views by recursively calling the `render()` method. - ```html - {{app-profile person=currentUser}} - ``` + After the element has been created, `didInsertElement` will + be called on this view and all of its child views. - ```html - -

    {{person.title}}

    - -

    {{person.signature}}

    - ``` + @method createElement + @return {Ember.View} receiver + */ + createElement: function() { + if (get(this, 'element')) { return this; } - You can also use `yield` inside a template to - include the **contents** of the custom tag: + var buffer = this.renderToBuffer(); + set(this, 'element', buffer.element()); - ```html - {{#app-profile person=currentUser}} -

    Admin mode

    - {{/app-profile}} - ``` + return this; + }, - ```html - -

    {{person.title}}

    - {{yield}} - ``` + /** + Called when a view is going to insert an element into the DOM. - If you want to customize the component, in order to - handle events or actions, you implement a subclass - of `Ember.Component` named after the name of the - component. Note that `Component` needs to be appended to the name of - your subclass like `AppProfileComponent`. + @event willInsertElement + */ + willInsertElement: Ember.K, - For example, you could implement the action - `hello` for the `app-profile` component: + /** + Called when the element of the view has been inserted into the DOM + or after the view was re-rendered. Override this function to do any + set up that requires an element in the document body. - ```javascript - App.AppProfileComponent = Ember.Component.extend({ - actions: { - hello: function(name) { - console.log("Hello", name); - } - } - }); - ``` + @event didInsertElement + */ + didInsertElement: Ember.K, - And then use it in the component's template: + /** + Called when the view is about to rerender, but before anything has + been torn down. This is a good opportunity to tear down any manual + observers you have installed based on the DOM state - ```html - + @event willClearRender + */ + willClearRender: Ember.K, -

    {{person.title}}

    - {{yield}} + /** + Run this callback on the current view (unless includeSelf is false) and recursively on child views. - - ``` + @method invokeRecursively + @param fn {Function} + @param includeSelf {Boolean} Includes itself if true. + @private + */ + invokeRecursively: function(fn, includeSelf) { + var childViews = (includeSelf === false) ? this._childViews : [this]; + var currentViews, view, currentChildViews; - Components must have a `-` in their name to avoid - conflicts with built-in controls that wrap HTML - elements. This is consistent with the same - requirement in web components. + while (childViews.length) { + currentViews = childViews.slice(); + childViews = []; - @class Component - @namespace Ember - @extends Ember.View -*/ -Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { - init: function() { - this._super(); - set(this, 'context', this); - set(this, 'controller', this); - }, + for (var i=0, l=currentViews.length; i` tag for views. + + @property tagName + @type String + @default null + */ + + // We leave this null by default so we can tell the difference between + // the default case and a user-specified tag. + tagName: null, + + /** + The WAI-ARIA role of the control represented by this view. For example, a + button may have a role of type 'button', or a pane may have a role of + type 'alertdialog'. This property is used by assistive software to help + visually challenged users navigate rich web applications. + + The full list of valid WAI-ARIA roles is available at: + [http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization) + + @property ariaRole + @type String + @default null + */ + ariaRole: null, + + /** + Standard CSS class names to apply to the view's outer element. This + property automatically inherits any class names defined by the view's + superclasses as well. + + @property classNames + @type Array + @default ['ember-view'] + */ + classNames: ['ember-view'], + + /** + A list of properties of the view to apply as class names. If the property + is a string value, the value of that string will be applied as a class + name. + + ```javascript + // Applies the 'high' class to the view element + Ember.View.extend({ + classNameBindings: ['priority'] + priority: 'high' + }); + ``` + + If the value of the property is a Boolean, the name of that property is + added as a dasherized class name. + + ```javascript + // Applies the 'is-urgent' class to the view element + Ember.View.extend({ + classNameBindings: ['isUrgent'] + isUrgent: true + }); + ``` + + If you would prefer to use a custom value instead of the dasherized + property name, you can pass a binding like this: + + ```javascript + // Applies the 'urgent' class to the view element + Ember.View.extend({ + classNameBindings: ['isUrgent:urgent'] + isUrgent: true + }); + ``` + + This list of properties is inherited from the view's superclasses as well. + + @property classNameBindings + @type Array + @default [] + */ + classNameBindings: EMPTY_ARRAY, + + /** + A list of properties of the view to apply as attributes. If the property is + a string value, the value of that string will be applied as the attribute. + + ```javascript + // Applies the type attribute to the element + // with the value "button", like
    + Ember.View.extend({ + attributeBindings: ['type'], + type: 'button' + }); + ``` + + If the value of the property is a Boolean, the name of that property is + added as an attribute. + + ```javascript + // Renders something like
    + Ember.View.extend({ + attributeBindings: ['enabled'], + enabled: true + }); + ``` + + @property attributeBindings + */ + attributeBindings: EMPTY_ARRAY, + + // ....................................................... + // CORE DISPLAY METHODS + // + + /** + Setup a view, but do not finish waking it up. + + * configure `childViews` + * register the view with the global views hash, which is used for event + dispatch + + @method init + @private + */ + init: function() { + this.elementId = this.elementId || guidFor(this); + + this._super(); + + // setup child views. be sure to clone the child views array first + this._childViews = this._childViews.slice(); + + Ember.assert("Only arrays are allowed for 'classNameBindings'", typeOf(this.classNameBindings) === 'array'); + this.classNameBindings = A(this.classNameBindings.slice()); + + Ember.assert("Only arrays are allowed for 'classNames'", typeOf(this.classNames) === 'array'); + this.classNames = A(this.classNames.slice()); + }, + + appendChild: function(view, options) { + return this.currentState.appendChild(this, view, options); + }, + + /** + Removes the child view from the parent view. + + @method removeChild + @param {Ember.View} view + @return {Ember.View} receiver + */ + removeChild: function(view) { + // If we're destroying, the entire subtree will be + // freed, and the DOM will be handled separately, + // so no need to mess with childViews. + if (this.isDestroying) { return; } + + // update parent node + set(view, '_parentView', null); + + // remove view from childViews array. + var childViews = this._childViews; + + a_removeObject(childViews, view); + + this.propertyDidChange('childViews'); // HUH?! what happened to will change? + + return this; + }, + + /** + Removes all children from the `parentView`. + + @method removeAllChildren + @return {Ember.View} receiver + */ + removeAllChildren: function() { + return this.mutateChildViews(function(parentView, view) { + parentView.removeChild(view); + }); + }, + + destroyAllChildren: function() { + return this.mutateChildViews(function(parentView, view) { + view.destroy(); + }); + }, + + /** + Removes the view from its `parentView`, if one is found. Otherwise + does nothing. + + @method removeFromParent + @return {Ember.View} receiver + */ + removeFromParent: function() { + var parent = this._parentView; + + // Remove DOM element from parent + this.remove(); + + if (parent) { parent.removeChild(this); } + return this; + }, + + /** + You must call `destroy` on a view to destroy the view (and all of its + child views). This will remove the view from any parent node, then make + sure that the DOM element managed by the view can be released by the + memory manager. + + @method destroy + */ + destroy: function() { + var childViews = this._childViews, + // get parentView before calling super because it'll be destroyed + nonVirtualParentView = get(this, 'parentView'), + viewName = this.viewName, + childLen, i; + + if (!this._super()) { return; } + + childLen = childViews.length; + for (i=childLen-1; i>=0; i--) { + childViews[i].removedFromDOM = true; + } + + // remove from non-virtual parent view if viewName was specified + if (viewName && nonVirtualParentView) { + nonVirtualParentView.set(viewName, null); + } + + childLen = childViews.length; + for (i=childLen-1; i>=0; i--) { + childViews[i].destroy(); + } + + return this; + }, + + /** + Instantiates a view to be added to the childViews array during view + initialization. You generally will not call this method directly unless + you are overriding `createChildViews()`. Note that this method will + automatically configure the correct settings on the new view instance to + act as a child of the parent. + + @method createChildView + @param {Class|String} viewClass + @param {Hash} [attrs] Attributes to add + @return {Ember.View} new instance + */ + createChildView: function(view, attrs) { + if (!view) { + throw new TypeError("createChildViews first argument must exist"); + } + + if (view.isView && view._parentView === this && view.container === this.container) { + return view; + } + + attrs = attrs || {}; + attrs._parentView = this; + + if (CoreView.detect(view)) { + attrs.templateData = attrs.templateData || get(this, 'templateData'); + + attrs.container = this.container; + view = view.create(attrs); + + // don't set the property on a virtual view, as they are invisible to + // consumers of the view API + if (view.viewName) { + set(get(this, 'concreteView'), view.viewName, view); + } + } else if ('string' === typeof view) { + var fullName = 'view:' + view; + var ViewKlass = this.container.lookupFactory(fullName); + + Ember.assert("Could not find view: '" + fullName + "'", !!ViewKlass); + + attrs.templateData = get(this, 'templateData'); + view = ViewKlass.create(attrs); } else { - this.triggerAction('stop'); + Ember.assert('You must pass instance or subclass of View', view.isView); + attrs.container = this.container; + + if (!get(view, 'templateData')) { + attrs.templateData = get(this, 'templateData'); + } + + setProperties(view, attrs); + } - } - }); - ``` - When used inside a template these component actions are configured to - trigger actions in the outer application context: + return view; + }, - ```handlebars - {{! application.hbs }} - {{play-button play="musicStarted" stop="musicStopped"}} - ``` + becameVisible: Ember.K, + becameHidden: Ember.K, - When the component receives a browser `click` event it translate this - interaction into application-specific semantics ("play" or "stop") and - triggers the specified action name on the controller for the template - where the component is used: + /** + When the view's `isVisible` property changes, toggle the visibility + element of the actual DOM element. + @method _isVisibleDidChange + @private + */ + _isVisibleDidChange: observer('isVisible', function() { + if (this._isVisible === get(this, 'isVisible')) { return ; } + run.scheduleOnce('render', this, this._toggleVisibility); + }), - ```javascript - App.ApplicationController = Ember.Controller.extend({ - actions: { - musicStarted: function(){ - // called when the play button is clicked - // and the music started playing - }, - musicStopped: function(){ - // called when the play button is clicked - // and the music stopped playing + _toggleVisibility: function() { + var $el = this.$(); + if (!$el) { return; } + + var isVisible = get(this, 'isVisible'); + + if (this._isVisible === isVisible) { return ; } + + $el.toggle(isVisible); + + this._isVisible = isVisible; + + if (this._isAncestorHidden()) { return; } + + if (isVisible) { + this._notifyBecameVisible(); + } else { + this._notifyBecameHidden(); } - } - }); - ``` + }, - If no action name is passed to `sendAction` a default name of "action" - is assumed. + _notifyBecameVisible: function() { + this.trigger('becameVisible'); - ```javascript - App.NextButtonComponent = Ember.Component.extend({ - click: function(){ - this.sendAction(); - } - }); - ``` + this.forEachChildView(function(view) { + var isVisible = get(view, 'isVisible'); - ```handlebars - {{! application.hbs }} - {{next-button action="playNextSongInAlbum"}} - ``` + if (isVisible || isVisible === null) { + view._notifyBecameVisible(); + } + }); + }, - ```javascript - App.ApplicationController = Ember.Controller.extend({ - actions: { - playNextSongInAlbum: function(){ - ... + _notifyBecameHidden: function() { + this.trigger('becameHidden'); + this.forEachChildView(function(view) { + var isVisible = get(view, 'isVisible'); + + if (isVisible || isVisible === null) { + view._notifyBecameHidden(); + } + }); + }, + + _isAncestorHidden: function() { + var parent = get(this, 'parentView'); + + while (parent) { + if (get(parent, 'isVisible') === false) { return true; } + + parent = get(parent, 'parentView'); } + + return false; + }, + + clearBuffer: function() { + this.invokeRecursively(nullViewsBuffer); + }, + + transitionTo: function(state, children) { + var priorState = this.currentState, + currentState = this.currentState = this.states[state]; + this.state = state; + + if (priorState && priorState.exit) { priorState.exit(this); } + if (currentState.enter) { currentState.enter(this); } + if (state === 'inDOM') { meta(this).cache.element = undefined; } + + if (children !== false) { + this.forEachChildView(function(view) { + view.transitionTo(state); + }); + } + }, + + // ....................................................... + // EVENT HANDLING + // + + /** + Handle events from `Ember.EventDispatcher` + + @method handleEvent + @param eventName {String} + @param evt {Event} + @private + */ + handleEvent: function(eventName, evt) { + return this.currentState.handleEvent(this, eventName, evt); + }, + + registerObserver: function(root, path, target, observer) { + if (!observer && 'function' === typeof target) { + observer = target; + target = null; + } + + if (!root || typeof root !== 'object') { + return; + } + + var view = this, + stateCheckedObserver = function() { + view.currentState.invokeObserver(this, observer); + }, + scheduledObserver = function() { + run.scheduleOnce('render', this, stateCheckedObserver); + }; + + addObserver(root, path, target, scheduledObserver); + + this.one('willClearRender', function() { + removeObserver(root, path, target, scheduledObserver); + }); } + }); - ``` - @method sendAction - @param [action] {String} the action to trigger - @param [context] {*} a context to send with the action - */ - sendAction: function(action) { - var actionName, - contexts = a_slice.call(arguments, 1); + /* + Describe how the specified actions should behave in the various + states that a view can exist in. Possible states: - // Send the default action - if (action === undefined) { - actionName = get(this, 'action'); - Ember.assert("The default action was triggered on the component " + this.toString() + ", but the action name (" + actionName + ") was not a string.", isNone(actionName) || typeof actionName === 'string'); - } else { - actionName = get(this, action); - Ember.assert("The " + action + " action was triggered on the component " + this.toString() + ", but the action name (" + actionName + ") was not a string.", isNone(actionName) || typeof actionName === 'string'); + * preRender: when a view is first instantiated, and after its + element was destroyed, it is in the preRender state + * inBuffer: once a view has been rendered, but before it has + been inserted into the DOM, it is in the inBuffer state + * hasElement: the DOM representation of the view is created, + and is ready to be inserted + * inDOM: once a view has been inserted into the DOM it is in + the inDOM state. A view spends the vast majority of its + existence in this state. + * destroyed: once a view has been destroyed (using the destroy + method), it is in this state. No further actions can be invoked + on a destroyed view. + */ + + // in the destroyed state, everything is illegal + + // before rendering has begun, all legal manipulations are noops. + + // inside the buffer, legal manipulations are done on the buffer + + // once the view has been inserted into the DOM, legal manipulations + // are done on the DOM element. + + function notifyMutationListeners() { + run.once(View, 'notifyMutationListeners'); } - // If no action name for that action could be found, just abort. - if (actionName === undefined) { return; } + var DOMManager = { + prepend: function(view, html) { + view.$().prepend(html); + notifyMutationListeners(); + }, - this.triggerAction({ - action: actionName, - actionContext: contexts + after: function(view, html) { + view.$().after(html); + notifyMutationListeners(); + }, + + html: function(view, html) { + view.$().html(html); + notifyMutationListeners(); + }, + + replace: function(view) { + var element = get(view, 'element'); + + set(view, 'element', null); + + view._insertElementLater(function() { + jQuery(element).replaceWith(get(view, 'element')); + notifyMutationListeners(); + }); + }, + + remove: function(view) { + view.$().remove(); + notifyMutationListeners(); + }, + + empty: function(view) { + view.$().empty(); + notifyMutationListeners(); + } + }; + + View.reopen({ + domManager: DOMManager }); - } -}); -})(); + View.reopenClass({ + /** + Parse a path and return an object which holds the parsed properties. + For example a path like "content.isEnabled:enabled:disabled" will return the + following object: -(function() { + ```javascript + { + path: "content.isEnabled", + className: "enabled", + falsyClassName: "disabled", + classNames: ":enabled:disabled" + } + ``` -})(); + @method _parsePropertyPath + @static + @private + */ + _parsePropertyPath: function(path) { + var split = path.split(':'), + propertyPath = split[0], + classNames = "", + className, + falsyClassName; + // check if the property is defined as prop:class or prop:trueClass:falseClass + if (split.length > 1) { + className = split[1]; + if (split.length === 3) { falsyClassName = split[2]; } + classNames = ':' + className; + if (falsyClassName) { classNames += ":" + falsyClassName; } + } -(function() { -/** -`Ember.ViewTargetActionSupport` is a mixin that can be included in a -view class to add a `triggerAction` method with semantics similar to -the Handlebars `{{action}}` helper. It provides intelligent defaults -for the action's target: the view's controller; and the context that is -sent with the action: the view's context. + return { + path: propertyPath, + classNames: classNames, + className: (className === '') ? undefined : className, + falsyClassName: falsyClassName + }; + }, -Note: In normal Ember usage, the `{{action}}` helper is usually the best -choice. This mixin is most often useful when you are doing more complex -event handling in custom View subclasses. + /** + Get the class name for a given value, based on the path, optional + `className` and optional `falsyClassName`. -For example: + - if a `className` or `falsyClassName` has been specified: + - if the value is truthy and `className` has been specified, + `className` is returned + - if the value is falsy and `falsyClassName` has been specified, + `falsyClassName` is returned + - otherwise `null` is returned + - if the value is `true`, the dasherized last part of the supplied path + is returned + - if the value is not `false`, `undefined` or `null`, the `value` + is returned + - if none of the above rules apply, `null` is returned -```javascript -App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, { - action: 'save', - click: function() { - this.triggerAction(); // Sends the `save` action, along with the current context - // to the current controller - } -}); -``` + @method _classStringForValue + @param path + @param val + @param className + @param falsyClassName + @static + @private + */ + _classStringForValue: function(path, val, className, falsyClassName) { + // When using the colon syntax, evaluate the truthiness or falsiness + // of the value to determine which className to return + if (className || falsyClassName) { + if (className && !!val) { + return className; -The `action` can be provided as properties of an optional object argument -to `triggerAction` as well. + } else if (falsyClassName && !val) { + return falsyClassName; -```javascript -App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, { - click: function() { - this.triggerAction({ - action: 'save' - }); // Sends the `save` action, along with the current context - // to the current controller - } -}); -``` + } else { + return null; + } -@class ViewTargetActionSupport -@namespace Ember -@extends Ember.TargetActionSupport -*/ -Ember.ViewTargetActionSupport = Ember.Mixin.create(Ember.TargetActionSupport, { - /** - @property target - */ - target: Ember.computed.alias('controller'), - /** - @property actionContext - */ - actionContext: Ember.computed.alias('context') -}); + // If value is a Boolean and true, return the dasherized property + // name. + } else if (val === true) { + // Normalize property path to be suitable for use + // as a class name. For exaple, content.foo.barBaz + // becomes bar-baz. + var parts = path.split('.'); + return dasherize(parts[parts.length-1]); -})(); + // If the value is not false, undefined, or null, return the current + // value of the property. + } else if (val !== false && val != null) { + return val; + // Nothing to display. Return null so that the old class is removed + // but no new class is added. + } else { + return null; + } + } + }); + var mutation = EmberObject.extend(Evented).create(); -(function() { + View.addMutationListener = function(callback) { + mutation.on('change', callback); + }; -})(); + View.removeMutationListener = function(callback) { + mutation.off('change', callback); + }; + View.notifyMutationListeners = function() { + mutation.trigger('change'); + }; + /** + Global views hash -(function() { -/** -Ember Views + @property views + @static + @type Hash + */ + View.views = {}; -@module ember -@submodule ember-views -@requires ember-runtime -@main ember-views -*/ + // If someone overrides the child views computed property when + // defining their class, we want to be able to process the user's + // supplied childViews and then restore the original computed property + // at view initialization time. This happens in Ember.ContainerView's init + // method. + View.childViewsProperty = childViewsProperty; + View.applyAttributeBindings = function(elem, name, value) { + var type = typeOf(value); + + // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js + if (name !== 'value' && (type === 'string' || (type === 'number' && !isNaN(value)))) { + if (value !== elem.attr(name)) { + elem.attr(name, value); + } + } else if (name === 'value' || type === 'boolean') { + if (isNone(value) || value === false) { + // `null`, `undefined` or `false` should remove attribute + elem.removeAttr(name); + elem.prop(name, ''); + } else if (value !== elem.prop(name)) { + // value should always be properties + elem.prop(name, value); + } + } else if (!value) { + elem.removeAttr(name); + } + }; + + __exports__.CoreView = CoreView; + __exports__.View = View; + __exports__.ViewCollection = ViewCollection; + }); })(); (function() { @@ -23682,21 +27594,28 @@ define("metamorph", "use strict"; // ========================================================================== // Project: metamorph - // Copyright: ©2011 My Company Inc. All rights reserved. + // Copyright: ©2014 Tilde, Inc. All rights reserved. // ========================================================================== var K = function() {}, guid = 0, - document = this.document, - disableRange = ('undefined' === typeof ENV ? {} : ENV).DISABLE_RANGE_API, + disableRange = (function(){ + if ('undefined' !== typeof MetamorphENV) { + return MetamorphENV.DISABLE_RANGE_API; + } else if ('undefined' !== ENV) { + return ENV.DISABLE_RANGE_API; + } else { + return false; + } + })(), // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges - supportsRange = (!disableRange) && document && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, + supportsRange = (!disableRange) && typeof document !== 'undefined' && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, // 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 ­ - needsShy = document && (function() { + needsShy = typeof document !== 'undefined' && (function() { var testEl = document.createElement('div'); testEl.innerHTML = "
    "; testEl.firstChild.innerHTML = ""; @@ -23800,7 +27719,7 @@ define("metamorph", /** * @public - * + * * Remove this object (including starting and ending * placeholders). * @@ -24036,6 +27955,10 @@ define("metamorph", // swallow some of the content. node = firstNodeFor(start.parentNode, html); + if (outerToo) { + start.parentNode.removeChild(start); + } + // copy the nodes for the HTML between the starting and ending // placeholder. while (node) { @@ -24148,5093 +28071,11529 @@ define("metamorph", })(); (function() { -/** -@module ember -@submodule ember-handlebars-compiler -*/ - -// Eliminate dependency on any Ember to simplify precompilation workflow -var objectCreate = Object.create || function(parent) { - function F() {} - F.prototype = parent; - return new F(); -}; - -var Handlebars = this.Handlebars || (Ember.imports && Ember.imports.Handlebars); -if (!Handlebars && typeof require === 'function') { - Handlebars = require('handlebars'); -} - -Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1. Include a SCRIPT tag in the HTML HEAD linking to the Handlebars file before you link to Ember.", Handlebars); -Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1, COMPILER_REVISION expected: 4, got: " + Handlebars.COMPILER_REVISION + " - Please note: Builds of master may have other COMPILER_REVISION values.", Handlebars.COMPILER_REVISION === 4); - -/** - Prepares the Handlebars templating library for use inside Ember's view - system. - - The `Ember.Handlebars` object is the standard Handlebars library, extended to - use Ember's `get()` method instead of direct property access, which allows - computed properties to be used inside templates. - - To create an `Ember.Handlebars` template, call `Ember.Handlebars.compile()`. - This will return a function that can be used by `Ember.View` for rendering. - - @class Handlebars - @namespace Ember -*/ -Ember.Handlebars = objectCreate(Handlebars); - -/** - Register a bound helper or custom view helper. - - ## Simple bound helper example - - ```javascript - Ember.Handlebars.helper('capitalize', function(value) { - return value.toUpperCase(); - }); - ``` - - The above bound helper can be used inside of templates as follows: - - ```handlebars - {{capitalize name}} - ``` - - In this case, when the `name` property of the template's context changes, - the rendered value of the helper will update to reflect this change. - - For more examples of bound helpers, see documentation for - `Ember.Handlebars.registerBoundHelper`. - - ## Custom view helper example - - Assuming a view subclass named `App.CalendarView` were defined, a helper - for rendering instances of this view could be registered as follows: - - ```javascript - Ember.Handlebars.helper('calendar', App.CalendarView): - ``` - - The above bound helper can be used inside of templates as follows: - - ```handlebars - {{calendar}} - ``` - - Which is functionally equivalent to: - - ```handlebars - {{view App.CalendarView}} - ``` - - Options in the helper will be passed to the view in exactly the same - manner as with the `view` helper. - - @method helper - @for Ember.Handlebars - @param {String} name - @param {Function|Ember.View} function or view class constructor - @param {String} dependentKeys* -*/ -Ember.Handlebars.helper = function(name, value) { - Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", !Ember.Component.detect(value) || name.match(/-/)); - - if (Ember.View.detect(value)) { - Ember.Handlebars.registerHelper(name, Ember.Handlebars.makeViewHelper(value)); - } else { - Ember.Handlebars.registerBoundHelper.apply(null, arguments); - } -}; - -/** - @private - - Returns a helper function that renders the provided ViewClass. - - Used internally by Ember.Handlebars.helper and other methods - involving helper/component registration. - - @method helper - @for Ember.Handlebars - @param {Function} ViewClass view class constructor -*/ -Ember.Handlebars.makeViewHelper = function(ViewClass) { - return function(options) { - Ember.assert("You can only pass attributes (such as name=value) not bare values to a helper for a View", arguments.length < 2); - return Ember.Handlebars.helpers.view.call(this, ViewClass, options); - }; -}; - -/** -@class helpers -@namespace Ember.Handlebars -*/ -Ember.Handlebars.helpers = objectCreate(Handlebars.helpers); - -/** - Override the the opcode compiler and JavaScript compiler for Handlebars. - - @class Compiler - @namespace Ember.Handlebars - @private - @constructor -*/ -Ember.Handlebars.Compiler = function() {}; - -// Handlebars.Compiler doesn't exist in runtime-only -if (Handlebars.Compiler) { - Ember.Handlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype); -} - -Ember.Handlebars.Compiler.prototype.compiler = Ember.Handlebars.Compiler; - -/** - @class JavaScriptCompiler - @namespace Ember.Handlebars - @private - @constructor -*/ -Ember.Handlebars.JavaScriptCompiler = function() {}; - -// Handlebars.JavaScriptCompiler doesn't exist in runtime-only -if (Handlebars.JavaScriptCompiler) { - Ember.Handlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype); - Ember.Handlebars.JavaScriptCompiler.prototype.compiler = Ember.Handlebars.JavaScriptCompiler; -} - - -Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars"; - -Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() { - return "''"; -}; - -/** - @private - - Override the default buffer for Ember Handlebars. By default, Handlebars - creates an empty String at the beginning of each invocation and appends to - it. Ember's Handlebars overrides this to append to a single shared buffer. - - @method appendToBuffer - @param string {String} -*/ -Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) { - return "data.buffer.push("+string+");"; -}; - -// Hacks ahead: -// Handlebars presently has a bug where the `blockHelperMissing` hook -// doesn't get passed the name of the missing helper name, but rather -// gets passed the value of that missing helper evaluated on the current -// context, which is most likely `undefined` and totally useless. -// -// So we alter the compiled template function to pass the name of the helper -// instead, as expected. -// -// This can go away once the following is closed: -// https://github.com/wycats/handlebars.js/issues/617 - -var DOT_LOOKUP_REGEX = /helpers\.(.*?)\)/, - BRACKET_STRING_LOOKUP_REGEX = /helpers\['(.*?)'/, - INVOCATION_SPLITTING_REGEX = /(.*blockHelperMissing\.call\(.*)(stack[0-9]+)(,.*)/; - -Ember.Handlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation = function(source) { - var helperInvocation = source[source.length - 1], - helperName = (DOT_LOOKUP_REGEX.exec(helperInvocation) || BRACKET_STRING_LOOKUP_REGEX.exec(helperInvocation))[1], - matches = INVOCATION_SPLITTING_REGEX.exec(helperInvocation); - - source[source.length - 1] = matches[1] + "'" + helperName + "'" + matches[3]; -} -var stringifyBlockHelperMissing = Ember.Handlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation; - -var originalBlockValue = Ember.Handlebars.JavaScriptCompiler.prototype.blockValue; -Ember.Handlebars.JavaScriptCompiler.prototype.blockValue = function() { - originalBlockValue.apply(this, arguments); - stringifyBlockHelperMissing(this.source); -}; - -var originalAmbiguousBlockValue = Ember.Handlebars.JavaScriptCompiler.prototype.ambiguousBlockValue; -Ember.Handlebars.JavaScriptCompiler.prototype.ambiguousBlockValue = function() { - originalAmbiguousBlockValue.apply(this, arguments); - stringifyBlockHelperMissing(this.source); -}; - -var prefix = "ember" + (+new Date()), incr = 1; - -/** - @private - - Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that - all simple mustaches in Ember's Handlebars will also set up an observer to - keep the DOM up to date when the underlying property changes. - - @method mustache - @for Ember.Handlebars.Compiler - @param mustache -*/ -Ember.Handlebars.Compiler.prototype.mustache = function(mustache) { - if (mustache.isHelper && mustache.id.string === 'control') { - mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]); - mustache.hash.pairs.push(["controlID", new Handlebars.AST.StringNode(prefix + incr++)]); - } else if (mustache.params.length || mustache.hash) { - // no changes required - } else { - var id = new Handlebars.AST.IdNode([{ part: '_triageMustache' }]); - - // Update the mustache node to include a hash value indicating whether the original node - // was escaped. This will allow us to properly escape values when the underlying value - // changes and we need to re-render the value. - if (!mustache.escaped) { - mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]); - mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]); - } - mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped); - } - - return Handlebars.Compiler.prototype.mustache.call(this, mustache); -}; - -/** - Used for precompilation of Ember Handlebars templates. This will not be used - during normal app execution. - - @method precompile - @for Ember.Handlebars - @static - @param {String} string The template to precompile -*/ -Ember.Handlebars.precompile = function(string) { - var ast = Handlebars.parse(string); - - var options = { - knownHelpers: { - action: true, - unbound: true, - bindAttr: true, - template: true, - view: true, - _triageMustache: true - }, - data: true, - stringParams: true - }; - - var environment = new Ember.Handlebars.Compiler().compile(ast, options); - return new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true); -}; - -// We don't support this for Handlebars runtime-only -if (Handlebars.compile) { - /** - The entry point for Ember Handlebars. This replaces the default - `Handlebars.compile` and turns on template-local data and String - parameters. - - @method compile - @for Ember.Handlebars - @static - @param {String} string The template to compile - @return {Function} - */ - Ember.Handlebars.compile = function(string) { - var ast = Handlebars.parse(string); - var options = { data: true, stringParams: true }; - var environment = new Ember.Handlebars.Compiler().compile(ast, options); - var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true); - - var template = Ember.Handlebars.template(templateSpec); - template.isMethod = false; //Make sure we don't wrap templates with ._super - - return template; - }; -} - - -})(); - -(function() { -var slice = Array.prototype.slice, - originalTemplate = Ember.Handlebars.template; - -/** - @private - - If a path starts with a reserved keyword, returns the root - that should be used. - - @method normalizePath - @for Ember - @param root {Object} - @param path {String} - @param data {Hash} -*/ -var normalizePath = Ember.Handlebars.normalizePath = function(root, path, data) { - var keywords = (data && data.keywords) || {}, - keyword, isKeyword; - - // Get the first segment of the path. For example, if the - // path is "foo.bar.baz", returns "foo". - keyword = path.split('.', 1)[0]; - - // Test to see if the first path is a keyword that has been - // passed along in the view's data hash. If so, we will treat - // that object as the new root. - if (keywords.hasOwnProperty(keyword)) { - // Look up the value in the template's data hash. - root = keywords[keyword]; - isKeyword = true; - - // Handle cases where the entire path is the reserved - // word. In that case, return the object itself. - if (path === keyword) { - path = ''; - } else { - // Strip the keyword from the path and look up - // the remainder from the newly found root. - path = path.substr(keyword.length+1); - } - } - - return { root: root, path: path, isKeyword: isKeyword }; -}; - - -/** - Lookup both on root and on window. If the path starts with - a keyword, the corresponding object will be looked up in the - template's data hash and used to resolve the path. - - @method get - @for Ember.Handlebars - @param {Object} root The object to look up the property on - @param {String} path The path to be lookedup - @param {Object} options The template's option hash -*/ -var handlebarsGet = Ember.Handlebars.get = function(root, path, options) { - var data = options && options.data, - normalizedPath = normalizePath(root, path, data), - value; - - // In cases where the path begins with a keyword, change the - // root to the value represented by that keyword, and ensure - // the path is relative to it. - root = normalizedPath.root; - path = normalizedPath.path; - - value = Ember.get(root, path); - - // If the path starts with a capital letter, look it up on Ember.lookup, - // which defaults to the `window` object in browsers. - if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) { - value = Ember.get(Ember.lookup, path); - } - return value; -}; - -Ember.Handlebars.resolveParams = function(context, params, options) { - var resolvedParams = [], types = options.types, param, type; - - for (var i=0, l=params.length; isomeString
    ') - ``` - - @method htmlSafe - @for Ember.String - @static - @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars -*/ -Ember.String.htmlSafe = function(str) { - return new Handlebars.SafeString(str); -}; - -var htmlSafe = Ember.String.htmlSafe; - -if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { - - /** - Mark a string as being safe for unescaped output with Handlebars. - - ```javascript - '
    someString
    '.htmlSafe() - ``` - - See [Ember.String.htmlSafe](/api/classes/Ember.String.html#method_htmlSafe). - - @method htmlSafe - @for String - @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars - */ - String.prototype.htmlSafe = function() { - return htmlSafe(this); - }; -} - -})(); - - - -(function() { -Ember.Handlebars.resolvePaths = function(options) { - var ret = [], - contexts = options.contexts, - roots = options.roots, - data = options.data; - - for (var i=0, l=contexts.length; i - ``` - - The above handlebars template will fill the ``'s `src` attribute will - the value of the property referenced with `"imageUrl"` and its `alt` - attribute with the value of the property referenced with `"imageTitle"`. - - If the rendering context of this template is the following object: - - ```javascript - { - imageUrl: 'http://lolcats.info/haz-a-funny', - imageTitle: 'A humorous image of a cat' - } - ``` - - The resulting HTML output will be: - - ```html - A humorous image of a cat - ``` - - `bind-attr` cannot redeclare existing DOM element attributes. The use of `src` - in the following `bind-attr` example will be ignored and the hard coded value - of `src="/failwhale.gif"` will take precedence: - - ```handlebars - imageTitle - ``` - - ### `bind-attr` and the `class` attribute - - `bind-attr` supports a special syntax for handling a number of cases unique - to the `class` DOM element attribute. The `class` attribute combines - multiple discrete values into a single attribute as a space-delimited - list of strings. Each string can be: - - * a string return value of an object's property. - * a boolean return value of an object's property - * a hard-coded value - - A string return value works identically to other uses of `bind-attr`. The - return value of the property will become the value of the attribute. For - example, the following view and template: - - ```javascript - AView = Ember.View.extend({ - someProperty: function() { - return "aValue"; - }.property() - }) - ``` - - ```handlebars - - ``` - - A boolean return value will insert a specified class name if the property - returns `true` and remove the class name if the property returns `false`. - - A class name is provided via the syntax - `somePropertyName:class-name-if-true`. - - ```javascript - AView = Ember.View.extend({ - someBool: true - }) - ``` - - ```handlebars - - ``` - - Result in the following rendered output: - - ```html - - ``` - - An additional section of the binding can be provided if you want to - replace the existing class instead of removing it when the boolean - value changes: - - ```handlebars - - ``` - - A hard-coded value can be used by prepending `:` to the desired - class name: `:class-name-to-always-apply`. - - ```handlebars - - ``` - - Results in the following rendered output: - - ```html - - ``` - - All three strategies - string return value, boolean return value, and - hard-coded value – can be combined in a single declaration: - - ```handlebars - - ``` - - @method bind-attr - @for Ember.Handlebars.helpers - @param {Hash} options - @return {String} HTML string -*/ -EmberHandlebars.registerHelper('bind-attr', function(options) { - - var attrs = options.hash; - - Ember.assert("You must specify at least one hash argument to bind-attr", !!Ember.keys(attrs).length); - - var view = options.data.view; - var ret = []; - var ctx = this; - - // Generate a unique id for this element. This will be added as a - // data attribute to the element so it can be looked up when - // the bound property changes. - var dataId = ++Ember.uuid; - - // Handle classes differently, as we can bind multiple classes - var classBindings = attrs['class']; - if (classBindings != null) { - var classResults = EmberHandlebars.bindClasses(this, classBindings, view, dataId, options); - - ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"'); - delete attrs['class']; - } - - var attrKeys = Ember.keys(attrs); - - // For each attribute passed, create an observer and emit the - // current value of the property as an attribute. - forEach.call(attrKeys, function(attr) { - var path = attrs[attr], - normalized; - - Ember.assert(fmt("You must provide an expression as the value of bound attribute. You specified: %@=%@", [attr, path]), typeof path === 'string'); - - normalized = normalizePath(ctx, path, options.data); - - var value = (path === 'this') ? normalized.root : handlebarsGet(ctx, path, options), - type = Ember.typeOf(value); - - Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean'); - - var observer, invoker; - - observer = function observer() { - var result = handlebarsGet(ctx, path, options); - - Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [result]), result === null || result === undefined || typeof result === 'number' || typeof result === 'string' || typeof result === 'boolean'); - - var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']"); - - // If we aren't able to find the element, it means the element - // to which we were bound has been removed from the view. - // In that case, we can assume the template has been re-rendered - // and we need to clean up the observer. - if (!elem || elem.length === 0) { - Ember.removeObserver(normalized.root, normalized.path, invoker); - return; - } - - Ember.View.applyAttributeBindings(elem, attr, result); - }; - - // Add an observer to the view for when the property changes. - // When the observer fires, find the element using the - // unique data id and update the attribute to the new value. - // Note: don't add observer when path is 'this' or path - // is whole keyword e.g. {{#each x in list}} ... {{bind-attr attr="x"}} - if (path !== 'this' && !(normalized.isKeyword && normalized.path === '' )) { - view.registerObserver(normalized.root, normalized.path, observer); - } - - // if this changes, also change the logic in ember-views/lib/views/view.js - if ((type === 'string' || (type === 'number' && !isNaN(value)))) { - ret.push(attr + '="' + Handlebars.Utils.escapeExpression(value) + '"'); - } else if (value && type === 'boolean') { - // The developer controls the attr name, so it should always be safe - ret.push(attr + '="' + attr + '"'); - } - }, this); - - // Add the unique identifier - // NOTE: We use all lower-case since Firefox has problems with mixed case in SVG - ret.push('data-bindattr-' + dataId + '="' + dataId + '"'); - return new EmberHandlebars.SafeString(ret.join(' ')); -}); - -/** - See `bind-attr` - - @method bindAttr - @for Ember.Handlebars.helpers - @deprecated - @param {Function} context - @param {Hash} options - @return {String} HTML string -*/ -EmberHandlebars.registerHelper('bindAttr', EmberHandlebars.helpers['bind-attr']); - -/** - @private - - Helper that, given a space-separated string of property paths and a context, - returns an array of class names. Calling this method also has the side - effect of setting up observers at those property paths, such that if they - change, the correct class name will be reapplied to the DOM element. - - For example, if you pass the string "fooBar", it will first look up the - "fooBar" value of the context. If that value is true, it will add the - "foo-bar" class to the current element (i.e., the dasherized form of - "fooBar"). If the value is a string, it will add that string as the class. - Otherwise, it will not add any new class name. - - @method bindClasses - @for Ember.Handlebars - @param {Ember.Object} context The context from which to lookup properties - @param {String} classBindings A string, space-separated, of class bindings - to use - @param {Ember.View} view The view in which observers should look for the - element to update - @param {Srting} bindAttrId Optional bindAttr id used to lookup elements - @return {Array} An array of class names to add -*/ -EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, options) { - var ret = [], newClass, value, elem; - - // Helper method to retrieve the property from the context and - // determine which class string to return, based on whether it is - // a Boolean or not. - var classStringForPath = function(root, parsedPath, options) { - var val, - path = parsedPath.path; - - if (path === 'this') { - val = root; - } else if (path === '') { - val = true; - } else { - val = handlebarsGet(root, path, options); - } - - return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName); - }; - - // For each property passed, loop through and setup - // an observer. - forEach.call(classBindings.split(' '), function(binding) { - - // Variable in which the old class value is saved. The observer function - // closes over this variable, so it knows which string to remove when - // the property changes. - var oldClass; - - var observer, invoker; - - var parsedPath = Ember.View._parsePropertyPath(binding), - path = parsedPath.path, - pathRoot = context, - normalized; - - if (path !== '' && path !== 'this') { - normalized = normalizePath(context, path, options.data); - - pathRoot = normalized.root; - path = normalized.path; - } - - // Set up an observer on the context. If the property changes, toggle the - // class name. - observer = function() { - // Get the current value of the property - newClass = classStringForPath(context, parsedPath, options); - elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$(); - - // If we can't find the element anymore, a parent template has been - // re-rendered and we've been nuked. Remove the observer. - if (!elem || elem.length === 0) { - Ember.removeObserver(pathRoot, path, invoker); - } else { - // If we had previously added a class to the element, remove it. - if (oldClass) { - elem.removeClass(oldClass); - } - - // If necessary, add a new class. Make sure we keep track of it so - // it can be removed in the future. - if (newClass) { - elem.addClass(newClass); - oldClass = newClass; - } else { - oldClass = null; - } - } - }; - - if (path !== '' && path !== 'this') { - view.registerObserver(pathRoot, path, observer); - } - - // We've already setup the observer; now we just need to figure out the - // correct behavior right now on the first pass through. - value = classStringForPath(context, parsedPath, options); - - if (value) { - ret.push(value); - - // Make sure we save the current value so that it can be removed if the - // observer fires. - oldClass = value; - } - }); - - return ret; -}; - - -})(); - - - -(function() { -/*globals Handlebars */ - -// TODO: Don't require the entire module -/** -@module ember -@submodule ember-handlebars -*/ - -var get = Ember.get, set = Ember.set; -var EmberHandlebars = Ember.Handlebars; -var LOWERCASE_A_Z = /^[a-z]/; -var VIEW_PREFIX = /^view\./; - -function makeBindings(thisContext, options) { - var hash = options.hash, - hashType = options.hashTypes; - - for (var prop in hash) { - if (hashType[prop] === 'ID') { - - var value = hash[prop]; - - if (Ember.IS_BINDING.test(prop)) { - Ember.warn("You're attempting to render a view by passing " + prop + "=" + value + " to a view helper, but this syntax is ambiguous. You should either surround " + value + " in quotes or remove `Binding` from " + prop + "."); - } else { - hash[prop + 'Binding'] = value; - hashType[prop + 'Binding'] = 'STRING'; - delete hash[prop]; - delete hashType[prop]; - } - } - } - - if (hash.hasOwnProperty('idBinding')) { - // id can't be bound, so just perform one-time lookup. - hash.id = EmberHandlebars.get(thisContext, hash.idBinding, options); - hashType.id = 'STRING'; - delete hash.idBinding; - delete hashType.idBinding; - } -} - -EmberHandlebars.ViewHelper = Ember.Object.create({ - - propertiesFromHTMLOptions: function(options) { - var hash = options.hash, data = options.data; - var extensions = {}, - classes = hash['class'], - dup = false; - - if (hash.id) { - extensions.elementId = hash.id; - dup = true; - } - - if (hash.tag) { - extensions.tagName = hash.tag; - dup = true; - } - - if (classes) { - classes = classes.split(' '); - extensions.classNames = classes; - dup = true; - } - - if (hash.classBinding) { - extensions.classNameBindings = hash.classBinding.split(' '); - dup = true; - } - - if (hash.classNameBindings) { - if (extensions.classNameBindings === undefined) extensions.classNameBindings = []; - extensions.classNameBindings = extensions.classNameBindings.concat(hash.classNameBindings.split(' ')); - dup = true; - } - - if (hash.attributeBindings) { - Ember.assert("Setting 'attributeBindings' via Handlebars is not allowed. Please subclass Ember.View and set it there instead."); - extensions.attributeBindings = null; - dup = true; - } - - if (dup) { - hash = Ember.$.extend({}, hash); - delete hash.id; - delete hash.tag; - delete hash['class']; - delete hash.classBinding; - } - - // Set the proper context for all bindings passed to the helper. This applies to regular attribute bindings - // as well as class name bindings. If the bindings are local, make them relative to the current context - // instead of the view. - var path; - - // Evaluate the context of regular attribute bindings: - for (var prop in hash) { - if (!hash.hasOwnProperty(prop)) { continue; } - - // Test if the property ends in "Binding" - if (Ember.IS_BINDING.test(prop) && typeof hash[prop] === 'string') { - path = this.contextualizeBindingPath(hash[prop], data); - if (path) { hash[prop] = path; } - } - } - - // Evaluate the context of class name bindings: - if (extensions.classNameBindings) { - for (var b in extensions.classNameBindings) { - var full = extensions.classNameBindings[b]; - if (typeof full === 'string') { - // Contextualize the path of classNameBinding so this: - // - // classNameBinding="isGreen:green" - // - // is converted to this: - // - // classNameBinding="_parentView.context.isGreen:green" - var parsedPath = Ember.View._parsePropertyPath(full); - path = this.contextualizeBindingPath(parsedPath.path, data); - if (path) { extensions.classNameBindings[b] = path + parsedPath.classNames; } - } - } - } - - return Ember.$.extend(hash, extensions); - }, - - // Transform bindings from the current context to a context that can be evaluated within the view. - // Returns null if the path shouldn't be changed. - // - // TODO: consider the addition of a prefix that would allow this method to return `path`. - contextualizeBindingPath: function(path, data) { - var normalized = Ember.Handlebars.normalizePath(null, path, data); - if (normalized.isKeyword) { - return 'templateData.keywords.' + path; - } else if (Ember.isGlobalPath(path)) { - return null; - } else if (path === 'this') { - return '_parentView.context'; - } else { - return '_parentView.context.' + path; - } - }, - - helper: function(thisContext, path, options) { - var data = options.data, - fn = options.fn, - newView; - - makeBindings(thisContext, options); - - if ('string' === typeof path) { - - // TODO: this is a lame conditional, this should likely change - // but something along these lines will likely need to be added - // as deprecation warnings - // - if (options.types[0] === 'STRING' && LOWERCASE_A_Z.test(path) && !VIEW_PREFIX.test(path)) { - Ember.assert("View requires a container", !!data.view.container); - newView = data.view.container.lookupFactory('view:' + path); - } else { - newView = EmberHandlebars.get(thisContext, path, options); - } - - Ember.assert("Unable to find view at path '" + path + "'", !!newView); - } else { - newView = path; - } - - Ember.assert(Ember.String.fmt('You must pass a view to the #view helper, not %@ (%@)', [path, newView]), Ember.View.detect(newView) || Ember.View.detectInstance(newView)); - - var viewOptions = this.propertiesFromHTMLOptions(options, thisContext); - var currentView = data.view; - viewOptions.templateData = data; - var newViewProto = newView.proto ? newView.proto() : newView; - - if (fn) { - Ember.assert("You cannot provide a template block if you also specified a templateName", !get(viewOptions, 'templateName') && !get(newViewProto, 'templateName')); - viewOptions.template = fn; - } - - // We only want to override the `_context` computed property if there is - // no specified controller. See View#_context for more information. - if (!newViewProto.controller && !newViewProto.controllerBinding && !viewOptions.controller && !viewOptions.controllerBinding) { - viewOptions._context = thisContext; - } - - currentView.appendChild(newView, viewOptions); - } -}); - -/** - `{{view}}` inserts a new instance of `Ember.View` into a template passing its - options to the `Ember.View`'s `create` method and using the supplied block as - the view's own template. - - An empty `` and the following template: - - ```handlebars - A span: - {{#view tagName="span"}} - hello. - {{/view}} - ``` - - Will result in HTML structure: - - ```html - - - -
    - A span: - - Hello. - -
    - - ``` - - ### `parentView` setting - - The `parentView` property of the new `Ember.View` instance created through - `{{view}}` will be set to the `Ember.View` instance of the template where - `{{view}}` was called. - - ```javascript - aView = Ember.View.create({ - template: Ember.Handlebars.compile("{{#view}} my parent: {{parentView.elementId}} {{/view}}") - }); - - aView.appendTo('body'); - ``` - - Will result in HTML structure: - - ```html -
    -
    - my parent: ember1 -
    -
    - ``` - - ### Setting CSS id and class attributes - - The HTML `id` attribute can be set on the `{{view}}`'s resulting element with - the `id` option. This option will _not_ be passed to `Ember.View.create`. - - ```handlebars - {{#view tagName="span" id="a-custom-id"}} - hello. - {{/view}} - ``` - - Results in the following HTML structure: - - ```html -
    - - hello. - -
    - ``` - - The HTML `class` attribute can be set on the `{{view}}`'s resulting element - with the `class` or `classNameBindings` options. The `class` option will - directly set the CSS `class` attribute and will not be passed to - `Ember.View.create`. `classNameBindings` will be passed to `create` and use - `Ember.View`'s class name binding functionality: - - ```handlebars - {{#view tagName="span" class="a-custom-class"}} - hello. - {{/view}} - ``` - - Results in the following HTML structure: - - ```html -
    - - hello. - -
    - ``` - - ### Supplying a different view class - - `{{view}}` can take an optional first argument before its supplied options to - specify a path to a custom view class. - - ```handlebars - {{#view "MyApp.CustomView"}} - hello. - {{/view}} - ``` - - The first argument can also be a relative path accessible from the current - context. - - ```javascript - MyApp = Ember.Application.create({}); - MyApp.OuterView = Ember.View.extend({ - innerViewClass: Ember.View.extend({ - classNames: ['a-custom-view-class-as-property'] - }), - template: Ember.Handlebars.compile('{{#view "view.innerViewClass"}} hi {{/view}}') - }); - - MyApp.OuterView.create().appendTo('body'); - ``` - - Will result in the following HTML: - - ```html -
    -
    - hi -
    -
    - ``` - - ### Blockless use - - If you supply a custom `Ember.View` subclass that specifies its own template - or provide a `templateName` option to `{{view}}` it can be used without - supplying a block. Attempts to use both a `templateName` option and supply a - block will throw an error. - - ```handlebars - {{view "MyApp.ViewWithATemplateDefined"}} - ``` - - ### `viewName` property - - You can supply a `viewName` option to `{{view}}`. The `Ember.View` instance - will be referenced as a property of its parent view by this name. - - ```javascript - aView = Ember.View.create({ - template: Ember.Handlebars.compile('{{#view viewName="aChildByName"}} hi {{/view}}') - }); - - aView.appendTo('body'); - aView.get('aChildByName') // the instance of Ember.View created by {{view}} helper - ``` - - @method view - @for Ember.Handlebars.helpers - @param {String} path - @param {Hash} options - @return {String} HTML string -*/ -EmberHandlebars.registerHelper('view', function(path, options) { - Ember.assert("The view helper only takes a single argument", arguments.length <= 2); - - // If no path is provided, treat path param as options. - if (path && path.data && path.data.isRenderData) { - options = path; - path = "Ember.View"; - } - - return EmberHandlebars.ViewHelper.helper(this, path, options); -}); - - -})(); - - - -(function() { -/*globals Handlebars */ - -// TODO: Don't require all of this module -/** -@module ember -@submodule ember-handlebars -*/ - -var get = Ember.get, handlebarsGet = Ember.Handlebars.get, fmt = Ember.String.fmt; - -/** - `{{collection}}` is a `Ember.Handlebars` helper for adding instances of - `Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html) - for additional information on how a `CollectionView` functions. - - `{{collection}}`'s primary use is as a block helper with a `contentBinding` - option pointing towards an `Ember.Array`-compatible object. An `Ember.View` - instance will be created for each item in its `content` property. Each view - will have its own `content` property set to the appropriate item in the - collection. - - The provided block will be applied as the template for each item's view. - - Given an empty `` the following template: - - ```handlebars - {{#collection contentBinding="App.items"}} - Hi {{view.content.name}} - {{/collection}} - ``` - - And the following application code - - ```javascript - App = Ember.Application.create() - App.items = [ - Ember.Object.create({name: 'Dave'}), - Ember.Object.create({name: 'Mary'}), - Ember.Object.create({name: 'Sara'}) - ] - ``` - - Will result in the HTML structure below - - ```html -
    -
    Hi Dave
    -
    Hi Mary
    -
    Hi Sara
    -
    - ``` - - ### Blockless Use - - If you provide an `itemViewClass` option that has its own `template` you can - omit the block. - - The following template: - - ```handlebars - {{collection contentBinding="App.items" itemViewClass="App.AnItemView"}} - ``` - - And application code - - ```javascript - App = Ember.Application.create(); - App.items = [ - Ember.Object.create({name: 'Dave'}), - Ember.Object.create({name: 'Mary'}), - Ember.Object.create({name: 'Sara'}) - ]; - - App.AnItemView = Ember.View.extend({ - template: Ember.Handlebars.compile("Greetings {{view.content.name}}") - }); - ``` - - Will result in the HTML structure below - - ```html -
    -
    Greetings Dave
    -
    Greetings Mary
    -
    Greetings Sara
    -
    - ``` - - ### Specifying a CollectionView subclass - - By default the `{{collection}}` helper will create an instance of - `Ember.CollectionView`. You can supply a `Ember.CollectionView` subclass to - the helper by passing it as the first argument: - - ```handlebars - {{#collection App.MyCustomCollectionClass contentBinding="App.items"}} - Hi {{view.content.name}} - {{/collection}} - ``` - - ### Forwarded `item.*`-named Options - - As with the `{{view}}`, helper options passed to the `{{collection}}` will be - set on the resulting `Ember.CollectionView` as properties. Additionally, - options prefixed with `item` will be applied to the views rendered for each - item (note the camelcasing): - - ```handlebars - {{#collection contentBinding="App.items" - itemTagName="p" - itemClassNames="greeting"}} - Howdy {{view.content.name}} - {{/collection}} - ``` - - Will result in the following HTML structure: - - ```html -
    -

    Howdy Dave

    -

    Howdy Mary

    -

    Howdy Sara

    -
    - ``` - - @method collection - @for Ember.Handlebars.helpers - @param {String} path - @param {Hash} options - @return {String} HTML string - @deprecated Use `{{each}}` helper instead. -*/ -Ember.Handlebars.registerHelper('collection', function(path, options) { - Ember.deprecate("Using the {{collection}} helper without specifying a class has been deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection'); - - // If no path is provided, treat path param as options. - if (path && path.data && path.data.isRenderData) { - options = path; - path = undefined; - Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1); - } else { - Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2); - } - - var fn = options.fn; - var data = options.data; - var inverse = options.inverse; - var view = options.data.view; - - // If passed a path string, convert that into an object. - // Otherwise, just default to the standard class. - var collectionClass; - collectionClass = path ? handlebarsGet(this, path, options) : Ember.CollectionView; - Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass); - - var hash = options.hash, itemHash = {}, match; - - // Extract item view class if provided else default to the standard class - var collectionPrototype = collectionClass.proto(), - itemViewClass; - - if (hash.itemView) { - var controller = data.keywords.controller; - Ember.assert('You specified an itemView, but the current context has no container to look the itemView up in. This probably means that you created a view manually, instead of through the container. Instead, use container.lookup("view:viewName"), which will properly instantiate your view.', controller && controller.container); - var container = controller.container; - itemViewClass = container.resolve('view:' + hash.itemView); - Ember.assert('You specified the itemView ' + hash.itemView + ", but it was not found at " + container.describe("view:" + hash.itemView) + " (and it was not registered in the container)", !!itemViewClass); - } else if (hash.itemViewClass) { - itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options); - } else { - itemViewClass = collectionPrototype.itemViewClass; - } - - Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewClass]), !!itemViewClass); - - delete hash.itemViewClass; - delete hash.itemView; - - // Go through options passed to the {{collection}} helper and extract options - // that configure item views instead of the collection itself. - for (var prop in hash) { - if (hash.hasOwnProperty(prop)) { - match = prop.match(/^item(.)(.*)$/); - - if (match && prop !== 'itemController') { - // Convert itemShouldFoo -> shouldFoo - itemHash[match[1].toLowerCase() + match[2]] = hash[prop]; - // Delete from hash as this will end up getting passed to the - // {{view}} helper method. - delete hash[prop]; - } - } - } - - if (fn) { - itemHash.template = fn; - delete options.fn; - } - - var emptyViewClass; - if (inverse && inverse !== Handlebars.VM.noop) { - emptyViewClass = get(collectionPrototype, 'emptyViewClass'); - emptyViewClass = emptyViewClass.extend({ - template: inverse, - tagName: itemHash.tagName - }); - } else if (hash.emptyViewClass) { - emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options); - } - if (emptyViewClass) { hash.emptyView = emptyViewClass; } - - if (!hash.keyword) { - itemHash._context = Ember.computed.alias('content'); - } - - var viewOptions = Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this); - hash.itemViewClass = itemViewClass.extend(viewOptions); - - return Ember.Handlebars.helpers.view.call(this, collectionClass, options); -}); - - -})(); - - - -(function() { -/*globals Handlebars */ -/** -@module ember -@submodule ember-handlebars -*/ - -var handlebarsGet = Ember.Handlebars.get; - -/** - `unbound` allows you to output a property without binding. *Important:* The - output will not be updated if the property changes. Use with caution. - - ```handlebars -
    {{unbound somePropertyThatDoesntChange}}
    - ``` - - `unbound` can also be used in conjunction with a bound helper to - render it in its unbound form: - - ```handlebars -
    {{unbound helperName somePropertyThatDoesntChange}}
    - ``` - - @method unbound - @for Ember.Handlebars.helpers - @param {String} property - @return {String} HTML string -*/ -Ember.Handlebars.registerHelper('unbound', function(property, fn) { - var options = arguments[arguments.length - 1], helper, context, out; - - if (arguments.length > 2) { - // Unbound helper call. - options.data.isUnbound = true; - helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helperMissing; - out = helper.apply(this, Array.prototype.slice.call(arguments, 1)); - delete options.data.isUnbound; - return out; - } - - context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this; - return handlebarsGet(context, property, fn); -}); - -})(); - - - -(function() { -/*jshint debug:true*/ -/** -@module ember -@submodule ember-handlebars -*/ - -var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath; - -/** - `log` allows you to output the value of a variable in the current rendering - context. - - ```handlebars - {{log myVariable}} - ``` - - @method log - @for Ember.Handlebars.helpers - @param {String} property -*/ -Ember.Handlebars.registerHelper('log', function(property, options) { - var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this, - normalized = normalizePath(context, property, options.data), - pathRoot = normalized.root, - path = normalized.path, - value = (path === 'this') ? pathRoot : handlebarsGet(pathRoot, path, options); - Ember.Logger.log(value); -}); - -/** - Execute the `debugger` statement in the current context. - - ```handlebars - {{debugger}} - ``` - - @method debugger - @for Ember.Handlebars.helpers - @param {String} property -*/ -Ember.Handlebars.registerHelper('debugger', function(options) { - debugger; -}); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-handlebars -*/ - -var get = Ember.get, set = Ember.set; - -Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, { - init: function() { - var itemController = get(this, 'itemController'); - var binding; - - if (itemController) { - var controller = get(this, 'controller.container').lookupFactory('controller:array').create({ - parentController: get(this, 'controller'), - itemController: itemController, - target: get(this, 'controller'), - _eachView: this - }); - - this.disableContentObservers(function() { - set(this, 'content', controller); - binding = new Ember.Binding('content', '_eachView.dataSource').oneWay(); - binding.connect(controller); - }); - - set(this, '_arrayController', controller); - } else { - this.disableContentObservers(function() { - binding = new Ember.Binding('content', 'dataSource').oneWay(); - binding.connect(this); - }); - } - - return this._super(); - }, - - _assertArrayLike: function(content) { - Ember.assert("The value that #each loops over must be an Array. You passed " + content.constructor + ", but it should have been an ArrayController", !Ember.ControllerMixin.detect(content) || (content && content.isGenerated) || content instanceof Ember.ArrayController); - Ember.assert("The value that #each loops over must be an Array. You passed " + ((Ember.ControllerMixin.detect(content) && content.get('model') !== undefined) ? ("" + content.get('model') + " (wrapped in " + content + ")") : ("" + content)), Ember.Array.detect(content)); - }, - - disableContentObservers: function(callback) { - Ember.removeBeforeObserver(this, 'content', null, '_contentWillChange'); - Ember.removeObserver(this, 'content', null, '_contentDidChange'); - - callback.call(this); - - Ember.addBeforeObserver(this, 'content', null, '_contentWillChange'); - Ember.addObserver(this, 'content', null, '_contentDidChange'); - }, - - itemViewClass: Ember._MetamorphView, - emptyViewClass: Ember._MetamorphView, - - createChildView: function(view, attrs) { - view = this._super(view, attrs); - - // At the moment, if a container view subclass wants - // to insert keywords, it is responsible for cloning - // the keywords hash. This will be fixed momentarily. - var keyword = get(this, 'keyword'); - var content = get(view, 'content'); - - if (keyword) { - var data = get(view, 'templateData'); - - data = Ember.copy(data); - data.keywords = view.cloneKeywords(); - set(view, 'templateData', data); - - // In this case, we do not bind, because the `content` of - // a #each item cannot change. - data.keywords[keyword] = content; - } - - // If {{#each}} is looping over an array of controllers, - // point each child view at their respective controller. - if (content && get(content, 'isController')) { - set(view, 'controller', content); - } - - return view; - }, - - destroy: function() { - if (!this._super()) { return; } - - var arrayController = get(this, '_arrayController'); - - if (arrayController) { - arrayController.destroy(); - } - - return this; - } -}); - -var GroupedEach = Ember.Handlebars.GroupedEach = function(context, path, options) { - var self = this, - normalized = Ember.Handlebars.normalizePath(context, path, options.data); - - this.context = context; - this.path = path; - this.options = options; - this.template = options.fn; - this.containingView = options.data.view; - this.normalizedRoot = normalized.root; - this.normalizedPath = normalized.path; - this.content = this.lookupContent(); - - this.addContentObservers(); - this.addArrayObservers(); - - this.containingView.on('willClearRender', function() { - self.destroy(); - }); -}; - -GroupedEach.prototype = { - contentWillChange: function() { - this.removeArrayObservers(); - }, - - contentDidChange: function() { - this.content = this.lookupContent(); - this.addArrayObservers(); - this.rerenderContainingView(); - }, - - contentArrayWillChange: Ember.K, - - contentArrayDidChange: function() { - this.rerenderContainingView(); - }, - - lookupContent: function() { - return Ember.Handlebars.get(this.normalizedRoot, this.normalizedPath, this.options); - }, - - addArrayObservers: function() { - if (!this.content) { return; } - - this.content.addArrayObserver(this, { - willChange: 'contentArrayWillChange', - didChange: 'contentArrayDidChange' - }); - }, - - removeArrayObservers: function() { - if (!this.content) { return; } - - this.content.removeArrayObserver(this, { - willChange: 'contentArrayWillChange', - didChange: 'contentArrayDidChange' - }); - }, - - addContentObservers: function() { - Ember.addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange); - Ember.addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange); - }, - - removeContentObservers: function() { - Ember.removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange); - Ember.removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange); - }, - - render: function() { - if (!this.content) { return; } - - var content = this.content, - contentLength = get(content, 'length'), - data = this.options.data, - template = this.template; - - data.insideEach = true; - for (var i = 0; i < contentLength; i++) { - template(content.objectAt(i), { data: data }); - } - }, - - rerenderContainingView: function() { - var self = this; - Ember.run.scheduleOnce('render', this, function() { - // It's possible it's been destroyed after we enqueued a re-render call. - if (!self.destroyed) { - self.containingView.rerender(); - } }); - }, - destroy: function() { - this.removeContentObservers(); - if (this.content) { - this.removeArrayObservers(); + __exports__["default"] = ComponentLookup; + }); +define("ember-handlebars/controls", + ["ember-handlebars/controls/checkbox","ember-handlebars/controls/text_field","ember-handlebars/controls/text_area","ember-metal/core","ember-handlebars-compiler","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { + "use strict"; + var Checkbox = __dependency1__["default"]; + var TextField = __dependency2__["default"]; + var TextArea = __dependency3__["default"]; + + var Ember = __dependency4__["default"]; + // Ember.assert + // var emberAssert = Ember.assert; + + var EmberHandlebars = __dependency5__["default"]; + var helpers = EmberHandlebars.helpers; + /** + @module ember + @submodule ember-handlebars-compiler + */ + + /** + + The `{{input}}` helper inserts an HTML `` tag into the template, + with a `type` value of either `text` or `checkbox`. If no `type` is provided, + `text` will be the default value applied. The attributes of `{{input}}` + match those of the native HTML tag as closely as possible for these two types. + + ## Use as text field + An `{{input}}` with no `type` or a `type` of `text` will render an HTML text input. + The following HTML attributes can be set via the helper: + + + + + + + + + + + + +
    `readonly``required``autofocus`
    `value``placeholder``disabled`
    `size``tabindex``maxlength`
    `name``min``max`
    `pattern``accept``autocomplete`
    `autosave``formaction``formenctype`
    `formmethod``formnovalidate``formtarget`
    `height``inputmode``multiple`
    `step``width``form`
    `selectionDirection``spellcheck` 
    + + + When set to a quoted string, these values will be directly applied to the HTML + element. When left unquoted, these values will be bound to a property on the + template's current rendering context (most typically a controller instance). + + ## Unbound: + + ```handlebars + {{input value="http://www.facebook.com"}} + ``` + + + ```html + + ``` + + ## Bound: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + firstName: "Stanley", + entryNotAllowed: true + }); + ``` + + + ```handlebars + {{input type="text" value=firstName disabled=entryNotAllowed size="50"}} + ``` + + + ```html + + ``` + + ## Extension + + Internally, `{{input type="text"}}` creates an instance of `Ember.TextField`, passing + arguments from the helper to `Ember.TextField`'s `create` method. You can extend the + capablilties of text inputs in your applications by reopening this class. For example, + if you are deploying to browsers where the `required` attribute is used, you + can add this to the `TextField`'s `attributeBindings` property: + + + ```javascript + Ember.TextField.reopen({ + attributeBindings: ['required'] + }); + ``` + + Keep in mind when writing `Ember.TextField` subclasses that `Ember.TextField` + itself extends `Ember.Component`, meaning that it does NOT inherit + the `controller` of the parent view. + + See more about [Ember components](api/classes/Ember.Component.html) + + + ## Use as checkbox + + An `{{input}}` with a `type` of `checkbox` will render an HTML checkbox input. + The following HTML attributes can be set via the helper: + + * `checked` + * `disabled` + * `tabindex` + * `indeterminate` + * `name` + * `autofocus` + * `form` + + + When set to a quoted string, these values will be directly applied to the HTML + element. When left unquoted, these values will be bound to a property on the + template's current rendering context (most typically a controller instance). + + ## Unbound: + + ```handlebars + {{input type="checkbox" name="isAdmin"}} + ``` + + ```html + + ``` + + ## Bound: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + isAdmin: true + }); + ``` + + + ```handlebars + {{input type="checkbox" checked=isAdmin }} + ``` + + + ```html + + ``` + + ## Extension + + Internally, `{{input type="checkbox"}}` creates an instance of `Ember.Checkbox`, passing + arguments from the helper to `Ember.Checkbox`'s `create` method. You can extend the + capablilties of checkbox inputs in your applications by reopening this class. For example, + if you wanted to add a css class to all checkboxes in your application: + + + ```javascript + Ember.Checkbox.reopen({ + classNames: ['my-app-checkbox'] + }); + ``` + + + @method input + @for Ember.Handlebars.helpers + @param {Hash} options + */ + function inputHelper(options) { + Ember.assert('You can only pass attributes to the `input` helper, not arguments', arguments.length < 2); + + var hash = options.hash, + types = options.hashTypes, + inputType = hash.type, + onEvent = hash.on; + + delete hash.type; + delete hash.on; + + if (inputType === 'checkbox') { + Ember.assert("{{input type='checkbox'}} does not support setting `value=someBooleanValue`; you must use `checked=someBooleanValue` instead.", options.hashTypes.value !== 'ID'); + return helpers.view.call(this, Checkbox, options); + } else { + if (inputType) { hash.type = inputType; } + hash.onEvent = onEvent || 'enter'; + return helpers.view.call(this, TextField, options); + } } - this.destroyed = true; - } -}; -/** - The `{{#each}}` helper loops over elements in a collection, rendering its - block once for each item. It is an extension of the base Handlebars `{{#each}}` - helper: - - ```javascript - Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}]; - ``` - - ```handlebars - {{#each Developers}} - {{name}} - {{/each}} - ``` - - `{{each}}` supports an alternative syntax with element naming: - - ```handlebars - {{#each person in Developers}} - {{person.name}} - {{/each}} - ``` - - When looping over objects that do not have properties, `{{this}}` can be used - to render the object: - - ```javascript - DeveloperNames = ['Yehuda', 'Tom', 'Paul'] - ``` - - ```handlebars - {{#each DeveloperNames}} - {{this}} - {{/each}} - ``` - ### {{else}} condition - `{{#each}}` can have a matching `{{else}}`. The contents of this block will render - if the collection is empty. - - ``` - {{#each person in Developers}} - {{person.name}} - {{else}} -

    Sorry, nobody is available for this task.

    - {{/each}} - ``` - ### Specifying a View class for items - If you provide an `itemViewClass` option that references a view class - with its own `template` you can omit the block. - - The following template: - - ```handlebars - {{#view App.MyView }} - {{each view.items itemViewClass="App.AnItemView"}} - {{/view}} - ``` - - And application code - - ```javascript - App = Ember.Application.create({ - MyView: Ember.View.extend({ - items: [ - Ember.Object.create({name: 'Dave'}), - Ember.Object.create({name: 'Mary'}), - Ember.Object.create({name: 'Sara'}) - ] - }) - }); - - App.AnItemView = Ember.View.extend({ - template: Ember.Handlebars.compile("Greetings {{name}}") - }); - ``` - - Will result in the HTML structure below - - ```html -
    -
    Greetings Dave
    -
    Greetings Mary
    -
    Greetings Sara
    -
    - ``` - - If an `itemViewClass` is defined on the helper, and therefore the helper is not - being used as a block, an `emptyViewClass` can also be provided optionally. - The `emptyViewClass` will match the behavior of the `{{else}}` condition - described above. That is, the `emptyViewClass` will render if the collection - is empty. - - ### Representing each item with a Controller. - By default the controller lookup within an `{{#each}}` block will be - the controller of the template where the `{{#each}}` was used. If each - item needs to be presented by a custom controller you can provide a - `itemController` option which references a controller by lookup name. - Each item in the loop will be wrapped in an instance of this controller - and the item itself will be set to the `content` property of that controller. - - This is useful in cases where properties of model objects need transformation - or synthesis for display: - - ```javascript - App.DeveloperController = Ember.ObjectController.extend({ - isAvailableForHire: function() { - return !this.get('content.isEmployed') && this.get('content.isSeekingWork'); - }.property('isEmployed', 'isSeekingWork') - }) - ``` - - ```handlebars - {{#each person in developers itemController="developer"}} - {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}} - {{/each}} - ``` - - Each itemController will receive a reference to the current controller as - a `parentController` property. - - ### (Experimental) Grouped Each - - When used in conjunction with the experimental [group helper](https://github.com/emberjs/group-helper), - you can inform Handlebars to re-render an entire group of items instead of - re-rendering them one at a time (in the event that they are changed en masse - or an item is added/removed). - - ```handlebars - {{#group}} - {{#each people}} - {{firstName}} {{lastName}} - {{/each}} - {{/group}} - ``` - - This can be faster than the normal way that Handlebars re-renders items - in some cases. - - If for some reason you have a group with more than one `#each`, you can make - one of the collections be updated in normal (non-grouped) fashion by setting - the option `groupedRows=true` (counter-intuitive, I know). - - For example, - - ```handlebars - {{dealershipName}} - - {{#group}} - {{#each dealers}} - {{firstName}} {{lastName}} - {{/each}} - - {{#each car in cars groupedRows=true}} - {{car.make}} {{car.model}} {{car.color}} - {{/each}} - {{/group}} - ``` - Any change to `dealershipName` or the `dealers` collection will cause the - entire group to be re-rendered. However, changes to the `cars` collection - will be re-rendered individually (as normal). - - Note that `group` behavior is also disabled by specifying an `itemViewClass`. - - @method each - @for Ember.Handlebars.helpers - @param [name] {String} name for item (used with `in`) - @param [path] {String} path - @param [options] {Object} Handlebars key/value pairs of options - @param [options.itemViewClass] {String} a path to a view class used for each item - @param [options.itemController] {String} name of a controller to be created for each item - @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper -*/ -Ember.Handlebars.registerHelper('each', function(path, options) { - if (arguments.length === 4) { - Ember.assert("If you pass more than one argument to the each helper, it must be in the form #each foo in bar", arguments[1] === "in"); - - var keywordName = arguments[0]; - - options = arguments[3]; - path = arguments[2]; - if (path === '') { path = "this"; } - - options.hash.keyword = keywordName; - } - - if (arguments.length === 1) { - options = path; - path = 'this'; - } - - options.hash.dataSourceBinding = path; - // Set up emptyView as a metamorph with no tag - //options.hash.emptyViewClass = Ember._MetamorphView; - - if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) { - new Ember.Handlebars.GroupedEach(this, path, options).render(); - } else { - return Ember.Handlebars.helpers.collection.call(this, 'Ember.Handlebars.EachView', options); - } -}); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-handlebars -*/ - -/** - `template` allows you to render a template from inside another template. - This allows you to re-use the same template in multiple places. For example: - - ```html - - ``` - - ```html - - ``` - - ```handlebars - {{#if isUser}} - {{template "user_info"}} - {{else}} - {{template "unlogged_user_info"}} - {{/if}} - ``` - - This helper looks for templates in the global `Ember.TEMPLATES` hash. If you - add ` - ``` - - Take note that `"welcome"` is a string and not an object - reference. - - @method loc - @for Ember.Handlebars.helpers - @param {String} str The string to format -*/ - -Ember.Handlebars.registerHelper('loc', function(str) { - return Ember.String.loc(str); -}); - -})(); - - - -(function() { - -})(); - - - -(function() { - -})(); - - - -(function() { -/** -@module ember -@submodule ember-handlebars -*/ - -var set = Ember.set, get = Ember.get; - -/** - The internal class used to create text inputs when the `{{input}}` - helper is used with `type` of `checkbox`. - - See Handlebars.helpers.input for usage details. - - ## Direct manipulation of `checked` - - The `checked` attribute of an `Ember.Checkbox` object should always be set - through the Ember object or by interacting with its rendered element - representation via the mouse, keyboard, or touch. Updating the value of the - checkbox via jQuery will result in the checked value of the object and its - element losing synchronization. - - ## Layout and LayoutName properties - - Because HTML `input` elements are self closing `layout` and `layoutName` - properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s - layout section for more information. - - @class Checkbox - @namespace Ember - @extends Ember.View -*/ -Ember.Checkbox = Ember.View.extend({ - classNames: ['ember-checkbox'], - - tagName: 'input', - - attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name'], - - type: "checkbox", - checked: false, - disabled: false, - indeterminate: false, - - init: function() { - this._super(); - this.on("change", this, this._updateElementValue); - }, - - didInsertElement: function() { - this._super(); - this.get('element').indeterminate = !!this.get('indeterminate'); - }, - - _updateElementValue: function() { - set(this, 'checked', this.$().prop('checked')); - } -}); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-handlebars -*/ - -var get = Ember.get, set = Ember.set; - -/** - Shared mixin used by `Ember.TextField` and `Ember.TextArea`. - - @class TextSupport - @namespace Ember - @private -*/ -Ember.TextSupport = Ember.Mixin.create({ - value: "", - - attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly'], - placeholder: null, - disabled: false, - maxlength: null, - - init: function() { - this._super(); - this.on("focusOut", this, this._elementValueDidChange); - this.on("change", this, this._elementValueDidChange); - this.on("paste", this, this._elementValueDidChange); - this.on("cut", this, this._elementValueDidChange); - this.on("input", this, this._elementValueDidChange); - this.on("keyUp", this, this.interpretKeyEvents); - }, - - /** - The action to be sent when the user presses the return key. - - This is similar to the `{{action}}` helper, but is fired when - the user presses the return key when editing a text field, and sends - the value of the field as the context. - - @property action - @type String - @default null - */ - action: null, - - /** - The event that should send the action. - - Options are: - - * `enter`: the user pressed enter - * `keyPress`: the user pressed a key - - @property onEvent - @type String - @default enter - */ - onEvent: 'enter', - - /** - Whether they `keyUp` event that triggers an `action` to be sent continues - propagating to other views. - - By default, when the user presses the return key on their keyboard and - the text field has an `action` set, the action will be sent to the view's - controller and the key event will stop propagating. - - If you would like parent views to receive the `keyUp` event even after an - action has been dispatched, set `bubbles` to true. - - @property bubbles - @type Boolean - @default false - */ - bubbles: false, - - interpretKeyEvents: function(event) { - var map = Ember.TextSupport.KEY_EVENTS; - var method = map[event.keyCode]; - - this._elementValueDidChange(); - if (method) { return this[method](event); } - }, - - _elementValueDidChange: function() { - set(this, 'value', this.$().val()); - }, - - /** - The action to be sent when the user inserts a new line. - - Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13. - Uses sendAction to send the `enter` action to the controller. - - @method insertNewline - @param {Event} event - */ - insertNewline: function(event) { - sendAction('enter', this, event); - sendAction('insert-newline', this, event); - }, - - /** - Called when the user hits escape. - - Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 27. - Uses sendAction to send the `escape-press` action to the controller. - - @method cancel - @param {Event} event - */ - cancel: function(event) { - sendAction('escape-press', this, event); - }, - - /** - Called when the text area is focused. - - @method focusIn - @param {Event} event - */ - focusIn: function(event) { - sendAction('focus-in', this, event); - }, - - /** - Called when the text area is blurred. - - @method focusOut - @param {Event} event - */ - focusOut: function(event) { - sendAction('focus-out', this, event); - }, - - /** - The action to be sent when the user presses a key. Enabled by setting - the `onEvent` property to `keyPress`. - - Uses sendAction to send the `keyPress` action to the controller. - - @method keyPress - @param {Event} event - */ - keyPress: function(event) { - sendAction('key-press', this, event); - } - -}); - -Ember.TextSupport.KEY_EVENTS = { - 13: 'insertNewline', - 27: 'cancel' -}; - -// In principle, this shouldn't be necessary, but the legacy -// sectionAction semantics for TextField are different from -// the component semantics so this method normalizes them. -function sendAction(eventName, view, event) { - var action = get(view, eventName), - on = get(view, 'onEvent'), - value = get(view, 'value'); - - // back-compat support for keyPress as an event name even though - // it's also a method name that consumes the event (and therefore - // incompatible with sendAction semantics). - if (on === eventName || (on === 'keyPress' && eventName === 'key-press')) { - view.sendAction('action', value); - } - - view.sendAction(eventName, value); - - if (action || on === eventName) { - if(!get(view, 'bubbles')) { - event.stopPropagation(); - } - } -} - -})(); - - - -(function() { -/** -@module ember -@submodule ember-handlebars -*/ - -var get = Ember.get, set = Ember.set; - -/** - - The internal class used to create text inputs when the `{{input}}` - helper is used with `type` of `text`. - - See [handlebars.helpers.input](Ember.Handlebars.helpers.html#method_input) for usage details. - - ## Layout and LayoutName properties - - Because HTML `input` elements are self closing `layout` and `layoutName` - properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s - layout section for more information. - - @class TextField - @namespace Ember - @extends Ember.Component - @uses Ember.TextSupport -*/ -Ember.TextField = Ember.Component.extend(Ember.TextSupport, - /** @scope Ember.TextField.prototype */ { - - classNames: ['ember-text-field'], - tagName: "input", - attributeBindings: ['type', 'value', 'size', 'pattern', 'name'], - - /** - The `value` attribute of the input element. As the user inputs text, this - property is updated live. - - @property value - @type String - @default "" - */ - value: "", - - /** - The `type` attribute of the input element. - - @property type - @type String - @default "text" - */ - type: "text", - - /** - The `size` of the text field in characters. - - @property size - @type String - @default null - */ - size: null, - - /** - The `pattern` the pattern attribute of input element. - - @property pattern - @type String - @default null - */ - pattern: null -}); - -})(); - - - -(function() { -/* -@module ember -@submodule ember-handlebars -*/ - -var get = Ember.get, set = Ember.set; - -/* - @class Button - @namespace Ember - @extends Ember.View - @uses Ember.TargetActionSupport - @deprecated -*/ -Ember.Button = Ember.View.extend(Ember.TargetActionSupport, { - classNames: ['ember-button'], - classNameBindings: ['isActive'], - - tagName: 'button', - - propagateEvents: false, - - attributeBindings: ['type', 'disabled', 'href', 'tabindex'], - - /* - @private - - Overrides `TargetActionSupport`'s `targetObject` computed - property to use Handlebars-specific path resolution. - - @property targetObject - */ - targetObject: Ember.computed(function() { - var target = get(this, 'target'), - root = get(this, 'context'), - data = get(this, 'templateData'); - - if (typeof target !== 'string') { return target; } - - return Ember.Handlebars.get(root, target, { data: data }); - }).property('target'), - - // Defaults to 'button' if tagName is 'input' or 'button' - type: Ember.computed(function(key) { - var tagName = this.tagName; - if (tagName === 'input' || tagName === 'button') { return 'button'; } - }), - - disabled: false, - - // Allow 'a' tags to act like buttons - href: Ember.computed(function() { - return this.tagName === 'a' ? '#' : null; - }), - - mouseDown: function() { - if (!get(this, 'disabled')) { - set(this, 'isActive', true); - this._mouseDown = true; - this._mouseEntered = true; - } - return get(this, 'propagateEvents'); - }, - - mouseLeave: function() { - if (this._mouseDown) { - set(this, 'isActive', false); - this._mouseEntered = false; - } - }, - - mouseEnter: function() { - if (this._mouseDown) { - set(this, 'isActive', true); - this._mouseEntered = true; - } - }, - - mouseUp: function(event) { - if (get(this, 'isActive')) { - // Actually invoke the button's target and action. - // This method comes from the Ember.TargetActionSupport mixin. - this.triggerAction(); - set(this, 'isActive', false); - } - - this._mouseDown = false; - this._mouseEntered = false; - return get(this, 'propagateEvents'); - }, - - keyDown: function(event) { - // Handle space or enter - if (event.keyCode === 13 || event.keyCode === 32) { - this.mouseDown(); - } - }, - - keyUp: function(event) { - // Handle space or enter - if (event.keyCode === 13 || event.keyCode === 32) { - this.mouseUp(); - } - }, - - // TODO: Handle proper touch behavior. Including should make inactive when - // finger moves more than 20x outside of the edge of the button (vs mouse - // which goes inactive as soon as mouse goes out of edges.) - - touchStart: function(touch) { - return this.mouseDown(touch); - }, - - touchEnd: function(touch) { - return this.mouseUp(touch); - }, - - init: function() { - Ember.deprecate("Ember.Button is deprecated and will be removed from future releases. Consider using the `{{action}}` helper."); - this._super(); - } -}); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-handlebars -*/ - -var get = Ember.get, set = Ember.set; - -/** - The internal class used to create textarea element when the `{{textarea}}` - helper is used. - - See [handlebars.helpers.textarea](/api/classes/Ember.Handlebars.helpers.html#method_textarea) for usage details. - - ## Layout and LayoutName properties - - Because HTML `textarea` elements do not contain inner HTML the `layout` and - `layoutName` properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s - layout section for more information. - - @class TextArea - @namespace Ember - @extends Ember.Component - @uses Ember.TextSupport -*/ -Ember.TextArea = Ember.Component.extend(Ember.TextSupport, { - classNames: ['ember-text-area'], - - tagName: "textarea", - attributeBindings: ['rows', 'cols', 'name'], - rows: null, - cols: null, - - _updateElementValue: Ember.observer('value', function() { - // We do this check so cursor position doesn't get affected in IE - var value = get(this, 'value'), - $el = this.$(); - if ($el && value !== $el.val()) { - $el.val(value); - } - }), - - init: function() { - this._super(); - this.on("didInsertElement", this, this._updateElementValue); - } - -}); - -})(); - - - -(function() { -/*jshint eqeqeq:false */ - -/** -@module ember -@submodule ember-handlebars -*/ - -var set = Ember.set, - get = Ember.get, - indexOf = Ember.EnumerableUtils.indexOf, - indexesOf = Ember.EnumerableUtils.indexesOf, - forEach = Ember.EnumerableUtils.forEach, - replace = Ember.EnumerableUtils.replace, - isArray = Ember.isArray, - precompileTemplate = Ember.Handlebars.compile; - -Ember.SelectOption = Ember.View.extend({ - tagName: 'option', - attributeBindings: ['value', 'selected'], - - defaultTemplate: function(context, options) { - options = { data: options.data, hash: {} }; - Ember.Handlebars.helpers.bind.call(context, "view.label", options); - }, - - init: function() { - this.labelPathDidChange(); - this.valuePathDidChange(); - - this._super(); - }, - - selected: Ember.computed(function() { - var content = get(this, 'content'), - selection = get(this, 'parentView.selection'); - if (get(this, 'parentView.multiple')) { - return selection && indexOf(selection, content.valueOf()) > -1; - } else { - // Primitives get passed through bindings as objects... since - // `new Number(4) !== 4`, we use `==` below - return content == selection; - } - }).property('content', 'parentView.selection'), - - labelPathDidChange: Ember.observer('parentView.optionLabelPath', function() { - var labelPath = get(this, 'parentView.optionLabelPath'); - - if (!labelPath) { return; } - - Ember.defineProperty(this, 'label', Ember.computed(function() { - return get(this, labelPath); - }).property(labelPath)); - }), - - valuePathDidChange: Ember.observer('parentView.optionValuePath', function() { - var valuePath = get(this, 'parentView.optionValuePath'); - - if (!valuePath) { return; } - - Ember.defineProperty(this, 'value', Ember.computed(function() { - return get(this, valuePath); - }).property(valuePath)); - }) -}); - -Ember.SelectOptgroup = Ember.CollectionView.extend({ - tagName: 'optgroup', - attributeBindings: ['label'], - - selectionBinding: 'parentView.selection', - multipleBinding: 'parentView.multiple', - optionLabelPathBinding: 'parentView.optionLabelPath', - optionValuePathBinding: 'parentView.optionValuePath', - - itemViewClassBinding: 'parentView.optionView' -}); - -/** - The `Ember.Select` view class renders a - [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element, - allowing the user to choose from a list of options. - - The text and `value` property of each ` - - - ``` + itemViewClassBinding: 'parentView.optionView' + }); - The `value` attribute of the selected ` - - - ``` + When using objects you need to tell the `Ember.Select` which property should + be accessed on each object to supply the `value` attribute of the `"); return buffer; } function program3(depth0,data) { - var stack1, hashTypes, hashContexts; - hashTypes = {}; - hashContexts = {}; - stack1 = helpers.each.call(depth0, "view.groupedContent", {hash:{},inverse:self.noop,fn:self.program(4, program4, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}); + var stack1; + stack1 = helpers.each.call(depth0, "view.groupedContent", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(4, program4, data),contexts:[depth0],types:["ID"],data:data}); if(stack1 || stack1 === 0) { data.buffer.push(stack1); } else { data.buffer.push(''); } } function program4(depth0,data) { - var hashContexts, hashTypes; - hashContexts = {'content': depth0,'label': depth0}; - hashTypes = {'content': "ID",'label': "ID"}; + data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.groupView", {hash:{ 'content': ("content"), 'label': ("label") - },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); + },hashTypes:{'content': "ID",'label': "ID"},hashContexts:{'content': depth0,'label': depth0},contexts:[depth0],types:["ID"],data:data}))); } function program6(depth0,data) { - var stack1, hashTypes, hashContexts; - hashTypes = {}; - hashContexts = {}; - stack1 = helpers.each.call(depth0, "view.content", {hash:{},inverse:self.noop,fn:self.program(7, program7, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}); + var stack1; + stack1 = helpers.each.call(depth0, "view.content", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(7, program7, data),contexts:[depth0],types:["ID"],data:data}); if(stack1 || stack1 === 0) { data.buffer.push(stack1); } else { data.buffer.push(''); } } function program7(depth0,data) { - var hashContexts, hashTypes; - hashContexts = {'content': depth0}; - hashTypes = {'content': "ID"}; + data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", {hash:{ 'content': ("") - },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); + },hashTypes:{'content': "ID"},hashContexts:{'content': depth0},contexts:[depth0],types:["ID"],data:data}))); } - hashTypes = {}; - hashContexts = {}; - stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}); + stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],data:data}); if(stack1 || stack1 === 0) { data.buffer.push(stack1); } - hashTypes = {}; - hashContexts = {}; - stack1 = helpers['if'].call(depth0, "view.optionGroupPath", {hash:{},inverse:self.program(6, program6, data),fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}); + stack1 = helpers['if'].call(depth0, "view.optionGroupPath", {hash:{},hashTypes:{},hashContexts:{},inverse:self.program(6, program6, data),fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],data:data}); if(stack1 || stack1 === 0) { data.buffer.push(stack1); } return buffer; }), - attributeBindings: ['multiple', 'disabled', 'tabindex', 'name'], + attributeBindings: ['multiple', 'disabled', 'tabindex', 'name', 'required', 'autofocus', + 'form', 'size'], - /** - The `multiple` attribute of the select element. Indicates whether multiple - options can be selected. + /** + The `multiple` attribute of the select element. Indicates whether multiple + options can be selected. - @property multiple - @type Boolean - @default false - */ - multiple: false, + @property multiple + @type Boolean + @default false + */ + multiple: false, - /** - The `disabled` attribute of the select element. Indicates whether - the element is disabled from interactions. + /** + The `disabled` attribute of the select element. Indicates whether + the element is disabled from interactions. - @property disabled - @type Boolean - @default false - */ - disabled: false, + @property disabled + @type Boolean + @default false + */ + disabled: false, - /** - The list of options. + /** + The `required` attribute of the select element. Indicates whether + a selected option is required for form validation. - If `optionLabelPath` and `optionValuePath` are not overridden, this should - be a list of strings, which will serve simultaneously as labels and values. + @property required + @type Boolean + @default false + */ + required: false, - Otherwise, this should be a list of objects. For instance: + /** + The list of options. - ```javascript - Ember.Select.create({ - content: Ember.A([ - { id: 1, firstName: 'Yehuda' }, - { id: 2, firstName: 'Tom' } - ]), - optionLabelPath: 'content.firstName', - optionValuePath: 'content.id' + If `optionLabelPath` and `optionValuePath` are not overridden, this should + be a list of strings, which will serve simultaneously as labels and values. + + Otherwise, this should be a list of objects. For instance: + + ```javascript + Ember.Select.create({ + content: A([ + { id: 1, firstName: 'Yehuda' }, + { id: 2, firstName: 'Tom' } + ]), + optionLabelPath: 'content.firstName', + optionValuePath: 'content.id' + }); + ``` + + @property content + @type Array + @default null + */ + content: null, + + /** + When `multiple` is `false`, the element of `content` that is currently + selected, if any. + + When `multiple` is `true`, an array of such elements. + + @property selection + @type Object or Array + @default null + */ + selection: null, + + /** + In single selection mode (when `multiple` is `false`), value can be used to + get the current selection's value or set the selection by it's value. + + It is not currently supported in multiple selection mode. + + @property value + @type String + @default null + */ + value: computed(function(key, value) { + if (arguments.length === 2) { return value; } + var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''); + return valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection'); + }).property('selection'), + + /** + If given, a top-most dummy option will be rendered to serve as a user + prompt. + + @property prompt + @type String + @default null + */ + prompt: null, + + /** + The path of the option labels. See [content](/api/classes/Ember.Select.html#property_content). + + @property optionLabelPath + @type String + @default 'content' + */ + optionLabelPath: 'content', + + /** + The path of the option values. See [content](/api/classes/Ember.Select.html#property_content). + + @property optionValuePath + @type String + @default 'content' + */ + optionValuePath: 'content', + + /** + The path of the option group. + When this property is used, `content` should be sorted by `optionGroupPath`. + + @property optionGroupPath + @type String + @default null + */ + optionGroupPath: null, + + /** + The view class for optgroup. + + @property groupView + @type Ember.View + @default Ember.SelectOptgroup + */ + groupView: SelectOptgroup, + + groupedContent: computed(function() { + var groupPath = get(this, 'optionGroupPath'); + var groupedContent = A(); + var content = get(this, 'content') || []; + + forEach(content, function(item) { + var label = get(item, groupPath); + + if (get(groupedContent, 'lastObject.label') !== label) { + groupedContent.pushObject({ + label: label, + content: A() + }); + } + + get(groupedContent, 'lastObject.content').push(item); + }); + + return groupedContent; + }).property('optionGroupPath', 'content.@each'), + + /** + The view class for option. + + @property optionView + @type Ember.View + @default Ember.SelectOption + */ + optionView: SelectOption, + + _change: function() { + if (get(this, 'multiple')) { + this._changeMultiple(); + } else { + this._changeSingle(); + } + }, + + selectionDidChange: observer('selection.@each', function() { + var selection = get(this, 'selection'); + if (get(this, 'multiple')) { + if (!isArray(selection)) { + set(this, 'selection', A([selection])); + return; + } + this._selectionDidChangeMultiple(); + } else { + this._selectionDidChangeSingle(); + } + }), + + valueDidChange: observer('value', function() { + var content = get(this, 'content'), + value = get(this, 'value'), + valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''), + selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection')), + selection; + + if (value !== selectedValue) { + selection = content ? content.find(function(obj) { + return value === (valuePath ? get(obj, valuePath) : obj); + }) : null; + + this.set('selection', selection); + } + }), + + + _triggerChange: function() { + var selection = get(this, 'selection'); + var value = get(this, 'value'); + + if (!isNone(selection)) { this.selectionDidChange(); } + if (!isNone(value)) { this.valueDidChange(); } + + this._change(); + }, + + _changeSingle: function() { + var selectedIndex = this.$()[0].selectedIndex, + content = get(this, 'content'), + prompt = get(this, 'prompt'); + + if (!content || !get(content, 'length')) { return; } + if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; } + + if (prompt) { selectedIndex -= 1; } + set(this, 'selection', content.objectAt(selectedIndex)); + }, + + + _changeMultiple: function() { + var options = this.$('option:selected'), + prompt = get(this, 'prompt'), + offset = prompt ? 1 : 0, + content = get(this, 'content'), + selection = get(this, 'selection'); + + if (!content) { return; } + if (options) { + var selectedIndexes = options.map(function() { + return this.index - offset; + }).toArray(); + var newSelection = content.objectsAt(selectedIndexes); + + if (isArray(selection)) { + replace(selection, 0, get(selection, 'length'), newSelection); + } else { + set(this, 'selection', newSelection); + } + } + }, + + _selectionDidChangeSingle: function() { + var el = this.get('element'); + if (!el) { return; } + + var content = get(this, 'content'), + selection = get(this, 'selection'), + selectionIndex = content ? indexOf(content, selection) : -1, + prompt = get(this, 'prompt'); + + if (prompt) { selectionIndex += 1; } + if (el) { el.selectedIndex = selectionIndex; } + }, + + _selectionDidChangeMultiple: function() { + var content = get(this, 'content'), + selection = get(this, 'selection'), + selectedIndexes = content ? indexesOf(content, selection) : [-1], + prompt = get(this, 'prompt'), + offset = prompt ? 1 : 0, + options = this.$('option'), + adjusted; + + if (options) { + options.each(function() { + adjusted = this.index > -1 ? this.index - offset : -1; + this.selected = indexOf(selectedIndexes, adjusted) > -1; + }); + } + }, + + init: function() { + this._super(); + this.on("didInsertElement", this, this._triggerChange); + this.on("change", this, this._change); + } }); + + __exports__["default"] = Select + __exports__.Select = Select; + __exports__.SelectOption = SelectOption; + __exports__.SelectOptgroup = SelectOptgroup; + }); +define("ember-handlebars/controls/text_area", + ["ember-metal/property_get","ember-views/views/component","ember-handlebars/controls/text_support","ember-metal/mixin","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + + /** + @module ember + @submodule ember-handlebars + */ + var get = __dependency1__.get; + var Component = __dependency2__["default"]; + var TextSupport = __dependency3__["default"]; + var observer = __dependency4__.observer; + + /** + The internal class used to create textarea element when the `{{textarea}}` + helper is used. + + See [handlebars.helpers.textarea](/api/classes/Ember.Handlebars.helpers.html#method_textarea) for usage details. + + ## Layout and LayoutName properties + + Because HTML `textarea` elements do not contain inner HTML the `layout` and + `layoutName` properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s + layout section for more information. + + @class TextArea + @namespace Ember + @extends Ember.Component + @uses Ember.TextSupport + */ + var TextArea = Component.extend(TextSupport, { + classNames: ['ember-text-area'], + + tagName: "textarea", + attributeBindings: ['rows', 'cols', 'name', 'selectionEnd', 'selectionStart', 'wrap'], + rows: null, + cols: null, + + _updateElementValue: observer('value', function() { + // We do this check so cursor position doesn't get affected in IE + var value = get(this, 'value'), + $el = this.$(); + if ($el && value !== $el.val()) { + $el.val(value); + } + }), + + init: function() { + this._super(); + this.on("didInsertElement", this, this._updateElementValue); + } + + }); + + __exports__["default"] = TextArea; + }); +define("ember-handlebars/controls/text_field", + ["ember-metal/property_get","ember-metal/property_set","ember-views/views/component","ember-handlebars/controls/text_support","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-handlebars + */ + + var get = __dependency1__.get; + var set = __dependency2__.set; + var Component = __dependency3__["default"]; + var TextSupport = __dependency4__["default"]; + + /** + + The internal class used to create text inputs when the `{{input}}` + helper is used with `type` of `text`. + + See [Handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details. + + ## Layout and LayoutName properties + + Because HTML `input` elements are self closing `layout` and `layoutName` + properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s + layout section for more information. + + @class TextField + @namespace Ember + @extends Ember.Component + @uses Ember.TextSupport + */ + var TextField = Component.extend(TextSupport, { + + classNames: ['ember-text-field'], + tagName: "input", + attributeBindings: ['type', 'value', 'size', 'pattern', 'name', 'min', 'max', + 'accept', 'autocomplete', 'autosave', 'formaction', + 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', + 'height', 'inputmode', 'list', 'multiple', 'pattern', 'step', + 'width'], + + /** + The `value` attribute of the input element. As the user inputs text, this + property is updated live. + + @property value + @type String + @default "" + */ + value: "", + + /** + The `type` attribute of the input element. + + @property type + @type String + @default "text" + */ + type: "text", + + /** + The `size` of the text field in characters. + + @property size + @type String + @default null + */ + size: null, + + /** + The `pattern` attribute of input element. + + @property pattern + @type String + @default null + */ + pattern: null, + + /** + The `min` attribute of input element used with `type="number"` or `type="range"`. + + @property min + @type String + @default null + */ + min: null, + + /** + The `max` attribute of input element used with `type="number"` or `type="range"`. + + @property max + @type String + @default null + */ + max: null + }); + + __exports__["default"] = TextField; + }); +define("ember-handlebars/controls/text_support", + ["ember-metal/property_get","ember-metal/property_set","ember-metal/mixin","ember-runtime/mixins/target_action_support","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-handlebars + */ + + var get = __dependency1__.get; + var set = __dependency2__.set; + var Mixin = __dependency3__.Mixin; + var TargetActionSupport = __dependency4__["default"]; + + /** + Shared mixin used by `Ember.TextField` and `Ember.TextArea`. + + @class TextSupport + @namespace Ember + @uses Ember.TargetActionSupport + @extends Ember.Mixin + @private + */ + var TextSupport = Mixin.create(TargetActionSupport, { + value: "", + + attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly', + 'autofocus', 'form', 'selectionDirection', 'spellcheck', 'required'], + placeholder: null, + disabled: false, + maxlength: null, + + init: function() { + this._super(); + this.on("focusOut", this, this._elementValueDidChange); + this.on("change", this, this._elementValueDidChange); + this.on("paste", this, this._elementValueDidChange); + this.on("cut", this, this._elementValueDidChange); + this.on("input", this, this._elementValueDidChange); + this.on("keyUp", this, this.interpretKeyEvents); + }, + + /** + The action to be sent when the user presses the return key. + + This is similar to the `{{action}}` helper, but is fired when + the user presses the return key when editing a text field, and sends + the value of the field as the context. + + @property action + @type String + @default null + */ + action: null, + + /** + The event that should send the action. + + Options are: + + * `enter`: the user pressed enter + * `keyPress`: the user pressed a key + + @property onEvent + @type String + @default enter + */ + onEvent: 'enter', + + /** + Whether they `keyUp` event that triggers an `action` to be sent continues + propagating to other views. + + By default, when the user presses the return key on their keyboard and + the text field has an `action` set, the action will be sent to the view's + controller and the key event will stop propagating. + + If you would like parent views to receive the `keyUp` event even after an + action has been dispatched, set `bubbles` to true. + + @property bubbles + @type Boolean + @default false + */ + bubbles: false, + + interpretKeyEvents: function(event) { + var map = TextSupport.KEY_EVENTS; + var method = map[event.keyCode]; + + this._elementValueDidChange(); + if (method) { return this[method](event); } + }, + + _elementValueDidChange: function() { + set(this, 'value', this.$().val()); + }, + + /** + The action to be sent when the user inserts a new line. + + Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13. + Uses sendAction to send the `enter` action to the controller. + + @method insertNewline + @param {Event} event + */ + insertNewline: function(event) { + sendAction('enter', this, event); + sendAction('insert-newline', this, event); + }, + + /** + Called when the user hits escape. + + Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 27. + Uses sendAction to send the `escape-press` action to the controller. + + @method cancel + @param {Event} event + */ + cancel: function(event) { + sendAction('escape-press', this, event); + }, + + /** + Called when the text area is focused. + + @method focusIn + @param {Event} event + */ + focusIn: function(event) { + sendAction('focus-in', this, event); + }, + + /** + Called when the text area is blurred. + + @method focusOut + @param {Event} event + */ + focusOut: function(event) { + sendAction('focus-out', this, event); + }, + + /** + The action to be sent when the user presses a key. Enabled by setting + the `onEvent` property to `keyPress`. + + Uses sendAction to send the `keyPress` action to the controller. + + @method keyPress + @param {Event} event + */ + keyPress: function(event) { + sendAction('key-press', this, event); + } + + }); + + TextSupport.KEY_EVENTS = { + 13: 'insertNewline', + 27: 'cancel' + }; + + // In principle, this shouldn't be necessary, but the legacy + // sectionAction semantics for TextField are different from + // the component semantics so this method normalizes them. + function sendAction(eventName, view, event) { + var action = get(view, eventName), + on = get(view, 'onEvent'), + value = get(view, 'value'); + + // back-compat support for keyPress as an event name even though + // it's also a method name that consumes the event (and therefore + // incompatible with sendAction semantics). + if (on === eventName || (on === 'keyPress' && eventName === 'key-press')) { + view.sendAction('action', value); + } + + view.sendAction(eventName, value); + + if (action || on === eventName) { + if(!get(view, 'bubbles')) { + event.stopPropagation(); + } + } + } + + __exports__["default"] = TextSupport; + }); +define("ember-handlebars/ext", + ["ember-metal/core","ember-runtime/system/string","ember-handlebars-compiler","ember-metal/property_get","ember-metal/binding","ember-metal/error","ember-metal/mixin","ember-metal/is_empty","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.FEATURES, Ember.assert, Ember.Handlebars, Ember.lookup + // var emberAssert = Ember.assert; + + var EmberStringUtils = __dependency2__["default"]; + var fmt = EmberStringUtils.fmt; + + var EmberHandlebars = __dependency3__["default"]; + var helpers = EmberHandlebars.helpers; + + var get = __dependency4__.get; + var isGlobalPath = __dependency5__.isGlobalPath; + var EmberError = __dependency6__["default"]; + var IS_BINDING = __dependency7__.IS_BINDING; + + // late bound via requireModule because of circular dependencies. + var resolveHelper, + SimpleHandlebarsView; + + var isEmpty = __dependency8__["default"]; + + var slice = [].slice, originalTemplate = EmberHandlebars.template; + + /** + If a path starts with a reserved keyword, returns the root + that should be used. + + @private + @method normalizePath + @for Ember + @param root {Object} + @param path {String} + @param data {Hash} + */ + function normalizePath(root, path, data) { + var keywords = (data && data.keywords) || {}, + keyword, isKeyword; + + // Get the first segment of the path. For example, if the + // path is "foo.bar.baz", returns "foo". + keyword = path.split('.', 1)[0]; + + // Test to see if the first path is a keyword that has been + // passed along in the view's data hash. If so, we will treat + // that object as the new root. + if (keywords.hasOwnProperty(keyword)) { + // Look up the value in the template's data hash. + root = keywords[keyword]; + isKeyword = true; + + // Handle cases where the entire path is the reserved + // word. In that case, return the object itself. + if (path === keyword) { + path = ''; + } else { + // Strip the keyword from the path and look up + // the remainder from the newly found root. + path = path.substr(keyword.length+1); + } + } + + return { root: root, path: path, isKeyword: isKeyword }; + }; + + + /** + Lookup both on root and on window. If the path starts with + a keyword, the corresponding object will be looked up in the + template's data hash and used to resolve the path. + + @method get + @for Ember.Handlebars + @param {Object} root The object to look up the property on + @param {String} path The path to be lookedup + @param {Object} options The template's option hash + */ + function handlebarsGet(root, path, options) { + var data = options && options.data, + normalizedPath = normalizePath(root, path, data), + value; + + if (Ember.FEATURES.isEnabled("ember-handlebars-caps-lookup")) { + + // If the path starts with a capital letter, look it up on Ember.lookup, + // which defaults to the `window` object in browsers. + if (isGlobalPath(path)) { + value = get(Ember.lookup, path); + } else { + + // In cases where the path begins with a keyword, change the + // root to the value represented by that keyword, and ensure + // the path is relative to it. + value = get(normalizedPath.root, normalizedPath.path); + } + + } else { + root = normalizedPath.root; + path = normalizedPath.path; + + value = get(root, path); + + if (value === undefined && root !== Ember.lookup && isGlobalPath(path)) { + value = get(Ember.lookup, path); + } + } + + return value; + } + + /** + This method uses `Ember.Handlebars.get` to lookup a value, then ensures + that the value is escaped properly. + + If `unescaped` is a truthy value then the escaping will not be performed. + + @method getEscaped + @for Ember.Handlebars + @param {Object} root The object to look up the property on + @param {String} path The path to be lookedup + @param {Object} options The template's option hash + */ + function getEscaped(root, path, options) { + var result = handlebarsGet(root, path, options); + + if (result === null || result === undefined) { + result = ""; + } else if (!(result instanceof Handlebars.SafeString)) { + result = String(result); + } + if (!options.hash.unescaped){ + result = Handlebars.Utils.escapeExpression(result); + } + + return result; + }; + + function resolveParams(context, params, options) { + var resolvedParams = [], types = options.types, param, type; + + for (var i=0, l=params.length; i{{user.name}} + +
    +
    {{user.role.label}}
    + {{user.role.id}} + +

    {{user.role.description}}

    +
    + ``` + + `{{with}}` can be our best friend in these cases, + instead of writing `user.role.*` over and over, we use `{{#with user.role}}`. + Now the context within the `{{#with}} .. {{/with}}` block is `user.role` so you can do the following: + + ```handlebars +
    {{user.name}}
    + +
    + {{#with user.role}} +
    {{label}}
    + {{id}} + +

    {{description}}

    + {{/with}} +
    + ``` + + ### `as` operator + + This operator aliases the scope to a new name. It's helpful for semantic clarity and to retain + default scope or to reference from another `{{with}}` block. + + ```handlebars + // posts might not be + {{#with user.posts as blogPosts}} +
    + There are {{blogPosts.length}} blog posts written by {{user.name}}. +
    + + {{#each post in blogPosts}} +
  • {{post.title}}
  • + {{/each}} + {{/with}} + ``` + + Without the `as` operator, it would be impossible to reference `user.name` in the example above. + + NOTE: The alias should not reuse a name from the bound property path. + For example: `{{#with foo.bar as foo}}` is not supported because it attempts to alias using + the first part of the property path, `foo`. Instead, use `{{#with foo.bar as baz}}`. + + ### `controller` option + + Adding `controller='something'` instructs the `{{with}}` helper to create and use an instance of + the specified controller with the new context as its content. + + This is very similar to using an `itemController` option with the `{{each}}` helper. + + ```handlebars + {{#with users.posts controller='userBlogPosts'}} + {{!- The current context is wrapped in our controller instance }} + {{/with}} + ``` + + In the above example, the template provided to the `{{with}}` block is now wrapped in the + `userBlogPost` controller, which provides a very elegant way to decorate the context with custom + functions/properties. + + @method with + @for Ember.Handlebars.helpers + @param {Function} context + @param {Hash} options + @return {String} HTML string + */ + function withHelper(context, options) { + if (arguments.length === 4) { + var keywordName, path, rootPath, normalized, contextPath; + + Ember.assert("If you pass more than one argument to the with helper, it must be in the form #with foo as bar", arguments[1] === "as"); + options = arguments[3]; + keywordName = arguments[2]; + path = arguments[0]; + + Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); + + var localizedOptions = o_create(options); + localizedOptions.data = o_create(options.data); + localizedOptions.data.keywords = o_create(options.data.keywords || {}); + + if (isGlobalPath(path)) { + contextPath = path; + } else { + normalized = normalizePath(this, path, options.data); + path = normalized.path; + rootPath = normalized.root; + + // This is a workaround for the fact that you cannot bind separate objects + // together. When we implement that functionality, we should use it here. + var contextKey = jQuery.expando + guidFor(rootPath); + localizedOptions.data.keywords[contextKey] = rootPath; + // if the path is '' ("this"), just bind directly to the current context + contextPath = path ? contextKey + '.' + path : contextKey; + } + + emberBind(localizedOptions.data.keywords, keywordName, contextPath); + + return bind.call(this, path, localizedOptions, true, exists); + } else { + Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2); + Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); + return helpers.bind.call(options.contexts[0], context, options); + } + } + + + /** + See [boundIf](/api/classes/Ember.Handlebars.helpers.html#method_boundIf) + and [unboundIf](/api/classes/Ember.Handlebars.helpers.html#method_unboundIf) + + @method if + @for Ember.Handlebars.helpers + @param {Function} context + @param {Hash} options + @return {String} HTML string + */ + function ifHelper(context, options) { + Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2); + Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop); + if (options.data.isUnbound) { + return helpers.unboundIf.call(options.contexts[0], context, options); + } else { + return helpers.boundIf.call(options.contexts[0], context, options); + } + } + + /** + @method unless + @for Ember.Handlebars.helpers + @param {Function} context + @param {Hash} options + @return {String} HTML string + */ + function unlessHelper(context, options) { + Ember.assert("You must pass exactly one argument to the unless helper", arguments.length === 2); + Ember.assert("You must pass a block to the unless helper", options.fn && options.fn !== Handlebars.VM.noop); + + var fn = options.fn, inverse = options.inverse; + + options.fn = inverse; + options.inverse = fn; + + if (options.data.isUnbound) { + return helpers.unboundIf.call(options.contexts[0], context, options); + } else { + return helpers.boundIf.call(options.contexts[0], context, options); + } + } + + /** + `bind-attr` allows you to create a binding between DOM element attributes and + Ember objects. For example: + + ```handlebars + imageTitle + ``` + + The above handlebars template will fill the ``'s `src` attribute will + the value of the property referenced with `"imageUrl"` and its `alt` + attribute with the value of the property referenced with `"imageTitle"`. + + If the rendering context of this template is the following object: + + ```javascript + { + imageUrl: 'http://lolcats.info/haz-a-funny', + imageTitle: 'A humorous image of a cat' + } + ``` + + The resulting HTML output will be: + + ```html + A humorous image of a cat + ``` + + `bind-attr` cannot redeclare existing DOM element attributes. The use of `src` + in the following `bind-attr` example will be ignored and the hard coded value + of `src="/failwhale.gif"` will take precedence: + + ```handlebars + imageTitle + ``` + + ### `bind-attr` and the `class` attribute + + `bind-attr` supports a special syntax for handling a number of cases unique + to the `class` DOM element attribute. The `class` attribute combines + multiple discrete values into a single attribute as a space-delimited + list of strings. Each string can be: + + * a string return value of an object's property. + * a boolean return value of an object's property + * a hard-coded value + + A string return value works identically to other uses of `bind-attr`. The + return value of the property will become the value of the attribute. For + example, the following view and template: + + ```javascript + AView = View.extend({ + someProperty: function() { + return "aValue"; + }.property() + }) + ``` + + ```handlebars + + ``` + + A boolean return value will insert a specified class name if the property + returns `true` and remove the class name if the property returns `false`. + + A class name is provided via the syntax + `somePropertyName:class-name-if-true`. + + ```javascript + AView = View.extend({ + someBool: true + }) + ``` + + ```handlebars + + ``` + + Result in the following rendered output: + + ```html + + ``` + + An additional section of the binding can be provided if you want to + replace the existing class instead of removing it when the boolean + value changes: + + ```handlebars + + ``` + + A hard-coded value can be used by prepending `:` to the desired + class name: `:class-name-to-always-apply`. + + ```handlebars + + ``` + + Results in the following rendered output: + + ```html + + ``` + + All three strategies - string return value, boolean return value, and + hard-coded value – can be combined in a single declaration: + + ```handlebars + + ``` + + @method bind-attr + @for Ember.Handlebars.helpers + @param {Hash} options + @return {String} HTML string + */ + function bindAttrHelper(options) { + var attrs = options.hash; + + Ember.assert("You must specify at least one hash argument to bind-attr", !!keys(attrs).length); + + var view = options.data.view; + var ret = []; + + // we relied on the behavior of calling without + // context to mean this === window, but when running + // "use strict", it's possible for this to === undefined; + var ctx = this || window; + + // Generate a unique id for this element. This will be added as a + // data attribute to the element so it can be looked up when + // the bound property changes. + var dataId = ++Ember.uuid; + + // Handle classes differently, as we can bind multiple classes + var classBindings = attrs['class']; + if (classBindings != null) { + var classResults = bindClasses(ctx, classBindings, view, dataId, options); + + ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"'); + delete attrs['class']; + } + + var attrKeys = keys(attrs); + + // For each attribute passed, create an observer and emit the + // current value of the property as an attribute. + forEach.call(attrKeys, function(attr) { + var path = attrs[attr], + normalized; + + Ember.assert(fmt("You must provide an expression as the value of bound attribute. You specified: %@=%@", [attr, path]), typeof path === 'string'); + + normalized = normalizePath(ctx, path, options.data); + + var value = (path === 'this') ? normalized.root : handlebarsGet(ctx, path, options), + type = typeOf(value); + + Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean'); + + var observer, invoker; + + observer = function observer() { + var result = handlebarsGet(ctx, path, options); + + Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [result]), + result === null || result === undefined || typeof result === 'number' || + typeof result === 'string' || typeof result === 'boolean'); + + var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']"); + + // If we aren't able to find the element, it means the element + // to which we were bound has been removed from the view. + // In that case, we can assume the template has been re-rendered + // and we need to clean up the observer. + if (!elem || elem.length === 0) { + removeObserver(normalized.root, normalized.path, invoker); + return; + } + + View.applyAttributeBindings(elem, attr, result); + }; + + // Add an observer to the view for when the property changes. + // When the observer fires, find the element using the + // unique data id and update the attribute to the new value. + // Note: don't add observer when path is 'this' or path + // is whole keyword e.g. {{#each x in list}} ... {{bind-attr attr="x"}} + if (path !== 'this' && !(normalized.isKeyword && normalized.path === '' )) { + view.registerObserver(normalized.root, normalized.path, observer); + } + + // if this changes, also change the logic in ember-views/lib/views/view.js + if ((type === 'string' || (type === 'number' && !isNaN(value)))) { + ret.push(attr + '="' + Handlebars.Utils.escapeExpression(value) + '"'); + } else if (value && type === 'boolean') { + // The developer controls the attr name, so it should always be safe + ret.push(attr + '="' + attr + '"'); + } + }, this); + + // Add the unique identifier + // NOTE: We use all lower-case since Firefox has problems with mixed case in SVG + ret.push('data-bindattr-' + dataId + '="' + dataId + '"'); + return new SafeString(ret.join(' ')); + } + + /** + See `bind-attr` + + @method bindAttr + @for Ember.Handlebars.helpers + @deprecated + @param {Function} context + @param {Hash} options + @return {String} HTML string + */ + function bindAttrHelperDeprecated() { + Ember.warn("The 'bindAttr' view helper is deprecated in favor of 'bind-attr'"); + return helpers['bind-attr'].apply(this, arguments); + } + + /** + Helper that, given a space-separated string of property paths and a context, + returns an array of class names. Calling this method also has the side + effect of setting up observers at those property paths, such that if they + change, the correct class name will be reapplied to the DOM element. + + For example, if you pass the string "fooBar", it will first look up the + "fooBar" value of the context. If that value is true, it will add the + "foo-bar" class to the current element (i.e., the dasherized form of + "fooBar"). If the value is a string, it will add that string as the class. + Otherwise, it will not add any new class name. + + @private + @method bindClasses + @for Ember.Handlebars + @param {Ember.Object} context The context from which to lookup properties + @param {String} classBindings A string, space-separated, of class bindings + to use + @param {View} view The view in which observers should look for the + element to update + @param {Srting} bindAttrId Optional bindAttr id used to lookup elements + @return {Array} An array of class names to add + */ + function bindClasses(context, classBindings, view, bindAttrId, options) { + var ret = [], newClass, value, elem; + + // Helper method to retrieve the property from the context and + // determine which class string to return, based on whether it is + // a Boolean or not. + var classStringForPath = function(root, parsedPath, options) { + var val, + path = parsedPath.path; + + if (path === 'this') { + val = root; + } else if (path === '') { + val = true; + } else { + val = handlebarsGet(root, path, options); + } + + return View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName); + }; + + // For each property passed, loop through and setup + // an observer. + forEach.call(classBindings.split(' '), function(binding) { + + // Variable in which the old class value is saved. The observer function + // closes over this variable, so it knows which string to remove when + // the property changes. + var oldClass; + + var observer, invoker; + + var parsedPath = View._parsePropertyPath(binding), + path = parsedPath.path, + pathRoot = context, + normalized; + + if (path !== '' && path !== 'this') { + normalized = normalizePath(context, path, options.data); + + pathRoot = normalized.root; + path = normalized.path; + } + + // Set up an observer on the context. If the property changes, toggle the + // class name. + observer = function() { + // Get the current value of the property + newClass = classStringForPath(context, parsedPath, options); + elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$(); + + // If we can't find the element anymore, a parent template has been + // re-rendered and we've been nuked. Remove the observer. + if (!elem || elem.length === 0) { + removeObserver(pathRoot, path, invoker); + } else { + // If we had previously added a class to the element, remove it. + if (oldClass) { + elem.removeClass(oldClass); + } + + // If necessary, add a new class. Make sure we keep track of it so + // it can be removed in the future. + if (newClass) { + elem.addClass(newClass); + oldClass = newClass; + } else { + oldClass = null; + } + } + }; + + if (path !== '' && path !== 'this') { + view.registerObserver(pathRoot, path, observer); + } + + // We've already setup the observer; now we just need to figure out the + // correct behavior right now on the first pass through. + value = classStringForPath(context, parsedPath, options); + + if (value) { + ret.push(value); + + // Make sure we save the current value so that it can be removed if the + // observer fires. + oldClass = value; + } + }); + + return ret; + }; + + __exports__.bind = bind; + __exports__._triageMustacheHelper = _triageMustacheHelper; + __exports__.resolveHelper = resolveHelper; + __exports__.bindHelper = bindHelper; + __exports__.boundIfHelper = boundIfHelper; + __exports__.unboundIfHelper = unboundIfHelper; + __exports__.withHelper = withHelper; + __exports__.ifHelper = ifHelper; + __exports__.unlessHelper = unlessHelper; + __exports__.bindAttrHelper = bindAttrHelper; + __exports__.bindAttrHelperDeprecated = bindAttrHelperDeprecated; + __exports__.bindClasses = bindClasses; + }); +define("ember-handlebars/helpers/collection", + ["ember-metal/core","ember-metal/utils","ember-handlebars-compiler","ember-runtime/system/string","ember-metal/property_get","ember-handlebars/ext","ember-handlebars/helpers/view","ember-metal/computed","ember-views/views/collection_view","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-handlebars + */ + + var Ember = __dependency1__["default"]; + // Ember.assert, Ember.deprecate + var inspect = __dependency2__.inspect; + + // var emberAssert = Ember.assert; + // emberDeprecate = Ember.deprecate; + + var EmberHandlebars = __dependency3__["default"]; + var helpers = EmberHandlebars.helpers; + + var EmberStringUtils = __dependency4__["default"]; + var fmt = EmberStringUtils.fmt; + + var get = __dependency5__.get; + var handlebarsGet = __dependency6__.handlebarsGet; + var ViewHelper = __dependency7__.ViewHelper; + var computed = __dependency8__.computed; + var CollectionView = __dependency9__["default"]; + + var alias = computed.alias; + /** + `{{collection}}` is a `Ember.Handlebars` helper for adding instances of + `Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html) + for additional information on how a `CollectionView` functions. + + `{{collection}}`'s primary use is as a block helper with a `contentBinding` + option pointing towards an `Ember.Array`-compatible object. An `Ember.View` + instance will be created for each item in its `content` property. Each view + will have its own `content` property set to the appropriate item in the + collection. + + The provided block will be applied as the template for each item's view. + + Given an empty `` the following template: + + ```handlebars + {{#collection contentBinding="App.items"}} + Hi {{view.content.name}} + {{/collection}} + ``` + + And the following application code + + ```javascript + App = Ember.Application.create() + App.items = [ + Ember.Object.create({name: 'Dave'}), + Ember.Object.create({name: 'Mary'}), + Ember.Object.create({name: 'Sara'}) + ] + ``` + + Will result in the HTML structure below + + ```html +
    +
    Hi Dave
    +
    Hi Mary
    +
    Hi Sara
    +
    + ``` + + ### Blockless use in a collection + + If you provide an `itemViewClass` option that has its own `template` you can + omit the block. + + The following template: + + ```handlebars + {{collection contentBinding="App.items" itemViewClass="App.AnItemView"}} + ``` + + And application code + + ```javascript + App = Ember.Application.create(); + App.items = [ + Ember.Object.create({name: 'Dave'}), + Ember.Object.create({name: 'Mary'}), + Ember.Object.create({name: 'Sara'}) + ]; + + App.AnItemView = Ember.View.extend({ + template: Ember.Handlebars.compile("Greetings {{view.content.name}}") + }); + ``` + + Will result in the HTML structure below + + ```html +
    +
    Greetings Dave
    +
    Greetings Mary
    +
    Greetings Sara
    +
    + ``` + + ### Specifying a CollectionView subclass + + By default the `{{collection}}` helper will create an instance of + `Ember.CollectionView`. You can supply a `Ember.CollectionView` subclass to + the helper by passing it as the first argument: + + ```handlebars + {{#collection App.MyCustomCollectionClass contentBinding="App.items"}} + Hi {{view.content.name}} + {{/collection}} + ``` + + ### Forwarded `item.*`-named Options + + As with the `{{view}}`, helper options passed to the `{{collection}}` will be + set on the resulting `Ember.CollectionView` as properties. Additionally, + options prefixed with `item` will be applied to the views rendered for each + item (note the camelcasing): + + ```handlebars + {{#collection contentBinding="App.items" + itemTagName="p" + itemClassNames="greeting"}} + Howdy {{view.content.name}} + {{/collection}} + ``` + + Will result in the following HTML structure: + + ```html +
    +

    Howdy Dave

    +

    Howdy Mary

    +

    Howdy Sara

    +
    + ``` + + @method collection + @for Ember.Handlebars.helpers + @param {String} path + @param {Hash} options + @return {String} HTML string + @deprecated Use `{{each}}` helper instead. + */ + function collectionHelper(path, options) { + Ember.deprecate("Using the {{collection}} helper without specifying a class has been deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection'); + + // If no path is provided, treat path param as options. + if (path && path.data && path.data.isRenderData) { + options = path; + path = undefined; + Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1); + } else { + Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2); + } + + var fn = options.fn; + var data = options.data; + var inverse = options.inverse; + var view = options.data.view; + + + var controller, container; + // If passed a path string, convert that into an object. + // Otherwise, just default to the standard class. + var collectionClass; + if (path) { + controller = data.keywords.controller; + container = controller && controller.container; + collectionClass = handlebarsGet(this, path, options) || container.lookupFactory('view:' + path); + Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass); + } + else { + collectionClass = CollectionView; + } + + var hash = options.hash, itemHash = {}, match; + + // Extract item view class if provided else default to the standard class + var collectionPrototype = collectionClass.proto(), itemViewClass; + + if (hash.itemView) { + controller = data.keywords.controller; + Ember.assert('You specified an itemView, but the current context has no ' + + 'container to look the itemView up in. This probably means ' + + 'that you created a view manually, instead of through the ' + + 'container. Instead, use container.lookup("view:viewName"), ' + + 'which will properly instantiate your view.', + controller && controller.container); + container = controller.container; + itemViewClass = container.lookupFactory('view:' + hash.itemView); + Ember.assert('You specified the itemView ' + hash.itemView + ", but it was " + + "not found at " + container.describe("view:" + hash.itemView) + + " (and it was not registered in the container)", !!itemViewClass); + } else if (hash.itemViewClass) { + itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options); + } else { + itemViewClass = collectionPrototype.itemViewClass; + } + + Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewClass]), !!itemViewClass); + + delete hash.itemViewClass; + delete hash.itemView; + + // Go through options passed to the {{collection}} helper and extract options + // that configure item views instead of the collection itself. + for (var prop in hash) { + if (hash.hasOwnProperty(prop)) { + match = prop.match(/^item(.)(.*)$/); + + if (match && prop !== 'itemController') { + // Convert itemShouldFoo -> shouldFoo + itemHash[match[1].toLowerCase() + match[2]] = hash[prop]; + // Delete from hash as this will end up getting passed to the + // {{view}} helper method. + delete hash[prop]; + } + } + } + + if (fn) { + itemHash.template = fn; + delete options.fn; + } + + var emptyViewClass; + if (inverse && inverse !== EmberHandlebars.VM.noop) { + emptyViewClass = get(collectionPrototype, 'emptyViewClass'); + emptyViewClass = emptyViewClass.extend({ + template: inverse, + tagName: itemHash.tagName + }); + } else if (hash.emptyViewClass) { + emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options); + } + if (emptyViewClass) { hash.emptyView = emptyViewClass; } + + if (!hash.keyword) { + itemHash._context = alias('content'); + } + + var viewOptions = ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this); + hash.itemViewClass = itemViewClass.extend(viewOptions); + + return helpers.view.call(this, collectionClass, options); + } + + __exports__["default"] = collectionHelper; + }); +define("ember-handlebars/helpers/debug", + ["ember-metal/core","ember-metal/utils","ember-metal/logger","ember-metal/property_get","ember-handlebars/ext","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { + "use strict"; + /*jshint debug:true*/ + + /** + @module ember + @submodule ember-handlebars + */ + var Ember = __dependency1__["default"]; + // Ember.FEATURES, + var inspect = __dependency2__.inspect; + var Logger = __dependency3__["default"]; + + var get = __dependency4__.get; + var normalizePath = __dependency5__.normalizePath; + var handlebarsGet = __dependency5__.handlebarsGet; + + var a_slice = [].slice; + + /** + `log` allows you to output the value of variables in the current rendering + context. `log` also accepts primitive types such as strings or numbers. + + ```handlebars + {{log "myVariable:" myVariable }} + ``` + + @method log + @for Ember.Handlebars.helpers + @param {String} property + */ + function logHelper() { + var params = a_slice.call(arguments, 0, -1), + options = arguments[arguments.length - 1], + logger = Logger.log, + values = [], + allowPrimitives = false; + + + allowPrimitives = true; + + + for (var i = 0; i < params.length; i++) { + var type = options.types[i]; + + if (type === 'ID' || !allowPrimitives) { + var context = (options.contexts && options.contexts[i]) || this, + normalized = normalizePath(context, params[i], options.data); + + if (normalized.path === 'this') { + values.push(normalized.root); + } else { + values.push(handlebarsGet(normalized.root, normalized.path, options)); + } + } else { + values.push(params[i]); + } + } + + logger.apply(logger, values); + }; + + /** + Execute the `debugger` statement in the current context. + + ```handlebars + {{debugger}} + ``` + + Before invoking the `debugger` statement, there + are a few helpful variables defined in the + body of this helper that you can inspect while + debugging that describe how and where this + helper was invoked: + + - templateContext: this is most likely a controller + from which this template looks up / displays properties + - typeOfTemplateContext: a string description of + what the templateContext is + + For example, if you're wondering why a value `{{foo}}` + isn't rendering as expected within a template, you + could place a `{{debugger}}` statement, and when + the `debugger;` breakpoint is hit, you can inspect + `templateContext`, determine if it's the object you + expect, and/or evaluate expressions in the console + to perform property lookups on the `templateContext`: + + ``` + > templateContext.get('foo') // -> "" + ``` + + @method debugger + @for Ember.Handlebars.helpers + @param {String} property + */ + function debuggerHelper(options) { + + // These are helpful values you can inspect while debugging. + var templateContext = this; + var typeOfTemplateContext = inspect(templateContext); + + debugger; + } + + __exports__.logHelper = logHelper; + __exports__.debuggerHelper = debuggerHelper; + }); +define("ember-handlebars/helpers/each", + ["ember-metal/core","ember-handlebars-compiler","ember-runtime/system/string","ember-metal/property_get","ember-metal/property_set","ember-handlebars/views/metamorph_view","ember-views/views/collection_view","ember-metal/binding","ember-runtime/controllers/controller","ember-runtime/controllers/array_controller","ember-runtime/mixins/array","ember-runtime/copy","ember-metal/run_loop","ember-metal/observer","ember-metal/events","ember-handlebars/ext","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __exports__) { + "use strict"; + + /** + @module ember + @submodule ember-handlebars + */ + var Ember = __dependency1__["default"]; + // Ember.assert;, Ember.K + // var emberAssert = Ember.assert, + var K = Ember.K; + + var EmberHandlebars = __dependency2__["default"]; + var helpers = EmberHandlebars.helpers; + + var EmberStringUtils = __dependency3__["default"]; + var fmt = EmberStringUtils.fmt; + + var get = __dependency4__.get; + var set = __dependency5__.set; + var _Metamorph = __dependency6__._Metamorph; + var _MetamorphView = __dependency6__._MetamorphView; + var CollectionView = __dependency7__["default"]; + var Binding = __dependency8__.Binding; + var ControllerMixin = __dependency9__.ControllerMixin; + var ArrayController = __dependency10__["default"]; + var EmberArray = __dependency11__["default"]; + var copy = __dependency12__["default"]; + var run = __dependency13__["default"]; + var addObserver = __dependency14__.addObserver; + var removeObserver = __dependency14__.removeObserver; + var addBeforeObserver = __dependency14__.addBeforeObserver; + var removeBeforeObserver = __dependency14__.removeBeforeObserver; + var on = __dependency15__.on; + + var handlebarsGet = __dependency16__.handlebarsGet; + + var EachView = CollectionView.extend(_Metamorph, { + init: function() { + var itemController = get(this, 'itemController'); + var binding; + + if (itemController) { + var controller = get(this, 'controller.container').lookupFactory('controller:array').create({ + _isVirtual: true, + parentController: get(this, 'controller'), + itemController: itemController, + target: get(this, 'controller'), + _eachView: this + }); + + this.disableContentObservers(function() { + set(this, 'content', controller); + binding = new Binding('content', '_eachView.dataSource').oneWay(); + binding.connect(controller); + }); + + set(this, '_arrayController', controller); + } else { + this.disableContentObservers(function() { + binding = new Binding('content', 'dataSource').oneWay(); + binding.connect(this); + }); + } + + return this._super(); + }, + + _assertArrayLike: function(content) { + Ember.assert(fmt("The value that #each loops over must be an Array. You " + + "passed %@, but it should have been an ArrayController", + [content.constructor]), + !ControllerMixin.detect(content) || + (content && content.isGenerated) || + content instanceof ArrayController); + Ember.assert(fmt("The value that #each loops over must be an Array. You passed %@", [(ControllerMixin.detect(content) && content.get('model') !== undefined) ? fmt("'%@' (wrapped in %@)", [content.get('model'), content]) : content]), EmberArray.detect(content)); + }, + + disableContentObservers: function(callback) { + removeBeforeObserver(this, 'content', null, '_contentWillChange'); + removeObserver(this, 'content', null, '_contentDidChange'); + + callback.call(this); + + addBeforeObserver(this, 'content', null, '_contentWillChange'); + addObserver(this, 'content', null, '_contentDidChange'); + }, + + itemViewClass: _MetamorphView, + emptyViewClass: _MetamorphView, + + createChildView: function(view, attrs) { + view = this._super(view, attrs); + + // At the moment, if a container view subclass wants + // to insert keywords, it is responsible for cloning + // the keywords hash. This will be fixed momentarily. + var keyword = get(this, 'keyword'); + var content = get(view, 'content'); + + if (keyword) { + var data = get(view, 'templateData'); + + data = copy(data); + data.keywords = view.cloneKeywords(); + set(view, 'templateData', data); + + // In this case, we do not bind, because the `content` of + // a #each item cannot change. + data.keywords[keyword] = content; + } + + // If {{#each}} is looping over an array of controllers, + // point each child view at their respective controller. + if (content && content.isController) { + set(view, 'controller', content); + } + + return view; + }, + + destroy: function() { + if (!this._super()) { return; } + + var arrayController = get(this, '_arrayController'); + + if (arrayController) { + arrayController.destroy(); + } + + return this; + } + }); + + // Defeatureify doesn't seem to like nested functions that need to be removed + function _addMetamorphCheck() { + EachView.reopen({ + _checkMetamorph: on('didInsertElement', function() { + Ember.assert("The metamorph tags, " + + this.morph.start + " and " + this.morph.end + + ", have different parents.\nThe browser has fixed your template to output valid HTML (for example, check that you have properly closed all tags and have used a TBODY tag when creating a table with '{{#each}}')", + document.getElementById( this.morph.start ).parentNode === + document.getElementById( this.morph.end ).parentNode + ); + }) + }); + } + + // until ember-debug is es6ed + var runInDebug = function(f){f()}; + runInDebug( function() { + _addMetamorphCheck(); + }); + + var GroupedEach = EmberHandlebars.GroupedEach = function(context, path, options) { + var self = this, + normalized = EmberHandlebars.normalizePath(context, path, options.data); + + this.context = context; + this.path = path; + this.options = options; + this.template = options.fn; + this.containingView = options.data.view; + this.normalizedRoot = normalized.root; + this.normalizedPath = normalized.path; + this.content = this.lookupContent(); + + this.addContentObservers(); + this.addArrayObservers(); + + this.containingView.on('willClearRender', function() { + self.destroy(); + }); + }; + + GroupedEach.prototype = { + contentWillChange: function() { + this.removeArrayObservers(); + }, + + contentDidChange: function() { + this.content = this.lookupContent(); + this.addArrayObservers(); + this.rerenderContainingView(); + }, + + contentArrayWillChange: K, + + contentArrayDidChange: function() { + this.rerenderContainingView(); + }, + + lookupContent: function() { + return handlebarsGet(this.normalizedRoot, this.normalizedPath, this.options); + }, + + addArrayObservers: function() { + if (!this.content) { return; } + + this.content.addArrayObserver(this, { + willChange: 'contentArrayWillChange', + didChange: 'contentArrayDidChange' + }); + }, + + removeArrayObservers: function() { + if (!this.content) { return; } + + this.content.removeArrayObserver(this, { + willChange: 'contentArrayWillChange', + didChange: 'contentArrayDidChange' + }); + }, + + addContentObservers: function() { + addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange); + addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange); + }, + + removeContentObservers: function() { + removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange); + removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange); + }, + + render: function() { + if (!this.content) { return; } + + var content = this.content, + contentLength = get(content, 'length'), + data = this.options.data, + template = this.template; + + data.insideEach = true; + for (var i = 0; i < contentLength; i++) { + template(content.objectAt(i), { data: data }); + } + }, + + rerenderContainingView: function() { + var self = this; + run.scheduleOnce('render', this, function() { + // It's possible it's been destroyed after we enqueued a re-render call. + if (!self.destroyed) { + self.containingView.rerender(); + } + }); + }, + + destroy: function() { + this.removeContentObservers(); + if (this.content) { + this.removeArrayObservers(); + } + this.destroyed = true; + } + }; + + /** + The `{{#each}}` helper loops over elements in a collection, rendering its + block once for each item. It is an extension of the base Handlebars `{{#each}}` + helper: + + ```javascript + Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}]; + ``` + + ```handlebars + {{#each Developers}} + {{name}} + {{/each}} + ``` + + `{{each}}` supports an alternative syntax with element naming: + + ```handlebars + {{#each person in Developers}} + {{person.name}} + {{/each}} + ``` + + When looping over objects that do not have properties, `{{this}}` can be used + to render the object: + + ```javascript + DeveloperNames = ['Yehuda', 'Tom', 'Paul'] + ``` + + ```handlebars + {{#each DeveloperNames}} + {{this}} + {{/each}} + ``` + ### {{else}} condition + `{{#each}}` can have a matching `{{else}}`. The contents of this block will render + if the collection is empty. + + ``` + {{#each person in Developers}} + {{person.name}} + {{else}} +

    Sorry, nobody is available for this task.

    + {{/each}} + ``` + ### Specifying a View class for items + If you provide an `itemViewClass` option that references a view class + with its own `template` you can omit the block. + + The following template: + + ```handlebars + {{#view App.MyView }} + {{each view.items itemViewClass="App.AnItemView"}} + {{/view}} + ``` + + And application code + + ```javascript + App = Ember.Application.create({ + MyView: Ember.View.extend({ + items: [ + Ember.Object.create({name: 'Dave'}), + Ember.Object.create({name: 'Mary'}), + Ember.Object.create({name: 'Sara'}) + ] + }) + }); + + App.AnItemView = Ember.View.extend({ + template: Ember.Handlebars.compile("Greetings {{name}}") + }); + ``` + + Will result in the HTML structure below + + ```html +
    +
    Greetings Dave
    +
    Greetings Mary
    +
    Greetings Sara
    +
    + ``` + + If an `itemViewClass` is defined on the helper, and therefore the helper is not + being used as a block, an `emptyViewClass` can also be provided optionally. + The `emptyViewClass` will match the behavior of the `{{else}}` condition + described above. That is, the `emptyViewClass` will render if the collection + is empty. + + ### Representing each item with a Controller. + By default the controller lookup within an `{{#each}}` block will be + the controller of the template where the `{{#each}}` was used. If each + item needs to be presented by a custom controller you can provide a + `itemController` option which references a controller by lookup name. + Each item in the loop will be wrapped in an instance of this controller + and the item itself will be set to the `content` property of that controller. + + This is useful in cases where properties of model objects need transformation + or synthesis for display: + + ```javascript + App.DeveloperController = Ember.ObjectController.extend({ + isAvailableForHire: function() { + return !this.get('content.isEmployed') && this.get('content.isSeekingWork'); + }.property('isEmployed', 'isSeekingWork') + }) + ``` + + ```handlebars + {{#each person in developers itemController="developer"}} + {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}} + {{/each}} + ``` + + Each itemController will receive a reference to the current controller as + a `parentController` property. + + ### (Experimental) Grouped Each + + When used in conjunction with the experimental [group helper](https://github.com/emberjs/group-helper), + you can inform Handlebars to re-render an entire group of items instead of + re-rendering them one at a time (in the event that they are changed en masse + or an item is added/removed). + + ```handlebars + {{#group}} + {{#each people}} + {{firstName}} {{lastName}} + {{/each}} + {{/group}} + ``` + + This can be faster than the normal way that Handlebars re-renders items + in some cases. + + If for some reason you have a group with more than one `#each`, you can make + one of the collections be updated in normal (non-grouped) fashion by setting + the option `groupedRows=true` (counter-intuitive, I know). + + For example, + + ```handlebars + {{dealershipName}} + + {{#group}} + {{#each dealers}} + {{firstName}} {{lastName}} + {{/each}} + + {{#each car in cars groupedRows=true}} + {{car.make}} {{car.model}} {{car.color}} + {{/each}} + {{/group}} + ``` + Any change to `dealershipName` or the `dealers` collection will cause the + entire group to be re-rendered. However, changes to the `cars` collection + will be re-rendered individually (as normal). + + Note that `group` behavior is also disabled by specifying an `itemViewClass`. + + @method each + @for Ember.Handlebars.helpers + @param [name] {String} name for item (used with `in`) + @param [path] {String} path + @param [options] {Object} Handlebars key/value pairs of options + @param [options.itemViewClass] {String} a path to a view class used for each item + @param [options.itemController] {String} name of a controller to be created for each item + @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper + */ + function eachHelper(path, options) { + var ctx; + + if (arguments.length === 4) { + Ember.assert("If you pass more than one argument to the each helper, it must be in the form #each foo in bar", arguments[1] === "in"); + + var keywordName = arguments[0]; + + options = arguments[3]; + path = arguments[2]; + if (path === '') { path = "this"; } + + options.hash.keyword = keywordName; + } + + if (arguments.length === 1) { + options = path; + path = 'this'; + } + + options.hash.dataSourceBinding = path; + // Set up emptyView as a metamorph with no tag + //options.hash.emptyViewClass = Ember._MetamorphView; + + // can't rely on this default behavior when use strict + ctx = this || window; + if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) { + new GroupedEach(ctx, path, options).render(); + } else { + // ES6TODO: figure out how to do this without global lookup. + return helpers.collection.call(ctx, 'Ember.Handlebars.EachView', options); + } + } + + __exports__.EachView = EachView; + __exports__.GroupedEach = GroupedEach; + __exports__.eachHelper = eachHelper; + }); +define("ember-handlebars/helpers/loc", + ["ember-runtime/system/string","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var EmberStringUtils = __dependency1__["default"]; + var loc = EmberStringUtils.loc; + + /** + @module ember + @submodule ember-handlebars + */ + + // ES6TODO: + // Pretty sure this can be expressed as + // var locHelper EmberStringUtils.loc ? + + /** + `loc` looks up the string in the localized strings hash. + This is a convenient way to localize text. For example: + + ```html + + ``` + + Take note that `"welcome"` is a string and not an object + reference. + + @method loc + @for Ember.Handlebars.helpers + @param {String} str The string to format + */ + function locHelper(str) { + return loc(str); + } + + __exports__["default"] = locHelper; + }); +define("ember-handlebars/helpers/partial", + ["ember-metal/core","ember-metal/is_none","ember-handlebars/ext","ember-handlebars/helpers/binding","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.assert + // var emberAssert = Ember.assert; + + var isNone = __dependency2__.isNone; + var handlebarsGet = __dependency3__.handlebarsGet; + var bind = __dependency4__.bind; + + /** + @module ember + @submodule ember-handlebars + */ + + /** + The `partial` helper renders another template without + changing the template context: + + ```handlebars + {{foo}} + {{partial "nav"}} + ``` + + The above example template will render a template named + "_nav", which has the same context as the parent template + it's rendered into, so if the "_nav" template also referenced + `{{foo}}`, it would print the same thing as the `{{foo}}` + in the above example. + + If a "_nav" template isn't found, the `partial` helper will + fall back to a template named "nav". + + ## Bound template names + + The parameter supplied to `partial` can also be a path + to a property containing a template name, e.g.: + + ```handlebars + {{partial someTemplateName}} + ``` + + The above example will look up the value of `someTemplateName` + on the template context (e.g. a controller) and use that + value as the name of the template to render. If the resolved + value is falsy, nothing will be rendered. If `someTemplateName` + changes, the partial will be re-rendered using the new template + name. + + ## Setting the partial's context with `with` + + The `partial` helper can be used in conjunction with the `with` + helper to set a context that will be used by the partial: + + ```handlebars + {{#with currentUser}} + {{partial "user_info"}} + {{/with}} + ``` + + @method partial + @for Ember.Handlebars.helpers + @param {String} partialName the name of the template to render minus the leading underscore + */ + + function partialHelper(name, options) { + + var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; + + if (options.types[0] === "ID") { + // Helper was passed a property path; we need to + // create a binding that will re-render whenever + // this property changes. + options.fn = function(context, fnOptions) { + var partialName = handlebarsGet(context, name, fnOptions); + renderPartial(context, partialName, fnOptions); + }; + + return bind.call(context, name, options, true, exists); + } else { + // Render the partial right into parent template. + renderPartial(context, name, options); + } + } + + function exists(value) { + return !isNone(value); + } + + function renderPartial(context, name, options) { + var nameParts = name.split("/"), + lastPart = nameParts[nameParts.length - 1]; + + nameParts[nameParts.length - 1] = "_" + lastPart; + + var view = options.data.view, + underscoredName = nameParts.join("/"), + template = view.templateForName(underscoredName), + deprecatedTemplate = !template && view.templateForName(name); + + Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate); + + template = template || deprecatedTemplate; + + template(context, { data: options.data }); + } + + __exports__["default"] = partialHelper; + }); +define("ember-handlebars/helpers/shared", + ["ember-handlebars/ext","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var handlebarsGet = __dependency1__.handlebarsGet; + + function resolvePaths(options) { + var ret = [], + contexts = options.contexts, + roots = options.roots, + data = options.data; + + for (var i=0, l=contexts.length; i + {{#with loggedInUser}} + Last Login: {{lastLogin}} + User Info: {{template "user_info"}} + {{/with}} + + ``` + + ```html + + ``` + + ```handlebars + {{#if isUser}} + {{template "user_info"}} + {{else}} + {{template "unlogged_user_info"}} + {{/if}} + ``` + + This helper looks for templates in the global `Ember.TEMPLATES` hash. If you + add `",""===e.firstChild.innerHTML}(),r=this.document&&function(){var e=document.createElement("div");return e.innerHTML="Test: Value","Test:"===e.childNodes[0].nodeValue&&" Value"===e.childNodes[2].nodeValue}(),n=function(e,t){if(e.getAttribute("id")===t)return e;var r,i,o,a=e.childNodes.length;for(r=0;a>r;r++)if(i=e.childNodes[r],o=1===i.nodeType&&n(i,t))return o},i=function(e,i){t&&(i="­"+i);var o=[];if(r&&(i=i.replace(/(\s+)(",""===e.firstChild.innerHTML}(),a=r&&function(){var e=r.createElement("div");return e.innerHTML="Test: Value","Test:"===e.childNodes[0].nodeValue&&" Value"===e.childNodes[2].nodeValue}(),s=function(r){var n;n=this instanceof s?this:new e,n.innerHTML=r;var i="metamorph-"+t++;return n.start=i+"-start",n.end=i+"-end",n};e.prototype=s.prototype;var u,c,l,h,m,f,p,d,b;if(h=function(){return this.startTag()+this.innerHTML+this.endTag()},d=function(){return""},b=function(){return""},i)u=function(e,t){var n=r.createRange(),i=r.getElementById(e.start),o=r.getElementById(e.end);return t?(n.setStartBefore(i),n.setEndAfter(o)):(n.setStartAfter(i),n.setEndBefore(o)),n},c=function(e,t){var r=u(this,t);r.deleteContents();var n=r.createContextualFragment(e);r.insertNode(n)},l=function(){var e=u(this,!0);e.deleteContents()},m=function(e){var t=r.createRange();t.setStart(e),t.collapse(!1);var n=t.createContextualFragment(this.outerHTML());e.appendChild(n)},f=function(e){var t=r.createRange(),n=r.getElementById(this.end);t.setStartAfter(n),t.setEndAfter(n);var i=t.createContextualFragment(e);t.insertNode(i)},p=function(e){var t=r.createRange(),n=r.getElementById(this.start);t.setStartAfter(n),t.setEndAfter(n);var i=t.createContextualFragment(e);t.insertNode(i)};else{var v={select:[1,""],fieldset:[1,"
    ","
    "],table:[1,"","
    "],tbody:[2,"","
    "],tr:[3,"","
    "],colgroup:[2,"","
    "],map:[1,"",""],_default:[0,"",""]},E=function(e,t){if(e.getAttribute("id")===t)return e;var r,n,i,o=e.childNodes.length;for(r=0;o>r;r++)if(n=e.childNodes[r],i=1===n.nodeType&&E(n,t))return i},g=function(e,t){var n=[];if(a&&(t=t.replace(/(\s+)(",""===e.firstChild.innerHTML}()),i="undefined"!=typeof document&&function(){var e=document.createElement("div");return e.innerHTML="Test: Value","Test:"===e.childNodes[0].nodeValue&&" Value"===e.childNodes[2].nodeValue}(),a=function(e,t){if(e.getAttribute("id")===t)return e;var r,n,i,o=e.childNodes.length;for(r=0;o>r;r++)if(n=e.childNodes[r],i=1===n.nodeType&&a(n,t))return i},o=function(e,t){n&&(t="­"+t);var r=[];if(i&&(t=t.replace(/(\s+)(",""===e.firstChild.innerHTML}(),a=document&&function(){var e=document.createElement("div");return e.innerHTML="Test: Value","Test:"===e.childNodes[0].nodeValue&&" Value"===e.childNodes[2].nodeValue}(),o=function(r){var n;n=this instanceof o?this:new e,n.innerHTML=r;var i="metamorph-"+t++;return n.start=i+"-start",n.end=i+"-end",n};e.prototype=o.prototype;var s,l,u,c,h,m,p,f,d;if(c=function(){return this.startTag()+this.innerHTML+this.endTag()},f=function(){return""},d=function(){return""},n)s=function(e,t){var r=document.createRange(),n=document.getElementById(e.start),i=document.getElementById(e.end);return t?(r.setStartBefore(n),r.setEndAfter(i)):(r.setStartAfter(n),r.setEndBefore(i)),r},l=function(e,t){var r=s(this,t);r.deleteContents();var n=r.createContextualFragment(e);r.insertNode(n)},u=function(){var e=s(this,!0);e.deleteContents()},h=function(e){var t=document.createRange();t.setStart(e),t.collapse(!1);var r=t.createContextualFragment(this.outerHTML());e.appendChild(r)},m=function(e){var t=document.createRange(),r=document.getElementById(this.end);t.setStartAfter(r),t.setEndAfter(r);var n=t.createContextualFragment(e);t.insertNode(n)},p=function(e){var t=document.createRange(),r=document.getElementById(this.start);t.setStartAfter(r),t.setEndAfter(r);var n=t.createContextualFragment(e);t.insertNode(n)};else{var g={select:[1,""],fieldset:[1,"
    ","
    "],table:[1,"","
    "],tbody:[2,"","
    "],tr:[3,"","
    "],colgroup:[2,"","
    "],map:[1,"",""],_default:[0,"",""]},v=function(e,t){if(e.getAttribute("id")===t)return e;var r,n,i,a=e.childNodes.length;for(r=0;a>r;r++)if(n=e.childNodes[r],i=1===n.nodeType&&v(n,t))return i},b=function(e,t){var r=[];if(a&&(t=t.replace(/(\s+)(