diff --git a/app/assets/javascripts/supplier/app/controllers/modal_section_add_tables_controller.js.coffee b/app/assets/javascripts/supplier/app/controllers/modal_section_add_tables_controller.js.coffee index 5b0e49c4..69aa76dd 100644 --- a/app/assets/javascripts/supplier/app/controllers/modal_section_add_tables_controller.js.coffee +++ b/app/assets/javascripts/supplier/app/controllers/modal_section_add_tables_controller.js.coffee @@ -21,5 +21,20 @@ Qsupplier.App.ModalSectionAddTablesController = Ember.ObjectController.extend number_start: s number_end: e , (result,state,xhr)=> - @store.pushPayload 'table', result + #@store.pushPayload 'table', result + @store.pushPayload result @send 'close' + + #TODO remove followin code if Ember pushPayload is working + #properly with associations + section_id = @get('model.id') + tables_that_should_be_in_section = @store.all('table').filter((t)->t.get('section.id') == section_id) + current_table_ids = @get('model.tables').mapProperty('id') + for table in tables_that_should_be_in_section.toArray() + @get('model.tables').pushObject(table) unless table.get('id') in current_table_ids + #TODO it still does not work for the second client side action, + #soo the good old reload for now for the failing case + window.location.reload() if result.tables.length != tables_that_should_be_in_section.toArray().length + return + + diff --git a/app/assets/javascripts/supplier/app/controllers/modal_section_arrange_tables_controller.js.coffee b/app/assets/javascripts/supplier/app/controllers/modal_section_arrange_tables_controller.js.coffee new file mode 100644 index 00000000..48a64434 --- /dev/null +++ b/app/assets/javascripts/supplier/app/controllers/modal_section_arrange_tables_controller.js.coffee @@ -0,0 +1,20 @@ +Qsupplier.App.ModalSectionArrangeTablesController = Ember.ObjectController.extend + arrange_type: 'distributed' + by_row_count: 0 + by_column_count: 0 + isDistributed: (->@get('arrange_type') is 'distributed').property('arrange_type') + isByRow: (->@get('arrange_type') is 'by_row').property('arrange_type') + isByColumn: (->@get('arrange_type') is 'by_column').property('arrange_type') + actions: + close: -> + @get('model.cancel').call(@) if @get('model.cancel') + @send 'closeModal' + confirm: -> + @get('model.ok').call(@) + @send 'closeModal' + arrangeTables: -> + debugger + 3 + makeDistributed: -> @set 'arrange_type', 'distributed' + makeByRow: -> @set 'arrange_type', 'by_row' + makeByColumn: -> @set 'arrange_type', 'by_column' 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 62f9d299..91d95a57 100644 --- a/app/assets/javascripts/supplier/app/controllers/section_controller.js.coffee +++ b/app/assets/javascripts/supplier/app/controllers/section_controller.js.coffee @@ -12,7 +12,7 @@ Qsupplier.App.SectionController = Ember.ObjectController.extend #$('#add-tables-modal').modal() @send 'openModal', 'modal_section_add_tables', @get('model') arrangeTables: -> - $('#arrange-tables-modal').modal() + @send 'openModal', 'modal_section_arrange_tables', @get('model') textures: ['wood1', 'wood2'] sections: (-> @get('controllers.sections.model')).property('controllers.sections.model') diff --git a/app/assets/javascripts/supplier/app/templates/modal_section_arrange_tables.emblem b/app/assets/javascripts/supplier/app/templates/modal_section_arrange_tables.emblem new file mode 100644 index 00000000..7db29599 --- /dev/null +++ b/app/assets/javascripts/supplier/app/templates/modal_section_arrange_tables.emblem @@ -0,0 +1,67 @@ +modal-dialog action="close" + .modal-header + h3.flush--top=t 'section.arrange_tables.modal.title' + hr + .modal-body + p=t 'section.arrange_tables.modal.body_header' + .arrange-type-buttons + if isDistributed + span.arrange-tables-current-type.distributed=t 'section.arrange_tables.modal.distributed' + else + button.arrange-tables-type-button.distributed{action 'makeDistributed'}=t 'section.arrange_tables.modal.distributed' + if isByRow + span.arrange-tables-current-type.by_row=t 'section.arrange_tables.modal.by_row' + else + button.arrange-tables-type-button.by_row{action 'makeByRow'}=t 'section.arrange_tables.modal.by_row' + if isByColumn + span.arrange-tables-current-type.by_column=t 'section.arrange_tables.modal.by_column' + else + button.arrange-tables-type-button.by_column{action 'makeByColumn'}=t 'section.arrange_tables.modal.by_column' + .arrange-content + if isDistributed + span.explanation + if isByRow + .form-row + .form-label + label for="arrange-tables-by-row-count" + = t 'section.arrange_tables.modal.by_row_count' + .form-field + Qsupplier.App.NumberField valueBinding="by_row_count" + /input.input-mini#arrange-tables-by-row-count type="text" value=0 + =t 'models.plural.table' + if isByColumn + .form-row + .form-label + label for="arrange-tables-by-column-count" + = t 'section.arrange_tables.modal.by_column_count' + .form-field + Qsupplier.App.NumberField valueBinding="by_column_count" + /input.input-mini#arrange-tables-by-row-count type="text" value=0 + =t 'models.plural.table' + .modal-footer + hr + button.confirm-cancel{action "close"}=t 'section.arrange_tables.modal.close_button' + button.confirm-ok.right{action "arrangeTables"}=t 'section.arrange_tables.modal.arrange_button' + + + /form.form-horizontal + .control-group + label.control-label for='arrange-tables-distributed' data-t='section.arrange_tables.modal.distributed' = t('supplier.section.arrange_tables.modal.distributed') + .controls + input#arrange-tables-distributed type="radio" name="arrange-table-option" checked=true value="distributed" + .control-group + label.control-label for='arrange-tables-by_row' data-t='section.arrange_tables.modal.by_row' = t('supplier.section.arrange_tables.modal.by_row') + .controls + input#arrange-tables-by_row type="radio" name="arrange-table-option" value="by_row" + label for="arrange-tables-by-row-count" data-t='section.arrange_tables.modal.by_row_count' = t('supplier.section.arrange_tables.modal.by_row_count') + input.input-mini#arrange-tables-by-row-count type="text" value=0 + ' + span data-t='models.plural.table' + .control-group + label.control-label for='arrange-tables-by_column' data-t='section.arrange_tables.modal.by_column' = t('supplier.section.arrange_tables.modal.by_column') + .controls + input#arrange-tables-by_column type="radio" name="arrange-table-option" value="by_column" + label for="arrange-tables-by-column-count" data-t='section.arrange_tables.modal.by_column_count' = t('supplier.section.arrange_tables.modal.by_column_count') + input.input-mini#arrange-tables-by-column-count type="text" value=0 + ' + span data-t='models.plural.table' diff --git a/app/assets/stylesheets/supplier/foundation1/_qmodal.css.sass b/app/assets/stylesheets/supplier/foundation1/_qmodal.css.sass index 5fbbe4f4..98eeefcd 100644 --- a/app/assets/stylesheets/supplier/foundation1/_qmodal.css.sass +++ b/app/assets/stylesheets/supplier/foundation1/_qmodal.css.sass @@ -1,6 +1,6 @@ .modal margin: 10px auto - width: 300px + width: 500px background-color: #fff padding: 1em diff --git a/app/assets/stylesheets/supplier/foundation1/_qsections.css.sass b/app/assets/stylesheets/supplier/foundation1/_qsections.css.sass index 74f2e764..febfaea6 100644 --- a/app/assets/stylesheets/supplier/foundation1/_qsections.css.sass +++ b/app/assets/stylesheets/supplier/foundation1/_qsections.css.sass @@ -15,6 +15,12 @@ @extend .fa @extend .fa-lg @extend .fa-save +.arrange-tables-type-button + +button($bg: $secondary-color, $padding: $button-tny) +.arrange-tables-current-type + display: inline-block + margin-left: 10px + margin-right: 10px .section-actions-menu-header padding-top: 4px padding-bottom: 4px diff --git a/app/controllers/suppliers/sections_controller.rb b/app/controllers/suppliers/sections_controller.rb index d4bfe407..fb9f2499 100644 --- a/app/controllers/suppliers/sections_controller.rb +++ b/app/controllers/suppliers/sections_controller.rb @@ -132,6 +132,7 @@ module Suppliers table = Table.new(number: table_number) table.supplier = current_supplier table.section = @section + table.save end @section.arrange_tables_in_grid table_json = ActiveModel::ArraySerializer.new(@section.tables, each_serializer: SupplierTableSerializer, root: false).as_json diff --git a/app/models/list.rb b/app/models/list.rb index 3f608111..cf6e07d9 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -22,7 +22,7 @@ class List attr_protected :supplier_id - validates :table_id, presence: true + #validates :table_id, presence: true, table can be deleted validates :supplier_id, presence: true view :by_supplier_id_and_id, key: [:supplier_id, :_id] diff --git a/app/models/section.rb b/app/models/section.rb index 4eecdbb8..7a3c5331 100644 --- a/app/models/section.rb +++ b/app/models/section.rb @@ -46,28 +46,32 @@ class Section def width=(val) val = val.to_f + return val if width == val self.path[0] ||= [0.0, 0.0] self.path[1] ||= [0.0, 0.0] self.path[2] ||= [0.0, 0.0] self.path[3] ||= [0.0, 0.0] - unless path[1][0] == val && path[2][0] == val - self.path[1][0] = val - self.path[2][0] = val - path_will_change! - end + path[0][0] = 0 + path[1][0] = val + path[2][0] = val + path[3][0] = 0 + path_will_change! + val end def height=(val) val = val.to_f + return val if height == val self.path[0] ||= [0.0, 0.0] self.path[1] ||= [0.0, 0.0] self.path[2] ||= [0.0, 0.0] self.path[3] ||= [0.0, 0.0] - unless path[2][1] == val && path[3][1] == val - self.path[2][1] = val - self.path[3][1] = val - path_will_change! - end + path[0][1] = 0 + path[1][1] = 0 + path[2][1] = val + path[3][1] = val + path_will_change! + val end def tables_with_active_list_id diff --git a/spec/models/section_spec.rb b/spec/models/section_spec.rb index 91bff18f..fdc55d1c 100644 --- a/spec/models/section_spec.rb +++ b/spec/models/section_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe :section do before :each do - @section = build :section + @section = build :section, path: [[10, 30], [20,30], [20,40], [10, 40]] end describe :path do diff --git a/vendor/assets/ember/development/ember-data.js b/vendor/assets/ember/development/ember-data.js index 318e9826..b0c71a61 100644 --- a/vendor/assets/ember/development/ember-data.js +++ b/vendor/assets/ember/development/ember-data.js @@ -1,11 +1,11 @@ // Fetched from channel: canary, with url http://builds.emberjs.com/canary/ember-data.js -// Fetched on: 2014-03-22T07:36:41Z +// Fetched on: 2014-07-01T13:19:27Z /*! * @overview Ember Data * @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.9+canary.9a5cc255 */ (function(global) { var define, requireModule, require, requirejs; @@ -62,44 +62,37 @@ var define, requireModule, require, requirejs; }; })(); -define("activemodel-adapter/lib/initializers", - ["../../ember-data/lib/system/container_proxy","./system/active_model_serializer","./system/active_model_adapter"], - function(__dependency1__, __dependency2__, __dependency3__) { - "use strict"; - var ContainerProxy = __dependency1__["default"]; - var ActiveModelSerializer = __dependency2__["default"]; - var ActiveModelAdapter = __dependency3__["default"]; - - Ember.onLoad('Ember.Application', function(Application) { - Application.initializer({ - name: "activeModelAdapter", - - initialize: function(container, application) { - var proxy = new ContainerProxy(container); - proxy.registerDeprecations([ - {deprecated: 'serializer:_ams', valid: 'serializer:-active-model'}, - {deprecated: 'adapter:_ams', valid: 'adapter:-active-model'} - ]); - - application.register('serializer:-active-model', ActiveModelSerializer); - application.register('adapter:-active-model', ActiveModelAdapter); - } - }); - }); - }); define("activemodel-adapter/lib/main", - ["./system","./initializers","exports"], - function(__dependency1__, __dependency2__, __exports__) { + ["./system","exports"], + function(__dependency1__, __exports__) { "use strict"; var ActiveModelAdapter = __dependency1__.ActiveModelAdapter; var ActiveModelSerializer = __dependency1__.ActiveModelSerializer; var EmbeddedRecordsMixin = __dependency1__.EmbeddedRecordsMixin; - __exports__.ActiveModelAdapter = ActiveModelAdapter; __exports__.ActiveModelSerializer = ActiveModelSerializer; __exports__.EmbeddedRecordsMixin = EmbeddedRecordsMixin; }); +define("activemodel-adapter/lib/setup-container", + ["../../ember-data/lib/system/container_proxy","./system/active_model_serializer","./system/active_model_adapter","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var ContainerProxy = __dependency1__["default"]; + var ActiveModelSerializer = __dependency2__["default"]; + var ActiveModelAdapter = __dependency3__["default"]; + + __exports__["default"] = function setupActiveModelAdapter(container, application){ + var proxy = new ContainerProxy(container); + proxy.registerDeprecations([ + {deprecated: 'serializer:_ams', valid: 'serializer:-active-model'}, + {deprecated: 'adapter:_ams', valid: 'adapter:-active-model'} + ]); + + container.register('serializer:-active-model', ActiveModelSerializer); + container.register('adapter:-active-model', ActiveModelAdapter); + }; + }); define("activemodel-adapter/lib/system", ["./system/embedded_records_mixin","./system/active_model_adapter","./system/active_model_serializer","exports"], function(__dependency1__, __dependency2__, __dependency3__, __exports__) { @@ -132,10 +125,11 @@ define("activemodel-adapter/lib/system/active_model_adapter", /** The ActiveModelAdapter is a subclass of the RESTAdapter designed to integrate - with a JSON API that uses an underscored naming convention instead of camelcasing. + with a JSON API that uses an underscored naming convention instead of camelCasing. It has been designed to work out of the box with the [active_model_serializers](http://github.com/rails-api/active_model_serializers) - Ruby gem. + Ruby gem. This Adapter expects specific settings using ActiveModel::Serializers, + `embed :ids, include: true` which sideloads the records. This adapter extends the DS.RESTAdapter by making consistent use of the camelization, decamelization and pluralization methods to normalize the serialized JSON into a @@ -145,6 +139,10 @@ define("activemodel-adapter/lib/system/active_model_adapter", The ActiveModelAdapter expects the JSON returned from your server to follow the REST adapter conventions substituting underscored keys for camelcased ones. + + Unlike the DS.RESTAdapter, async relationship keys must be the singular form + of the relationship name, followed by "_id" for DS.belongsTo relationships, + or "_ids" for DS.hasMany relationships. ### Conventional Names @@ -166,6 +164,7 @@ define("activemodel-adapter/lib/system/active_model_adapter", ```js { "famous_person": { + "id": 1, "first_name": "Barack", "last_name": "Obama", "occupation": "President" @@ -173,6 +172,42 @@ define("activemodel-adapter/lib/system/active_model_adapter", } ``` + Let's imagine that `Occupation` is just another model: + + ```js + App.Person = DS.Model.extend({ + firstName: DS.attr('string'), + lastName: DS.attr('string'), + occupation: DS.belongsTo('occupation') + }); + + App.Occupation = DS.Model.extend({ + name: DS.attr('string'), + salary: DS.attr('number'), + people: DS.hasMany('person') + }); + ``` + + The JSON needed to avoid extra server calls, should look like this: + + ```js + { + "people": [{ + "id": 1, + "first_name": "Barack", + "last_name": "Obama", + "occupation_id": 1 + }], + + "occupations": [{ + "id": 1, + "name": "President", + "salary": 100000, + "person_ids": [1] + }] + } + ``` + @class ActiveModelAdapter @constructor @namespace DS @@ -192,7 +227,7 @@ define("activemodel-adapter/lib/system/active_model_adapter", @method pathForType @param {String} type - @returns String + @return String */ pathForType: function(type) { var decamelized = decamelize(type); @@ -214,7 +249,7 @@ define("activemodel-adapter/lib/system/active_model_adapter", @method ajaxError @param jqXHR - @returns error + @return error */ ajaxError: function(jqXHR) { var error = this._super(jqXHR); @@ -256,16 +291,102 @@ define("activemodel-adapter/lib/system/active_model_serializer", capitalize = Ember.String.capitalize, decamelize = Ember.String.decamelize, underscore = Ember.String.underscore; + /** + The ActiveModelSerializer is a subclass of the RESTSerializer designed to integrate + with a JSON API that uses an underscored naming convention instead of camelCasing. + It has been designed to work out of the box with the + [active_model_serializers](http://github.com/rails-api/active_model_serializers) + Ruby gem. This Serializer expects specific settings using ActiveModel::Serializers, + `embed :ids, include: true` which sideloads the records. + This serializer extends the DS.RESTSerializer by making consistent + use of the camelization, decamelization and pluralization methods to + normalize the serialized JSON into a format that is compatible with + a conventional Rails backend and Ember Data. + + ## JSON Structure + + The ActiveModelSerializer expects the JSON returned from your server + to follow the REST adapter conventions substituting underscored keys + for camelcased ones. + + ### Conventional Names + + Attribute names in your JSON payload should be the underscored versions of + the attributes in your Ember.js models. + + For example, if you have a `Person` model: + + ```js + App.FamousPerson = DS.Model.extend({ + firstName: DS.attr('string'), + lastName: DS.attr('string'), + occupation: DS.attr('string') + }); + ``` + + The JSON returned should look like this: + + ```js + { + "famous_person": { + "id": 1, + "first_name": "Barack", + "last_name": "Obama", + "occupation": "President" + } + } + ``` + + Let's imagine that `Occupation` is just another model: + + ```js + App.Person = DS.Model.extend({ + firstName: DS.attr('string'), + lastName: DS.attr('string'), + occupation: DS.belongsTo('occupation') + }); + + App.Occupation = DS.Model.extend({ + name: DS.attr('string'), + salary: DS.attr('number'), + people: DS.hasMany('person') + }); + ``` + + The JSON needed to avoid extra server calls, should look like this: + + ```js + { + "people": [{ + "id": 1, + "first_name": "Barack", + "last_name": "Obama", + "occupation_id": 1 + }], + + "occupations": [{ + "id": 1, + "name": "President", + "salary": 100000, + "person_ids": [1] + }] + } + ``` + + @class ActiveModelSerializer + @namespace DS + @extends DS.RESTSerializer + */ var ActiveModelSerializer = RESTSerializer.extend({ // SERIALIZE /** - Converts camelcased attributes to underscored when serializing. + Converts camelCased attributes to underscored when serializing. @method keyForAttribute @param {String} attribute - @returns String + @return String */ keyForAttribute: function(attr) { return decamelize(attr); @@ -278,11 +399,10 @@ define("activemodel-adapter/lib/system/active_model_serializer", @method keyForRelationship @param {String} key @param {String} kind - @returns String + @return String */ keyForRelationship: function(key, kind) { key = decamelize(key); - //if(key === 'active_list') debugger; if (kind === "belongsTo") { return key + "_id"; } else if (kind === "hasMany") { @@ -292,7 +412,7 @@ define("activemodel-adapter/lib/system/active_model_serializer", } }, - /** + /* Does not serialize hasMany relationships by default. */ serializeHasMany: Ember.K, @@ -322,29 +442,19 @@ define("activemodel-adapter/lib/system/active_model_serializer", 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)); + + if (belongsTo) { + key = this.keyForAttribute(key); + json[key + "_type"] = capitalize(belongsTo.constructor.typeKey); + } }, // EXTRACT /** - Extracts the model typeKey from underscored root objects. + Add extra step to `DS.RESTSerializer.normalize` so links are normalized. - @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 + If your payload looks like: ```js { @@ -355,6 +465,7 @@ define("activemodel-adapter/lib/system/active_model_serializer", } } ``` + The normalized version would look like this ```js @@ -371,7 +482,7 @@ define("activemodel-adapter/lib/system/active_model_serializer", @param {subclass of DS.Model} type @param {Object} hash @param {String} prop - @returns Object + @return Object */ normalize: function(type, hash, prop) { @@ -384,7 +495,7 @@ define("activemodel-adapter/lib/system/active_model_serializer", Convert `snake_cased` links to `camelCase` @method normalizeLinks - @param {Object} hash + @param {Object} data */ normalizeLinks: function(data){ @@ -425,10 +536,10 @@ define("activemodel-adapter/lib/system/active_model_serializer", @private */ normalizeRelationships: function(type, hash) { - var payloadKey, payload; if (this.keyForRelationship) { type.eachRelationship(function(key, relationship) { + var payloadKey, payload; if (relationship.options.polymorphic) { payloadKey = this.keyForAttribute(key); payload = hash[payloadKey]; @@ -463,24 +574,83 @@ define("activemodel-adapter/lib/system/embedded_records_mixin", "use strict"; var get = Ember.get; var forEach = Ember.EnumerableUtils.forEach; + var camelize = Ember.String.camelize; 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. + ## Using Embedded Records + + `DS.EmbeddedRecordsMixin` supports serializing embedded records. + + To set up embedded records, include the mixin when extending a serializer + then define and configure embedded (model) relationships. + + Below is an example of a per-type serializer ('post' type). ```js App.PostSerializer = DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, { attrs: { - comments: {embedded: 'always'} + author: {embedded: 'always'}, + comments: {serialize: 'ids'} } }) ``` - Currently only `{embedded: 'always'}` records are supported. + The `attrs` option for a resource `{embedded: 'always'}` is shorthand for: + + ```js + {serialize: 'records', deserialize: 'records'} + ``` + + ### Configuring Attrs + + A resource's `attrs` option may be set to use `ids`, `records` or `no` for the + `serialize` and `deserialize` settings. + + The `attrs` property can be set on the ApplicationSerializer or a per-type + serializer. + + In the case where embedded JSON is expected while extracting a payload (reading) + the setting is `deserialize: 'records'`, there is no need to use `ids` when + extracting as that is the default behavior without this mixin if you are using + the vanilla ActiveModelAdapter. Likewise, to embed JSON in the payload while + serializing `serialize: 'records'` is the setting to use. There is an option of + not embedding JSON in the serialized payload by using `serialize: 'ids'`. If you + do not want the relationship sent at all, you can use `serialize: 'no'`. + + + ### ActiveModelSerializer defaults + If you do not overwrite `attrs` for a specific relationship, the `ActiveModelSerializer` + will behave in the following way: + + BelongsTo: `{serialize:'id', deserialize:'id'}` + HasMany: `{serialize:'no', deserialize:'ids'}` + + ### Model Relationships + + Embedded records must have a model defined to be extracted and serialized. + + To successfully extract and serialize embedded records the model relationships + must be setup correcty See the + [defining relationships](/guides/models/defining-models/#toc_defining-relationships) + section of the **Defining Models** guide page. + + Records without an `id` property are not considered embedded records, model + instances must have an `id` property to be used with Ember Data. + + ### Example JSON payloads, Models and Serializers + + **When customizing a serializer it is imporant to grok what the cusomizations + are, please read the docs for the methods this mixin provides, in case you need + to modify to fit your specific needs.** + + For example review the docs for each method of this mixin: + + * [extractArray](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_extractArray) + * [extractSingle](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_extractSingle) + * [serializeBelongsTo](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_serializeBelongsTo) + * [serializeHasMany](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_serializeHasMany) @class EmbeddedRecordsMixin @namespace DS @@ -488,102 +658,464 @@ define("activemodel-adapter/lib/system/embedded_records_mixin", var EmbeddedRecordsMixin = Ember.Mixin.create({ /** - Serialize has-many relationship when it is configured as embedded objects. + Serialize `belongsTo` relationship when it is configured as an embedded object. + + This example of an author model belongs to a post model: + + ```js + Post = DS.Model.extend({ + title: DS.attr('string'), + body: DS.attr('string'), + author: DS.belongsTo('author') + }); + + Author = DS.Model.extend({ + name: DS.attr('string'), + post: DS.belongsTo('post') + }); + ``` + + Use a custom (type) serializer for the post model to configure embedded author + + ```js + App.PostSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { + attrs: { + author: {embedded: 'always'} + } + }) + ``` + + A payload with an attribute configured for embedded records can serialize + the records together under the root attribute's payload: + + ```js + { + "post": { + "id": "1" + "title": "Rails is omakase", + "author": { + "id": "2" + "name": "dhh" + } + } + } + ``` + + @method serializeBelongsTo + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ + serializeBelongsTo: function(record, json, relationship) { + var attr = relationship.key; + var attrs = this.get('attrs'); + if (noSerializeOptionSpecified(attrs, attr)) { + this._super(record, json, relationship); + return; + } + var includeIds = hasSerializeIdsOption(attrs, attr); + var includeRecords = hasSerializeRecordsOption(attrs, attr); + var embeddedRecord = record.get(attr); + if (includeIds) { + key = this.keyForRelationship(attr, relationship.kind); + if (!embeddedRecord) { + json[key] = null; + } else { + json[key] = get(embeddedRecord, 'id'); + } + } else if (includeRecords) { + var key = this.keyForRelationship(attr); + if (!embeddedRecord) { + json[key] = null; + } else { + json[key] = embeddedRecord.serialize({includeId: true}); + this.removeEmbeddedForeignKey(record, embeddedRecord, relationship, json[key]); + } + } + }, + + /** + Serialize `hasMany` relationship when it is configured as embedded objects. + + This example of a post model has many comments: + + ```js + Post = DS.Model.extend({ + title: DS.attr('string'), + body: DS.attr('string'), + comments: DS.hasMany('comment') + }); + + Comment = DS.Model.extend({ + body: DS.attr('string'), + post: DS.belongsTo('post') + }); + ``` + + Use a custom (type) serializer for the post model to configure embedded comments + + ```js + App.PostSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { + attrs: { + comments: {embedded: 'always'} + } + }) + ``` + + A payload with an attribute configured for embedded records can serialize + the records together under the root attribute's payload: + + ```js + { + "post": { + "id": "1" + "title": "Rails is omakase", + "body": "I want this for my ORM, I want that for my template language..." + "comments": [{ + "id": "1", + "body": "Rails is unagi" + }, { + "id": "2", + "body": "Omakase O_o" + }] + } + } + ``` + + The attrs options object can use more specific instruction for extracting and + serializing. When serializing, an option to embed `ids` or `records` can be set. + When extracting the only option is `records`. + + So `{embedded: 'always'}` is shorthand for: + `{serialize: 'records', deserialize: 'records'}` + + To embed the `ids` for a related object (using a hasMany relationship): + + ```js + App.PostSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { + attrs: { + comments: {serialize: 'ids', deserialize: 'records'} + } + }) + ``` + + ```js + { + "post": { + "id": "1" + "title": "Rails is omakase", + "body": "I want this for my ORM, I want that for my template language..." + "comments": ["1", "2"] + } + } + ``` @method serializeHasMany + @param {DS.Model} record + @param {Object} json + @param {Object} relationship */ 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; + var attr = relationship.key; + var attrs = this.get('attrs'); + if (noSerializeOptionSpecified(attrs, attr)) { + this._super(record, json, relationship); + return; + } + var includeIds = hasSerializeIdsOption(attrs, attr); + var includeRecords = hasSerializeRecordsOption(attrs, attr); + var key; + if (includeIds) { + key = this.keyForRelationship(attr, relationship.kind); + json[key] = get(record, attr).mapBy('id'); + } else if (includeRecords) { + key = getKeyForAttribute.call(this, attr); + json[key] = get(record, attr).map(function(embeddedRecord) { + var serializedEmbeddedRecord = embeddedRecord.serialize({includeId: true}); + this.removeEmbeddedForeignKey(record, embeddedRecord, relationship, serializedEmbeddedRecord); + return serializedEmbeddedRecord; }, this); } }, /** - Extract embedded objects out of the payload for a single object - and add them as sideloaded objects instead. + When serializing an embedded record, modify the property (in the json payload) + that refers to the parent record (foreign key for relationship). - @method extractSingle + Serializing a `belongsTo` relationship removes the property that refers to the + parent record + + Serializing a `hasMany` relationship does not remove the property that refers to + the parent record. + + @method removeEmbeddedForeignKey + @param {DS.Model} record + @param {DS.Model} embeddedRecord + @param {Object} relationship + @param {Object} json */ - 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); + removeEmbeddedForeignKey: function (record, embeddedRecord, relationship, json) { + if (relationship.kind === 'hasMany') { + return; + } else if (relationship.kind === 'belongsTo') { + var parentRecord = record.constructor.inverseFor(relationship.key); + if (parentRecord) { + var name = parentRecord.name; + var embeddedSerializer = this.store.serializerFor(embeddedRecord.constructor); + var parentKey = embeddedSerializer.keyForRelationship(name, parentRecord.kind); + if (parentKey) { + delete json[parentKey]; + } + } + } }, /** - Extract embedded objects out of a standard payload - and add them as sideloaded objects instead. + Extract an embedded object from the payload for a single object + and add the object in the compound document (side-loaded) format instead. + + A payload with an attribute configured for embedded records needs to be extracted: + + ```js + { + "post": { + "id": 1 + "title": "Rails is omakase", + "author": { + "id": 2 + "name": "dhh" + } + "comments": [] + } + } + ``` + + Ember Data is expecting a payload with a compound document (side-loaded) like: + + ```js + { + "post": { + "id": "1" + "title": "Rails is omakase", + "author": "2" + "comments": [] + }, + "authors": [{ + "id": "2" + "post": "1" + "name": "dhh" + }] + "comments": [] + } + ``` + + The payload's `author` attribute represents an object with a `belongsTo` relationship. + The `post` attribute under `author` is the foreign key with the id for the post + + @method extractSingle + @param {DS.Store} store + @param {subclass of DS.Model} primaryType + @param {Object} payload + @param {String} recordId + @return Object the primary response to the original request + */ + extractSingle: function(store, primaryType, payload, recordId) { + var key = primaryType.typeKey; + var root = getKeyForAttribute.call(this, key); + var partial = payload[root]; + + updatePayloadWithEmbedded(this, store, primaryType, payload, partial); + + return this._super(store, primaryType, payload, recordId); + }, + + /** + Extract embedded objects in an array when an attr is configured for embedded, + and add them as side-loaded objects instead. + + A payload with an attr configured for embedded records needs to be extracted: + + ```js + { + "post": { + "id": "1" + "title": "Rails is omakase", + "comments": [{ + "id": "1", + "body": "Rails is unagi" + }, { + "id": "2", + "body": "Omakase O_o" + }] + } + } + ``` + + Ember Data is expecting a payload with compound document (side-loaded) like: + + ```js + { + "post": { + "id": "1" + "title": "Rails is omakase", + "comments": ["1", "2"] + }, + "comments": [{ + "id": "1", + "body": "Rails is unagi" + }, { + "id": "2", + "body": "Omakase O_o" + }] + } + ``` + + The payload's `comments` attribute represents records in a `hasMany` relationship @method extractArray + @param {DS.Store} store + @param {subclass of DS.Model} primaryType + @param {Object} payload + @return {Array} The primary array that was returned in response + to the original query. */ - extractArray: function(store, type, payload) { - var root = this.keyForAttribute(type.typeKey), - partials = payload[pluralize(root)]; + extractArray: function(store, primaryType, payload) { + var key = primaryType.typeKey; + var root = getKeyForAttribute.call(this, key); + var partials = payload[pluralize(root)]; forEach(partials, function(partial) { - updatePayloadWithEmbedded(store, this, type, partial, payload); + updatePayloadWithEmbedded(this, store, primaryType, payload, partial); }, this); - return this._super(store, type, payload); + return this._super(store, primaryType, payload); } }); - function updatePayloadWithEmbedded(store, serializer, type, partial, payload) { + // `keyForAttribute` is optional but may be defined when extending a serializer prototype + var getKeyForAttribute = function(attr) { + return (this.keyForAttribute) ? this.keyForAttribute(attr) : attr; + }; + + // checks config for attrs option to embedded (always) - serialize and deserialize + function hasEmbeddedAlwaysOption(attrs, attr) { + var option = attrsOption(attrs, attr); + return option && option.embedded === 'always'; + } + + // checks config for attrs option to serialize ids + function hasSerializeRecordsOption(attrs, attr) { + var alwaysEmbed = hasEmbeddedAlwaysOption(attrs, attr); + var option = attrsOption(attrs, attr); + return alwaysEmbed || (option && (option.serialize === 'records')); + } + + // checks config for attrs option to serialize records + function hasSerializeIdsOption(attrs, attr) { + var option = attrsOption(attrs, attr); + return option && (option.serialize === 'ids' || option.serialize === 'id'); + } + + // checks config for attrs option to serialize records + function noSerializeOptionSpecified(attrs, attr) { + var option = attrsOption(attrs, attr); + var serializeRecords = hasSerializeRecordsOption(attrs, attr); + var serializeIds = hasSerializeIdsOption(attrs, attr); + return !(option && (option.serialize || option.embedded)); + } + + // checks config for attrs option to deserialize records + // a defined option object for a resource is treated the same as + // `deserialize: 'records'` + function hasDeserializeRecordsOption(attrs, attr) { + var alwaysEmbed = hasEmbeddedAlwaysOption(attrs, attr); + var option = attrsOption(attrs, attr); + var hasSerializingOption = option && (option.deserialize || option.serialize); + return alwaysEmbed || hasSerializingOption /* option.deserialize === 'records' */; + } + + function attrsOption(attrs, attr) { + return attrs && (attrs[Ember.String.camelize(attr)] || attrs[attr]); + } + + // chooses a relationship kind to branch which function is used to update payload + // does not change payload if attr is not embedded + function updatePayloadWithEmbedded(serializer, store, type, payload, partial) { 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; + if (hasDeserializeRecordsOption(attrs, key)) { + if (relationship.kind === "hasMany") { + updatePayloadWithEmbeddedHasMany(serializer, store, key, relationship, payload, partial); + } + if (relationship.kind === "belongsTo") { + updatePayloadWithEmbeddedBelongsTo(serializer, store, key, relationship, payload, partial); } - - 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); + }); + } + + // handles embedding for `hasMany` relationship + function updatePayloadWithEmbeddedHasMany(serializer, store, primaryType, relationship, payload, partial) { + var embeddedSerializer = store.serializerFor(relationship.type.typeKey); + var primaryKey = get(serializer, 'primaryKey'); + var attr = relationship.type.typeKey; + // underscore forces the embedded records to be side loaded. + // it is needed when main type === relationship.type + var embeddedTypeKey = '_' + serializer.typeForRoot(relationship.type.typeKey); + var expandedKey = serializer.keyForRelationship(primaryType, relationship.kind); + var attribute = getKeyForAttribute.call(serializer, primaryType); + var ids = []; + + if (!partial[attribute]) { + return; + } + + payload[embeddedTypeKey] = payload[embeddedTypeKey] || []; + + forEach(partial[attribute], function(data) { + var embeddedType = store.modelFor(attr); + updatePayloadWithEmbedded(embeddedSerializer, store, embeddedType, payload, data); + ids.push(data[primaryKey]); + payload[embeddedTypeKey].push(data); + }); + + partial[expandedKey] = ids; + delete partial[attribute]; + } + + // handles embedding for `belongsTo` relationship + function updatePayloadWithEmbeddedBelongsTo(serializer, store, primaryType, relationship, payload, partial) { + var attrs = serializer.get('attrs'); + + if (!attrs || + !(hasDeserializeRecordsOption(attrs, Ember.String.camelize(primaryType)) || + hasDeserializeRecordsOption(attrs, primaryType))) { + return; + } + var attr = relationship.type.typeKey; + var _serializer = store.serializerFor(relationship.type.typeKey); + var primaryKey = get(_serializer, 'primaryKey'); + var embeddedTypeKey = Ember.String.pluralize(attr); // TODO don't use pluralize + var expandedKey = _serializer.keyForRelationship(primaryType, relationship.kind); + var attribute = getKeyForAttribute.call(_serializer, primaryType); + + if (!partial[attribute]) { + return; + } + payload[embeddedTypeKey] = payload[embeddedTypeKey] || []; + var embeddedType = store.modelFor(relationship.type.typeKey); + // Recursive call for nested record + updatePayloadWithEmbedded(_serializer, store, embeddedType, payload, partial[attribute]); + partial[expandedKey] = partial[attribute].id; + // Need to move an embedded `belongsTo` object into a pluralized collection + payload[embeddedTypeKey].push(partial[attribute]); + // Need a reference to the parent so relationship works between both `belongsTo` records + partial[attribute][relationship.parentType.typeKey + '_id'] = partial.id; + delete partial[attribute]; } __exports__["default"] = EmbeddedRecordsMixin; @@ -610,8 +1142,9 @@ define("ember-data/lib/adapters/fixture_adapter", @module ember-data */ - var get = Ember.get, fmt = Ember.String.fmt, - indexOf = Ember.EnumerableUtils.indexOf; + var get = Ember.get; + var fmt = Ember.String.fmt; + var indexOf = Ember.EnumerableUtils.indexOf; var counter = 0; @@ -619,11 +1152,11 @@ define("ember-data/lib/adapters/fixture_adapter", /** `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 + It's primarily used for development and testing. You can also use + `DS.FixtureAdapter` while working on the API but is 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 + system would do. It's possible to develop your entire application with `DS.FixtureAdapter`. For information on how to use the `FixtureAdapter` in your @@ -634,7 +1167,7 @@ define("ember-data/lib/adapters/fixture_adapter", @namespace DS @extends DS.Adapter */ - var FixtureAdapter = Adapter.extend({ + __exports__["default"] = Adapter.extend({ // by default, fixtures are already in normalized form serializer: null, @@ -746,10 +1279,10 @@ define("ember-data/lib/adapters/fixture_adapter", var fixtures = this.fixturesForType(type), fixture; - Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures); + Ember.assert("Unable to find fixtures for model type "+type.toString() +". If you're defining your fixtures using `Model.FIXTURES = ...`, please change it to `Model.reopenClass({ FIXTURES: ... })`.", fixtures); if (fixtures) { - fixture = Ember.A(fixtures).findProperty('id', id); + fixture = Ember.A(fixtures).findBy('id', id); } if (fixture) { @@ -946,8 +1479,6 @@ define("ember-data/lib/adapters/fixture_adapter", }, "DS: FixtureAdapter#simulateRemoteCall"); } }); - - __exports__["default"] = FixtureAdapter; }); define("ember-data/lib/adapters/rest_adapter", ["../system/adapter","exports"], @@ -958,7 +1489,7 @@ define("ember-data/lib/adapters/rest_adapter", */ var Adapter = __dependency1__["default"]; - var get = Ember.get, set = Ember.set; + var get = Ember.get; var forEach = Ember.ArrayPolyfills.forEach; /** @@ -1048,7 +1579,7 @@ define("ember-data/lib/adapters/rest_adapter", ```js - DS.RESTAdapter.reopen({ + App.ApplicationAdapter = DS.RESTAdapter.extend({ headers: { "API_KEY": "secret key", "ANOTHER_HEADER": "Some header value" @@ -1057,7 +1588,8 @@ define("ember-data/lib/adapters/rest_adapter", ``` `headers` can also be used as a computed property to support dynamic - headers. + headers. In the example below, the `session` object has been + injected into an adapter by Ember's container. ```js App.ApplicationAdapter = DS.RESTAdapter.extend({ @@ -1070,12 +1602,30 @@ define("ember-data/lib/adapters/rest_adapter", }); ``` + In some cases, your dynamic headers may require data from some + object outside of Ember's observer system (for example + `document.cookie`). You can use the + [volatile](/api/classes/Ember.ComputedProperty.html#method_volatile) + function to set the property into a non-cached mode causing the headers to + be recomputed with every request. + + ```js + App.ApplicationAdapter = DS.RESTAdapter.extend({ + headers: function() { + return { + "API_KEY": Ember.get(document.cookie.match(/apiKey\=([^;]*)/), "1"), + "ANOTHER_HEADER": "Some header value" + }; + }.property().volatile() + }); + ``` + @class RESTAdapter @constructor @namespace DS @extends DS.Adapter */ - var RESTAdapter = Adapter.extend({ + __exports__["default"] = Adapter.extend({ defaultSerializer: '-rest', /** Endpoint paths can be prefixed with a `namespace` by setting the namespace @@ -1109,12 +1659,14 @@ define("ember-data/lib/adapters/rest_adapter", */ /** - 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. + 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. For dynamic headers see [headers + customization](/api/data/classes/DS.RESTAdapter.html#toc_headers-customization). ```javascript - DS.RESTAdapter.reopen({ + App.ApplicationAdapter = DS.RESTAdapter.extend({ headers: { "API_KEY": "secret key", "ANOTHER_HEADER": "Some header value" @@ -1139,7 +1691,7 @@ define("ember-data/lib/adapters/rest_adapter", @param {DS.Store} store @param {subclass of DS.Model} type @param {String} id - @returns {Promise} promise + @return {Promise} promise */ find: function(store, type, id) { return this.ajax(this.buildURL(type.typeKey, id), 'GET'); @@ -1157,7 +1709,7 @@ define("ember-data/lib/adapters/rest_adapter", @param {DS.Store} store @param {subclass of DS.Model} type @param {String} sinceToken - @returns {Promise} promise + @return {Promise} promise */ findAll: function(store, type, sinceToken) { var query; @@ -1184,7 +1736,7 @@ define("ember-data/lib/adapters/rest_adapter", @param {DS.Store} store @param {subclass of DS.Model} type @param {Object} query - @returns {Promise} promise + @return {Promise} promise */ findQuery: function(store, type, query) { return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query }); @@ -1222,7 +1774,7 @@ define("ember-data/lib/adapters/rest_adapter", @param {DS.Store} store @param {subclass of DS.Model} type @param {Array} ids - @returns {Promise} promise + @return {Promise} promise */ findMany: function(store, type, ids) { return this.ajax(this.buildURL(type.typeKey), 'GET', { data: { ids: ids } }); @@ -1255,7 +1807,7 @@ define("ember-data/lib/adapters/rest_adapter", @param {DS.Store} store @param {DS.Model} record @param {String} url - @returns {Promise} promise + @return {Promise} promise */ findHasMany: function(store, record, url) { var host = get(this, 'host'), @@ -1294,7 +1846,7 @@ define("ember-data/lib/adapters/rest_adapter", @param {DS.Store} store @param {DS.Model} record @param {String} url - @returns {Promise} promise + @return {Promise} promise */ findBelongsTo: function(store, record, url) { var id = get(record, 'id'), @@ -1317,7 +1869,7 @@ define("ember-data/lib/adapters/rest_adapter", @param {DS.Store} store @param {subclass of DS.Model} type @param {DS.Model} record - @returns {Promise} promise + @return {Promise} promise */ createRecord: function(store, type, record) { var data = {}; @@ -1342,7 +1894,7 @@ define("ember-data/lib/adapters/rest_adapter", @param {DS.Store} store @param {subclass of DS.Model} type @param {DS.Model} record - @returns {Promise} promise + @return {Promise} promise */ updateRecord: function(store, type, record) { var data = {}; @@ -1364,7 +1916,7 @@ define("ember-data/lib/adapters/rest_adapter", @param {DS.Store} store @param {subclass of DS.Model} type @param {DS.Model} record - @returns {Promise} promise + @return {Promise} promise */ deleteRecord: function(store, type, record) { var id = get(record, 'id'); @@ -1385,7 +1937,7 @@ define("ember-data/lib/adapters/rest_adapter", @method buildURL @param {String} type @param {String} id - @returns {String} url + @return {String} url */ buildURL: function(type, id) { var url = [], @@ -1450,17 +2002,17 @@ define("ember-data/lib/adapters/rest_adapter", endpoint of "/line_items/". ```js - DS.RESTAdapter.reopen({ + App.ApplicationAdapter = DS.RESTAdapter.extend({ pathForType: function(type) { var decamelized = Ember.String.decamelize(type); return Ember.String.pluralize(decamelized); - }; + } }); ``` @method pathForType @param {String} type - @returns {String} path + @return {String} path **/ pathForType: function(type) { var camelized = Ember.String.camelize(type); @@ -1502,7 +2054,7 @@ define("ember-data/lib/adapters/rest_adapter", @return {Object} jqXHR */ ajaxError: function(jqXHR) { - if (jqXHR) { + if (jqXHR && typeof jqXHR === 'object') { jqXHR.then = null; } @@ -1548,7 +2100,7 @@ define("ember-data/lib/adapters/rest_adapter", }; Ember.$.ajax(hash); - }, "DS: RestAdapter#ajax " + type + " to " + url); + }, "DS: RESTAdapter#ajax " + type + " to " + url); }, /** @@ -1585,8 +2137,6 @@ define("ember-data/lib/adapters/rest_adapter", } }); - - __exports__["default"] = RESTAdapter; }); define("ember-data/lib/core", ["exports"], @@ -1607,11 +2157,11 @@ define("ember-data/lib/core", /** @property VERSION @type String - @default '1.0.0-beta.7+canary.f85a7438' + @default '1.0.0-beta.9+canary.9a5cc255' @static */ DS = Ember.Namespace.create({ - VERSION: '1.0.0-beta.7+canary.f85a7438' + VERSION: '1.0.0-beta.9+canary.9a5cc255' }); if (Ember.libraries) { @@ -1621,6 +2171,90 @@ define("ember-data/lib/core", __exports__["default"] = DS; }); +define("ember-data/lib/ember-initializer", + ["./setup-container"], + function(__dependency1__) { + "use strict"; + var setupContainer = __dependency1__["default"]; + + var K = Ember.K; + + /** + @module ember-data + */ + + /** + + This code initializes Ember-Data onto an Ember application. + + If an Ember.js developer defines a subclass of DS.Store on their application, + as `App.ApplicationStore` (or via a module system that resolves to `store: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.ApplicationStore = DS.Store.extend({ + adapter: 'custom' + }); + + App.PostsController = Ember.ArrayController.extend({ + // ... + }); + + When the application is initialized, `App.ApplicationStore` 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: "ember-data", + initialize: setupContainer + }); + + // Deprecated initializers to satisfy old code that depended on them + + Application.initializer({ + name: "store", + after: "ember-data", + initialize: K + }); + + Application.initializer({ + name: "activeModelAdapter", + before: "store", + initialize: K + }); + + Application.initializer({ + name: "transforms", + before: "store", + initialize: K + }); + + Application.initializer({ + name: "data-adapter", + before: "store", + initialize: K + }); + + Application.initializer({ + name: "injectStore", + before: "store", + initialize: K + }); + }); + }); define("ember-data/lib/ext/date", [], function() { @@ -1686,122 +2320,110 @@ define("ember-data/lib/ext/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__) { +define("ember-data/lib/initializers/data_adapter", + ["../system/debug/debug_adapter","exports"], + function(__dependency1__, __exports__) { "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; + var DebugAdapter = __dependency1__["default"]; /** - @module ember-data + Configures a container with injections on Ember applications + for the Ember-Data store. Accepts an optional namespace argument. + + @method initializeStoreInjections + @param {Ember.Container} container */ + __exports__["default"] = function initializeDebugAdapter(container){ + container.register('data-adapter:main', DebugAdapter); + }; + }); +define("ember-data/lib/initializers/store", + ["../serializers","../adapters","../system/container_proxy","../system/store","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + var JSONSerializer = __dependency1__.JSONSerializer; + var RESTSerializer = __dependency1__.RESTSerializer; + var RESTAdapter = __dependency2__.RESTAdapter; + var ContainerProxy = __dependency3__["default"]; + var Store = __dependency4__["default"]; - var set = Ember.set; + /** + Configures a container for use with an Ember-Data + store. Accepts an optional namespace argument. - /* - 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. + @method initializeStore + @param {Ember.Container} container + @param {Object} [application] an application namespace */ + __exports__["default"] = function initializeStore(container, application){ + Ember.deprecate('Specifying a custom Store for Ember Data on your global namespace as `App.Store` ' + + 'has been deprecated. Please use `App.ApplicationStore` instead.', !(application && application.Store)); - Ember.onLoad('Ember.Application', function(Application) { - Application.initializer({ - name: "store", + container.register('store:main', container.lookupFactory('store:application') || (application && application.Store) || Store); - initialize: function(container, application) { - application.register('store:main', application.Store || Store); + // allow older names to be looked up - // 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'} + ]); - 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 + container.register('serializer:-default', JSONSerializer); + container.register('serializer:-rest', RESTSerializer); + container.register('adapter:-rest', RESTAdapter); - // 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'); + }; + }); +define("ember-data/lib/initializers/store_injections", + ["exports"], + function(__exports__) { + "use strict"; + /** + Configures a container with injections on Ember applications + for the Ember-Data store. Accepts an optional namespace argument. - // Eagerly generate the store so defaultStore is populated. - // TODO: Do this in a finisher hook - container.lookup('store:main'); - } - }); + @method initializeStoreInjections + @param {Ember.Container} container + */ + __exports__["default"] = function initializeStoreInjections(container){ + container.injection('controller', 'store', 'store:main'); + container.injection('route', 'store', 'store:main'); + container.injection('serializer', 'store', 'store:main'); + container.injection('data-adapter', 'store', 'store:main'); + }; + }); +define("ember-data/lib/initializers/transforms", + ["../transforms","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var BooleanTransform = __dependency1__.BooleanTransform; + var DateTransform = __dependency1__.DateTransform; + var StringTransform = __dependency1__.StringTransform; + var NumberTransform = __dependency1__.NumberTransform; - Application.initializer({ - name: "transforms", - before: "store", + /** + Configures a container for use with Ember-Data + transforms. - 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'); - } - }); - - }); + @method initializeTransforms + @param {Ember.Container} container + */ + __exports__["default"] = function initializeTransforms(container){ + container.register('transform:boolean', BooleanTransform); + container.register('transform:date', DateTransform); + container.register('transform:number', NumberTransform); + container.register('transform:string', StringTransform); + }; }); 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__) { + ["./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","./ember-initializer","./setup-container","./system/container_proxy","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __exports__) { "use strict"; /** Ember Data @@ -1854,8 +2476,9 @@ define("ember-data/lib/main", var hasMany = __dependency16__.hasMany; var belongsTo = __dependency16__.belongsTo; + var setupContainer = __dependency18__["default"]; - var ContainerProxy = __dependency18__["default"]; + var ContainerProxy = __dependency19__["default"]; DS.Store = Store; DS.PromiseArray = PromiseArray; @@ -1907,6 +2530,8 @@ define("ember-data/lib/main", DS.ContainerProxy = ContainerProxy; + DS._setupContainer = setupContainer; + Ember.lookup.DS = DS; __exports__["default"] = DS; @@ -1926,7 +2551,10 @@ define("ember-data/lib/serializers/json_serializer", function(__dependency1__, __exports__) { "use strict"; var RelationshipChange = __dependency1__.RelationshipChange; - var get = Ember.get, set = Ember.set, isNone = Ember.isNone; + var get = Ember.get; + var set = Ember.set; + var isNone = Ember.isNone; + var map = Ember.ArrayPolyfills.map; /** In Ember Data a Serializer is used to serialize and deserialize @@ -1943,7 +2571,7 @@ define("ember-data/lib/serializers/json_serializer", @class JSONSerializer @namespace DS */ - var JSONSerializer = Ember.Object.extend({ + __exports__["default"] = Ember.Object.extend({ /** The primaryKey is used when serializing and deserializing data. Ember Data always uses the `id` property to store the id of @@ -1966,6 +2594,35 @@ define("ember-data/lib/serializers/json_serializer", */ primaryKey: 'id', + /** + The `attrs` object can be used to declare a simple mapping between + property names on `DS.Model` records and payload keys in the + serialized JSON object representing the record. An object with the + property `key` can also be used to designate the attribute's key on + the response payload. + + Example + + ```javascript + App.Person = DS.Model.extend({ + firstName: DS.attr('string'), + lastName: DS.attr('string'), + occupation: DS.attr('string'), + admin: DS.attr('boolean') + }); + + App.PersonSerializer = DS.JSONSerializer.extend({ + attrs: { + admin: 'is_admin', + occupation: {key: 'career'} + } + }); + ``` + + @property attrs + @type {Object} + */ + /** Given a subclass of `DS.Model` and a JSON object this method will iterate through each attribute of the `DS.Model` and invoke the @@ -2026,10 +2683,45 @@ define("ember-data/lib/serializers/json_serializer", normalize: function(type, hash) { if (!hash) { return hash; } + this.normalizeId(hash); + this.normalizeUsingDeclaredMapping(type, hash); this.applyTransforms(type, hash); return hash; }, + /** + @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 normalizeId + @private + */ + normalizeId: function(hash) { + var primaryKey = get(this, 'primaryKey'); + + if (primaryKey === 'id') { return; } + + hash.id = hash[primaryKey]; + delete hash[primaryKey]; + }, + // SERIALIZE /** Called when a record is saved in order to convert the @@ -2090,7 +2782,7 @@ define("ember-data/lib/serializers/json_serializer", var json = { POST_TTL: post.get('title'), POST_BDY: post.get('body'), - POST_CMS: post.get('comments').mapProperty('id') + POST_CMS: post.get('comments').mapBy('id') } if (options.includeId) { @@ -2204,7 +2896,7 @@ define("ember-data/lib/serializers/json_serializer", `serializeAttribute` can be used to customize how `DS.attr` properties are serialized - For example if you wanted to ensure all you attributes were always + For example if you wanted to ensure all your attributes were always serialized as properties on an `attributes` object you could write: @@ -2225,7 +2917,8 @@ define("ember-data/lib/serializers/json_serializer", */ serializeAttribute: function(record, json, key, attribute) { var attrs = get(this, 'attrs'); - var value = get(record, key), type = attribute.type; + var value = get(record, key); + var type = attribute.type; if (type) { var transform = this.transformFor(type); @@ -2266,7 +2959,6 @@ define("ember-data/lib/serializers/json_serializer", */ serializeBelongsTo: function(record, json, relationship) { var key = relationship.key; - var belongsTo = get(record, key); key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key; @@ -2308,11 +3000,11 @@ define("ember-data/lib/serializers/json_serializer", */ serializeHasMany: function(record, json, relationship) { var key = relationship.key; - + var payloadKey = this.keyForRelationship ? this.keyForRelationship(key, "hasMany") : key; var relationshipType = RelationshipChange.determineRelationshipType(record.constructor, relationship); if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') { - json[key] = get(record, key).mapBy('id'); + json[payloadKey] = get(record, key).mapBy('id'); // TODO support for polymorphic manyToNone and manyToMany relationships } }, @@ -2576,8 +3268,11 @@ define("ember-data/lib/serializers/json_serializer", @param {Object} payload @return {Array} array An array of deserialized objects */ - extractArray: function(store, type, payload) { - return this.normalize(type, payload); + extractArray: function(store, type, arrayPayload) { + var serializer = this; + return map.call(arrayPayload, function(singlePayload) { + return serializer.normalize(type, singlePayload); + }); }, /** @@ -2666,24 +3361,26 @@ define("ember-data/lib/serializers/json_serializer", return transform; } }); - - __exports__["default"] = JSONSerializer; }); define("ember-data/lib/serializers/rest_serializer", - ["./json_serializer","exports"], - function(__dependency1__, __exports__) { + ["./json_serializer","ember-inflector/lib/system/string","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; /** @module ember-data */ var JSONSerializer = __dependency1__["default"]; - var get = Ember.get, set = Ember.set; + var get = Ember.get; + var set = Ember.set; var forEach = Ember.ArrayPolyfills.forEach; var map = Ember.ArrayPolyfills.map; + var camelize = Ember.String.camelize; + + var singularize = __dependency2__.singularize; function coerceId(id) { - return id == null ? null : id+''; + return id == null ? null : id + ''; } /** @@ -2699,7 +3396,7 @@ define("ember-data/lib/serializers/rest_serializer", ## Across the Board Normalization - There are also a number of hooks that you might find useful to defined + There are also a number of hooks that you might find useful to define 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 @@ -2726,7 +3423,7 @@ define("ember-data/lib/serializers/rest_serializer", @namespace DS @extends DS.JSONSerializer */ - var RESTSerializer = JSONSerializer.extend({ + __exports__["default"] = JSONSerializer.extend({ /** If you want to do normalizations specific to some part of the payload, you can specify those under `normalizeHash`. @@ -2838,7 +3535,7 @@ define("ember-data/lib/serializers/rest_serializer", @param {subclass of DS.Model} type @param {Object} hash @param {String} prop - @returns {Object} + @return {Object} */ normalize: function(type, hash, prop) { this.normalizeId(hash); @@ -2851,7 +3548,8 @@ define("ember-data/lib/serializers/rest_serializer", this.normalizeHash[prop](hash); } - return this._super(type, hash, prop); + this.applyTransforms(type, hash); + return hash; }, /** @@ -2862,7 +3560,7 @@ define("ember-data/lib/serializers/rest_serializer", ```js App.ApplicationSerializer = DS.RESTSerializer.extend({ - normalizePayload: function(type, payload) { + normalizePayload: function(payload) { delete payload.version; delete payload.status; return payload; @@ -2871,48 +3569,13 @@ define("ember-data/lib/serializers/rest_serializer", ``` @method normalizePayload - @param {subclass of DS.Model} type @param {Object} payload - @returns {Object} the normalized payload + @return {Object} the normalized payload */ - normalizePayload: function(type, payload) { + normalizePayload: function(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 @@ -2985,12 +3648,12 @@ define("ember-data/lib/serializers/rest_serializer", ```js App.PostSerializer = DS.RESTSerializer.extend({ // First, restructure the top-level so it's organized by type - extractSingle: function(store, type, payload, id, requestType) { + extractSingle: function(store, type, payload, id) { var comments = payload._embedded.comment; delete payload._embedded; payload = { comments: comments, post: payload }; - return this._super(store, type, payload, id, requestType); + return this._super(store, type, payload, id); }, normalizeHash: { @@ -3020,39 +3683,38 @@ define("ember-data/lib/serializers/rest_serializer", @method extractSingle @param {DS.Store} store - @param {subclass of DS.Model} type + @param {subclass of DS.Model} primaryType @param {Object} payload - @param {String} id - @param {'find'|'createRecord'|'updateRecord'|'deleteRecord'} requestType - @returns {Object} the primary response to the original request + @param {String} recordId + @return {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; + extractSingle: function(store, primaryType, rawPayload, recordId) { + var payload = this.normalizePayload(rawPayload); + var primaryTypeName = primaryType.typeKey; + var primaryRecord; for (var prop in payload) { - var typeName = this.typeForRoot(prop), - type = store.modelFor(typeName), - isPrimary = type.typeKey === primaryTypeName; + var typeName = this.typeForRoot(prop); + var type = store.modelFor(typeName); + var isPrimary = type.typeKey === primaryTypeName; + var value = payload[prop]; // legacy support for singular resources - if (isPrimary && Ember.typeOf(payload[prop]) !== "array" ) { - primaryRecord = this.normalize(primaryType, payload[prop], prop); + if (isPrimary && Ember.typeOf(value) !== "array" ) { + primaryRecord = this.normalize(primaryType, value, prop); continue; } /*jshint loopfunc:true*/ - forEach.call(payload[prop], function(hash) { - var typeName = this.typeForRoot(prop), - type = store.modelFor(typeName), - typeSerializer = store.serializerFor(type); + forEach.call(value, function(hash) { + var typeName = this.typeForRoot(prop); + var type = store.modelFor(typeName); + var typeSerializer = store.serializerFor(type); hash = typeSerializer.normalize(type, hash, prop); - var isFirstCreatedRecord = isPrimary && !recordId && !primaryRecord, - isUpdatedRecord = isPrimary && coerceId(hash.id) === recordId; + var isFirstCreatedRecord = isPrimary && !recordId && !primaryRecord; + var isUpdatedRecord = isPrimary && coerceId(hash.id) === recordId; // find the primary record. // @@ -3114,7 +3776,7 @@ define("ember-data/lib/serializers/rest_serializer", 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) { + extractArray: function(store, type, payload) { var posts = payload._embedded.post; var comments = []; var postCache = {}; @@ -3132,7 +3794,7 @@ define("ember-data/lib/serializers/rest_serializer", payload = { comments: comments, posts: payload }; - return this._super(store, type, payload, id, requestType); + return this._super(store, type, payload); }, normalizeHash: { @@ -3166,31 +3828,29 @@ define("ember-data/lib/serializers/rest_serializer", @method extractArray @param {DS.Store} store - @param {subclass of DS.Model} type + @param {subclass of DS.Model} primaryType @param {Object} payload - @param {'findAll'|'findMany'|'findHasMany'|'findQuery'} requestType - @returns {Array} The primary array that was returned in response + @return {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; + extractArray: function(store, primaryType, rawPayload) { + var payload = this.normalizePayload(rawPayload); + var primaryTypeName = primaryType.typeKey; + var primaryArray; for (var prop in payload) { - var typeKey = prop, - forcedSecondary = false; + var typeKey = prop; + var 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)); + var typeName = this.typeForRoot(typeKey); + var type = store.modelFor(typeName); + var typeSerializer = store.serializerFor(type); + var isPrimary = (!forcedSecondary && (type.typeKey === primaryTypeName)); /*jshint loopfunc:true*/ var normalizedArray = map.call(payload[prop], function(hash) { @@ -3238,13 +3898,13 @@ define("ember-data/lib/serializers/rest_serializer", @param {DS.Store} store @param {Object} payload */ - pushPayload: function(store, payload) { - payload = this.normalizePayload(null, payload); + pushPayload: function(store, rawPayload) { + var payload = this.normalizePayload(rawPayload); for (var prop in payload) { - var typeName = this.typeForRoot(prop), - type = store.modelFor(typeName), - typeSerializer = store.serializerFor(type); + var typeName = this.typeForRoot(prop); + var type = store.modelFor(typeName); + var typeSerializer = store.serializerFor(type); /*jshint loopfunc:true*/ var normalizedArray = map.call(Ember.makeArray(payload[prop]), function(hash) { @@ -3256,27 +3916,50 @@ define("ember-data/lib/serializers/rest_serializer", }, /** - You can use this method to normalize the JSON root keys returned - into the model type expected by your store. + This method is used to convert each JSON root key in the payload + into a typeKey that it can use to look up the appropriate model for + that part of the payload. By default the typeKey for a model is its + name in camelCase, so if your JSON root key is 'fast-car' you would + use typeForRoot to convert it to 'fastCar' so that Ember Data finds + the `FastCar` model. - For example, your server may return underscored root keys rather than - the expected camelcased versions. + If you diverge from this norm you should also consider changes to + store._normalizeTypeKey as well. + + For example, your server may return prefixed root keys like so: + + ```js + { + "response-fast-car": { + "id": "1", + "name": "corvette" + } + } + ``` + + In order for Ember Data to know that the model corresponding to + the 'response-fast-car' hash is `FastCar` (typeKey: 'fastCar'), + you can override typeForRoot to convert 'response-fast-car' to + 'fastCar' like so: ```js App.ApplicationSerializer = DS.RESTSerializer.extend({ typeForRoot: function(root) { - var camelized = Ember.String.camelize(root); - return Ember.String.singularize(camelized); + // 'response-fast-car' should become 'fast-car' + var subRoot = root.substring(9); + + // _super normalizes 'fast-car' to 'fastCar' + return this._super(subRoot); } }); ``` @method typeForRoot - @param {String} root - @returns {String} the model's typeKey + @param {String} key + @return {String} the model's typeKey */ - typeForRoot: function(root) { - return Ember.String.singularize(root); + typeForRoot: function(key) { + return camelize(singularize(key)); }, // SERIALIZE @@ -3340,7 +4023,7 @@ define("ember-data/lib/serializers/rest_serializer", var json = { POST_TTL: post.get('title'), POST_BDY: post.get('body'), - POST_CMS: post.get('comments').mapProperty('id') + POST_CMS: post.get('comments').mapBy('id') } if (options.includeId) { @@ -3429,7 +4112,9 @@ define("ember-data/lib/serializers/rest_serializer", /** You can use this method to customize the root keys serialized into the JSON. - By default the REST Serializer sends camelized root keys. + By default the REST Serializer sends the typeKey of a model, whih is a camelized + version of the name. + For example, your server may expect underscored root objects. ```js @@ -3448,8 +4133,7 @@ define("ember-data/lib/serializers/rest_serializer", @param {Object} options */ serializeIntoHash: function(hash, type, record, options) { - var root = Ember.String.camelize(type.typeKey); - hash[root] = this.serialize(record, options); + hash[type.typeKey] = this.serialize(record, options); }, /** @@ -3463,14 +4147,34 @@ define("ember-data/lib/serializers/rest_serializer", @param {Object} relationship */ serializePolymorphicType: function(record, json, relationship) { - var key = relationship.key, - belongsTo = get(record, key); + var key = relationship.key; + var belongsTo = get(record, key); key = this.keyForAttribute ? this.keyForAttribute(key) : key; - json[key + "Type"] = Ember.String.camelize(belongsTo.constructor.typeKey); + json[key + "Type"] = belongsTo.constructor.typeKey; } }); + }); +define("ember-data/lib/setup-container", + ["./initializers/store","./initializers/transforms","./initializers/store_injections","./initializers/data_adapter","../../../activemodel-adapter/lib/setup-container","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { + "use strict"; + var initializeStore = __dependency1__["default"]; + var initializeTransforms = __dependency2__["default"]; + var initializeStoreInjections = __dependency3__["default"]; + var initializeDataAdapter = __dependency4__["default"]; + var setupActiveModelContainer = __dependency5__["default"]; - __exports__["default"] = RESTSerializer; + __exports__["default"] = function setupContainer(container, application){ + // application is not a required argument. This ensures + // testing setups can setup a container without booting an + // entire ember application. + + initializeDataAdapter(container, application); + initializeTransforms(container, application); + initializeStoreInjections(container, application); + initializeStore(container, application); + setupActiveModelContainer(container, application); + }; }); define("ember-data/lib/system/adapter", ["exports"], @@ -3480,10 +4184,19 @@ define("ember-data/lib/system/adapter", @module ember-data */ - var get = Ember.get, set = Ember.set; + var get = Ember.get; + var set = Ember.set; var map = Ember.ArrayPolyfills.map; - var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + var errorProps = [ + 'description', + 'fileName', + 'lineNumber', + 'message', + 'name', + 'number', + 'stack' + ]; /** A `DS.InvalidError` is used by an adapter to signal the external API @@ -3525,14 +4238,15 @@ define("ember-data/lib/system/adapter", @class InvalidError @namespace DS */ - var InvalidError = function(errors) { + function InvalidError(errors) { var tmp = Error.prototype.constructor.call(this, "The backend rejected the commit because it was invalid: " + Ember.inspect(errors)); this.errors = errors; for (var i=0, l=errorProps.length; i self.attributeLimit) { return false; } var desc = capitalize(underscore(name).replace('_', ' ')); @@ -4580,8 +5235,8 @@ define("ember-data/lib/system/debug/debug_adapter", }, getRecordColumnValues: function(record) { - var self = this, count = 0, - columnValues = { id: get(record, 'id') }; + var self = this, count = 0; + var columnValues = { id: get(record, 'id') }; record.eachAttribute(function(key) { if (count++ > self.attributeLimit) { @@ -4594,7 +5249,8 @@ define("ember-data/lib/system/debug/debug_adapter", }, getRecordKeywords: function(record) { - var keywords = [], keys = Ember.A(['id']); + var keywords = []; + var keys = Ember.A(['id']); record.eachAttribute(function(key) { keys.push(key); }); @@ -4648,8 +5304,6 @@ define("ember-data/lib/system/debug/debug_adapter", } }); - - __exports__["default"] = DebugAdapter; }); define("ember-data/lib/system/debug/debug_info", ["../model","exports"], @@ -5013,7 +5667,7 @@ define("ember-data/lib/system/model/attributes", @return {Attribute} */ - function attr(type, options) { + __exports__["default"] = function attr(type, options) { options = options || {}; var meta = { @@ -5051,15 +5705,15 @@ define("ember-data/lib/system/model/attributes", // 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; + var get = Ember.get; + var isEmpty = Ember.isEmpty; + var map = Ember.EnumerableUtils.map; /** @module ember-data @@ -5068,13 +5722,75 @@ define("ember-data/lib/system/model/errors", /** Holds validation errors for a given record organized by attribute names. + Every DS.Model has an `errors` property that is an instance of + `DS.Errors`. This can be used to display validation error + messages returned from the server when a `record.save()` rejects. + This works automatically with `DS.ActiveModelAdapter`, but you + can implement [ajaxError](api/data/classes/DS.RESTAdapter.html#method_ajaxError) + in other adapters as well. + + For Example, if you had an `User` model that looked like this: + + ```javascript + App.User = DS.Model.extend({ + username: attr('string'), + email: attr('string') + }); + ``` + And you attempted to save a record that did not validate on the backend. + + ```javascript + var user = store.createRecord('user', { + username: 'tomster', + email: 'invalidEmail' + }); + user.save(); + ``` + + Your backend data store might return a response that looks like + this. This response will be used to populate the error object. + + ```javascript + { + "errors": { + "username": ["This username is already taken!"], + "email": ["Doesn't look like a valid email."] + } + } + ``` + + Errors can be displayed to the user by accessing their property name + or using the `messages` property to get an array of all errors. + + ```handlebars + {{#each errors.messages}} +
+ {{message}} +
+ {{/each}} + + + {{#each errors.username}} +
+ {{message}} +
+ {{/each}} + + + {{#each errors.email}} +
+ {{message}} +
+ {{/each}} + ``` + @class Errors @namespace DS @extends Ember.Object @uses Ember.Enumerable @uses Ember.Evented */ - var Errors = Ember.Object.extend(Ember.Enumerable, Ember.Evented, { + __exports__["default"] = Ember.Object.extend(Ember.Enumerable, Ember.Evented, { /** Register with target handler @@ -5118,15 +5834,38 @@ define("ember-data/lib/system/model/errors", /** Returns errors for a given attribute + ```javascript + var user = store.createRecord('user', { + username: 'tomster', + email: 'invalidEmail' + }); + user.save().catch(function(){ + user.get('errors').errorsFor('email'); // ["Doesn't look like a valid email."] + }); + ``` + @method errorsFor @param {String} attribute - @returns {Array} + @return {Array} */ errorsFor: function(attribute) { return get(this, 'errorsByAttributeName').get(attribute); }, /** + An array containing all of the error messages for this + record. This is useful for displaying all errors to the user. + + ```handlebars + {{#each errors.messages}} +
+ {{message}} +
+ {{/each}} + ``` + + @property messages + @type {Array} */ messages: Ember.computed.mapBy('content', 'message'), @@ -5177,6 +5916,14 @@ define("ember-data/lib/system/model/errors", Adds error messages to a given attribute and sends `becameInvalid` event to the record. + Example: + + ```javascript + if (!user.get('username') { + user.get('errors').add('username', 'This field is required'); + } + ``` + @method add @param {String} attribute @param {Array|String} messages @@ -5202,7 +5949,7 @@ define("ember-data/lib/system/model/errors", _findOrCreateMessages: function(attribute, messages) { var errors = this.errorsFor(attribute); - return Ember.makeArray(messages).map(function(message) { + return map(Ember.makeArray(messages), function(message) { return errors.findBy('message', message) || { attribute: attribute, message: message @@ -5214,6 +5961,27 @@ define("ember-data/lib/system/model/errors", Removes all error messages from the given attribute and sends `becameValid` event to the record if there no more errors left. + Example: + + ```javascript + App.User = DS.Model.extend({ + email: DS.attr('string'), + twoFactorAuth: DS.attr('boolean'), + phone: DS.attr('string') + }); + + App.UserEditRoute = Ember.Route.extend({ + actions: { + save: function(user) { + if (!user.get('twoFactorAuth')) { + user.get('errors').remove('phone'); + } + user.save(); + } + } + }); + ``` + @method remove @param {String} attribute */ @@ -5235,6 +6003,19 @@ define("ember-data/lib/system/model/errors", Removes all error messages and sends `becameValid` event to the record. + Example: + + ```javascript + App.UserEditRoute = Ember.Route.extend({ + actions: { + retrySave: function(user) { + user.get('errors').clear(); + user.save(); + } + } + }); + ``` + @method clear */ clear: function() { @@ -5249,16 +6030,27 @@ define("ember-data/lib/system/model/errors", /** Checks if there is error messages for the given attribute. + ```javascript + App.UserEditRoute = Ember.Route.extend({ + actions: { + save: function(user) { + if (user.get('errors').has('email')) { + return alert('Please update your email before attempting to save.'); + } + user.save(); + } + } + }); + ``` + @method has @param {String} attribute - @returns {Boolean} true if there some errors on given attribute + @return {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"], @@ -5271,9 +6063,10 @@ define("ember-data/lib/system/model/model", @module ember-data */ - var get = Ember.get, set = Ember.set, - merge = Ember.merge, - Promise = Ember.RSVP.Promise; + var get = Ember.get; + var set = Ember.set; + var merge = Ember.merge; + var Promise = Ember.RSVP.Promise; var JSONSerializer; var retrieveFromCurrentState = Ember.computed('currentState', function(key, value) { @@ -5448,9 +6241,9 @@ define("ember-data/lib/system/model/model", */ 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 + If this property is `true` the record is in the `valid` state. + + A record will be in the `valid` state when the adapter did not report any server-side validation failures. @property isValid @@ -5482,15 +6275,14 @@ define("ember-data/lib/system/model/model", /** 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. + changes to the backend for any reason other than a server-side + validation error. Example ```javascript record.get('isError'); // false - record.set('foo', 'invalid value'); + record.set('foo', 'valid value'); record.save().then(null, function() { record.get('isError'); // true }); @@ -5572,7 +6364,7 @@ define("ember-data/lib/system/model/model", ``` @property errors - @type {Object} + @type {DS.Errors} */ errors: Ember.computed(function() { var errors = Errors.create(); @@ -5598,7 +6390,7 @@ define("ember-data/lib/system/model/model", @method serialize @param {Object} options - @returns {Object} an object whose values are primitive JSON values only + @return {Object} an object whose values are primitive JSON values only */ serialize: function(options) { var store = get(this, 'store'); @@ -5617,7 +6409,7 @@ define("ember-data/lib/system/model/model", @method toJSON @param {Object} options - @returns {Object} A JSON representation of the object. + @return {Object} A JSON representation of the object. */ toJSON: function(options) { if (!JSONSerializer) { JSONSerializer = requireModule("ember-data/lib/serializers/json_serializer")["default"]; } @@ -6334,7 +7126,8 @@ define("ember-data/lib/system/model/states", @module ember-data */ - var get = Ember.get, set = Ember.set; + var get = Ember.get; + var set = Ember.set; /* This file encapsulates the various states that a record can transition through during its lifecycle. @@ -6348,7 +7141,7 @@ define("ember-data/lib/system/model/states", it would be in the `root.loaded.created.uncommitted` state. If a record has had local modifications made to it that are in the process of being saved, the record would be in the - `root.loaded.updated.inFlight` state. (These state paths will be + `root.loaded.updated.inFlight` state. (This state paths will be explained in more detail below.) Events are sent by the record or its store to the record's @@ -6392,7 +7185,7 @@ define("ember-data/lib/system/model/states", * loading ``` - The `DS.Model` states are themselves stateless. What we mean is + The `DS.Model` states are themselves stateless. What that means 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 @@ -6552,8 +7345,8 @@ define("ember-data/lib/system/model/states", // 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. + // * isValid: 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. @@ -6658,8 +7451,7 @@ define("ember-data/lib/system/model/states", } }, - // A record is in the `invalid` state when its client-side - // invalidations have failed, or if the adapter has indicated + // A record is in the `invalid` if the adapter has indicated // the the record failed server-side invalidations. invalid: { // FLAGS @@ -6679,6 +7471,11 @@ define("ember-data/lib/system/model/states", becomeDirty: Ember.K, + willCommit: function(record) { + get(record, 'errors').clear(); + record.transitionTo('inFlight'); + }, + rolledBack: function(record) { get(record, 'errors').clear(); }, @@ -6689,6 +7486,10 @@ define("ember-data/lib/system/model/states", invokeLifecycleCallbacks: function(record) { record.triggerLater('becameInvalid', record); + }, + + exit: function(record) { + record._inFlightAttributes = {}; } } }; @@ -7011,7 +7812,11 @@ define("ember-data/lib/system/model/states", invokeLifecycleCallbacks: function(record) { record.triggerLater('didDelete', record); record.triggerLater('didCommit', record); - } + }, + + willCommit: Ember.K, + + didCommit: Ember.K } }, @@ -7059,7 +7864,8 @@ define("ember-data/lib/system/record_array_manager", var FilteredRecordArray = __dependency1__.FilteredRecordArray; var AdapterPopulatedRecordArray = __dependency1__.AdapterPopulatedRecordArray; var ManyArray = __dependency1__.ManyArray; - var get = Ember.get, set = Ember.set; + var get = Ember.get; + var set = Ember.set; var forEach = Ember.EnumerableUtils.forEach; /** @@ -7068,7 +7874,7 @@ define("ember-data/lib/system/record_array_manager", @private @extends Ember.Object */ - var RecordArrayManager = Ember.Object.extend({ + __exports__["default"] = Ember.Object.extend({ init: function() { this.filteredRecordArrays = Ember.MapWithDefault.create({ defaultValue: function() { return []; } @@ -7251,10 +8057,12 @@ define("ember-data/lib/system/record_array_manager", @method createFilteredRecordArray @param {Class} type @param {Function} filter + @param {Object} query (optional @return {DS.FilteredRecordArray} */ - createFilteredRecordArray: function(type, filter) { + createFilteredRecordArray: function(type, filter, query) { var array = FilteredRecordArray.create({ + query: query, type: type, content: Ember.A(), store: this.store, @@ -7280,7 +8088,8 @@ define("ember-data/lib/system/record_array_manager", type: type, query: query, content: Ember.A(), - store: this.store + store: this.store, + manager: this }); this._adapterPopulatedRecordArrays.push(array); @@ -7320,8 +8129,8 @@ define("ember-data/lib/system/record_array_manager", willDestroy: function(){ this._super(); - flatten(values(this.filteredRecordArrays.values)).forEach(destroy); - this._adapterPopulatedRecordArrays.forEach(destroy); + forEach(flatten(values(this.filteredRecordArrays.values)), destroy); + forEach(this._adapterPopulatedRecordArrays, destroy); } }); @@ -7350,8 +8159,6 @@ define("ember-data/lib/system/record_array_manager", return result; } - - __exports__["default"] = RecordArrayManager; }); define("ember-data/lib/system/record_arrays", ["./record_arrays/record_array","./record_arrays/filtered_record_array","./record_arrays/adapter_populated_record_array","./record_arrays/many_array","exports"], @@ -7380,7 +8187,8 @@ define("ember-data/lib/system/record_arrays/adapter_populated_record_array", @module ember-data */ - var get = Ember.get, set = Ember.set; + var get = Ember.get; + var set = Ember.set; /** Represents an ordered list of records whose order and membership is @@ -7392,7 +8200,7 @@ define("ember-data/lib/system/record_arrays/adapter_populated_record_array", @namespace DS @extends DS.RecordArray */ - var AdapterPopulatedRecordArray = RecordArray.extend({ + __exports__["default"] = RecordArray.extend({ query: null, replace: function() { @@ -7414,15 +8222,17 @@ define("ember-data/lib/system/record_arrays/adapter_populated_record_array", this.setProperties({ content: Ember.A(records), isLoaded: true, - meta: meta + meta: Ember.copy(meta) }); + records.forEach(function(record) { + this.manager.recordArraysForRecord(record).add(this); + }, this); + // TODO: should triggering didLoad event be the last action of the runLoop? Ember.run.once(this, 'trigger', 'didLoad'); } }); - - __exports__["default"] = AdapterPopulatedRecordArray; }); define("ember-data/lib/system/record_arrays/filtered_record_array", ["./record_array","exports"], @@ -7446,7 +8256,7 @@ define("ember-data/lib/system/record_arrays/filtered_record_array", @namespace DS @extends DS.RecordArray */ - var FilteredRecordArray = RecordArray.extend({ + __exports__["default"] = RecordArray.extend({ /** The filterFunction is a function used to test records from the store to determine if they should be part of the record array. @@ -7485,13 +8295,15 @@ define("ember-data/lib/system/record_arrays/filtered_record_array", @method updateFilter @private */ - updateFilter: Ember.observer(function() { + _updateFilter: function() { var manager = get(this, 'manager'); manager.updateFilter(this, get(this, 'type'), get(this, 'filterFunction')); + }, + + updateFilter: Ember.observer(function() { + Ember.run.once(this, this._updateFilter); }, 'filterFunction') }); - - __exports__["default"] = FilteredRecordArray; }); define("ember-data/lib/system/record_arrays/many_array", ["./record_array","../changes","exports"], @@ -7547,7 +8359,7 @@ define("ember-data/lib/system/record_arrays/many_array", @namespace DS @extends DS.RecordArray */ - var ManyArray = RecordArray.extend({ + __exports__["default"] = RecordArray.extend({ init: function() { this._super.apply(this, arguments); this._changesToSync = Ember.OrderedSet.create(); @@ -7618,11 +8430,10 @@ define("ember-data/lib/system/record_arrays/many_array", 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')); + owner = get(this, 'owner'); - var unloadedRecords = records.filterProperty('isEmpty', true); - store.fetchMany(unloadedRecords, owner, resolver); + var unloadedRecords = records.filterBy('isEmpty', true); + store.fetchMany(unloadedRecords, owner); }, // Overrides Ember.Array's replace method to implement @@ -7730,8 +8541,6 @@ define("ember-data/lib/system/record_arrays/many_array", return record; } }); - - __exports__["default"] = ManyArray; }); define("ember-data/lib/system/record_arrays/record_array", ["../store","exports"], @@ -7742,7 +8551,8 @@ define("ember-data/lib/system/record_arrays/record_array", */ var PromiseArray = __dependency1__.PromiseArray; - var get = Ember.get, set = Ember.set; + var get = Ember.get; + var set = Ember.set; /** A record array is an array that contains records of a certain type. The record @@ -7757,7 +8567,7 @@ define("ember-data/lib/system/record_arrays/record_array", @uses Ember.Evented */ - var RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { + __exports__["default"] = Ember.ArrayProxy.extend(Ember.Evented, { /** The model type contained by this record array. @@ -7920,8 +8730,41 @@ define("ember-data/lib/system/record_arrays/record_array", this._super(); } }); + }); +define("ember-data/lib/system/relationship-meta", + ["../../../ember-inflector/lib/system","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var singularize = __dependency1__.singularize; - __exports__["default"] = RecordArray; + function typeForRelationshipMeta(store, meta) { + var typeKey, type; + + typeKey = meta.type || meta.key; + if (typeof typeKey === 'string') { + if (meta.kind === 'hasMany') { + typeKey = singularize(typeKey); + } + type = store.modelFor(typeKey); + } else { + type = meta.type; + } + + return type; + } + + __exports__.typeForRelationshipMeta = typeForRelationshipMeta;function relationshipFromMeta(store, meta) { + return { + key: meta.key, + kind: meta.kind, + type: typeForRelationshipMeta(store, meta), + options: meta.options, + parentType: meta.parentType, + isRelationship: true + }; + } + + __exports__.relationshipFromMeta = relationshipFromMeta; }); define("ember-data/lib/system/relationships", ["./relationships/belongs_to","./relationships/has_many","../system/relationships/ext","exports"], @@ -7939,17 +8782,19 @@ define("ember-data/lib/system/relationships", __exports__.hasMany = hasMany; }); define("ember-data/lib/system/relationships/belongs_to", - ["../model","../store","../changes","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + ["../model","../store","../changes","../relationship-meta","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { "use strict"; - var get = Ember.get, set = Ember.set, - isNone = Ember.isNone; - + var get = Ember.get; + var set = Ember.set; + var isNone = Ember.isNone; var Promise = Ember.RSVP.Promise; var Model = __dependency1__.Model; var PromiseObject = __dependency2__.PromiseObject; var RelationshipChange = __dependency3__.RelationshipChange; + var relationshipFromMeta = __dependency4__.relationshipFromMeta; + var typeForRelationshipMeta = __dependency4__.typeForRelationshipMeta; /** @module ember-data @@ -7957,28 +8802,30 @@ define("ember-data/lib/system/relationships/belongs_to", function asyncBelongsTo(type, options, meta) { return Ember.computed('data', function(key, value) { - var data = get(this, 'data'), - store = get(this, 'store'), - promiseLabel = "DS: Async belongsTo " + this + " : " + key, - promise; + var data = get(this, 'data'); + var store = get(this, 'store'); + var promiseLabel = "DS: Async belongsTo " + this + " : " + key; + var promise; + + meta.key = key; if (arguments.length === 2) { - Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof store.modelFor(type)); + Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof typeForRelationshipMeta(store, meta)); return value === undefined ? null : PromiseObject.create({ promise: Promise.cast(value, promiseLabel) }); } - var link = data.links && data.links[key], - belongsTo = data[key]; + var link = data.links && data.links[key]; + var belongsTo = data[key]; - if(!isNone(belongsTo)) { + if (!isNone(belongsTo)) { promise = store.fetchRecord(belongsTo) || Promise.cast(belongsTo, promiseLabel); return PromiseObject.create({ promise: promise }); } else if (link) { - promise = store.findBelongsTo(this, link, meta); + promise = store.findBelongsTo(this, link, relationshipFromMeta(store, meta)); return PromiseObject.create({ promise: promise }); @@ -8040,7 +8887,7 @@ define("ember-data/lib/system/relationships/belongs_to", 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' || Model.detect(type))); + Ember.assert("The first argument to DS.belongsTo must be a string representing a model type key, e.g. use DS.belongsTo('person') to define a relation to the App.Person model", !!type && (typeof type === 'string' || Model.detect(type))); } options = options || {}; @@ -8049,7 +8896,8 @@ define("ember-data/lib/system/relationships/belongs_to", type: type, isRelationship: true, options: options, - kind: 'belongsTo' + kind: 'belongsTo', + key: null }; if (options.async) { @@ -8057,8 +8905,9 @@ define("ember-data/lib/system/relationships/belongs_to", } return Ember.computed('data', function(key, value) { - var data = get(this, 'data'), - store = get(this, 'store'), belongsTo, typeClass; + var data = get(this, 'data'); + var store = get(this, 'store'); + var belongsTo, typeClass; if (typeof type === 'string') { typeClass = store.modelFor(type); @@ -8102,8 +8951,12 @@ define("ember-data/lib/system/relationships/belongs_to", var oldParent = get(record, key); if (oldParent) { - var store = get(record, 'store'), - change = RelationshipChange.createChange(record, oldParent, store, { key: key, kind: "belongsTo", changeType: "remove" }); + var store = get(record, 'store'); + var change = RelationshipChange.createChange(record, oldParent, store, { + key: key, + kind: 'belongsTo', + changeType: 'remove' + }); change.sync(); this._changesToSync[key] = change; @@ -8123,8 +8976,12 @@ define("ember-data/lib/system/relationships/belongs_to", var newParent = get(record, key); if (newParent) { - var store = get(record, 'store'), - change = RelationshipChange.createChange(record, newParent, store, { key: key, kind: "belongsTo", changeType: "add" }); + var store = get(record, 'store'); + var change = RelationshipChange.createChange(record, newParent, store, { + key: key, + kind: 'belongsTo', + changeType: 'add' + }); change.sync(); } @@ -8137,13 +8994,16 @@ define("ember-data/lib/system/relationships/belongs_to", __exports__["default"] = belongsTo; }); define("ember-data/lib/system/relationships/ext", - ["../../../../ember-inflector/lib/system","../model"], - function(__dependency1__, __dependency2__) { + ["../../../../ember-inflector/lib/system","../relationship-meta","../model"], + function(__dependency1__, __dependency2__, __dependency3__) { "use strict"; var singularize = __dependency1__.singularize; - var Model = __dependency2__.Model; + var typeForRelationshipMeta = __dependency2__.typeForRelationshipMeta; + var relationshipFromMeta = __dependency2__.relationshipFromMeta; + var Model = __dependency3__.Model; - var get = Ember.get, set = Ember.set; + var get = Ember.get; + var set = Ember.set; /** @module ember-data @@ -8255,17 +9115,24 @@ define("ember-data/lib/system/relationships/ext", if (options.inverse === null) { return null; } - var inverseName, inverseKind; + var inverseName, inverseKind, inverse; if (options.inverse) { inverseName = options.inverse; - inverseKind = Ember.get(inverseType, 'relationshipsByName').get(inverseName).kind; + inverse = Ember.get(inverseType, 'relationshipsByName').get(inverseName); + + Ember.assert("We found no inverse relationships by the name of '" + inverseName + "' on the '" + inverseType.typeKey + + "' model. This is most likely due to a missing attribute on your model definition.", !Ember.isNone(inverse)); + + inverseKind = inverse.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 + ". Look at http://emberjs.com/guides/models/defining-models/#toc_explicit-inverses for how to explicitly specify inverses", possibleRelationships.length === 1); + Ember.assert("You defined the '" + name + "' relationship on " + this + ", but multiple possible inverse relationships of type " + + this + " were found on " + inverseType + ". Look at http://emberjs.com/guides/models/defining-models/#toc_explicit-inverses for how to explicitly specify inverses", + possibleRelationships.length === 1); inverseName = possibleRelationships[0].name; inverseKind = possibleRelationships[0].kind; @@ -8340,18 +9207,15 @@ define("ember-data/lib/system/relationships/ext", // 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); - } - - var relationshipsForType = map.get(meta.type); + meta.key = name; + var relationshipsForType = map.get(typeForRelationshipMeta(this.store, meta)); relationshipsForType.push({ name: name, kind: meta.kind }); } }); return map; - }), + }).cacheable(false), /** A hash containing lists of the model's relationships, grouped @@ -8431,11 +9295,8 @@ define("ember-data/lib/system/relationships/ext", // 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); - } + meta.key = name; + type = typeForRelationshipMeta(this.store, meta); Ember.assert("You specified a hasMany (" + meta.type + ") on " + meta.parentType + " but " + meta.type + " was not found.", type); @@ -8447,7 +9308,7 @@ define("ember-data/lib/system/relationships/ext", }); return types; - }), + }).cacheable(false), /** A map whose keys are the relationships of a model and whose values are @@ -8481,29 +9342,19 @@ define("ember-data/lib/system/relationships/ext", @readOnly */ relationshipsByName: Ember.computed(function() { - var map = Ember.Map.create(), type; + var map = Ember.Map.create(); 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); + var relationship = relationshipFromMeta(this.store, meta); + relationship.type = typeForRelationshipMeta(this.store, meta); + map.set(name, relationship); } }); return map; - }), + }).cacheable(false), /** A map whose keys are the fields of the model and whose values are strings @@ -8604,30 +9455,38 @@ define("ember-data/lib/system/relationships/ext", }); }); define("ember-data/lib/system/relationships/has_many", - ["../store","exports"], - function(__dependency1__, __exports__) { + ["../store","../relationship-meta","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; /** @module ember-data */ var PromiseArray = __dependency1__.PromiseArray; - var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties; + + var relationshipFromMeta = __dependency2__.relationshipFromMeta; + var typeForRelationshipMeta = __dependency2__.typeForRelationshipMeta; + + var get = Ember.get; + var set = Ember.set; + var setProperties = Ember.setProperties; function asyncHasMany(type, options, meta) { return Ember.computed('data', function(key) { var relationship = this._relationships[key], promiseLabel = "DS: Async hasMany " + this + " : " + key; + meta.key = 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); + rel = store.findHasMany(this, link, relationshipFromMeta(store, meta), resolver); } else { - rel = store.findMany(this, data[key], meta.type, resolver); + rel = store.findMany(this, data[key], typeForRelationshipMeta(store, meta), resolver); } // cache the promise so we can use it // when we come back and don't need to rebuild @@ -8671,7 +9530,8 @@ define("ember-data/lib/system/relationships/has_many", type: type, isRelationship: true, options: options, - kind: 'hasMany' + kind: 'hasMany', + key: null }; if (options.async) { @@ -8681,8 +9541,8 @@ define("ember-data/lib/system/relationships/has_many", 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); + 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).isEvery('isEmpty', false)); + return store.findMany(this, data[key], typeForRelationshipMeta(store, meta)); }); }).meta(meta).readOnly(); } @@ -8776,8 +9636,8 @@ define("ember-data/lib/system/relationships/has_many", __exports__["default"] = hasMany; }); define("ember-data/lib/system/store", - ["./adapter","exports"], - function(__dependency1__, __exports__) { + ["./adapter","ember-inflector/lib/system/string","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; /*globals Ember*/ /*jshint eqnull:true*/ @@ -8786,8 +9646,11 @@ define("ember-data/lib/system/store", @module ember-data */ - var Adapter = __dependency1__["default"]; - var get = Ember.get, set = Ember.set; + var InvalidError = __dependency1__.InvalidError; + var Adapter = __dependency1__.Adapter; + var singularize = __dependency2__.singularize; + var get = Ember.get; + var set = Ember.set; var once = Ember.run.once; var isNone = Ember.isNone; var forEach = Ember.EnumerableUtils.forEach; @@ -8797,6 +9660,8 @@ define("ember-data/lib/system/store", var copy = Ember.copy; var Store, PromiseObject, PromiseArray, RecordArrayManager, Model; + var camelize = Ember.String.camelize; + // Implementors Note: // // The variables in this file are consistently named according to the following @@ -8883,7 +9748,7 @@ define("ember-data/lib/system/store", 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 + `store#push` that will deserialize payloads if the Serializer implements a `pushPayload` method. [update](#method_update) works like `push`, except it can handle @@ -8966,7 +9831,7 @@ define("ember-data/lib/system/store", @property defaultAdapter @private - @returns DS.Adapter + @return DS.Adapter */ defaultAdapter: Ember.computed('adapter', function() { var adapter = get(this, 'adapter'); @@ -9006,12 +9871,11 @@ define("ember-data/lib/system/store", @param {String} type @param {Object} properties a hash of properties to set on the newly created record. - @returns {DS.Model} record + @return {DS.Model} record */ - createRecord: function(type, properties) { - type = this.modelFor(type); - - properties = copy(properties) || {}; + createRecord: function(typeName, inputPropoperties) { + var type = this.modelFor(typeName); + var properties = copy(inputPropoperties) || {}; // If the passed properties do not include a primary key, // give the adapter an opportunity to generate one. Typically, @@ -9044,7 +9908,7 @@ define("ember-data/lib/system/store", @method _generateId @private @param {String} type - @returns {String} if the adapter can generate one, an ID + @return {String} if the adapter can generate one, an ID */ _generateId: function(type) { var adapter = this.adapterFor(type); @@ -9178,9 +10042,8 @@ define("ember-data/lib/system/store", @param {String|Integer} id @return {Promise} promise */ - findById: function(type, id) { - type = this.modelFor(type); - + findById: function(typeName, id) { + var type = this.modelFor(typeName); var record = this.recordForId(type, id); var fetchedRecord = this.fetchRecord(record); @@ -9195,11 +10058,12 @@ define("ember-data/lib/system/store", @method findByIds @param {String} type @param {Array} ids - @returns {Promise} promise + @return {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")); @@ -9213,16 +10077,15 @@ define("ember-data/lib/system/store", @method fetchRecord @private @param {DS.Model} record - @returns {Promise} promise + @return {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 type = record.constructor; + var 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); @@ -9236,17 +10099,22 @@ define("ember-data/lib/system/store", /** 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. + This method will synchronously return the record if it is available in the store, + otherwise it will return `null`. A record is available if it has been fetched earlier, or + pushed manually into the store. + + _Note: This is an synchronous method and does not return a promise._ ```js var post = store.getById('post', 1); + + post.get('id'); // 1 ``` @method getById @param {String or subclass of DS.Model} type @param {String|Integer} id - @param {DS.Model} record + @return {DS.Model|null} record */ getById: function(type, id) { if (this.hasRecordForId(type, id)) { @@ -9269,9 +10137,9 @@ define("ember-data/lib/system/store", @return {Promise} promise */ reloadRecord: function(record) { - var type = record.constructor, - adapter = this.adapterFor(type), - id = get(record, 'id'); + var type = record.constructor; + var adapter = this.adapterFor(type); + var 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); @@ -9299,7 +10167,9 @@ define("ember-data/lib/system/store", @return {Promise} promise */ fetchMany: function(records, owner) { - if (!records.length) { return; } + if (!records.length) { + return Ember.RSVP.resolve(records); + } // Group By Type var recordsByTypeMap = Ember.MapWithDefault.create({ @@ -9313,7 +10183,7 @@ define("ember-data/lib/system/store", var promises = []; forEach(recordsByTypeMap, function(type, records) { - var ids = records.mapProperty('id'), + var ids = records.mapBy('id'), adapter = this.adapterFor(type); Ember.assert("You tried to load many records but you have no adapter (for " + type + ")", adapter); @@ -9331,11 +10201,11 @@ define("ember-data/lib/system/store", @method hasRecordForId @param {String or subclass of DS.Model} type @param {String|Integer} id - @returns {Boolean} + @return {Boolean} */ - hasRecordForId: function(type, id) { - id = coerceId(id); - type = this.modelFor(type); + hasRecordForId: function(typeName, inputId) { + var type = this.modelFor(typeName); + var id = coerceId(inputId); return !!this.typeMapFor(type).idToRecord[id]; }, @@ -9347,16 +10217,15 @@ define("ember-data/lib/system/store", @private @param {String or subclass of DS.Model} type @param {String|Integer} id - @returns {DS.Model} record + @return {DS.Model} record */ - recordForId: function(type, id) { - type = this.modelFor(type); + recordForId: function(typeName, inputId) { + var type = this.modelFor(typeName); + var id = coerceId(inputId); + var idToRecord = this.typeMapFor(type).idToRecord; + var record = idToRecord[id]; - id = coerceId(id); - - var record = this.typeMapFor(type).idToRecord[id]; - - if (!record) { + if (!record || !idToRecord.hasOwnProperty(id)) { record = this.buildRecord(type, id); } @@ -9372,13 +10241,11 @@ define("ember-data/lib/system/store", @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); + findMany: function(owner, inputRecords, typeName, resolver) { + var type = this.modelFor(typeName); + var records = Ember.A(inputRecords); + var unloadedRecords = records.filterBy('isEmpty', true); + var manyArray = this.recordArrayManager.createManyArray(type, records); forEach(unloadedRecords, function(record) { record.loadingData(); @@ -9464,9 +10331,8 @@ define("ember-data/lib/system/store", @param {any} query an opaque query to be used by the adapter @return {Promise} promise */ - findQuery: function(type, query) { - type = this.modelFor(type); - + findQuery: function(typeName, query) { + var type = this.modelFor(typeName); var array = this.recordArrayManager .createAdapterPopulatedRecordArray(type, query); @@ -9488,8 +10354,8 @@ define("ember-data/lib/system/store", @param {String or subclass of DS.Model} type @return {DS.AdapterPopulatedRecordArray} */ - findAll: function(type) { - type = this.modelFor(type); + findAll: function(typeName) { + var type = this.modelFor(typeName); return this.fetchAll(type, this.all(type)); }, @@ -9499,11 +10365,11 @@ define("ember-data/lib/system/store", @private @param {DS.Model} type @param {DS.RecordArray} array - @returns {Promise} promise + @return {Promise} promise */ fetchAll: function(type, array) { - var adapter = this.adapterFor(type), - sinceToken = this.typeMapFor(type).metadata.since; + var adapter = this.adapterFor(type); + var sinceToken = this.typeMapFor(type).metadata.since; set(array, 'isUpdating', true); @@ -9542,11 +10408,10 @@ define("ember-data/lib/system/store", @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; + all: function(typeName) { + var type = this.modelFor(typeName); + var typeMap = this.typeMapFor(type); + var findAllCache = typeMap.findAllCache; if (findAllCache) { return findAllCache; } @@ -9623,9 +10488,12 @@ define("ember-data/lib/system/store", */ filter: function(type, query, filter) { var promise; + var length = arguments.length; + var array; + var hasQuery = length === 3; // allow an optional server query - if (arguments.length === 3) { + if (hasQuery) { promise = this.findQuery(type, query); } else if (arguments.length === 2) { filter = query; @@ -9633,10 +10501,15 @@ define("ember-data/lib/system/store", type = this.modelFor(type); - var array = this.recordArrayManager - .createFilteredRecordArray(type, filter); + if (hasQuery) { + array = this.recordArrayManager.createFilteredRecordArray(type, filter, query); + } else { + array = this.recordArrayManager.createFilteredRecordArray(type, filter); + } + promise = promise || Promise.cast(array); + return promiseArray(promise.then(function() { return array; }, null, "DS: Store#filter of " + type)); @@ -9732,11 +10605,13 @@ define("ember-data/lib/system/store", this._pendingSave = []; forEach(pending, function(tuple) { - var record = tuple[0], resolver = tuple[1], - adapter = this.adapterFor(record.constructor), - operation; + var record = tuple[0], resolver = tuple[1]; + var adapter = this.adapterFor(record.constructor); + var operation; - if (get(record, 'isNew')) { + if (get(record, 'currentState.stateName') === 'root.deleted.saved') { + return resolver.resolve(record); + } else if (get(record, 'isNew')) { operation = 'createRecord'; } else if (get(record, 'isDeleted')) { operation = 'deleteRecord'; @@ -9810,8 +10685,8 @@ define("ember-data/lib/system/store", @param {Object} data */ updateId: function(record, data) { - var oldId = get(record, 'id'), - id = coerceId(data.id); + var oldId = get(record, 'id'); + var 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); @@ -9829,9 +10704,9 @@ define("ember-data/lib/system/store", @return {Object} typeMap */ typeMapFor: function(type) { - var typeMaps = get(this, 'typeMaps'), - guid = Ember.guidFor(type), - typeMap; + var typeMaps = get(this, 'typeMaps'); + var guid = Ember.guidFor(type); + var typeMap; typeMap = typeMaps[guid]; @@ -9864,8 +10739,8 @@ define("ember-data/lib/system/store", the existing data, not replace it. */ _load: function(type, data, partial) { - var id = coerceId(data.id), - record = this.recordForId(type, id); + var id = coerceId(data.id); + var record = this.recordForId(type, id); record.setupData(data, partial); this.recordArrayManager.recordDidChange(record); @@ -9880,21 +10755,23 @@ define("ember-data/lib/system/store", @method modelFor @param {String or subclass of DS.Model} key - @returns {subclass of DS.Model} + @return {subclass of DS.Model} */ modelFor: function(key) { var factory; - if (typeof key === 'string') { var normalizedKey = this.container.normalize('model:' + key); factory = this.container.lookupFactory(normalizedKey); if (!factory) { throw new Ember.Error("No model was found for '" + key + "'"); } - factory.typeKey = normalizedKey.split(':', 2)[1]; + factory.typeKey = this._normalizeTypeKey(normalizedKey.split(':', 2)[1]); } else { - // A factory already supplied. + // A factory already supplied. Ensure it has a normalized key. factory = key; + if (factory.typeKey) { + factory.typeKey = this._normalizeTypeKey(factory.typeKey); + } } factory.store = this; @@ -9959,17 +10836,17 @@ define("ember-data/lib/system/store", @method push @param {String or subclass of DS.Model} type @param {Object} data - @returns {DS.Model} the record that was created or + @return {DS.Model} the record that was created or updated. */ - push: function(type, data, _partial) { + push: function(typeName, 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); + Ember.assert("You must include an `id` for " + typeName+ " in a hash passed to `push`", data.id != null); - type = this.modelFor(type); + var type = this.modelFor(typeName); // normalize relationship IDs into records data = normalizeRelationships(this, type, data); @@ -9982,13 +10859,9 @@ define("ember-data/lib/system/store", /** 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. + 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. @@ -10004,20 +10877,38 @@ define("ember-data/lib/system/store", ] } - store.pushPayload('post', pushData); + store.pushPayload(pushData); + ``` + + By default, the data will be deserialized using a default + serializer (the application serializer if it exists). + + Alternatively, `pushPayload` will accept a model type which + will determine which serializer will process the payload. + However, the serializer itself (processing this data via + `normalizePayload`) will not know which model it is + deserializing. + + ```js + App.ApplicationSerializer = DS.ActiveModelSerializer; + App.PostSerializer = DS.JSONSerializer; + store.pushPayload('comment', pushData); // Will use the ApplicationSerializer + store.pushPayload('post', pushData); // Will use the PostSerializer ``` @method pushPayload - @param {String} type + @param {String} type Optionally, a model used to determine which serializer will be used @param {Object} payload */ - pushPayload: function (type, payload) { + pushPayload: function (type, inputPayload) { var serializer; - if (!payload) { + var payload; + if (!inputPayload) { 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 { + payload = inputPayload; serializer = this.serializerFor(type); } serializer.pushPayload(this, payload); @@ -10056,7 +10947,7 @@ define("ember-data/lib/system/store", @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); + Ember.assert("You must include an `id` for " + type + " in a hash passed to `update`", data.id != null); return this.push(type, data, true); }, @@ -10085,8 +10976,8 @@ define("ember-data/lib/system/store", @param {String or subclass of DS.Model} type @param {Object} metadata */ - metaForType: function(type, metadata) { - type = this.modelFor(type); + metaForType: function(typeName, metadata) { + var type = this.modelFor(typeName); Ember.merge(this.typeMapFor(type).metadata, metadata); }, @@ -10100,13 +10991,13 @@ define("ember-data/lib/system/store", @param {subclass of DS.Model} type @param {String} id @param {Object} data - @returns {DS.Model} record + @return {DS.Model} record */ buildRecord: function(type, id, data) { - var typeMap = this.typeMapFor(type), - idToRecord = typeMap.idToRecord; + var typeMap = this.typeMapFor(type); + var idToRecord = typeMap.idToRecord; - Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToRecord[id]); + Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToRecord.hasOwnProperty(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 @@ -10145,9 +11036,9 @@ define("ember-data/lib/system/store", @param {DS.Model} record */ dematerializeRecord: function(record) { - var type = record.constructor, - typeMap = this.typeMapFor(type), - id = get(record, 'id'); + var type = record.constructor; + var typeMap = this.typeMapFor(type); + var id = get(record, 'id'); record.updateRecordArrays(); @@ -10164,8 +11055,8 @@ define("ember-data/lib/system/store", // ........................ addRelationshipChangeFor: function(childRecord, childKey, parentRecord, parentKey, change) { - var clientId = childRecord.clientId, - parentClientId = parentRecord ? parentRecord : parentRecord; + var clientId = childRecord.clientId; + var parentClientId = parentRecord ? parentRecord : parentRecord; var key = childKey + parentKey; var changes = this._relationshipChanges; if (!(clientId in changes)) { @@ -10181,8 +11072,8 @@ define("ember-data/lib/system/store", }, removeRelationshipChangeFor: function(clientRecord, childKey, parentRecord, parentKey, type) { - var clientId = clientRecord.clientId, - parentClientId = parentRecord ? parentRecord.clientId : parentRecord; + var clientId = clientRecord.clientId; + var 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])){ @@ -10220,7 +11111,7 @@ define("ember-data/lib/system/store", @method adapterFor @private @param {subclass of DS.Model} type - @returns DS.Adapter + @return DS.Adapter */ adapterFor: function(type) { var container = this.container, adapter; @@ -10261,18 +11152,33 @@ define("ember-data/lib/system/store", }, willDestroy: function() { - var map = this.typeMaps; - var keys = Ember.keys(map); + var typeMaps = this.typeMaps; + var keys = Ember.keys(typeMaps); var store = this; - var types = keys.map(byType); + + var types = map(keys, byType); this.recordArrayManager.destroy(); - types.forEach(this.unloadAll, this); + forEach(types, this.unloadAll, this); function byType(entry) { - return map[entry].type; + return typeMaps[entry]['type']; } + + }, + + /** + All typeKeys are camelCase internally. Changing this function may + require changes to other normalization hooks (such as typeForRoot). + + @method _normalizeTypeKey + @private + @param {String} type + @return {String} if the adapter can generate one, an ID + */ + _normalizeTypeKey: function(key) { + return camelize(singularize(key)); } }); @@ -10336,14 +11242,22 @@ define("ember-data/lib/system/store", // in the payload, so add them back in manually. function addUnsavedRecords(record, key, data) { if(record) { - data.pushObjects(record.get(key).filterBy('isNew')); + var unsavedRecords = uniqById(Ember.A(data), record.get(key).filterBy('isNew')); + Ember.A(data).pushObjects(unsavedRecords); } } + function uniqById(data, records) { + var currentIds = data.mapBy("id"); + return records.reject(function(record) { + return Ember.A(currentIds).contains(record.id); + }); + } + // Delegation to the adapter and promise management /** A `PromiseArray` is an object that acts like both an `Ember.Array` - and a promise. When the promise is resolved the the resulting value + and a promise. When the promise is resolved the resulting value will be set to the `PromiseArray`'s `content` property. This makes it easy to create data bindings with the `PromiseArray` that will be updated when the promise resolves. @@ -10431,9 +11345,9 @@ define("ember-data/lib/system/store", } function serializerForAdapter(adapter, type) { - var serializer = adapter.serializer, - defaultSerializer = adapter.defaultSerializer, - container = adapter.container; + var serializer = adapter.serializer; + var defaultSerializer = adapter.defaultSerializer; + var container = adapter.container; if (container && serializer === undefined) { serializer = serializerFor(container, type.typeKey, defaultSerializer); @@ -10449,9 +11363,9 @@ define("ember-data/lib/system/store", } 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; + var promise = adapter.find(store, type, id); + var serializer = serializerForAdapter(adapter, type); + var 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); @@ -10466,9 +11380,9 @@ define("ember-data/lib/system/store", } 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; + var promise = adapter.findMany(store, type, ids, owner); + var serializer = serializerForAdapter(adapter, type); + var label = "DS: Handle Adapter#findMany of " + type; return Promise.cast(promise, label).then(function(adapterPayload) { var payload = serializer.extract(store, type, adapterPayload, null, 'findMany'); @@ -10480,9 +11394,9 @@ define("ember-data/lib/system/store", } 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; + var promise = adapter.findHasMany(store, record, link, relationship); + var serializer = serializerForAdapter(adapter, relationship.type); + var 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'); @@ -10495,9 +11409,9 @@ define("ember-data/lib/system/store", } 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; + var promise = adapter.findBelongsTo(store, record, link, relationship); + var serializer = serializerForAdapter(adapter, relationship.type); + var 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'); @@ -10509,9 +11423,9 @@ define("ember-data/lib/system/store", } function _findAll(adapter, store, type, sinceToken) { - var promise = adapter.findAll(store, type, sinceToken), - serializer = serializerForAdapter(adapter, type), - label = "DS: Handle Adapter#findAll of " + type; + var promise = adapter.findAll(store, type, sinceToken); + var serializer = serializerForAdapter(adapter, type); + var label = "DS: Handle Adapter#findAll of " + type; return Promise.cast(promise, label).then(function(adapterPayload) { var payload = serializer.extract(store, type, adapterPayload, null, 'findAll'); @@ -10525,9 +11439,9 @@ define("ember-data/lib/system/store", } 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; + var promise = adapter.findQuery(store, type, query, recordArray); + var serializer = serializerForAdapter(adapter, type); + var label = "DS: Handle Adapter#findQuery of " + type; return Promise.cast(promise, label).then(function(adapterPayload) { var payload = serializer.extract(store, type, adapterPayload, null, 'findQuery'); @@ -10540,10 +11454,10 @@ define("ember-data/lib/system/store", } 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; + var type = record.constructor; + var promise = adapter[operation](store, type, record); + var serializer = serializerForAdapter(adapter, type); + var 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)); @@ -10559,7 +11473,7 @@ define("ember-data/lib/system/store", store.didSaveRecord(record, payload); return record; }, function(reason) { - if (reason instanceof DS.InvalidError) { + if (reason instanceof InvalidError) { store.recordWasInvalid(record, reason.errors); } else { store.recordWasError(record, reason); @@ -10604,12 +11518,13 @@ define("ember-data/lib/transforms/base", Example ```javascript - App.RawTransform = DS.Transform.extend({ + // Converts centigrade in the JSON to fahrenheit in the app + App.TemperatureTransform = DS.Transform.extend({ deserialize: function(serialized) { - return serialized; + return (serialized * 1.8) + 32; }, serialize: function(deserialized) { - return deserialized; + return (deserialized - 32) / 1.8; } }); ``` @@ -10620,14 +11535,14 @@ define("ember-data/lib/transforms/base", var attr = DS.attr; App.Requirement = DS.Model.extend({ name: attr('string'), - optionsArray: attr('raw') + temperature: attr('temperature') }); ``` @class Transform @namespace DS */ - var Transform = Ember.Object.extend({ + __exports__["default"] = Ember.Object.extend({ /** When given a deserialized value from a record attribute this method must return the serialized value. @@ -10663,10 +11578,7 @@ define("ember-data/lib/transforms/base", @return The deserialized value */ deserialize: Ember.required() - }); - - __exports__["default"] = Transform; }); define("ember-data/lib/transforms/boolean", ["./base","exports"], @@ -10695,7 +11607,7 @@ define("ember-data/lib/transforms/boolean", @extends DS.Transform @namespace DS */ - var BooleanTransform = Transform.extend({ + __exports__["default"] = Transform.extend({ deserialize: function(serialized) { var type = typeof serialized; @@ -10714,7 +11626,6 @@ define("ember-data/lib/transforms/boolean", return Boolean(deserialized); } }); - __exports__["default"] = BooleanTransform; }); define("ember-data/lib/transforms/date", ["./base","exports"], @@ -10740,7 +11651,12 @@ define("ember-data/lib/transforms/date", @namespace DS */ var Transform = __dependency1__["default"]; - var DateTransform = Transform.extend({ + + function pad(num) { + return num < 10 ? "0"+num : ""+num; + } + + __exports__["default"] = Transform.extend({ deserialize: function(serialized) { var type = typeof serialized; @@ -10760,21 +11676,37 @@ define("ember-data/lib/transforms/date", 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 days = [ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat" + ]; + var months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" + ]; + var utcYear = date.getUTCFullYear(); + var utcMonth = date.getUTCMonth(); + var utcDayOfMonth = date.getUTCDate(); + var utcDay = date.getUTCDay(); + var utcHours = date.getUTCHours(); + var utcMinutes = date.getUTCMinutes(); + var utcSeconds = date.getUTCSeconds(); var dayOfWeek = days[utcDay]; var dayOfMonth = pad(utcDayOfMonth); @@ -10785,11 +11717,8 @@ define("ember-data/lib/transforms/date", } else { return null; } - } - + } }); - - __exports__["default"] = DateTransform; }); define("ember-data/lib/transforms/number", ["./base","exports"], @@ -10820,8 +11749,7 @@ define("ember-data/lib/transforms/number", @extends DS.Transform @namespace DS */ - var NumberTransform = Transform.extend({ - + __exports__["default"] = Transform.extend({ deserialize: function(serialized) { return empty(serialized) ? null : Number(serialized); }, @@ -10830,8 +11758,6 @@ define("ember-data/lib/transforms/number", return empty(deserialized) ? null : Number(deserialized); } }); - - __exports__["default"] = NumberTransform; }); define("ember-data/lib/transforms/string", ["./base","exports"], @@ -10861,19 +11787,14 @@ define("ember-data/lib/transforms/string", @extends DS.Transform @namespace DS */ - var StringTransform = Transform.extend({ - + __exports__["default"] = 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"], @@ -10909,11 +11830,11 @@ define("ember-inflector/lib/main", function(__dependency1__, __dependency2__, __exports__) { "use strict"; var Inflector = __dependency1__.Inflector; - var inflections = __dependency1__.inflections; + var defaultRules = __dependency1__.defaultRules; var pluralize = __dependency1__.pluralize; var singularize = __dependency1__.singularize; - Inflector.defaultRules = inflections; + Inflector.defaultRules = defaultRules; Ember.Inflector = Inflector; Ember.String.pluralize = pluralize; @@ -11070,8 +11991,8 @@ define("ember-inflector/lib/system/inflector", ```js var inflector = new Ember.Inflector(Ember.Inflector.defaultRules); - inflector.pluralize('cow') //=> 'kine' - inflector.singularize('kine') //=> 'cow' + inflector.pluralize('cow'); //=> 'kine' + inflector.singularize('kine'); //=> 'cow' ``` Creating an inflector and adding rules later. @@ -11079,13 +12000,13 @@ define("ember-inflector/lib/system/inflector", ```javascript var inflector = Ember.Inflector.inflector; - inflector.pluralize('advice') // => 'advices' + inflector.pluralize('advice'); // => 'advices' inflector.uncountable('advice'); - inflector.pluralize('advice') // => 'advice' + inflector.pluralize('advice'); // => 'advice' - inflector.pluralize('formula') // => 'formulas' + inflector.pluralize('formula'); // => 'formulas' inflector.irregular('formula', 'formulae'); - inflector.pluralize('formula') // => 'formulae' + inflector.pluralize('formula'); // => 'formulae' // you would not need to add these as they are the default rules inflector.plural(/$/, 's'); diff --git a/vendor/assets/ember/development/ember.js b/vendor/assets/ember/development/ember.js index cece6e9e..46115472 100644 --- a/vendor/assets/ember/development/ember.js +++ b/vendor/assets/ember/development/ember.js @@ -1,5 +1,5 @@ // Fetched from channel: canary, with url http://builds.emberjs.com/canary/ember.js -// Fetched on: 2014-03-22T07:36:29Z +// Fetched on: 2014-07-01T13:19:13Z /*! * @overview Ember - JavaScript Application Framework * @copyright Copyright 2011-2014 Tilde Inc. and contributors @@ -7,10 +7,9 @@ * 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.6.0-beta.1+canary.1efbf7a1 + * @version 1.7.0-beta.1+canary.83fe4e00 */ - (function() { var define, requireModule, require, requirejs, Ember; @@ -26,8 +25,7 @@ var define, requireModule, require, requirejs, Ember; }; requirejs = require = requireModule = function(name) { - - if (seen[name]) { return seen[name]; } + if (seen.hasOwnProperty(name)) { return seen[name]; } seen[name] = {}; if (!registry[name]) { @@ -75,8 +73,3545 @@ var define, requireModule, require, requirejs, Ember; requirejs = require = requireModule = Ember.__loader.require; } })(); -(function() { -define("ember-debug", + +define("backburner", + ["backburner/utils","backburner/deferred_action_queues","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var Utils = __dependency1__["default"]; + var DeferredActionQueues = __dependency2__.DeferredActionQueues; + + var slice = [].slice, + pop = [].pop, + each = Utils.each, + isString = Utils.isString, + isFunction = Utils.isFunction, + isNumber = Utils.isNumber, + timers = [], + global = this, + NUMBER = /\d+/; + + // In IE 6-8, try/finally doesn't work without a catch. + // Unfortunately, this is impossible to test for since wrapping it in a parent try/catch doesn't trigger the bug. + // This tests for another broken try/catch behavior that only exhibits in the same versions of IE. + var needsIETryCatchFix = (function(e,x){ + try{ x(); } + catch(e) { } // jshint ignore:line + return !!e; + })(); + + function isCoercableNumber(number) { + return isNumber(number) || NUMBER.test(number); + } + + function Backburner(queueNames, options) { + this.queueNames = queueNames; + this.options = options || {}; + if (!this.options.defaultQueue) { + this.options.defaultQueue = queueNames[0]; + } + this.instanceStack = []; + this._debouncees = []; + this._throttlers = []; + } + + Backburner.prototype = { + queueNames: null, + options: null, + currentInstance: null, + instanceStack: null, + + begin: function() { + var options = this.options, + onBegin = options && options.onBegin, + previousInstance = this.currentInstance; + + if (previousInstance) { + this.instanceStack.push(previousInstance); + } + + this.currentInstance = new DeferredActionQueues(this.queueNames, options); + if (onBegin) { + onBegin(this.currentInstance, previousInstance); + } + }, + + end: function() { + var options = this.options, + onEnd = options && options.onEnd, + currentInstance = this.currentInstance, + nextInstance = null; + + // Prevent double-finally bug in Safari 6.0.2 and iOS 6 + // This bug appears to be resolved in Safari 6.0.5 and iOS 7 + var finallyAlreadyCalled = false; + try { + currentInstance.flush(); + } finally { + if (!finallyAlreadyCalled) { + finallyAlreadyCalled = true; + + this.currentInstance = null; + + if (this.instanceStack.length) { + nextInstance = this.instanceStack.pop(); + this.currentInstance = nextInstance; + } + + if (onEnd) { + onEnd(currentInstance, nextInstance); + } + } + } + }, + + run: function(target, method /*, args */) { + var onError = getOnError(this.options); + + this.begin(); + + if (!method) { + method = target; + target = null; + } + + if (isString(method)) { + method = target[method]; + } + + var args = slice.call(arguments, 2); + + // guard against Safari 6's double-finally bug + var didFinally = false; + + if (onError) { + try { + return method.apply(target, args); + } catch(error) { + onError(error); + } finally { + if (!didFinally) { + didFinally = true; + this.end(); + } + } + } else { + try { + return method.apply(target, args); + } finally { + if (!didFinally) { + didFinally = true; + this.end(); + } + } + } + }, + + defer: function(queueName, target, method /* , args */) { + if (!method) { + method = target; + target = null; + } + + if (isString(method)) { + method = target[method]; + } + + 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, false, stack); + }, + + deferOnce: function(queueName, target, method /* , args */) { + if (!method) { + method = target; + target = null; + } + + if (isString(method)) { + method = target[method]; + } + + 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); + }, + + setTimeout: function() { + var args = slice.call(arguments), + length = args.length, + method, wait, target, + methodOrTarget, methodOrWait, methodOrArgs; + + if (length === 0) { + return; + } else if (length === 1) { + method = args.shift(); + wait = 0; + } else if (length === 2) { + methodOrTarget = args[0]; + methodOrWait = args[1]; + + if (isFunction(methodOrWait) || isFunction(methodOrTarget[methodOrWait])) { + target = args.shift(); + method = args.shift(); + wait = 0; + } else if (isCoercableNumber(methodOrWait)) { + method = args.shift(); + wait = args.shift(); + } else { + method = args.shift(); + wait = 0; + } + } else { + var last = args[args.length - 1]; + + if (isCoercableNumber(last)) { + wait = args.pop(); + } else { + wait = 0; + } + + methodOrTarget = args[0]; + methodOrArgs = args[1]; + + if (isFunction(methodOrArgs) || (isString(methodOrArgs) && + methodOrTarget !== null && + methodOrArgs in methodOrTarget)) { + target = args.shift(); + method = args.shift(); + } else { + method = args.shift(); + } + } + + var executeAt = (+new Date()) + parseInt(wait, 10); + + if (isString(method)) { + method = target[method]; + } + + var onError = getOnError(this.options); + + function fn() { + if (onError) { + try { + method.apply(target, args); + } catch (e) { + onError(e); + } + } else { + method.apply(target, args); + } + } + + // find position to insert + var i = searchTimer(executeAt, timers); + + timers.splice(i, 0, executeAt, fn); + + updateLaterTimer(this, executeAt, wait); + + return fn; + }, + + throttle: function(target, method /* , args, wait, [immediate] */) { + var self = this, + args = arguments, + immediate = pop.call(args), + wait, + throttler, + index, + timer; + + if (isNumber(immediate) || isString(immediate)) { + wait = immediate; + immediate = true; + } else { + wait = pop.call(args); + } + + wait = parseInt(wait, 10); + + index = findThrottler(target, method, this._throttlers); + if (index > -1) { return this._throttlers[index]; } // throttled + + timer = global.setTimeout(function() { + if (!immediate) { + self.run.apply(self, args); + } + var index = findThrottler(target, method, self._throttlers); + if (index > -1) { + self._throttlers.splice(index, 1); + } + }, wait); + + if (immediate) { + self.run.apply(self, args); + } + + throttler = [target, method, timer]; + + this._throttlers.push(throttler); + + return throttler; + }, + + debounce: function(target, method /* , args, wait, [immediate] */) { + var self = this, + args = arguments, + immediate = pop.call(args), + wait, + index, + debouncee, + timer; + + if (isNumber(immediate) || isString(immediate)) { + wait = immediate; + immediate = false; + } else { + wait = pop.call(args); + } + + wait = parseInt(wait, 10); + // Remove debouncee + index = findDebouncee(target, method, this._debouncees); + + if (index > -1) { + debouncee = this._debouncees[index]; + this._debouncees.splice(index, 1); + clearTimeout(debouncee[2]); + } + + timer = global.setTimeout(function() { + if (!immediate) { + self.run.apply(self, args); + } + var index = findDebouncee(target, method, self._debouncees); + if (index > -1) { + self._debouncees.splice(index, 1); + } + }, wait); + + if (immediate && index === -1) { + self.run.apply(self, args); + } + + debouncee = [target, method, timer]; + + self._debouncees.push(debouncee); + + return debouncee; + }, + + cancelTimers: function() { + var clearItems = function(item) { + clearTimeout(item[2]); + }; + + each(this._throttlers, clearItems); + this._throttlers = []; + + each(this._debouncees, clearItems); + this._debouncees = []; + + if (this._laterTimer) { + clearTimeout(this._laterTimer); + this._laterTimer = null; + } + timers = []; + + if (this._autorun) { + clearTimeout(this._autorun); + this._autorun = null; + } + }, + + hasTimers: function() { + return !!timers.length || !!this._debouncees.length || !!this._throttlers.length || this._autorun; + }, + + cancel: function(timer) { + var timerType = typeof timer; + + if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce + return timer.queue.cancel(timer); + } 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, this._throttlers, timer) || + this._cancelItem(findDebouncee, this._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], array); + + 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; + Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce; + Backburner.prototype.later = Backburner.prototype.setTimeout; + + if (needsIETryCatchFix) { + var originalRun = Backburner.prototype.run; + Backburner.prototype.run = wrapInTryCatch(originalRun); + + var originalEnd = Backburner.prototype.end; + Backburner.prototype.end = wrapInTryCatch(originalEnd); + } + + function wrapInTryCatch(func) { + return function () { + try { + return func.apply(this, arguments); + } catch (e) { + throw e; + } + }; + } + + function getOnError(options) { + return options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]); + } + + + function createAutorun(backburner) { + backburner.begin(); + backburner._autorun = global.setTimeout(function() { + backburner._autorun = null; + backburner.end(); + }); + } + + function updateLaterTimer(self, executeAt, wait) { + if (!self._laterTimer || executeAt < self._laterTimerExpiresAt) { + self._laterTimer = global.setTimeout(function() { + self._laterTimer = null; + self._laterTimerExpiresAt = null; + executeTimers(self); + }, wait); + self._laterTimerExpiresAt = executeAt; + } + } + + function executeTimers(self) { + var now = +new Date(), + time, fns, i, l; + + self.run(function() { + i = searchTimer(now, timers); + + fns = timers.splice(0, i); + + for (i = 1, l = fns.length; i < l; i += 2) { + self.schedule(self.options.defaultQueue, null, fns[i]); + } + }); + + if (timers.length) { + updateLaterTimer(self, timers[0], timers[0] - now); + } + } + + function findDebouncee(target, method, debouncees) { + return findItem(target, method, debouncees); + } + + function findThrottler(target, method, throttlers) { + return findItem(target, method, throttlers); + } + + function findItem(target, method, collection) { + var item, + index = -1; + + for (var i = 0, l = collection.length; i < l; i++) { + item = collection[i]; + if (item[0] === target && item[1] === method) { + index = i; + break; + } + } + + return index; + } + + function searchTimer(time, timers) { + var start = 0, + end = timers.length - 2, + middle, l; + + while (start < end) { + // since timers is an array of pairs 'l' will always + // be an integer + l = (end - start) / 2; + + // compensate for the index in case even number + // of pairs inside timers + middle = start + l - (l % 2); + + if (time >= timers[middle]) { + start = middle + 2; + } else { + end = middle; + } + } + + return (time >= timers[start]) ? start + 2 : start; + } + + __exports__.Backburner = Backburner; + }); +define("backburner/deferred_action_queues", + ["backburner/utils","backburner/queue","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var Utils = __dependency1__["default"]; + var Queue = __dependency2__.Queue; + + var each = Utils.each, + isString = Utils.isString; + + function DeferredActionQueues(queueNames, options) { + var queues = this.queues = {}; + this.queueNames = queueNames = queueNames || []; + + this.options = options; + + each(queueNames, function(queueName) { + queues[queueName] = new Queue(this, queueName, options); + }); + } + + DeferredActionQueues.prototype = { + queueNames: null, + queues: null, + options: null, + + schedule: function(queueName, target, method, args, onceFlag, stack) { + var queues = this.queues, + queue = queues[queueName]; + + if (!queue) { throw new Error("You attempted to schedule an action in a queue (" + queueName + ") that doesn't exist"); } + + if (onceFlag) { + return queue.pushUnique(target, method, args, stack); + } else { + return queue.push(target, method, args, stack); + } + }, + + invoke: function(target, method, args, _) { + if (args && args.length > 0) { + method.apply(target, args); + } else { + method.call(target); + } + }, + + invokeWithOnError: function(target, method, args, onError) { + try { + if (args && args.length > 0) { + method.apply(target, args); + } else { + method.call(target); + } + } catch(error) { + onError(error); + } + }, + + flush: function() { + var queues = this.queues, + queueNames = this.queueNames, + queueName, queue, queueItems, priorQueueNameIndex, + queueNameIndex = 0, numberOfQueues = queueNames.length, + options = this.options, + onError = options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]), + invoke = onError ? this.invokeWithOnError : this.invoke; + + outerloop: + while (queueNameIndex < numberOfQueues) { + queueName = queueNames[queueNameIndex]; + queue = queues[queueName]; + queueItems = queue._queueBeingFlushed = queue._queue.slice(); + queue._queue = []; + + var queueOptions = queue.options, // TODO: write a test for this + before = queueOptions && queueOptions.before, + after = queueOptions && queueOptions.after, + target, method, args, stack, + queueIndex = 0, numberOfQueueItems = queueItems.length; + + if (numberOfQueueItems && before) { before(); } + + while (queueIndex < numberOfQueueItems) { + target = queueItems[queueIndex]; + method = queueItems[queueIndex+1]; + args = queueItems[queueIndex+2]; + stack = queueItems[queueIndex+3]; // Debugging assistance + + if (isString(method)) { method = target[method]; } + + // method could have been nullified / canceled during flush + if (method) { + invoke(target, method, args, onError); + } + + queueIndex += 4; + } + + queue._queueBeingFlushed = null; + if (numberOfQueueItems && after) { after(); } + + if ((priorQueueNameIndex = indexOfPriorQueueWithActions(this, queueNameIndex)) !== -1) { + queueNameIndex = priorQueueNameIndex; + continue outerloop; + } + + queueNameIndex++; + } + } + }; + + function indexOfPriorQueueWithActions(daq, currentQueueIndex) { + var queueName, queue; + + for (var i = 0, l = currentQueueIndex; i <= l; i++) { + queueName = daq.queueNames[i]; + queue = daq.queues[queueName]; + if (queue._queue.length) { return i; } + } + + return -1; + } + + __exports__.DeferredActionQueues = DeferredActionQueues; + }); +define("backburner/queue", + ["exports"], + function(__exports__) { + "use strict"; + function Queue(daq, name, options) { + this.daq = daq; + this.name = name; + this.globalOptions = options; + this.options = options[name]; + this._queue = []; + } + + Queue.prototype = { + daq: null, + name: null, + options: null, + onError: null, + _queue: null, + + push: function(target, method, args, stack) { + var queue = this._queue; + queue.push(target, method, args, stack); + return {queue: this, target: target, method: method}; + }, + + pushUnique: function(target, method, args, stack) { + var queue = this._queue, currentTarget, currentMethod, i, l; + + for (i = 0, l = queue.length; i < l; i += 4) { + currentTarget = queue[i]; + currentMethod = queue[i+1]; + + if (currentTarget === target && currentMethod === method) { + queue[i+2] = args; // replace args + queue[i+3] = stack; // replace stack + return {queue: this, target: target, method: method}; + } + } + + queue.push(target, method, args, stack); + return {queue: this, target: target, method: method}; + }, + + // TODO: remove me, only being used for Ember.run.sync + flush: function() { + var queue = this._queue, + globalOptions = this.globalOptions, + options = this.options, + before = options && options.before, + after = options && options.after, + onError = globalOptions.onError || (globalOptions.onErrorTarget && globalOptions.onErrorTarget[globalOptions.onErrorMethod]), + target, method, args, stack, i, l = queue.length; + + if (l && before) { before(); } + for (i = 0; i < l; i += 4) { + target = queue[i]; + method = queue[i+1]; + args = queue[i+2]; + stack = queue[i+3]; // Debugging assistance + + // TODO: error handling + if (args && args.length > 0) { + if (onError) { + try { + method.apply(target, args); + } catch (e) { + onError(e); + } + } else { + method.apply(target, args); + } + } else { + if (onError) { + try { + method.call(target); + } catch(e) { + onError(e); + } + } else { + method.call(target); + } + } + } + if (l && after) { after(); } + + // check if new items have been added + if (queue.length > l) { + this._queue = queue.slice(l); + this.flush(); + } else { + this._queue.length = 0; + } + }, + + cancel: function(actionToCancel) { + var queue = this._queue, currentTarget, currentMethod, i, l; + + for (i = 0, l = queue.length; i < l; i += 4) { + currentTarget = queue[i]; + currentMethod = queue[i+1]; + + if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) { + queue.splice(i, 4); + return true; + } + } + + // if not found in current queue + // could be in the queue that is being flushed + queue = this._queueBeingFlushed; + if (!queue) { + return; + } + for (i = 0, l = queue.length; i < l; i += 4) { + currentTarget = queue[i]; + currentMethod = queue[i+1]; + + if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) { + // don't mess with array during flush + // just nullify the method + queue[i+1] = null; + return true; + } + } + } + }; + + __exports__.Queue = Queue; + }); +define("backburner/utils", + ["exports"], + function(__exports__) { + "use strict"; + __exports__["default"] = { + each: function(collection, callback) { + for (var i = 0; i < collection.length; i++) { + callback(collection[i]); + } + }, + + isString: function(suspect) { + return typeof suspect === 'string'; + }, + + isFunction: function(suspect) { + return typeof suspect === 'function'; + }, + + isNumber: function(suspect) { + return typeof suspect === 'number'; + } + }; + }); + +define("container", + ["container/container","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /* + 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 + @private + */ + + /* + 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; + + if (Ember.ENV && typeof Ember.ENV.MODEL_FACTORY_INJECTIONS !== 'undefined') { + Ember.MODEL_FACTORY_INJECTIONS = !!Ember.ENV.MODEL_FACTORY_INJECTIONS; + } + + + var Container = __dependency1__["default"]; + + __exports__["default"] = Container; + }); +define("container/container", + ["container/inheriting_dict","ember-metal/core","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var InheritingDict = __dependency1__["default"]; + var Ember = __dependency2__["default"]; + // Ember.assert + + // A lightweight container that helps to assemble and decouple components. + // Public api for the container is still in flux. + // The public api, specified on the application namespace should be considered the stable api. + function Container(parent) { + this.parent = parent; + this.children = []; + + this.resolver = parent && parent.resolver || function() {}; + + this.registry = new InheritingDict(parent && parent.registry); + this.cache = 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 = {}; + + this.factoryTypeInjections = new InheritingDict(parent && parent.factoryTypeInjections); + this.factoryInjections = {}; + + this._options = new InheritingDict(parent && parent._options); + this._typeOptions = new InheritingDict(parent && parent._typeOptions); + } + + Container.prototype = { + + /** + @property parent + @type Container + @default null + */ + parent: null, + + /** + @property children + @type Array + @default [] + */ + children: null, + + /** + @property resolver + @type function + */ + resolver: null, + + /** + @property registry + @type InheritingDict + */ + registry: null, + + /** + @property cache + @type InheritingDict + */ + cache: null, + + /** + @property typeInjections + @type InheritingDict + */ + typeInjections: null, + + /** + @property injections + @type Object + @default {} + */ + injections: null, + + /** + @private + + @property _options + @type InheritingDict + @default null + */ + _options: null, + + /** + @private + + @property _typeOptions + @type InheritingDict + */ + _typeOptions: null, + + /** + Returns a new child of the current container. These children are configured + to correctly inherit from the current container. + + @method child + @return {Container} + */ + child: function() { + var container = new Container(this); + this.children.push(container); + return container; + }, + + /** + Sets a key-value pair on the current container. If a parent container, + has the same key, once set on a child, the parent and child will diverge + as expected. + + @method set + @param {Object} object + @param {String} key + @param {any} value + */ + set: function(object, key, value) { + object[key] = value; + }, + + /** + Registers a factory for later injection. + + Example: + + ```javascript + var container = new Container(); + + container.register('model:user', Person, {singleton: false }); + container.register('fruit:favorite', Orange); + container.register('communication:main', Email, {singleton: false}); + ``` + + @method register + @param {String} fullName + @param {Function} factory + @param {Object} options + */ + register: function(fullName, factory, options) { + Ember.assert('fullName must be a proper full name', validateFullName(fullName)); + + if (factory === undefined) { + throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`'); + } + + var normalizedName = this.normalize(fullName); + + if (this.cache.has(normalizedName)) { + throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.'); + } + + this.registry.set(normalizedName, factory); + this._options.set(normalizedName, options || {}); + }, + + /** + Unregister a fullName + + ```javascript + var container = new Container(); + container.register('model:user', User); + + container.lookup('model:user') instanceof User //=> true + + container.unregister('model:user') + container.lookup('model:user') === undefined //=> true + ``` + + @method unregister + @param {String} fullName + */ + unregister: function(fullName) { + Ember.assert('fullName must be a proper full name', 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); + }, + + /** + Given a fullName return the corresponding factory. + + By default `resolve` will retrieve the factory from + its container's registry. + + ```javascript + var container = new Container(); + container.register('api:twitter', Twitter); + + container.resolve('api:twitter') // => Twitter + ``` + + Optionally the container can be provided with a custom resolver. + If provided, `resolve` will first provide the custom resolver + the opportunity to resolve the fullName, otherwise it will fallback + to the registry. + + ```javascript + var container = new Container(); + container.resolver = function(fullName) { + // lookup via the module system of choice + }; + + // the twitter factory is added to the module system + container.resolve('api:twitter') // => Twitter + ``` + + @method resolve + @param {String} fullName + @return {Function} fullName's factory + */ + resolve: function(fullName) { + Ember.assert('fullName must be a proper full name', validateFullName(fullName)); + return resolve(this, this.normalize(fullName)); + }, + + /** + A hook that can be used to describe how the resolver will + attempt to find the factory. + + For example, the default Ember `.describe` returns the full + class name (including namespace) where Ember's resolver expects + to find the `fullName`. + + @method describe + @param {String} fullName + @return {string} described fullName + */ + describe: function(fullName) { + return fullName; + }, + + /** + A hook to enable custom fullName normalization behaviour + + @method normalize + @param {String} fullName + @return {string} normalized fullName + */ + normalize: function(fullName) { + return fullName; + }, + + /** + @method makeToString + + @param {any} factory + @param {string} fullName + @return {function} toString function + */ + makeToString: function(factory, fullName) { + return factory.toString(); + }, + + /** + Given a fullName return a corresponding instance. + + The default behaviour is for lookup to return a singleton instance. + The singleton is scoped to the container, allowing multiple containers + to all have their own locally scoped singletons. + + ```javascript + var container = new Container(); + container.register('api:twitter', Twitter); + + var twitter = container.lookup('api:twitter'); + + twitter instanceof Twitter; // => true + + // by default the container will return singletons + var twitter2 = container.lookup('api:twitter'); + twitter2 instanceof Twitter; // => true + + twitter === twitter2; //=> true + ``` + + If singletons are not wanted an optional flag can be provided at lookup. + + ```javascript + var container = new Container(); + container.register('api:twitter', Twitter); + + var twitter = container.lookup('api:twitter', { singleton: false }); + var twitter2 = container.lookup('api:twitter', { singleton: false }); + + twitter === twitter2; //=> false + ``` + + @method lookup + @param {String} fullName + @param {Object} options + @return {any} + */ + lookup: function(fullName, options) { + Ember.assert('fullName must be a proper full name', validateFullName(fullName)); + return lookup(this, this.normalize(fullName), options); + }, + + /** + Given a fullName return the corresponding factory. + + @method lookupFactory + @param {String} fullName + @return {any} + */ + lookupFactory: function(fullName) { + Ember.assert('fullName must be a proper full name', validateFullName(fullName)); + return factoryFor(this, this.normalize(fullName)); + }, + + /** + Given a fullName check if the container is aware of its factory + or singleton instance. + + @method has + @param {String} fullName + @return {Boolean} + */ + has: function(fullName) { + Ember.assert('fullName must be a proper full name', validateFullName(fullName)); + return has(this, this.normalize(fullName)); + }, + + /** + Allow registering options for all factories of a type. + + ```javascript + var container = new Container(); + + // if all of type `connection` must not be singletons + container.optionsForType('connection', { singleton: false }); + + container.register('connection:twitter', TwitterConnection); + container.register('connection:facebook', FacebookConnection); + + var twitter = container.lookup('connection:twitter'); + var twitter2 = container.lookup('connection:twitter'); + + twitter === twitter2; // => false + + var facebook = container.lookup('connection:facebook'); + var facebook2 = container.lookup('connection:facebook'); + + facebook === facebook2; // => false + ``` + + @method optionsForType + @param {String} type + @param {Object} options + */ + optionsForType: function(type, options) { + if (this.parent) { illegalChildOperation('optionsForType'); } + + this._typeOptions.set(type, options); + }, + + /** + @method options + @param {String} type + @param {Object} options + */ + options: function(type, options) { + this.optionsForType(type, options); + }, + + /** + Used only via `injection`. + + Provides a specialized form of injection, specifically enabling + all objects of one type to be injected with a reference to another + object. + + For example, provided each object of type `controller` needed a `router`. + one would do the following: + + ```javascript + var container = new Container(); + + container.register('router:main', Router); + container.register('controller:user', UserController); + container.register('controller:post', PostController); + + container.typeInjection('controller', 'router', 'router:main'); + + var user = container.lookup('controller:user'); + var post = container.lookup('controller:post'); + + user.router instanceof Router; //=> true + post.router instanceof Router; //=> true + + // both controllers share the same router + user.router === post.router; //=> true + ``` + + @private + @method typeInjection + @param {String} type + @param {String} property + @param {String} fullName + */ + typeInjection: function(type, property, fullName) { + Ember.assert('fullName must be a proper full name', 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); + }, + + /** + Defines injection rules. + + These rules are used to inject dependencies onto objects when they + are instantiated. + + Two forms of injections are possible: + + * Injecting one fullName on another fullName + * Injecting one fullName on a type + + Example: + + ```javascript + var container = new Container(); + + container.register('source:main', Source); + container.register('model:user', User); + container.register('model:post', Post); + + // injecting one fullName on another fullName + // eg. each user model gets a post model + container.injection('model:user', 'post', 'model:post'); + + // injecting one fullName on another type + container.injection('model', 'source', 'source:main'); + + var user = container.lookup('model:user'); + var post = container.lookup('model:post'); + + user.source instanceof Source; //=> true + post.source instanceof Source; //=> true + + user.post instanceof Post; //=> true + + // and both models share the same source + user.source === post.source; //=> true + ``` + + @method injection + @param {String} factoryName + @param {String} property + @param {String} injectionName + */ + injection: function(fullName, property, injectionName) { + if (this.parent) { illegalChildOperation('injection'); } + + validateFullName(injectionName); + var normalizedInjectionName = this.normalize(injectionName); + + if (fullName.indexOf(':') === -1) { + return this.typeInjection(fullName, property, normalizedInjectionName); + } + + Ember.assert('fullName must be a proper full name', validateFullName(fullName)); + var normalizedName = this.normalize(fullName); + + if (this.cache.has(normalizedName)) { + throw new Error("Attempted to register an injection for a type that has already been looked up. ('" + normalizedName + "', '" + property + "', '" + injectionName + "')"); + } + addInjection(this.injections, normalizedName, property, normalizedInjectionName); + }, + + + /** + Used only via `factoryInjection`. + + Provides a specialized form of injection, specifically enabling + all factory of one type to be injected with a reference to another + object. + + For example, provided each factory of type `model` needed a `store`. + one would do the following: + + ```javascript + var container = new Container(); + + container.register('store:main', SomeStore); + + container.factoryTypeInjection('model', 'store', 'store:main'); + + var store = container.lookup('store:main'); + var UserFactory = container.lookupFactory('model:user'); + + UserFactory.store instanceof SomeStore; //=> true + ``` + + @private + @method factoryTypeInjection + @param {String} type + @param {String} property + @param {String} fullName + */ + factoryTypeInjection: function(type, property, fullName) { + if (this.parent) { illegalChildOperation('factoryTypeInjection'); } + + addTypeInjection(this.factoryTypeInjections, type, property, this.normalize(fullName)); + }, + + /** + Defines factory injection rules. + + Similar to regular injection rules, but are run against factories, via + `Container#lookupFactory`. + + These rules are used to inject objects onto factories when they + are looked up. + + Two forms of injections are possible: + + * Injecting one fullName on another fullName + * Injecting one fullName on a type + + Example: + + ```javascript + var container = new Container(); + + container.register('store:main', Store); + container.register('store:secondary', OtherStore); + container.register('model:user', User); + container.register('model:post', Post); + + // injecting one fullName on another type + container.factoryInjection('model', 'store', 'store:main'); + + // injecting one fullName on another fullName + container.factoryInjection('model:post', 'secondaryStore', 'store:secondary'); + + var UserFactory = container.lookupFactory('model:user'); + var PostFactory = container.lookupFactory('model:post'); + var store = container.lookup('store:main'); + + UserFactory.store instanceof Store; //=> true + UserFactory.secondaryStore instanceof OtherStore; //=> false + + PostFactory.store instanceof Store; //=> true + PostFactory.secondaryStore instanceof OtherStore; //=> true + + // and both models share the same source instance + UserFactory.store === PostFactory.store; //=> true + ``` + + @method factoryInjection + @param {String} factoryName + @param {String} property + @param {String} injectionName + */ + factoryInjection: function(fullName, property, injectionName) { + if (this.parent) { illegalChildOperation('injection'); } + + var normalizedName = this.normalize(fullName); + var normalizedInjectionName = this.normalize(injectionName); + + validateFullName(injectionName); + + if (fullName.indexOf(':') === -1) { + return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName); + } + + Ember.assert('fullName must be a proper full name', validateFullName(fullName)); + + if (this.factoryCache.has(normalizedName)) { + throw new Error('Attempted to register a factoryInjection for a type that has already ' + + 'been looked up. (\'' + normalizedName + '\', \'' + property + '\', \'' + injectionName + '\')'); + } + + addInjection(this.factoryInjections, normalizedName, property, normalizedInjectionName); + }, + + /** + A depth first traversal, destroying the container, its descendant containers and all + their managed objects. + + @method destroy + */ + destroy: function() { + for (var i = 0, length = this.children.length; i < length; i++) { + this.children[i].destroy(); + } + + this.children = []; + + eachDestroyable(this, function(item) { + item.destroy(); + }); + + this.parent = undefined; + this.isDestroyed = true; + }, + + /** + @method reset + */ + reset: function() { + for (var i = 0, length = this.children.length; i < length; i++) { + resetCache(this.children[i]); + } + + resetCache(this); + } + }; + + function resolve(container, normalizedName) { + var cached = container.resolveCache.get(normalizedName); + if (cached) { return cached; } + + var resolved = container.resolver(normalizedName) || container.registry.get(normalizedName); + container.resolveCache.set(normalizedName, resolved); + + return resolved; + } + + function has(container, fullName){ + if (container.cache.has(fullName)) { + return true; + } + + return !!container.resolve(fullName); + } + + function lookup(container, fullName, options) { + options = options || {}; + + if (container.cache.has(fullName) && options.singleton !== false) { + return container.cache.get(fullName); + } + + var value = instantiate(container, fullName); + + if (value === undefined) { return; } + + if (isSingleton(container, fullName) && options.singleton !== false) { + container.cache.set(fullName, value); + } + + return value; + } + + function illegalChildOperation(operation) { + throw new Error(operation + ' is not currently supported on child containers'); + } + + function isSingleton(container, fullName) { + var singleton = option(container, fullName, 'singleton'); + + return singleton !== false; + } + + function buildInjections(container, injections) { + var hash = {}; + + if (!injections) { return hash; } + + var injection, injectable; + + for (var i = 0, length = injections.length; i < length; i++) { + injection = injections[i]; + injectable = lookup(container, injection.fullName); + + if (injectable !== undefined) { + hash[injection.property] = injectable; + } else { + throw new Error('Attempting to inject an unknown injection: `' + injection.fullName + '`'); + } + } + + return hash; + } + + function option(container, fullName, optionName) { + var options = container._options.get(fullName); + + if (options && options[optionName] !== undefined) { + return options[optionName]; + } + + var type = fullName.split(':')[0]; + options = container._typeOptions.get(type); + + if (options) { + return options[optionName]; + } + } + + function factoryFor(container, fullName) { + var cache = container.factoryCache; + if (cache.has(fullName)) { + return cache.get(fullName); + } + var factory = container.resolve(fullName); + if (factory === undefined) { return; } + + var type = fullName.split(':')[0]; + if (!factory || typeof factory.extend !== 'function' || (!Ember.MODEL_FACTORY_INJECTIONS && type === 'model')) { + // TODO: think about a 'safe' merge style extension + // for now just fallback to create time injection + return factory; + } else { + var injections = injectionsFor(container, fullName); + var factoryInjections = factoryInjectionsFor(container, fullName); + + factoryInjections._toString = container.makeToString(factory, fullName); + + var injectedFactory = factory.extend(injections); + injectedFactory.reopenClass(factoryInjections); + + cache.set(fullName, injectedFactory); + + return injectedFactory; + } + } + + function injectionsFor(container, fullName) { + var splitName = fullName.split(':'), + type = splitName[0], + injections = []; + + injections = injections.concat(container.typeInjections.get(type) || []); + injections = injections.concat(container.injections[fullName] || []); + + injections = buildInjections(container, injections); + injections._debugContainerKey = fullName; + injections.container = container; + + return injections; + } + + function factoryInjectionsFor(container, fullName) { + var splitName = fullName.split(':'), + type = splitName[0], + factoryInjections = []; + + factoryInjections = factoryInjections.concat(container.factoryTypeInjections.get(type) || []); + factoryInjections = factoryInjections.concat(container.factoryInjections[fullName] || []); + + factoryInjections = buildInjections(container, factoryInjections); + factoryInjections._debugContainerKey = fullName; + + return factoryInjections; + } + + function instantiate(container, fullName) { + var factory = factoryFor(container, fullName); + + if (option(container, fullName, 'instantiate') === false) { + return factory; + } + + if (factory) { + if (typeof factory.create !== 'function') { + throw new Error('Failed to create an instance of \'' + fullName + '\'. ' + + 'Most likely an improperly defined class or an invalid module export.'); + } + + if (typeof factory.extend === 'function') { + // assume the factory was extendable and is already injected + return factory.create(); + } else { + // assume the factory was extendable + // to create time injections + // TODO: support new'ing for instantiation and merge injections for pure JS Functions + return factory.create(injectionsFor(container, fullName)); + } + } + } + + function eachDestroyable(container, callback) { + container.cache.eachLocal(function(key, value) { + if (option(container, key, 'instantiate') === false) { return; } + callback(value); + }); + } + + function resetCache(container) { + container.cache.eachLocal(function(key, value) { + if (option(container, key, 'instantiate') === false) { return; } + value.destroy(); + }); + container.cache.dict = {}; + } + + function addTypeInjection(rules, type, property, fullName) { + var injections = rules.get(type); + + if (!injections) { + injections = []; + rules.set(type, injections); + } + + injections.push({ + property: property, + fullName: fullName + }); + } + + var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/; + function validateFullName(fullName) { + if (!VALID_FULL_NAME_REGEXP.test(fullName)) { + throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName); + } + return true; + } + + function addInjection(rules, factoryName, property, injectionName) { + var injections = rules[factoryName] = rules[factoryName] || []; + injections.push({ property: property, fullName: injectionName }); + } + + __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 = {}; + } + + 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 + @param {String} key + @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]); + } + } + } + }; + + __exports__["default"] = InheritingDict; + }); +define("ember-application", + ["ember-metal/core","ember-runtime/system/lazy_load","ember-application/system/dag","ember-application/system/resolver","ember-application/system/application","ember-application/ext/controller"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__) { + "use strict"; + var Ember = __dependency1__["default"]; + var runLoadHooks = __dependency2__.runLoadHooks; + + /** + Ember Application + + @module ember + @submodule ember-application + @requires ember-views, ember-routing + */ + + var DAG = __dependency3__["default"]; + var Resolver = __dependency4__.Resolver; + var DefaultResolver = __dependency4__.default; + var Application = __dependency5__["default"]; + // side effect of extending ControllerMixin + + Ember.Application = Application; + Ember.DAG = DAG; + Ember.Resolver = Resolver; + Ember.DefaultResolver = DefaultResolver; + + runLoadHooks('Ember.Application', Application); + }); +define("ember-application/ext/controller", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/error","ember-metal/utils","ember-metal/computed","ember-runtime/mixins/controller","ember-routing/system/controller_for","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-application + */ + + var Ember = __dependency1__["default"]; + // Ember.assert + var get = __dependency2__.get; + var set = __dependency3__.set; + var EmberError = __dependency4__["default"]; + var inspect = __dependency5__.inspect; + var computed = __dependency6__.computed; + var ControllerMixin = __dependency7__["default"]; + var meta = __dependency5__.meta; + var controllerFor = __dependency8__["default"]; + + function verifyNeedsDependencies(controller, container, needs) { + var dependency, i, l, missing = []; + + for (i=0, l=needs.length; i 1 ? 'they' : 'it') + " could not be found"); + } + } + + var defaultControllersComputedProperty = computed(function() { + var controller = this; + + return { + needs: get(controller, 'needs'), + container: get(controller, 'container'), + unknownProperty: function(controllerName) { + var needs = this.needs, + dependency, i, l; + for (i=0, l=needs.length; i 0) { + Ember.assert(' `' + inspect(this) + ' specifies `needs`, but does ' + + "not have a container. Please ensure this controller was " + + "instantiated with a container.", + this.container || meta(this, false).descs.controllers !== defaultControllersComputedProperty); + + if (this.container) { + verifyNeedsDependencies(this, this.container, needs); + } + + // if needs then initialize controllers proxy + get(this, 'controllers'); + } + + this._super.apply(this, arguments); + }, + + /** + @method controllerFor + @see {Ember.Route#controllerFor} + @deprecated Use `needs` instead + */ + controllerFor: function(controllerName) { + Ember.deprecate("Controller#controllerFor is deprecated, please use Controller#needs instead"); + return controllerFor(get(this, 'container'), controllerName); + }, + + /** + Stores the instances of other controllers available from within + this controller. Any controller listed by name in the `needs` + property will be accessible by name through this property. + + ```javascript + App.CommentsController = Ember.ArrayController.extend({ + needs: ['post'], + postTitle: function(){ + var currentPost = this.get('controllers.post'); // instance of App.PostController + return currentPost.get('title'); + }.property('controllers.post.title') + }); + ``` + + @see {Ember.ControllerMixin#needs} + @property {Object} controllers + @default null + */ + controllers: defaultControllersComputedProperty + }); + + __exports__["default"] = ControllerMixin; + }); +define("ember-application/system/application", + ["ember-metal","ember-metal/property_get","ember-metal/property_set","ember-runtime/system/lazy_load","ember-application/system/dag","ember-runtime/system/namespace","ember-runtime/mixins/deferred","ember-application/system/resolver","ember-metal/platform","ember-metal/run_loop","ember-metal/utils","container/container","ember-runtime/controllers/controller","ember-metal/enumerable_utils","ember-runtime/controllers/object_controller","ember-runtime/controllers/array_controller","ember-views/system/event_dispatcher","ember-views/system/jquery","ember-routing/system/route","ember-routing/system/router","ember-routing/location/hash_location","ember-routing/location/history_location","ember-routing/location/auto_location","ember-routing/location/none_location","ember-routing/system/cache","ember-metal/core","ember-handlebars-compiler","ember-application/system/deprecated-container","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__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-application + */ + + var Ember = __dependency1__["default"]; + // Ember.FEATURES, Ember.deprecate, Ember.assert, Ember.libraries, LOG_VERSION, Namespace, BOOTED + var get = __dependency2__.get; + var set = __dependency3__.set; + var runLoadHooks = __dependency4__.runLoadHooks; + var DAG = __dependency5__["default"]; + var Namespace = __dependency6__["default"]; + var DeferredMixin = __dependency7__["default"]; + var DefaultResolver = __dependency8__["default"]; + var create = __dependency9__.create; + var run = __dependency10__["default"]; + var canInvoke = __dependency11__.canInvoke; + var Container = __dependency12__["default"]; + var Controller = __dependency13__["default"]; + var EnumerableUtils = __dependency14__["default"]; + var ObjectController = __dependency15__["default"]; + var ArrayController = __dependency16__["default"]; + var EventDispatcher = __dependency17__["default"]; + //import ContainerDebugAdapter from "ember-extension-support/container_debug_adapter"; + var jQuery = __dependency18__["default"]; + var Route = __dependency19__["default"]; + var Router = __dependency20__["default"]; + var HashLocation = __dependency21__["default"]; + var HistoryLocation = __dependency22__["default"]; + var AutoLocation = __dependency23__["default"]; + var NoneLocation = __dependency24__["default"]; + var BucketCache = __dependency25__["default"]; + + var K = __dependency26__.K; + var EmberHandlebars = __dependency27__["default"]; + var DeprecatedContainer = __dependency28__["default"]; + + var ContainerDebugAdapter; + + /** + An instance of `Ember.Application` is the starting point for every Ember + application. It helps to instantiate, initialize and coordinate the many + objects that make up your app. + + Each Ember app has one and only one `Ember.Application` object. In fact, the + very first thing you should do in your application is create the instance: + + ```javascript + window.App = Ember.Application.create(); + ``` + + Typically, the application object is the only global variable. All other + classes in your app should be properties on the `Ember.Application` instance, + which highlights its first role: a global namespace. + + For example, if you define a view class, it might look like this: + + ```javascript + App.MyView = Ember.View.extend(); + ``` + + By default, calling `Ember.Application.create()` will automatically initialize + your application by calling the `Ember.Application.initialize()` method. If + you need to delay initialization, you can call your app's `deferReadiness()` + method. When you are ready for your app to be initialized, call its + `advanceReadiness()` method. + + You can define a `ready` method on the `Ember.Application` instance, which + will be run by Ember when the application is initialized. + + Because `Ember.Application` inherits from `Ember.Namespace`, any classes + you create will have useful string representations when calling `toString()`. + See the `Ember.Namespace` documentation for more information. + + While you can think of your `Ember.Application` as a container that holds the + other classes in your application, there are several other responsibilities + going on under-the-hood that you may want to understand. + + ### Event Delegation + + Ember uses a technique called _event delegation_. This allows the framework + to set up a global, shared event listener instead of requiring each view to + do it manually. For example, instead of each view registering its own + `mousedown` listener on its associated element, Ember sets up a `mousedown` + listener on the `body`. + + If a `mousedown` event occurs, Ember will look at the target of the event and + start walking up the DOM node tree, finding corresponding views and invoking + their `mouseDown` method as it goes. + + `Ember.Application` has a number of default events that it listens for, as + well as a mapping from lowercase events to camel-cased view method names. For + example, the `keypress` event causes the `keyPress` method on the view to be + called, the `dblclick` event causes `doubleClick` to be called, and so on. + + If there is a bubbling browser event that Ember does not listen for by + default, you can specify custom events and their corresponding view method + names by setting the application's `customEvents` property: + + ```javascript + App = Ember.Application.create({ + customEvents: { + // add support for the paste event + paste: "paste" + } + }); + ``` + + By default, the application sets up these event listeners on the document + body. However, in cases where you are embedding an Ember application inside + an existing page, you may want it to set up the listeners on an element + inside the body. + + For example, if only events inside a DOM element with the ID of `ember-app` + should be delegated, set your application's `rootElement` property: + + ```javascript + window.App = Ember.Application.create({ + rootElement: '#ember-app' + }); + ``` + + The `rootElement` can be either a DOM element or a jQuery-compatible selector + string. Note that *views appended to the DOM outside the root element will + not receive events.* If you specify a custom root element, make sure you only + append views inside it! + + To learn more about the advantages of event delegation and the Ember view + layer, and a list of the event listeners that are setup by default, visit the + [Ember View Layer guide](http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_event-delegation). + + ### Initializers + + Libraries on top of Ember can add initializers, like so: + + ```javascript + Ember.Application.initializer({ + name: 'api-adapter', + + initialize: function(container, application) { + application.register('api-adapter:main', ApiAdapter); + } + }); + ``` + + Initializers provide an opportunity to access the container, which + organizes the different components of an Ember application. Additionally + they provide a chance to access the instantiated application. Beyond + being used for libraries, initializers are also a great way to organize + dependency injection or setup in your own application. + + ### Routing + + In addition to creating your application's router, `Ember.Application` is + also responsible for telling the router when to start routing. Transitions + between routes can be logged with the `LOG_TRANSITIONS` flag, and more + detailed intra-transition logging can be logged with + the `LOG_TRANSITIONS_INTERNAL` flag: + + ```javascript + window.App = Ember.Application.create({ + LOG_TRANSITIONS: true, // basic logging of successful transitions + LOG_TRANSITIONS_INTERNAL: true // detailed logging of all routing steps + }); + ``` + + By default, the router will begin trying to translate the current URL into + application state once the browser emits the `DOMContentReady` event. If you + need to defer routing, you can call the application's `deferReadiness()` + method. Once routing can begin, call the `advanceReadiness()` method. + + If there is any setup required before routing begins, you can implement a + `ready()` method on your app that will be invoked immediately before routing + begins. + ``` + + @class Application + @namespace Ember + @extends Ember.Namespace + */ + + var Application = Namespace.extend(DeferredMixin, { + + /** + The root DOM element of the Application. This can be specified as an + element or a + [jQuery-compatible selector string](http://api.jquery.com/category/selectors/). + + This is the element that will be passed to the Application's, + `eventDispatcher`, which sets up the listeners for event delegation. Every + view in your application should be a child of the element you specify here. + + @property rootElement + @type DOMElement + @default 'body' + */ + rootElement: 'body', + + /** + The `Ember.EventDispatcher` responsible for delegating events to this + application's views. + + The event dispatcher is created by the application at initialization time + and sets up event listeners on the DOM element described by the + application's `rootElement` property. + + See the documentation for `Ember.EventDispatcher` for more information. + + @property eventDispatcher + @type Ember.EventDispatcher + @default null + */ + eventDispatcher: null, + + /** + The DOM events for which the event dispatcher should listen. + + By default, the application's `Ember.EventDispatcher` listens + for a set of standard DOM events, such as `mousedown` and + `keyup`, and delegates them to your application's `Ember.View` + instances. + + If you would like additional bubbling events to be delegated to your + views, set your `Ember.Application`'s `customEvents` property + to a hash containing the DOM event name as the key and the + corresponding view method name as the value. For example: + + ```javascript + App = Ember.Application.create({ + customEvents: { + // add support for the paste event + paste: "paste" + } + }); + ``` + + @property customEvents + @type Object + @default null + */ + customEvents: null, + + // Start off the number of deferrals at 1. This will be + // decremented by the Application's own `initialize` method. + _readinessDeferrals: 1, + + init: function() { + if (!this.$) { this.$ = jQuery; } + this.__container__ = this.buildContainer(); + + this.Router = this.defaultRouter(); + + this._super(); + + this.scheduleInitialize(); + + Ember.libraries.registerCoreLibrary('Handlebars', EmberHandlebars.VERSION); + Ember.libraries.registerCoreLibrary('jQuery', jQuery().jquery); + + if ( Ember.LOG_VERSION ) { + Ember.LOG_VERSION = false; // we only need to see this once per Application#init + + var nameLengths = EnumerableUtils.map(Ember.libraries, function(item) { + return get(item, "name.length"); + }); + + var maxNameLength = Math.max.apply(this, nameLengths); + + Ember.debug('-------------------------------'); + Ember.libraries.each(function(name, version) { + var spaces = new Array(maxNameLength - name.length + 1).join(" "); + Ember.debug([name, spaces, ' : ', version].join("")); + }); + Ember.debug('-------------------------------'); + } + }, + + /** + Build the container for the current application. + + Also register a default application view in case the application + itself does not. + + @private + @method buildContainer + @return {Ember.Container} the configured container + */ + buildContainer: function() { + var container = this.__container__ = Application.buildContainer(this); + + return container; + }, + + /** + If the application has not opted out of routing and has not explicitly + defined a router, supply a default router for the application author + to configure. + + This allows application developers to do: + + ```javascript + var App = Ember.Application.create(); + + App.Router.map(function() { + this.resource('posts'); + }); + ``` + + @private + @method defaultRouter + @return {Ember.Router} the default router + */ + + defaultRouter: function() { + if (this.Router === false) { return; } + var container = this.__container__; + + if (this.Router) { + container.unregister('router:main'); + container.register('router:main', this.Router); + } + + return container.lookupFactory('router:main'); + }, + + /** + Automatically initialize the application once the DOM has + become ready. + + The initialization itself is scheduled on the actions queue + which ensures that application loading finishes before + booting. + + If you are asynchronously loading code, you should call + `deferReadiness()` to defer booting, and then call + `advanceReadiness()` once all of your code has finished + loading. + + @private + @method scheduleInitialize + */ + scheduleInitialize: function() { + var self = this; + + if (!this.$ || this.$.isReady) { + run.schedule('actions', self, '_initialize'); + } else { + this.$().ready(function runInitialize() { + run(self, '_initialize'); + }); + } + }, + + /** + Use this to defer readiness until some condition is true. + + Example: + + ```javascript + App = Ember.Application.create(); + App.deferReadiness(); + + jQuery.getJSON("/auth-token", function(token) { + App.token = token; + App.advanceReadiness(); + }); + ``` + + This allows you to perform asynchronous setup logic and defer + booting your application until the setup has finished. + + However, if the setup requires a loading UI, it might be better + to use the router for this purpose. + + @method deferReadiness + */ + deferReadiness: function() { + Ember.assert("You must call deferReadiness on an instance of Ember.Application", this instanceof Application); + Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0); + this._readinessDeferrals++; + }, + + /** + Call `advanceReadiness` after any asynchronous setup logic has completed. + Each call to `deferReadiness` must be matched by a call to `advanceReadiness` + or the application will never become ready and routing will not begin. + + @method advanceReadiness + @see {Ember.Application#deferReadiness} + */ + advanceReadiness: function() { + Ember.assert("You must call advanceReadiness on an instance of Ember.Application", this instanceof Application); + this._readinessDeferrals--; + + if (this._readinessDeferrals === 0) { + run.once(this, this.didBecomeReady); + } + }, + + /** + Registers a factory that can be used for dependency injection (with + `App.inject`) or for service lookup. Each factory is registered with + a full name including two parts: `type:name`. + + A simple example: + + ```javascript + var App = Ember.Application.create(); + App.Orange = Ember.Object.extend(); + App.register('fruit:favorite', App.Orange); + ``` + + Ember will resolve factories from the `App` namespace automatically. + For example `App.CarsController` will be discovered and returned if + an application requests `controller:cars`. + + An example of registering a controller with a non-standard name: + + ```javascript + var App = Ember.Application.create(), + Session = Ember.Controller.extend(); + + App.register('controller:session', Session); + + // The Session controller can now be treated like a normal controller, + // despite its non-standard name. + App.ApplicationController = Ember.Controller.extend({ + needs: ['session'] + }); + ``` + + Registered factories are **instantiated** by having `create` + called on them. Additionally they are **singletons**, each time + they are looked up they return the same instance. + + Some examples modifying that default behavior: + + ```javascript + var App = Ember.Application.create(); + + App.Person = Ember.Object.extend(); + App.Orange = Ember.Object.extend(); + App.Email = Ember.Object.extend(); + App.session = Ember.Object.create(); + + App.register('model:user', App.Person, {singleton: false }); + App.register('fruit:favorite', App.Orange); + App.register('communication:main', App.Email, {singleton: false}); + App.register('session', App.session, {instantiate: false}); + ``` + + @method register + @param fullName {String} type:name (e.g., 'model:user') + @param factory {Function} (e.g., App.Person) + @param options {Object} (optional) disable instantiation or singleton usage + **/ + register: function() { + var container = this.__container__; + container.register.apply(container, arguments); + }, + + /** + Define a dependency injection onto a specific factory or all factories + of a type. + + When Ember instantiates a controller, view, or other framework component + it can attach a dependency to that component. This is often used to + provide services to a set of framework components. + + An example of providing a session object to all controllers: + + ```javascript + var App = Ember.Application.create(), + Session = Ember.Object.extend({ isAuthenticated: false }); + + // A factory must be registered before it can be injected + App.register('session:main', Session); + + // Inject 'session:main' onto all factories of the type 'controller' + // with the name 'session' + App.inject('controller', 'session', 'session:main'); + + App.IndexController = Ember.Controller.extend({ + isLoggedIn: Ember.computed.alias('session.isAuthenticated') + }); + ``` + + Injections can also be performed on specific factories. + + ```javascript + App.inject(, , ) + App.inject('route', 'source', 'source:main') + App.inject('route:application', 'email', 'model:email') + ``` + + It is important to note that injections can only be performed on + classes that are instantiated by Ember itself. Instantiating a class + directly (via `create` or `new`) bypasses the dependency injection + system. + + Ember-Data instantiates its models in a unique manner, and consequently + injections onto models (or all models) will not work as expected. Injections + on models can be enabled by setting `Ember.MODEL_FACTORY_INJECTIONS` + to `true`. + + @method inject + @param factoryNameOrType {String} + @param property {String} + @param injectionName {String} + **/ + inject: function() { + var container = this.__container__; + container.injection.apply(container, arguments); + }, + + /** + Calling initialize manually is not supported. + + Please see Ember.Application#advanceReadiness and + Ember.Application#deferReadiness. + + @private + @deprecated + @method initialize + **/ + initialize: function() { + Ember.deprecate('Calling initialize manually is not supported. Please see Ember.Application#advanceReadiness and Ember.Application#deferReadiness'); + }, + + /** + Initialize the application. This happens automatically. + + Run any initializers and run the application load hook. These hooks may + choose to defer readiness. For example, an authentication hook might want + to defer readiness until the auth token has been retrieved. + + @private + @method _initialize + */ + _initialize: function() { + if (this.isDestroyed) { return; } + + // At this point, the App.Router must already be assigned + if (this.Router) { + var container = this.__container__; + container.unregister('router:main'); + container.register('router:main', this.Router); + } + + this.runInitializers(); + runLoadHooks('application', this); + + // At this point, any initializers or load hooks that would have wanted + // to defer readiness have fired. In general, advancing readiness here + // will proceed to didBecomeReady. + this.advanceReadiness(); + + return this; + }, + + /** + Reset the application. This is typically used only in tests. It cleans up + the application in the following order: + + 1. Deactivate existing routes + 2. Destroy all objects in the container + 3. Create a new application container + 4. Re-route to the existing url + + Typical Example: + + ```javascript + + var App; + + run(function() { + App = Ember.Application.create(); + }); + + module("acceptance test", { + setup: function() { + App.reset(); + } + }); + + test("first test", function() { + // App is freshly reset + }); + + test("first test", function() { + // App is again freshly reset + }); + ``` + + Advanced Example: + + Occasionally you may want to prevent the app from initializing during + setup. This could enable extra configuration, or enable asserting prior + to the app becoming ready. + + ```javascript + + var App; + + run(function() { + App = Ember.Application.create(); + }); + + module("acceptance test", { + setup: function() { + run(function() { + App.reset(); + App.deferReadiness(); + }); + } + }); + + test("first test", function() { + ok(true, 'something before app is initialized'); + + run(function() { + App.advanceReadiness(); + }); + ok(true, 'something after app is initialized'); + }); + ``` + + @method reset + **/ + reset: function() { + this._readinessDeferrals = 1; + + function handleReset() { + var router = this.__container__.lookup('router:main'); + router.reset(); + + run(this.__container__, 'destroy'); + + this.buildContainer(); + + run.schedule('actions', this, function() { + this._initialize(); + }); + } + + run.join(this, handleReset); + }, + + /** + @private + @method runInitializers + */ + runInitializers: function() { + var initializers = get(this.constructor, 'initializers'), + container = this.__container__, + graph = new DAG(), + namespace = this, + name, initializer; + + for (name in initializers) { + initializer = initializers[name]; + graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after); + } + + graph.topsort(function (vertex) { + var initializer = vertex.value; + Ember.assert("No application initializer named '"+vertex.name+"'", initializer); + initializer(container, namespace); + }); + }, + + /** + @private + @method didBecomeReady + */ + didBecomeReady: function() { + this.setupEventDispatcher(); + this.ready(); // user hook + this.startRouting(); + + if (!Ember.testing) { + // Eagerly name all classes that are already loaded + Ember.Namespace.processAll(); + Ember.BOOTED = true; + } + + this.resolve(this); + }, + + /** + Setup up the event dispatcher to receive events on the + application's `rootElement` with any registered + `customEvents`. + + @private + @method setupEventDispatcher + */ + setupEventDispatcher: function() { + var customEvents = get(this, 'customEvents'), + rootElement = get(this, 'rootElement'), + dispatcher = this.__container__.lookup('event_dispatcher:main'); + + set(this, 'eventDispatcher', dispatcher); + dispatcher.setup(customEvents, rootElement); + }, + + /** + If the application has a router, use it to route to the current URL, and + trigger a new call to `route` whenever the URL changes. + + @private + @method startRouting + @property router {Ember.Router} + */ + startRouting: function() { + var router = this.__container__.lookup('router:main'); + if (!router) { return; } + + router.startRouting(); + }, + + handleURL: function(url) { + var router = this.__container__.lookup('router:main'); + + router.handleURL(url); + }, + + /** + Called when the Application has become ready. + The call will be delayed until the DOM has become ready. + + @event ready + */ + ready: K, + + /** + @deprecated Use 'Resolver' instead + Set this to provide an alternate class to `Ember.DefaultResolver` + + + @property resolver + */ + resolver: null, + + /** + Set this to provide an alternate class to `Ember.DefaultResolver` + + @property resolver + */ + Resolver: null, + + willDestroy: function() { + Ember.BOOTED = false; + // Ensure deactivation of routes before objects are destroyed + this.__container__.lookup('router:main').reset(); + + this.__container__.destroy(); + }, + + initializer: function(options) { + this.constructor.initializer(options); + } + }); + + Application.reopenClass({ + initializers: {}, + + /** + Initializer receives an object which has the following attributes: + `name`, `before`, `after`, `initialize`. The only required attribute is + `initialize, all others are optional. + + * `name` allows you to specify under which name the initializer is registered. + This must be a unique name, as trying to register two initializers with the + same name will result in an error. + + ```javascript + Ember.Application.initializer({ + name: 'namedInitializer', + initialize: function(container, application) { + Ember.debug("Running namedInitializer!"); + } + }); + ``` + + * `before` and `after` are used to ensure that this initializer is ran prior + or after the one identified by the value. This value can be a single string + or an array of strings, referencing the `name` of other initializers. + + An example of ordering initializers, we create an initializer named `first`: + + ```javascript + Ember.Application.initializer({ + name: 'first', + initialize: function(container, application) { + Ember.debug("First initializer!"); + } + }); + + // DEBUG: First initializer! + ``` + + We add another initializer named `second`, specifying that it should run + after the initializer named `first`: + + ```javascript + Ember.Application.initializer({ + name: 'second', + after: 'first', + + initialize: function(container, application) { + Ember.debug("Second initializer!"); + } + }); + + // DEBUG: First initializer! + // DEBUG: Second initializer! + ``` + + Afterwards we add a further initializer named `pre`, this time specifying + that it should run before the initializer named `first`: + + ```javascript + Ember.Application.initializer({ + name: 'pre', + before: 'first', + + initialize: function(container, application) { + Ember.debug("Pre initializer!"); + } + }); + + // DEBUG: Pre initializer! + // DEBUG: First initializer! + // DEBUG: Second initializer! + ``` + + Finally we add an initializer named `post`, specifying it should run after + both the `first` and the `second` initializers: + + ```javascript + Ember.Application.initializer({ + name: 'post', + after: ['first', 'second'], + + initialize: function(container, application) { + Ember.debug("Post initializer!"); + } + }); + + // DEBUG: Pre initializer! + // DEBUG: First initializer! + // DEBUG: Second initializer! + // DEBUG: Post initializer! + ``` + + * `initialize` is a callback function that receives two arguments, `container` + and `application` on which you can operate. + + Example of using `container` to preload data into the store: + + ```javascript + Ember.Application.initializer({ + name: "preload-data", + + initialize: function(container, application) { + var store = container.lookup('store:main'); + store.pushPayload(preloadedData); + } + }); + ``` + + Example of using `application` to register an adapter: + + ```javascript + Ember.Application.initializer({ + name: 'api-adapter', + + initialize: function(container, application) { + application.register('api-adapter:main', ApiAdapter); + } + }); + ``` + + @method initializer + @param initializer {Object} + */ + initializer: function(initializer) { + // If this is the first initializer being added to a subclass, we are going to reopen the class + // to make sure we have a new `initializers` object, which extends from the parent class' using + // prototypal inheritance. Without this, attempting to add initializers to the subclass would + // pollute the parent class as well as other subclasses. + if (this.superclass.initializers !== undefined && this.superclass.initializers === this.initializers) { + this.reopenClass({ + initializers: create(this.initializers) + }); + } + + Ember.assert("The initializer '" + initializer.name + "' has already been registered", !this.initializers[initializer.name]); + Ember.assert("An initializer cannot be registered without an initialize function", canInvoke(initializer, 'initialize')); + + this.initializers[initializer.name] = initializer; + }, + + /** + This creates a container with the default Ember naming conventions. + + It also configures the container: + + * registered views are created every time they are looked up (they are + not singletons) + * registered templates are not factories; the registered value is + returned directly. + * the router receives the application as its `namespace` property + * all controllers receive the router as their `target` and `controllers` + properties + * all controllers receive the application as their `namespace` property + * the application view receives the application controller as its + `controller` property + * the application view receives the application template as its + `defaultTemplate` property + + @private + @method buildContainer + @static + @param {Ember.Application} namespace the application to build the + container for. + @return {Ember.Container} the built container + */ + buildContainer: function(namespace) { + var container = new Container(); + + Container.defaultContainer = new DeprecatedContainer(container); + + container.set = set; + container.resolver = resolverFor(namespace); + container.normalize = container.resolver.normalize; + container.describe = container.resolver.describe; + container.makeToString = container.resolver.makeToString; + + container.optionsForType('component', { singleton: false }); + container.optionsForType('view', { singleton: false }); + container.optionsForType('template', { instantiate: false }); + container.optionsForType('helper', { instantiate: false }); + + container.register('application:main', namespace, { instantiate: false }); + + container.register('controller:basic', Controller, { instantiate: false }); + container.register('controller:object', ObjectController, { instantiate: false }); + container.register('controller:array', ArrayController, { instantiate: false }); + container.register('route:basic', Route, { instantiate: false }); + container.register('event_dispatcher:main', EventDispatcher); + + container.register('router:main', Router); + container.injection('router:main', 'namespace', 'application:main'); + + container.register('location:auto', AutoLocation); + container.register('location:hash', HashLocation); + container.register('location:history', HistoryLocation); + container.register('location:none', NoneLocation); + + container.injection('controller', 'target', 'router:main'); + container.injection('controller', 'namespace', 'application:main'); + + container.register('-bucket-cache:main', BucketCache); + container.injection('router', '_bucketCache', '-bucket-cache:main'); + container.injection('route', '_bucketCache', '-bucket-cache:main'); + container.injection('controller', '_bucketCache', '-bucket-cache:main'); + + container.injection('route', 'router', 'router:main'); + container.injection('location', 'rootURL', '-location-setting:root-url'); + + // DEBUGGING + container.register('resolver-for-debugging:main', container.resolver.__resolver__, { instantiate: false }); + container.injection('container-debug-adapter:main', 'resolver', 'resolver-for-debugging:main'); + container.injection('data-adapter:main', 'containerDebugAdapter', 'container-debug-adapter:main'); + // Custom resolver authors may want to register their own ContainerDebugAdapter with this key + + // ES6TODO: resolve this via import once ember-application package is ES6'ed + if (!ContainerDebugAdapter) { ContainerDebugAdapter = requireModule('ember-extension-support/container_debug_adapter')['default']; } + container.register('container-debug-adapter:main', ContainerDebugAdapter); + + return container; + } + }); + + /** + This function defines the default lookup rules for container lookups: + + * templates are looked up on `Ember.TEMPLATES` + * other names are looked up on the application after classifying the name. + For example, `controller:post` looks up `App.PostController` by default. + * if the default lookup fails, look for registered classes on the container + + This allows the application to register default injections in the container + that could be overridden by the normal naming convention. + + @private + @method resolverFor + @param {Ember.Namespace} namespace the namespace to look for classes + @return {*} the resolved value for a given lookup + */ + function resolverFor(namespace) { + if (namespace.get('resolver')) { + Ember.deprecate('Application.resolver is deprecated in favor of Application.Resolver', false); + } + + var ResolverClass = namespace.get('resolver') || namespace.get('Resolver') || DefaultResolver; + var resolver = ResolverClass.create({ + namespace: namespace + }); + + function resolve(fullName) { + return resolver.resolve(fullName); + } + + resolve.describe = function(fullName) { + return resolver.lookupDescription(fullName); + }; + + resolve.makeToString = function(factory, fullName) { + return resolver.makeToString(factory, fullName); + }; + + resolve.normalize = function(fullName) { + if (resolver.normalize) { + return resolver.normalize(fullName); + } else { + Ember.deprecate('The Resolver should now provide a \'normalize\' function', false); + return fullName; + } + }; + + resolve.__resolver__ = resolver; + + return resolve; + } + + __exports__["default"] = Application; + }); +define("ember-application/system/dag", + ["ember-metal/error","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var EmberError = __dependency1__["default"]; + + function visit(vertex, fn, visited, path) { + var name = vertex.name, + vertices = vertex.incoming, + names = vertex.incomingNames, + len = names.length, + i; + if (!visited) { + visited = {}; + } + if (!path) { + path = []; + } + if (visited.hasOwnProperty(name)) { + return; + } + path.push(name); + visited[name] = true; + for (i = 0; i < len; i++) { + visit(vertices[names[i]], fn, visited, path); + } + fn(vertex, path); + path.pop(); + } + + function DAG() { + this.names = []; + this.vertices = {}; + } + + DAG.prototype.add = function(name) { + if (!name) { return; } + if (this.vertices.hasOwnProperty(name)) { + return this.vertices[name]; + } + var vertex = { + name: name, incoming: {}, incomingNames: [], hasOutgoing: false, value: null + }; + this.vertices[name] = vertex; + this.names.push(name); + return vertex; + }; + + DAG.prototype.map = function(name, value) { + this.add(name).value = value; + }; + + DAG.prototype.addEdge = function(fromName, toName) { + if (!fromName || !toName || fromName === toName) { + return; + } + var from = this.add(fromName), to = this.add(toName); + if (to.incoming.hasOwnProperty(fromName)) { + return; + } + function checkCycle(vertex, path) { + if (vertex.name === toName) { + throw new EmberError("cycle detected: " + toName + " <- " + path.join(" <- ")); + } + } + visit(from, checkCycle); + from.hasOutgoing = true; + to.incoming[fromName] = from; + to.incomingNames.push(fromName); + }; + + DAG.prototype.topsort = function(fn) { + var visited = {}, + vertices = this.vertices, + names = this.names, + len = names.length, + i, vertex; + for (i = 0; i < len; i++) { + vertex = vertices[names[i]]; + if (!vertex.hasOutgoing) { + visit(vertex, fn, visited); + } + } + }; + + DAG.prototype.addEdges = function(name, value, before, after) { + var i; + this.map(name, value); + if (before) { + if (typeof before === 'string') { + this.addEdge(name, before); + } else { + for (i = 0; i < before.length; i++) { + this.addEdge(name, before[i]); + } + } + } + if (after) { + if (typeof after === 'string') { + this.addEdge(after, name); + } else { + for (i = 0; i < after.length; i++) { + this.addEdge(after[i], name); + } + } + } + }; + + __exports__["default"] = DAG; + }); +define("ember-application/system/deprecated-container", + ["exports"], + function(__exports__) { + "use strict"; + function DeprecatedContainer(container) { + this._container = container; + } + + DeprecatedContainer.deprecate = function(method) { + return function() { + var container = this._container; + + Ember.deprecate('Using the defaultContainer is no longer supported. [defaultContainer#' + method + '] see: http://git.io/EKPpnA', false); + return container[method].apply(container, arguments); + }; + }; + + DeprecatedContainer.prototype = { + _container: null, + lookup: DeprecatedContainer.deprecate('lookup'), + resolve: DeprecatedContainer.deprecate('resolve'), + register: DeprecatedContainer.deprecate('register') + }; + + __exports__["default"] = DeprecatedContainer; + }); +define("ember-application/system/resolver", + ["ember-metal/core","ember-metal/property_get","ember-metal/logger","ember-runtime/system/string","ember-runtime/system/object","ember-runtime/system/namespace","ember-handlebars","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-application + */ + + var Ember = __dependency1__["default"]; + // Ember.TEMPLATES, Ember.assert + var get = __dependency2__.get; + var Logger = __dependency3__["default"]; + var classify = __dependency4__.classify; + var capitalize = __dependency4__.capitalize; + var decamelize = __dependency4__.decamelize; + var EmberObject = __dependency5__["default"]; + var Namespace = __dependency6__["default"]; + var EmberHandlebars = __dependency7__["default"]; + + var Resolver = EmberObject.extend({ + /** + This will be set to the Application instance when it is + created. + + @property namespace + */ + namespace: null, + normalize: function(fullName) { + throw new Error("Invalid call to `resolver.normalize(fullName)`. Please override the 'normalize' method in subclass of `Ember.Resolver` to prevent falling through to this error."); + }, + resolve: function(fullName) { + throw new Error("Invalid call to `resolver.resolve(parsedName)`. Please override the 'resolve' method in subclass of `Ember.Resolver` to prevent falling through to this error."); + }, + parseName: function(parsedName) { + throw new Error("Invalid call to `resolver.resolveByType(parsedName)`. Please override the 'resolveByType' method in subclass of `Ember.Resolver` to prevent falling through to this error."); + }, + lookupDescription: function(fullName) { + throw new Error("Invalid call to `resolver.lookupDescription(fullName)`. Please override the 'lookupDescription' method in subclass of `Ember.Resolver` to prevent falling through to this error."); + }, + makeToString: function(factory, fullName) { + throw new Error("Invalid call to `resolver.makeToString(factory, fullName)`. Please override the 'makeToString' method in subclass of `Ember.Resolver` to prevent falling through to this error."); + }, + resolveOther: function(parsedName) { + throw new Error("Invalid call to `resolver.resolveOther(parsedName)`. Please override the 'resolveOther' method in subclass of `Ember.Resolver` to prevent falling through to this error."); + }, + _logLookup: function(found, parsedName) { + throw new Error("Invalid call to `resolver._logLookup(found, parsedName)`. Please override the '_logLookup' method in subclass of `Ember.Resolver` to prevent falling through to this error."); + } + }); + __exports__.Resolver = Resolver; + + + /** + The DefaultResolver defines the default lookup rules to resolve + container lookups before consulting the container for registered + items: + + * templates are looked up on `Ember.TEMPLATES` + * other names are looked up on the application after converting + the name. For example, `controller:post` looks up + `App.PostController` by default. + * there are some nuances (see examples below) + + ### How Resolving Works + + The container calls this object's `resolve` method with the + `fullName` argument. + + It first parses the fullName into an object using `parseName`. + + Then it checks for the presence of a type-specific instance + method of the form `resolve[Type]` and calls it if it exists. + For example if it was resolving 'template:post', it would call + the `resolveTemplate` method. + + Its last resort is to call the `resolveOther` method. + + The methods of this object are designed to be easy to override + in a subclass. For example, you could enhance how a template + is resolved like so: + + ```javascript + App = Ember.Application.create({ + Resolver: Ember.DefaultResolver.extend({ + resolveTemplate: function(parsedName) { + var resolvedTemplate = this._super(parsedName); + if (resolvedTemplate) { return resolvedTemplate; } + return Ember.TEMPLATES['not_found']; + } + }) + }); + ``` + + Some examples of how names are resolved: + + ``` + 'template:post' //=> Ember.TEMPLATES['post'] + 'template:posts/byline' //=> Ember.TEMPLATES['posts/byline'] + 'template:posts.byline' //=> Ember.TEMPLATES['posts/byline'] + 'template:blogPost' //=> Ember.TEMPLATES['blogPost'] + // OR + // Ember.TEMPLATES['blog_post'] + 'controller:post' //=> App.PostController + 'controller:posts.index' //=> App.PostsIndexController + 'controller:blog/post' //=> Blog.PostController + 'controller:basic' //=> Ember.Controller + 'route:post' //=> App.PostRoute + 'route:posts.index' //=> App.PostsIndexRoute + 'route:blog/post' //=> Blog.PostRoute + 'route:basic' //=> Ember.Route + 'view:post' //=> App.PostView + 'view:posts.index' //=> App.PostsIndexView + 'view:blog/post' //=> Blog.PostView + 'view:basic' //=> Ember.View + 'foo:post' //=> App.PostFoo + 'model:post' //=> App.Post + ``` + + @class DefaultResolver + @namespace Ember + @extends Ember.Object + */ + + __exports__["default"] = EmberObject.extend({ + /** + This will be set to the Application instance when it is + created. + + @property namespace + */ + namespace: null, + + normalize: function(fullName) { + var split = fullName.split(':', 2), + type = split[0], + name = split[1]; + + Ember.assert("Tried to normalize a container name without a colon (:) in it. You probably tried to lookup a name that did not contain a type, a colon, and a name. A proper lookup name would be `view:post`.", split.length === 2); + + if (type !== 'template') { + var result = name; + + if (result.indexOf('.') > -1) { + result = result.replace(/\.(.)/g, function(m) { return m.charAt(1).toUpperCase(); }); + } + + if (name.indexOf('_') > -1) { + result = result.replace(/_(.)/g, function(m) { return m.charAt(1).toUpperCase(); }); + } + + return type + ':' + result; + } else { + return fullName; + } + }, + + + /** + This method is called via the container's resolver method. + It parses the provided `fullName` and then looks up and + returns the appropriate template or class. + + @method resolve + @param {String} fullName the lookup string + @return {Object} the resolved factory + */ + resolve: function(fullName) { + var parsedName = this.parseName(fullName), + resolveMethodName = parsedName.resolveMethodName, + resolved; + + if (!(parsedName.name && parsedName.type)) { + throw new TypeError("Invalid fullName: `" + fullName + "`, must be of the form `type:name` "); + } + + if (this[resolveMethodName]) { + resolved = this[resolveMethodName](parsedName); + } + + if (!resolved) { + resolved = this.resolveOther(parsedName); + } + + if (parsedName.root && parsedName.root.LOG_RESOLVER) { + this._logLookup(resolved, parsedName); + } + + return resolved; + }, + /** + Convert the string name of the form "type:name" to + a Javascript object with the parsed aspects of the name + broken out. + + @protected + @param {String} fullName the lookup string + @method parseName + */ + parseName: function(fullName) { + var nameParts = fullName.split(":"), + type = nameParts[0], fullNameWithoutType = nameParts[1], + name = fullNameWithoutType, + namespace = get(this, 'namespace'), + root = namespace; + + if (type !== 'template' && name.indexOf('/') !== -1) { + var parts = name.split('/'); + name = parts[parts.length - 1]; + var namespaceName = capitalize(parts.slice(0, -1).join('.')); + root = Namespace.byName(namespaceName); + + Ember.assert('You are looking for a ' + name + ' ' + type + ' in the ' + namespaceName + ' namespace, but the namespace could not be found', root); + } + + return { + fullName: fullName, + type: type, + fullNameWithoutType: fullNameWithoutType, + name: name, + root: root, + resolveMethodName: "resolve" + classify(type) + }; + }, + + /** + Returns a human-readable description for a fullName. Used by the + Application namespace in assertions to describe the + precise name of the class that Ember is looking for, rather than + container keys. + + @protected + @param {String} fullName the lookup string + @method lookupDescription + */ + lookupDescription: function(fullName) { + var parsedName = this.parseName(fullName); + + if (parsedName.type === 'template') { + return "template at " + parsedName.fullNameWithoutType.replace(/\./g, '/'); + } + + var description = parsedName.root + "." + classify(parsedName.name); + if (parsedName.type !== 'model') { description += classify(parsedName.type); } + + return description; + }, + + makeToString: function(factory, fullName) { + return factory.toString(); + }, + /** + Given a parseName object (output from `parseName`), apply + the conventions expected by `Ember.Router` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method useRouterNaming + */ + useRouterNaming: function(parsedName) { + parsedName.name = parsedName.name.replace(/\./g, '_'); + if (parsedName.name === 'basic') { + parsedName.name = ''; + } + }, + /** + Look up the template in Ember.TEMPLATES + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveTemplate + */ + resolveTemplate: function(parsedName) { + var templateName = parsedName.fullNameWithoutType.replace(/\./g, '/'); + + if (Ember.TEMPLATES[templateName]) { + return Ember.TEMPLATES[templateName]; + } + + templateName = decamelize(templateName); + if (Ember.TEMPLATES[templateName]) { + return Ember.TEMPLATES[templateName]; + } + }, + /** + Lookup the view using `resolveOther` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveView + */ + resolveView: function(parsedName) { + this.useRouterNaming(parsedName); + return this.resolveOther(parsedName); + }, + /** + Lookup the controller using `resolveOther` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveController + */ + resolveController: function(parsedName) { + this.useRouterNaming(parsedName); + return this.resolveOther(parsedName); + }, + /** + Lookup the route using `resolveOther` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveRoute + */ + resolveRoute: function(parsedName) { + this.useRouterNaming(parsedName); + return this.resolveOther(parsedName); + }, + + /** + Lookup the model on the Application namespace + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveModel + */ + resolveModel: function(parsedName) { + var className = classify(parsedName.name), + factory = get(parsedName.root, className); + + if (factory) { return factory; } + }, + /** + Look up the specified object (from parsedName) on the appropriate + namespace (usually on the Application) + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveHelper + */ + resolveHelper: function(parsedName) { + return this.resolveOther(parsedName) || EmberHandlebars.helpers[parsedName.fullNameWithoutType]; + }, + /** + Look up the specified object (from parsedName) on the appropriate + namespace (usually on the Application) + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveOther + */ + resolveOther: function(parsedName) { + var className = classify(parsedName.name) + classify(parsedName.type), + factory = get(parsedName.root, className); + if (factory) { return factory; } + }, + + /** + @method _logLookup + @param {Boolean} found + @param {Object} parsedName + @private + */ + _logLookup: function(found, parsedName) { + var symbol, padding; + + if (found) { symbol = '[✓]'; } + else { symbol = '[ ]'; } + + if (parsedName.fullName.length > 60) { + padding = '.'; + } else { + padding = new Array(60 - parsedName.fullName.length).join('.'); + } + + Logger.info(symbol, parsedName.fullName, padding, this.lookupDescription(parsedName.fullName)); + } + }); + }); +define("ember-debug", ["ember-metal/core","ember-metal/error","ember-metal/logger"], function(__dependency1__, __dependency2__, __dependency3__) { "use strict"; @@ -105,8 +3640,9 @@ define("ember-debug", ```javascript // Test for truthiness Ember.assert('Must pass a valid object', obj); + // Fail unconditionally - Ember.assert('This code path should never be run') + Ember.assert('This code path should never be run'); ``` @method assert @@ -143,7 +3679,7 @@ define("ember-debug", `Ember.debug()` when doing a production build. ```javascript - Ember.debug("I'm a debug notice!"); + Ember.debug('I\'m a debug notice!'); ``` @method debug @@ -206,7 +3742,7 @@ define("ember-debug", no warnings will be shown in production. ```javascript - Ember.oldMethod = Ember.deprecateFunc("Please use the new, updated method", Ember.newMethod); + Ember.oldMethod = Ember.deprecateFunc('Please use the new, updated method', Ember.newMethod); ``` @method deprecateFunc @@ -227,10 +3763,10 @@ define("ember-debug", `Ember.runInDebug()` when doing a production build. ```javascript - Ember.runInDebug( function() { + Ember.runInDebug(function() { Ember.Handlebars.EachView.reopen({ didInsertElement: function() { - console.log("I'm happy"); + console.log('I\'m happy'); } }); }); @@ -238,9 +3774,10 @@ define("ember-debug", @method runInDebug @param {Function} func The function to be executed. + @since 1.5.0 */ Ember.runInDebug = function(func) { - func() + func(); }; // Inform the developer about the Ember Inspector if not installed. @@ -265,24 +3802,6776 @@ define("ember-debug", } } }); -})(); +define("ember-extension-support", + ["ember-metal/core","ember-extension-support/data_adapter","ember-extension-support/container_debug_adapter"], + function(__dependency1__, __dependency2__, __dependency3__) { + "use strict"; + /** + Ember Extension Support -(function() { -define("ember-metal/array", + @module ember + @submodule ember-extension-support + @requires ember-application + */ + + var Ember = __dependency1__["default"]; + var DataAdapter = __dependency2__["default"]; + var ContainerDebugAdapter = __dependency3__["default"]; + + Ember.DataAdapter = DataAdapter; + Ember.ContainerDebugAdapter = ContainerDebugAdapter; + }); +define("ember-extension-support/container_debug_adapter", + ["ember-metal/core","ember-runtime/system/native_array","ember-metal/utils","ember-runtime/system/string","ember-runtime/system/namespace","ember-runtime/system/object","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var emberA = __dependency2__.A; + var typeOf = __dependency3__.typeOf; + var dasherize = __dependency4__.dasherize; + var classify = __dependency4__.classify; + var Namespace = __dependency5__["default"]; + var EmberObject = __dependency6__["default"]; + + /** + @module ember + @submodule ember-extension-support + */ + + /** + The `ContainerDebugAdapter` helps the container and resolver interface + with tools that debug Ember such as the + [Ember Extension](https://github.com/tildeio/ember-extension) + for Chrome and Firefox. + + This class can be extended by a custom resolver implementer + to override some of the methods with library-specific code. + + The methods likely to be overridden are: + + * `canCatalogEntriesByType` + * `catalogEntriesByType` + + The adapter will need to be registered + in the application's container as `container-debug-adapter:main` + + Example: + + ```javascript + Application.initializer({ + name: "containerDebugAdapter", + + initialize: function(container, application) { + application.register('container-debug-adapter:main', require('app/container-debug-adapter')); + } + }); + ``` + + @class ContainerDebugAdapter + @namespace Ember + @extends EmberObject + @since 1.5.0 + */ + __exports__["default"] = EmberObject.extend({ + /** + The container of the application being debugged. + This property will be injected + on creation. + + @property container + @default null + */ + container: null, + + /** + The resolver instance of the application + being debugged. This property will be injected + on creation. + + @property resolver + @default null + */ + resolver: null, + + /** + Returns true if it is possible to catalog a list of available + classes in the resolver for a given type. + + @method canCatalogEntriesByType + @param {string} type The type. e.g. "model", "controller", "route" + @return {boolean} whether a list is available for this type. + */ + canCatalogEntriesByType: function(type) { + if (type === 'model' || type === 'template') return false; + return true; + }, + + /** + Returns the available classes a given type. + + @method catalogEntriesByType + @param {string} type The type. e.g. "model", "controller", "route" + @return {Array} An array of strings. + */ + catalogEntriesByType: function(type) { + var namespaces = emberA(Namespace.NAMESPACES), types = emberA(), self = this; + var typeSuffixRegex = new RegExp(classify(type) + "$"); + + namespaces.forEach(function(namespace) { + if (namespace !== Ember) { + for (var key in namespace) { + if (!namespace.hasOwnProperty(key)) { continue; } + if (typeSuffixRegex.test(key)) { + var klass = namespace[key]; + if (typeOf(klass) === 'class') { + types.push(dasherize(key.replace(typeSuffixRegex, ''))); + } + } + } + } + }); + return types; + } + }); + }); +define("ember-extension-support/data_adapter", + ["ember-metal/core","ember-metal/property_get","ember-metal/run_loop","ember-runtime/system/string","ember-runtime/system/namespace","ember-runtime/system/object","ember-runtime/system/native_array","ember-application/system/application","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var get = __dependency2__.get; + var run = __dependency3__["default"]; + var dasherize = __dependency4__.dasherize; + var Namespace = __dependency5__["default"]; + var EmberObject = __dependency6__["default"]; + var emberA = __dependency7__.A; + var Application = __dependency8__["default"]; + + /** + @module ember + @submodule ember-extension-support + */ + + /** + The `DataAdapter` helps a data persistence library + interface with tools that debug Ember such + as the [Ember Extension](https://github.com/tildeio/ember-extension) + for Chrome and Firefox. + + This class will be extended by a persistence library + which will override some of the methods with + library-specific code. + + The methods likely to be overridden are: + + * `getFilters` + * `detect` + * `columnsForType` + * `getRecords` + * `getRecordColumnValues` + * `getRecordKeywords` + * `getRecordFilterValues` + * `getRecordColor` + * `observeRecord` + + The adapter will need to be registered + in the application's container as `dataAdapter:main` + + Example: + + ```javascript + Application.initializer({ + name: "data-adapter", + + initialize: function(container, application) { + application.register('data-adapter:main', DS.DataAdapter); + } + }); + ``` + + @class DataAdapter + @namespace Ember + @extends EmberObject + */ + __exports__["default"] = EmberObject.extend({ + init: function() { + this._super(); + this.releaseMethods = emberA(); + }, + + /** + The container of the application being debugged. + This property will be injected + on creation. + + @property container + @default null + @since 1.3.0 + */ + container: null, + + + /** + The container-debug-adapter which is used + to list all models. + + @property containerDebugAdapter + @default undefined + @since 1.5.0 + **/ + containerDebugAdapter: undefined, + + /** + Number of attributes to send + as columns. (Enough to make the record + identifiable). + + @private + @property attributeLimit + @default 3 + @since 1.3.0 + */ + attributeLimit: 3, + + /** + Stores all methods that clear observers. + These methods will be called on destruction. + + @private + @property releaseMethods + @since 1.3.0 + */ + releaseMethods: emberA(), + + /** + Specifies how records can be filtered. + Records returned will need to have a `filterValues` + property with a key for every name in the returned array. + + @public + @method getFilters + @return {Array} List of objects defining filters. + The object should have a `name` and `desc` property. + */ + getFilters: function() { + return emberA(); + }, + + /** + Fetch the model types and observe them for changes. + + @public + @method watchModelTypes + + @param {Function} typesAdded Callback to call to add types. + Takes an array of objects containing wrapped types (returned from `wrapModelType`). + + @param {Function} typesUpdated Callback to call when a type has changed. + Takes an array of objects containing wrapped types. + + @return {Function} Method to call to remove all observers + */ + watchModelTypes: function(typesAdded, typesUpdated) { + var modelTypes = this.getModelTypes(), + self = this, typesToSend, releaseMethods = emberA(); + + typesToSend = modelTypes.map(function(type) { + var klass = type.klass; + var wrapped = self.wrapModelType(klass, type.name); + releaseMethods.push(self.observeModelType(klass, typesUpdated)); + return wrapped; + }); + + typesAdded(typesToSend); + + var release = function() { + releaseMethods.forEach(function(fn) { fn(); }); + self.releaseMethods.removeObject(release); + }; + this.releaseMethods.pushObject(release); + return release; + }, + + _nameToClass: function(type) { + if (typeof type === 'string') { + type = this.container.lookupFactory('model:' + type); + } + return type; + }, + + /** + Fetch the records of a given type and observe them for changes. + + @public + @method watchRecords + + @param {Function} recordsAdded Callback to call to add records. + Takes an array of objects containing wrapped records. + The object should have the following properties: + columnValues: {Object} key and value of a table cell + object: {Object} the actual record object + + @param {Function} recordsUpdated Callback to call when a record has changed. + Takes an array of objects containing wrapped records. + + @param {Function} recordsRemoved Callback to call when a record has removed. + Takes the following parameters: + index: the array index where the records were removed + count: the number of records removed + + @return {Function} Method to call to remove all observers + */ + watchRecords: function(type, recordsAdded, recordsUpdated, recordsRemoved) { + var self = this, releaseMethods = emberA(), records = this.getRecords(type), release; + + var recordUpdated = function(updatedRecord) { + recordsUpdated([updatedRecord]); + }; + + var recordsToSend = records.map(function(record) { + releaseMethods.push(self.observeRecord(record, recordUpdated)); + return self.wrapRecord(record); + }); + + + var contentDidChange = function(array, idx, removedCount, addedCount) { + for (var i = idx; i < idx + addedCount; i++) { + var record = array.objectAt(i); + var wrapped = self.wrapRecord(record); + releaseMethods.push(self.observeRecord(record, recordUpdated)); + recordsAdded([wrapped]); + } + + if (removedCount) { + recordsRemoved(idx, removedCount); + } + }; + + var observer = { didChange: contentDidChange, willChange: Ember.K }; + records.addArrayObserver(self, observer); + + release = function() { + releaseMethods.forEach(function(fn) { fn(); }); + records.removeArrayObserver(self, observer); + self.releaseMethods.removeObject(release); + }; + + recordsAdded(recordsToSend); + + this.releaseMethods.pushObject(release); + return release; + }, + + /** + Clear all observers before destruction + @private + @method willDestroy + */ + willDestroy: function() { + this._super(); + this.releaseMethods.forEach(function(fn) { + fn(); + }); + }, + + /** + Detect whether a class is a model. + + Test that against the model class + of your persistence library + + @private + @method detect + @param {Class} klass The class to test + @return boolean Whether the class is a model class or not + */ + detect: function(klass) { + return false; + }, + + /** + Get the columns for a given model type. + + @private + @method columnsForType + @param {Class} type The model type + @return {Array} An array of columns of the following format: + name: {String} name of the column + desc: {String} Humanized description (what would show in a table column name) + */ + columnsForType: function(type) { + return emberA(); + }, + + /** + Adds observers to a model type class. + + @private + @method observeModelType + @param {Class} type The model type class + @param {Function} typesUpdated Called when a type is modified. + @return {Function} The function to call to remove observers + */ + + observeModelType: function(type, typesUpdated) { + var self = this, records = this.getRecords(type); + + var onChange = function() { + typesUpdated([self.wrapModelType(type)]); + }; + var observer = { + didChange: function() { + run.scheduleOnce('actions', this, onChange); + }, + willChange: Ember.K + }; + + records.addArrayObserver(this, observer); + + var release = function() { + records.removeArrayObserver(self, observer); + }; + + return release; + }, + + + /** + Wraps a given model type and observes changes to it. + + @private + @method wrapModelType + @param {Class} type A model class + @param {String} Optional name of the class + @return {Object} contains the wrapped type and the function to remove observers + Format: + type: {Object} the wrapped type + The wrapped type has the following format: + name: {String} name of the type + count: {Integer} number of records available + columns: {Columns} array of columns to describe the record + object: {Class} the actual Model type class + release: {Function} The function to remove observers + */ + wrapModelType: function(type, name) { + var release, records = this.getRecords(type), + typeToSend, self = this; + + typeToSend = { + name: name || type.toString(), + count: get(records, 'length'), + columns: this.columnsForType(type), + object: type + }; + + + return typeToSend; + }, + + + /** + Fetches all models defined in the application. + + @private + @method getModelTypes + @return {Array} Array of model types + */ + getModelTypes: function() { + var types, self = this, + containerDebugAdapter = this.get('containerDebugAdapter'); + + if (containerDebugAdapter.canCatalogEntriesByType('model')) { + types = containerDebugAdapter.catalogEntriesByType('model'); + } else { + types = this._getObjectsOnNamespaces(); + } + + // New adapters return strings instead of classes + types = emberA(types).map(function(name) { + return { + klass: self._nameToClass(name), + name: name + }; + }); + types = emberA(types).filter(function(type) { + return self.detect(type.klass); + }); + + return emberA(types); + }, + + /** + Loops over all namespaces and all objects + attached to them + + @private + @method _getObjectsOnNamespaces + @return {Array} Array of model type strings + */ + _getObjectsOnNamespaces: function() { + var namespaces = emberA(Namespace.NAMESPACES), + types = emberA(), + self = this; + + namespaces.forEach(function(namespace) { + for (var key in namespace) { + if (!namespace.hasOwnProperty(key)) { continue; } + // Even though we will filter again in `getModelTypes`, + // we should not call `lookupContainer` on non-models + // (especially when `Ember.MODEL_FACTORY_INJECTIONS` is `true`) + if (!self.detect(namespace[key])) { continue; } + var name = dasherize(key); + if (!(namespace instanceof Application) && namespace.toString()) { + name = namespace + '/' + name; + } + types.push(name); + } + }); + return types; + }, + + /** + Fetches all loaded records for a given type. + + @private + @method getRecords + @return {Array} An array of records. + This array will be observed for changes, + so it should update when new records are added/removed. + */ + getRecords: function(type) { + return emberA(); + }, + + /** + Wraps a record and observers changes to it. + + @private + @method wrapRecord + @param {Object} record The record instance. + @return {Object} The wrapped record. Format: + columnValues: {Array} + searchKeywords: {Array} + */ + wrapRecord: function(record) { + var recordToSend = { object: record }, columnValues = {}, self = this; + + recordToSend.columnValues = this.getRecordColumnValues(record); + recordToSend.searchKeywords = this.getRecordKeywords(record); + recordToSend.filterValues = this.getRecordFilterValues(record); + recordToSend.color = this.getRecordColor(record); + + return recordToSend; + }, + + /** + Gets the values for each column. + + @private + @method getRecordColumnValues + @return {Object} Keys should match column names defined + by the model type. + */ + getRecordColumnValues: function(record) { + return {}; + }, + + /** + Returns keywords to match when searching records. + + @private + @method getRecordKeywords + @return {Array} Relevant keywords for search. + */ + getRecordKeywords: function(record) { + return emberA(); + }, + + /** + Returns the values of filters defined by `getFilters`. + + @private + @method getRecordFilterValues + @param {Object} record The record instance + @return {Object} The filter values + */ + getRecordFilterValues: function(record) { + return {}; + }, + + /** + Each record can have a color that represents its state. + + @private + @method getRecordColor + @param {Object} record The record instance + @return {String} The record's color + Possible options: black, red, blue, green + */ + getRecordColor: function(record) { + return null; + }, + + /** + Observes all relevant properties and re-sends the wrapped record + when a change occurs. + + @private + @method observerRecord + @param {Object} record The record instance + @param {Function} recordUpdated The callback to call when a record is updated. + @return {Function} The function to call to remove all observers. + */ + observeRecord: function(record, recordUpdated) { + return function(){}; + } + }); + }); +define("ember-extension-support/initializers", + [], + function() { + "use strict"; + + }); +define("ember-handlebars-compiler", + ["ember-metal/core","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /* global Handlebars:true */ + + /** + @module ember + @submodule ember-handlebars-compiler + */ + + var Ember = __dependency1__["default"]; + + // ES6Todo: you'll need to import debugger once debugger is es6'd. + if (typeof Ember.assert === 'undefined') { Ember.assert = function(){}; } + if (typeof Ember.FEATURES === 'undefined') { Ember.FEATURES = { isEnabled: function(){} }; } + + var objectCreate = Object.create || function(parent) { + function F() {} + F.prototype = parent; + return new F(); + }; + + // set up for circular references later + var View, Component; + + // ES6Todo: when ember-debug is es6'ed import this. + // var emberAssert = Ember.assert; + var Handlebars = (Ember.imports && Ember.imports.Handlebars) || (this && this.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 + */ + var EmberHandlebars = 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* + */ + EmberHandlebars.helper = function(name, value) { + if (!View) { View = requireModule('ember-views/views/view')['default']; } // ES6TODO: stupid circular dep + if (!Component) { Component = requireModule('ember-views/views/component')['default']; } // ES6TODO: stupid circular dep + + Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", !Component.detect(value) || name.match(/-/)); + + if (View.detect(value)) { + EmberHandlebars.registerHelper(name, EmberHandlebars.makeViewHelper(value)); + } else { + EmberHandlebars.registerBoundHelper.apply(null, arguments); + } + }; + + /** + Returns a helper function that renders the provided ViewClass. + + Used internally by Ember.Handlebars.helper and other methods + involving helper/component registration. + + @private + @method makeViewHelper + @for Ember.Handlebars + @param {Function} ViewClass view class constructor + @since 1.2.0 + */ + EmberHandlebars.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 found in '" + ViewClass.toString() + "'", arguments.length < 2); + return EmberHandlebars.helpers.view.call(this, ViewClass, options); + }; + }; + + /** + @class helpers + @namespace Ember.Handlebars + */ + EmberHandlebars.helpers = objectCreate(Handlebars.helpers); + + /** + Override the the opcode compiler and JavaScript compiler for Handlebars. + + @class Compiler + @namespace Ember.Handlebars + @private + @constructor + */ + EmberHandlebars.Compiler = function() {}; + + // Handlebars.Compiler doesn't exist in runtime-only + if (Handlebars.Compiler) { + EmberHandlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype); + } + + EmberHandlebars.Compiler.prototype.compiler = EmberHandlebars.Compiler; + + /** + @class JavaScriptCompiler + @namespace Ember.Handlebars + @private + @constructor + */ + EmberHandlebars.JavaScriptCompiler = function() {}; + + // Handlebars.JavaScriptCompiler doesn't exist in runtime-only + if (Handlebars.JavaScriptCompiler) { + EmberHandlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype); + EmberHandlebars.JavaScriptCompiler.prototype.compiler = EmberHandlebars.JavaScriptCompiler; + } + + + EmberHandlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars"; + + EmberHandlebars.JavaScriptCompiler.prototype.initializeBuffer = function() { + return "''"; + }; + + /** + 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. + + @private + @method appendToBuffer + @param string {String} + */ + EmberHandlebars.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/634 + + var DOT_LOOKUP_REGEX = /helpers\.(.*?)\)/, + BRACKET_STRING_LOOKUP_REGEX = /helpers\['(.*?)'/, + INVOCATION_SPLITTING_REGEX = /(.*blockHelperMissing\.call\(.*)(stack[0-9]+)(,.*)/; + + EmberHandlebars.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 = EmberHandlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation; + + var originalBlockValue = EmberHandlebars.JavaScriptCompiler.prototype.blockValue; + EmberHandlebars.JavaScriptCompiler.prototype.blockValue = function() { + originalBlockValue.apply(this, arguments); + stringifyBlockHelperMissing(this.source); + }; + + var originalAmbiguousBlockValue = EmberHandlebars.JavaScriptCompiler.prototype.ambiguousBlockValue; + EmberHandlebars.JavaScriptCompiler.prototype.ambiguousBlockValue = function() { + originalAmbiguousBlockValue.apply(this, arguments); + stringifyBlockHelperMissing(this.source); + }; + + /** + 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. + + @private + @method mustache + @for Ember.Handlebars.Compiler + @param mustache + */ + EmberHandlebars.Compiler.prototype.mustache = function(mustache) { + if (!(mustache.params.length || mustache.hash)) { + 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 + @param {Boolean} asObject optional parameter, defaulting to true, of whether or not the + compiled template should be returned as an Object or a String + */ + EmberHandlebars.precompile = function(string, asObject) { + var ast = Handlebars.parse(string); + + var options = { + knownHelpers: { + action: true, + unbound: true, + 'bind-attr': true, + template: true, + view: true, + _triageMustache: true + }, + data: true, + stringParams: true + }; + + asObject = asObject === undefined ? true : asObject; + + var environment = new EmberHandlebars.Compiler().compile(ast, options); + return new EmberHandlebars.JavaScriptCompiler().compile(environment, options, undefined, asObject); + }; + + // 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} + */ + EmberHandlebars.compile = function(string) { + var ast = Handlebars.parse(string); + var options = { data: true, stringParams: true }; + var environment = new EmberHandlebars.Compiler().compile(ast, options); + var templateSpec = new EmberHandlebars.JavaScriptCompiler().compile(environment, options, undefined, true); + + var template = EmberHandlebars.template(templateSpec); + template.isMethod = false; //Make sure we don't wrap templates with ._super + + return template; + }; + } + + __exports__["default"] = EmberHandlebars; + }); +define("ember-handlebars", + ["ember-handlebars-compiler","ember-metal/core","ember-runtime/system/lazy_load","ember-handlebars/loader","ember-handlebars/ext","ember-handlebars/string","ember-handlebars/helpers/shared","ember-handlebars/helpers/binding","ember-handlebars/helpers/collection","ember-handlebars/helpers/view","ember-handlebars/helpers/unbound","ember-handlebars/helpers/debug","ember-handlebars/helpers/each","ember-handlebars/helpers/template","ember-handlebars/helpers/partial","ember-handlebars/helpers/yield","ember-handlebars/helpers/loc","ember-handlebars/controls/checkbox","ember-handlebars/controls/select","ember-handlebars/controls/text_area","ember-handlebars/controls/text_field","ember-handlebars/controls/text_support","ember-handlebars/controls","ember-handlebars/component_lookup","ember-handlebars/views/handlebars_bound_view","ember-handlebars/views/metamorph_view","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__, __exports__) { + "use strict"; + var EmberHandlebars = __dependency1__["default"]; + var Ember = __dependency2__["default"]; + // to add to globals + + var runLoadHooks = __dependency3__.runLoadHooks; + var bootstrap = __dependency4__["default"]; + + var normalizePath = __dependency5__.normalizePath; + var template = __dependency5__.template; + var makeBoundHelper = __dependency5__.makeBoundHelper; + var registerBoundHelper = __dependency5__.registerBoundHelper; + var resolveHash = __dependency5__.resolveHash; + var resolveParams = __dependency5__.resolveParams; + var getEscaped = __dependency5__.getEscaped; + var handlebarsGet = __dependency5__.handlebarsGet; + var evaluateUnboundHelper = __dependency5__.evaluateUnboundHelper; + var helperMissingHelper = __dependency5__.helperMissingHelper; + var blockHelperMissingHelper = __dependency5__.blockHelperMissingHelper; + + + // side effect of extending StringUtils of htmlSafe + + var resolvePaths = __dependency7__["default"]; + var bind = __dependency8__.bind; + var _triageMustacheHelper = __dependency8__._triageMustacheHelper; + var resolveHelper = __dependency8__.resolveHelper; + var bindHelper = __dependency8__.bindHelper; + var boundIfHelper = __dependency8__.boundIfHelper; + var unboundIfHelper = __dependency8__.unboundIfHelper; + var withHelper = __dependency8__.withHelper; + var ifHelper = __dependency8__.ifHelper; + var unlessHelper = __dependency8__.unlessHelper; + var bindAttrHelper = __dependency8__.bindAttrHelper; + var bindAttrHelperDeprecated = __dependency8__.bindAttrHelperDeprecated; + var bindClasses = __dependency8__.bindClasses; + + var collectionHelper = __dependency9__["default"]; + var ViewHelper = __dependency10__.ViewHelper; + var viewHelper = __dependency10__.viewHelper; + var unboundHelper = __dependency11__["default"]; + var logHelper = __dependency12__.logHelper; + var debuggerHelper = __dependency12__.debuggerHelper; + var EachView = __dependency13__.EachView; + var GroupedEach = __dependency13__.GroupedEach; + var eachHelper = __dependency13__.eachHelper; + var templateHelper = __dependency14__["default"]; + var partialHelper = __dependency15__["default"]; + var yieldHelper = __dependency16__["default"]; + var locHelper = __dependency17__["default"]; + + + var Checkbox = __dependency18__["default"]; + var Select = __dependency19__.Select; + var SelectOption = __dependency19__.SelectOption; + var SelectOptgroup = __dependency19__.SelectOptgroup; + var TextArea = __dependency20__["default"]; + var TextField = __dependency21__["default"]; + var TextSupport = __dependency22__["default"]; + var inputHelper = __dependency23__.inputHelper; + var textareaHelper = __dependency23__.textareaHelper; + + + var ComponentLookup = __dependency24__["default"]; + var _HandlebarsBoundView = __dependency25__._HandlebarsBoundView; + var SimpleHandlebarsView = __dependency25__.SimpleHandlebarsView; + var _wrapMap = __dependency26__._wrapMap; + var _SimpleMetamorphView = __dependency26__._SimpleMetamorphView; + var _MetamorphView = __dependency26__._MetamorphView; + var _Metamorph = __dependency26__._Metamorph; + + + /** + Ember Handlebars + + @module ember + @submodule ember-handlebars + @requires ember-views + */ + + // Ember.Handlebars.Globals + EmberHandlebars.bootstrap = bootstrap; + EmberHandlebars.template = template; + EmberHandlebars.makeBoundHelper = makeBoundHelper; + EmberHandlebars.registerBoundHelper = registerBoundHelper; + EmberHandlebars.resolveHash = resolveHash; + EmberHandlebars.resolveParams = resolveParams; + EmberHandlebars.resolveHelper = resolveHelper; + EmberHandlebars.get = handlebarsGet; + EmberHandlebars.getEscaped = getEscaped; + EmberHandlebars.evaluateUnboundHelper = evaluateUnboundHelper; + EmberHandlebars.bind = bind; + EmberHandlebars.bindClasses = bindClasses; + EmberHandlebars.EachView = EachView; + EmberHandlebars.GroupedEach = GroupedEach; + EmberHandlebars.resolvePaths = resolvePaths; + EmberHandlebars.ViewHelper = ViewHelper; + EmberHandlebars.normalizePath = normalizePath; + + + // Ember Globals + Ember.Handlebars = EmberHandlebars; + Ember.ComponentLookup = ComponentLookup; + Ember._SimpleHandlebarsView = SimpleHandlebarsView; + Ember._HandlebarsBoundView = _HandlebarsBoundView; + Ember._SimpleMetamorphView = _SimpleMetamorphView; + Ember._MetamorphView = _MetamorphView; + Ember._Metamorph = _Metamorph; + Ember._metamorphWrapMap = _wrapMap; + Ember.TextSupport = TextSupport; + Ember.Checkbox = Checkbox; + Ember.Select = Select; + Ember.SelectOption = SelectOption; + Ember.SelectOptgroup = SelectOptgroup; + Ember.TextArea = TextArea; + Ember.TextField = TextField; + Ember.TextSupport = TextSupport; + + // register helpers + EmberHandlebars.registerHelper('helperMissing', helperMissingHelper); + EmberHandlebars.registerHelper('blockHelperMissing', blockHelperMissingHelper); + EmberHandlebars.registerHelper('bind', bindHelper); + EmberHandlebars.registerHelper('boundIf', boundIfHelper); + EmberHandlebars.registerHelper('_triageMustache', _triageMustacheHelper); + EmberHandlebars.registerHelper('unboundIf', unboundIfHelper); + EmberHandlebars.registerHelper('with', withHelper); + EmberHandlebars.registerHelper('if', ifHelper); + EmberHandlebars.registerHelper('unless', unlessHelper); + EmberHandlebars.registerHelper('bind-attr', bindAttrHelper); + EmberHandlebars.registerHelper('bindAttr', bindAttrHelperDeprecated); + EmberHandlebars.registerHelper('collection', collectionHelper); + EmberHandlebars.registerHelper("log", logHelper); + EmberHandlebars.registerHelper("debugger", debuggerHelper); + EmberHandlebars.registerHelper("each", eachHelper); + EmberHandlebars.registerHelper("loc", locHelper); + EmberHandlebars.registerHelper("partial", partialHelper); + EmberHandlebars.registerHelper("template", templateHelper); + EmberHandlebars.registerHelper("yield", yieldHelper); + EmberHandlebars.registerHelper("view", viewHelper); + EmberHandlebars.registerHelper("unbound", unboundHelper); + EmberHandlebars.registerHelper("input", inputHelper); + EmberHandlebars.registerHelper("textarea", textareaHelper); + + // run load hooks + runLoadHooks('Ember.Handlebars', EmberHandlebars); + + __exports__["default"] = EmberHandlebars; + }); +define("ember-handlebars/component_lookup", + ["ember-runtime/system/object","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var EmberObject = __dependency1__["default"]; + + var ComponentLookup = EmberObject.extend({ + lookupFactory: function(name, container) { + + container = container || this.container; + + var fullName = 'component:' + name, + templateFullName = 'template:components/' + name, + templateRegistered = container && container.has(templateFullName); + + if (templateRegistered) { + container.injection(fullName, 'layout', templateFullName); + } + + var Component = container.lookupFactory(fullName); + + // Only treat as a component if either the component + // or a template has been registered. + if (templateRegistered || Component) { + if (!Component) { + container.register(fullName, Ember.Component); + Component = container.lookupFactory(fullName); + } + return Component; + } + } + }); + + __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 + capabilities of text inputs in your applications by reopening this class. For example, + if you are building a Bootstrap project where `data-*` attributes are used, you + can add one to the `TextField`'s `attributeBindings` property: + + + ```javascript + Ember.TextField.reopen({ + attributeBindings: ['data-error'] + }); + ``` + + 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); + } + } + + __exports__.inputHelper = inputHelper;/** + `{{textarea}}` inserts a new instance of ` + ``` + + Bound: + + In the following example, the `writtenWords` property on `App.ApplicationController` + will be updated live as the user types 'Lots of text that IS bound' into + the text area of their browser's window. + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + writtenWords: "Lots of text that IS bound" + }); + ``` + + ```handlebars + {{textarea value=writtenWords}} + ``` + + Would result in the following HTML: + + ```html + + ``` + + If you wanted a one way binding between the text area and a div tag + somewhere else on your screen, you could use `Ember.computed.oneWay`: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + writtenWords: "Lots of text that IS bound", + outputWrittenWords: Ember.computed.oneWay("writtenWords") + }); + ``` + + ```handlebars + {{textarea value=writtenWords}} + +
+ {{outputWrittenWords}} +
+ ``` + + Would result in the following HTML: + + ```html + + + <-- the following div will be updated in real time as you type --> + +
+ Lots of text that IS bound +
+ ``` + + Finally, this example really shows the power and ease of Ember when two + properties are bound to eachother via `Ember.computed.alias`. Type into + either text area box and they'll both stay in sync. Note that + `Ember.computed.alias` costs more in terms of performance, so only use it when + your really binding in both directions: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + writtenWords: "Lots of text that IS bound", + twoWayWrittenWords: Ember.computed.alias("writtenWords") + }); + ``` + + ```handlebars + {{textarea value=writtenWords}} + {{textarea value=twoWayWrittenWords}} + ``` + + ```html + + + <-- both updated in real time --> + + + ``` + + ## Extension + + Internally, `{{textarea}}` creates an instance of `Ember.TextArea`, passing + arguments from the helper to `Ember.TextArea`'s `create` method. You can + extend the capabilities of text areas in your application by reopening this + class. For example, if you are building a Bootstrap project where `data-*` + attributes are used, you can globally add support for a `data-*` attribute + on all `{{textarea}}`s' in your app by reopening `Ember.TextArea` or + `Ember.TextSupport` and adding it to the `attributeBindings` concatenated + property: + + ```javascript + Ember.TextArea.reopen({ + attributeBindings: ['data-error'] + }); + ``` + + Keep in mind when writing `Ember.TextArea` subclasses that `Ember.TextArea` + 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) + + @method textarea + @for Ember.Handlebars.helpers + @param {Hash} options + */ + function textareaHelper(options) { + Ember.assert('You can only pass attributes to the `textarea` helper, not arguments', arguments.length < 2); + + var hash = options.hash, + types = options.hashTypes; + + return helpers.view.call(this, TextArea, options); + } + + __exports__.textareaHelper = textareaHelper; + }); +define("ember-handlebars/controls/checkbox", + ["ember-metal/property_get","ember-metal/property_set","ember-views/views/view","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var get = __dependency1__.get; + var set = __dependency2__.set; + var View = __dependency3__["default"]; + + /** + @module ember + @submodule ember-handlebars + */ + + /** + The internal class used to create text inputs when the `{{input}}` + helper is used with `type` of `checkbox`. + + See [handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_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 + */ + __exports__["default"] = View.extend({ + instrumentDisplay: '{{input type="checkbox"}}', + + classNames: ['ember-checkbox'], + + tagName: 'input', + + attributeBindings: [ + 'type', + 'checked', + 'indeterminate', + 'disabled', + 'tabindex', + 'name', + 'autofocus', + 'required', + 'form' + ], + + type: 'checkbox', + checked: false, + disabled: false, + indeterminate: false, + + init: function() { + this._super(); + this.on('change', this, this._updateElementValue); + }, + + didInsertElement: function() { + this._super(); + get(this, 'element').indeterminate = !!get(this, 'indeterminate'); + }, + + _updateElementValue: function() { + set(this, 'checked', this.$().prop('checked')); + } + }); + }); +define("ember-handlebars/controls/select", + ["ember-handlebars-compiler","ember-metal/enumerable_utils","ember-metal/property_get","ember-metal/property_set","ember-views/views/view","ember-views/views/collection_view","ember-metal/utils","ember-metal/is_none","ember-metal/computed","ember-runtime/system/native_array","ember-metal/mixin","ember-metal/properties","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-handlebars + */ + + var EmberHandlebars = __dependency1__["default"]; + + var forEach = __dependency2__.forEach; + var indexOf = __dependency2__.indexOf; + var indexesOf = __dependency2__.indexesOf; + var replace = __dependency2__.replace; + + var get = __dependency3__.get; + var set = __dependency4__.set; + var View = __dependency5__["default"]; + var CollectionView = __dependency6__["default"]; + var isArray = __dependency7__.isArray; + var isNone = __dependency8__["default"]; + var computed = __dependency9__.computed; + var emberA = __dependency10__.A; + var observer = __dependency11__.observer; + var defineProperty = __dependency12__.defineProperty; + + var precompileTemplate = EmberHandlebars.compile; + + var SelectOption = View.extend({ + instrumentDisplay: 'Ember.SelectOption', + + tagName: 'option', + attributeBindings: ['value', 'selected'], + + defaultTemplate: function(context, options) { + options = { data: options.data, hash: {} }; + EmberHandlebars.helpers.bind.call(context, "view.label", options); + }, + + init: function() { + this.labelPathDidChange(); + this.valuePathDidChange(); + + this._super(); + }, + + selected: 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; // jshint ignore:line + } + }).property('content', 'parentView.selection'), + + labelPathDidChange: observer('parentView.optionLabelPath', function() { + var labelPath = get(this, 'parentView.optionLabelPath'); + + if (!labelPath) { return; } + + defineProperty(this, 'label', computed(function() { + return get(this, labelPath); + }).property(labelPath)); + }), + + valuePathDidChange: observer('parentView.optionValuePath', function() { + var valuePath = get(this, 'parentView.optionValuePath'); + + if (!valuePath) { return; } + + defineProperty(this, 'value', computed(function() { + return get(this, valuePath); + }).property(valuePath)); + }) + }); + + var SelectOptgroup = CollectionView.extend({ + instrumentDisplay: 'Ember.SelectOptgroup', + + 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 ` + + + ``` + + The `value` attribute of the selected `"); + return buffer; + } + + function program3(depth0,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) { + + + data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.groupView", {hash:{ + 'content': ("content"), + 'label': ("label") + },hashTypes:{'content': "ID",'label': "ID"},hashContexts:{'content': depth0,'label': depth0},contexts:[depth0],types:["ID"],data:data}))); + } + + function program6(depth0,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) { + + + data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", {hash:{ + 'content': ("") + },hashTypes:{'content': "ID"},hashContexts:{'content': depth0},contexts:[depth0],types:["ID"],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); } + 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', 'required', 'autofocus', + 'form', 'size'], + + /** + The `multiple` attribute of the select element. Indicates whether multiple + options can be selected. + + @property multiple + @type Boolean + @default false + */ + multiple: false, + + /** + The `disabled` attribute of the select element. Indicates whether + the element is disabled from interactions. + + @property disabled + @type Boolean + @default false + */ + disabled: false, + + /** + The `required` attribute of the select element. Indicates whether + a selected option is required for form validation. + + @property required + @type Boolean + @default false + @since 1.5.0 + */ + required: false, + + /** + The list of options. + + 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: Ember.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 = emberA(); + 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: emberA() + }); + } + + 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', emberA([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 + */ + __exports__["default"] = Component.extend(TextSupport, { + instrumentDisplay: '{{textarea}}', + + 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); + } + }); + }); +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 + */ + __exports__["default"] = Component.extend(TextSupport, { + instrumentDisplay: '{{input type="text"}}', + + 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', '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 + @since 1.4.0 + */ + min: null, + + /** + The `max` attribute of input element used with `type="number"` or `type="range"`. + + @property max + @type String + @default null + @since 1.4.0 + */ + max: null + }); + }); +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', + 'title', 'autocapitalize', 'autocorrect'], + 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 + // sendAction 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 fmt = __dependency2__.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 + @since 1.4.0 + */ + 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; + } + + __exports__.getEscaped = getEscaped;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) { + var bindContext, preserveContext, controller, helperName = 'with'; + + 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]; + + if (path) { + helperName += ' ' + path + ' as ' + keywordName; + } + + 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; + } + + localizedOptions.hash.keywordName = keywordName; + localizedOptions.hash.keywordPath = contextPath; + + bindContext = this; + context = path; + options = localizedOptions; + preserveContext = true; + } 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); + + helperName += ' ' + context; + bindContext = options.contexts[0]; + preserveContext = false; + } + + options.helperName = helperName; + options.isWithHelper = true; + + return bind.call(bindContext, context, options, preserveContext, exists); + } + /** + 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); + + options.helperName = options.helperName || ('if ' + context); + + 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, helperName = 'unless'; + + if (context) { + helperName += ' ' + context; + } + + options.fn = inverse; + options.inverse = fn; + + options.helperName = options.helperName || helperName; + + 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 = 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; + + 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, observer); + 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; + + 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, observer); + } 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 fmt = __dependency4__.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 = this; + } else { + itemHash._context = alias('content'); + } + + var viewOptions = ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this); + hash.itemViewClass = itemViewClass.extend(viewOptions); + + options.helperName = options.helperName || 'collection'; + + 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 = 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); + Ember.Logger.info('Use `this` to access the context of the calling template.'); + + 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-views/views/collection_view","ember-metal/binding","ember-runtime/mixins/controller","ember-runtime/controllers/array_controller","ember-runtime/mixins/array","ember-runtime/copy","ember-metal/run_loop","ember-metal/events","ember-handlebars/ext","ember-metal/computed","ember-metal/observer","ember-handlebars/views/metamorph_view","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __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 fmt = __dependency3__.fmt; + var get = __dependency4__.get; + var set = __dependency5__.set; + var CollectionView = __dependency6__["default"]; + var Binding = __dependency7__.Binding; + var ControllerMixin = __dependency8__["default"]; + var ArrayController = __dependency9__["default"]; + var EmberArray = __dependency10__["default"]; + var copy = __dependency11__["default"]; + var run = __dependency12__["default"]; + var on = __dependency13__.on; + var handlebarsGet = __dependency14__.handlebarsGet; + var computed = __dependency15__.computed; + + var addObserver = __dependency16__.addObserver; + var removeObserver = __dependency16__.removeObserver; + var addBeforeObserver = __dependency16__.addBeforeObserver; + var removeBeforeObserver = __dependency16__.removeBeforeObserver; + + var _Metamorph = __dependency17__._Metamorph; + var _MetamorphView = __dependency17__._MetamorphView; + + 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'), + options = this.options, + data = options.data, + template = this.template; + + data.insideEach = true; + for (var i = 0; i < contentLength; i++) { + var context = content.objectAt(i); + options.data.keywords[options.hash.keyword] = context; + template(context, { 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 `model` 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('model.isEmployed') && this.get('model.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, helperName = 'each'; + + 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]; + + helperName += ' ' + keywordName + ' in ' + path; + + if (path === '') { path = "this"; } + + options.hash.keyword = keywordName; + + } else if (arguments.length === 1) { + options = path; + path = 'this'; + } else { + helperName += ' ' + path; + } + + 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; + + options.helperName = options.helperName || helperName; + + 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 loc = __dependency1__.loc; + + /** + @module ember + @submodule ember-handlebars + */ + + // ES6TODO: + // Pretty sure this can be expressed as + // var locHelper EmberStringUtils.loc ? + + /** + Calls [Ember.String.loc](/api/classes/Ember.String.html#method_loc) with the + provided string. + + This is a convenient way to localize text. For example: + + ```html + + ``` + + Take note that `"welcome"` is a string and not an object + reference. + + See [Ember.String.loc](/api/classes/Ember.String.html#method_loc) for how to + set up localized string references. + + @method loc + @for Ember.Handlebars.helpers + @param {String} str The string to format + @see {Ember.String#loc} + */ + __exports__["default"] = function locHelper(str) { + return loc(str); + } + }); +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 + */ + + __exports__["default"] = function partialHelper(name, options) { + + var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; + + options.helperName = options.helperName || 'partial'; + + 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("/"); + var lastPart = nameParts[nameParts.length - 1]; + + nameParts[nameParts.length - 1] = "_" + lastPart; + + var view = options.data.view; + var underscoredName = nameParts.join("/"); + var template = view.templateForName(underscoredName); + var deprecatedTemplate = !template && view.templateForName(name); + + Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate); + + template = template || deprecatedTemplate; + + template(context, { data: options.data }); + } + }); +define("ember-handlebars/helpers/shared", + ["ember-handlebars/ext","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var handlebarsGet = __dependency1__.handlebarsGet; + + __exports__["default"] = 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 ` - ``` - - 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}()),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+)(";return testEl.firstChild.innerHTML===""}();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"}();var findChildById=function(element,id){if(element.getAttribute("id")===id){return element}var len=element.childNodes.length,idx,node,found;for(idx=0;idx0){var len=matches.length,idx;for(idx=0;idxTest');canSet=el.options.length===1}innerHTMLTags[tagName]=canSet;return canSet};function setInnerHTML(element,html){var tagName=element.tagName;if(canSetInnerHTML(tagName)){setInnerHTMLWithoutFix(element,html)}else{var outerHTML=element.outerHTML||(new XMLSerializer).serializeToString(element);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}__exports__.setInnerHTML=setInnerHTML;function isSimpleClick(event){var modifier=event.shiftKey||event.metaKey||event.altKey||event.ctrlKey,secondaryClick=event.which>1;return!modifier&&!secondaryClick}__exports__.isSimpleClick=isSimpleClick});define("ember-views/views/collection_view",["ember-metal/core","ember-metal/platform","ember-metal/binding","ember-metal/merge","ember-metal/property_get","ember-metal/property_set","ember-runtime/system/string","ember-views/views/container_view","ember-views/views/core_view","ember-views/views/view","ember-metal/mixin","ember-runtime/mixins/array","exports"],function(__dependency1__,__dependency2__,__dependency3__,__dependency4__,__dependency5__,__dependency6__,__dependency7__,__dependency8__,__dependency9__,__dependency10__,__dependency11__,__dependency12__,__exports__){"use strict";var Ember=__dependency1__["default"];var create=__dependency2__.create;var isGlobalPath=__dependency3__.isGlobalPath;var merge=__dependency4__["default"];var get=__dependency5__.get;var set=__dependency6__.set;var fmt=__dependency7__.fmt;var ContainerView=__dependency8__["default"];var CoreView=__dependency9__["default"];var View=__dependency10__["default"];var observer=__dependency11__.observer;var beforeObserver=__dependency11__.beforeObserver;var EmberArray=__dependency12__["default"];var CollectionView=ContainerView.extend({content:null,emptyViewClass:View,emptyView:null,itemViewClass:View,init:function(){var ret=this._super();this._contentDidChange();return ret},_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)}),_contentDidChange: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)}),_assertArrayLike:function(content){},destroy:function(){if(!this._super()){return}var content=get(this,"content");if(content){content.removeArrayObserver(this)}if(this._createdEmptyView){this._createdEmptyView.destroy()}return this},arrayWillChange:function(content,start,removedCount){var emptyView=get(this,"emptyView");if(emptyView&&emptyView instanceof View){emptyView.removeFromParent()}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()}},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&&isGlobalPath(itemViewClass)){itemViewClass=get(itemViewClass)||itemViewClass}for(idx=start;idx0){var changedViews=views.slice(start,start+removed);this.currentState.childViewsWillChange(this,views,start,removed);this.initializeViews(changedViews,null,null)}},removeChild:function(child){this.removeObject(child);return this},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){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=lengthBefore;i--){if(childViews[i]){childViews[i].destroy()}}},_applyClassNameBindings:function(classBindings){var classNames=this.classNames,elem,newClass,dasherizedClass;forEach(classBindings,function(binding){var oldClass;var parsedPath=View._parsePropertyPath(binding);var observer=function(){newClass=this._classStringForProperty(binding);elem=this.$();if(oldClass){elem.removeClass(oldClass);classNames.removeObject(oldClass)}if(newClass){elem.addClass(newClass);oldClass=newClass}else{oldClass=null}};dasherizedClass=this._classStringForProperty(binding);if(dasherizedClass){addObject(classNames,dasherizedClass);oldClass=dasherizedClass}this.registerObserver(this,parsedPath.path,observer);this.one("willClearRender",function(){if(oldClass){classNames.removeObject(oldClass);oldClass=null}})},this)},_unspecifiedAttributeBindings:null,_applyAttributeBindings:function(buffer,attributeBindings){var attributeValue,unspecifiedAttributeBindings=this._unspecifiedAttributeBindings=this._unspecifiedAttributeBindings||{};forEach(attributeBindings,function(binding){var split=binding.split(":"),property=split[0],attributeName=split[1]||property;if(property in this){this._setupAttributeBindingObservation(property,attributeName);attributeValue=get(this,property);View.applyAttributeBindings(buffer,attributeName,attributeValue)}else{unspecifiedAttributeBindings[property]=attributeName}},this);this.setUnknownProperty=this._setUnknownProperty},_setupAttributeBindingObservation:function(property,attributeName){var attributeValue,elem;var observer=function(){elem=this.$();attributeValue=get(this,property);View.applyAttributeBindings(elem,attributeName,attributeValue)};this.registerObserver(this,property,observer)},setUnknownProperty:null,_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)},_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:computed("_parentView",function(key,value){if(value!==undefined){return this.currentState.setElement(this,value)}else{return this.currentState.getElement(this)}}),$: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=0;i--){childViews[i].removedFromDOM=true}if(viewName&&nonVirtualParentView){nonVirtualParentView.set(viewName,null)}childLen=childViews.length;for(i=childLen-1;i>=0;i--){childViews[i].destroy()}return this},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);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);attrs.templateData=get(this,"templateData");view=ViewKlass.create(attrs)}else{attrs.container=this.container;if(!get(view,"templateData")){attrs.templateData=get(this,"templateData")}setProperties(view,attrs)}return view},becameVisible:Ember.K,becameHidden:Ember.K,_isVisibleDidChange:observer("isVisible",function(){if(this._isVisible===get(this,"isVisible")){return}run.scheduleOnce("render",this,this._toggleVisibility)}),_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()}},_notifyBecameVisible:function(){this.trigger("becameVisible");this.forEachChildView(function(view){var isVisible=get(view,"isVisible");if(isVisible||isVisible===null){view._notifyBecameVisible()}})},_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){this._transitionTo(state,children)},_transitionTo:function(state,children){var priorState=this.currentState;var 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)})}},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)})}});function notifyMutationListeners(){run.once(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(){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({_parsePropertyPath:function(path){var split=path.split(":"),propertyPath=split[0],classNames="",className,falsyClassName;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}},_classStringForValue:function(path,val,className,falsyClassName){if(isArray(val)){val=get(val,"length")!==0}if(className||falsyClassName){if(className&&!!val){return className}else if(falsyClassName&&!val){return falsyClassName}else{return null}}else if(val===true){var parts=path.split(".");return dasherize(parts[parts.length-1])}else if(val!==false&&val!=null){return val}else{return null}}});var mutation=EmberObject.extend(Evented).create();View.addMutationListener=function(callback){mutation.on("change",callback)};View.removeMutationListener=function(callback){mutation.off("change",callback)};View.notifyMutationListeners=function(){mutation.trigger("change")};View.views={};View.childViewsProperty=childViewsProperty;View.applyAttributeBindings=function(elem,name,value){var type=typeOf(value);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){elem.removeAttr(name);elem.prop(name,"")}else if(value!==elem.prop(name)){elem.prop(name,value)}}else if(!value){elem.removeAttr(name)}};__exports__["default"]=View});define("ember-views/views/view_collection",["ember-metal/enumerable_utils","exports"],function(__dependency1__,__exports__){"use strict";var forEach=__dependency1__.forEach;function ViewCollection(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";testEl.firstChild.innerHTML="";return testEl.firstChild.innerHTML===""}(),movesWhitespace=document&&function(){var testEl=document.createElement("div");testEl.innerHTML="Test: Value";return testEl.childNodes[0].nodeValue==="Test:"&&testEl.childNodes[2].nodeValue===" Value"}();var Metamorph=function(html){var self;if(this instanceof Metamorph){self=this}else{self=new K}self.innerHTML=html;var myGuid="metamorph-"+guid++;self.start=myGuid+"-start";self.end=myGuid+"-end";return self};K.prototype=Metamorph.prototype;var rangeFor,htmlFunc,removeFunc,outerHTMLFunc,appendToFunc,afterFunc,prependFunc,startTagFunc,endTagFunc;outerHTMLFunc=function(){return this.startTag()+this.innerHTML+this.endTag()};startTagFunc=function(){return""};endTagFunc=function(){return""};if(supportsRange){rangeFor=function(morph,outerToo){var range=document.createRange();var before=document.getElementById(morph.start);var after=document.getElementById(morph.end);if(outerToo){range.setStartBefore(before);range.setEndAfter(after)}else{range.setStartAfter(before);range.setEndBefore(after)}return range};htmlFunc=function(html,outerToo){var range=rangeFor(this,outerToo);range.deleteContents();var fragment=range.createContextualFragment(html);range.insertNode(fragment)};removeFunc=function(){var range=rangeFor(this,true);range.deleteContents()};appendToFunc=function(node){var range=document.createRange();range.setStart(node);range.collapse(false);var frag=range.createContextualFragment(this.outerHTML());node.appendChild(frag)};afterFunc=function(html){var range=document.createRange();var after=document.getElementById(this.end);range.setStartAfter(after);range.setEndAfter(after);var fragment=range.createContextualFragment(html);range.insertNode(fragment)};prependFunc=function(html){var range=document.createRange();var start=document.getElementById(this.start);range.setStartAfter(start);range.setEndAfter(start);var fragment=range.createContextualFragment(html);range.insertNode(fragment)}}else{Metamorph._wrapMap={select:[1,""],fieldset:[1,"
    ","
    "],table:[1,"","
    "],tbody:[2,"","
    "],tr:[3,"","
    "],colgroup:[2,"","
    "],map:[1,"",""],_default:[0,"",""]};var findChildById=function(element,id){if(element.getAttribute("id")===id){return element}var len=element.childNodes.length,idx,node,found;for(idx=0;idx0){var len=matches.length,idx;for(idx=0;idx2&&key.slice(keyLength-2)==="[]"){isArray=true;key=key.slice(0,keyLength-2);if(!queryParams[key]){queryParams[key]=[]}}value=pair[1]?decodeURIComponent(pair[1]):""}if(isArray){queryParams[key].push(value)}else{queryParams[key]=decodeURIComponent(value)}}return queryParams},recognize:function(path){var states=[this.rootState],pathLen,i,l,queryStart,queryParams={},isSlashDropped=false;path=decodeURI(path);queryStart=path.indexOf("?");if(queryStart!==-1){var queryString=path.substr(queryStart+1,path.length);path=path.substr(0,queryStart);queryParams=this.parseQueryString(queryString)}if(path.charAt(0)!=="/"){path="/"+path}pathLen=path.length;if(pathLen>1&&path.charAt(pathLen-1)==="/"){path=path.substr(0,pathLen-1);isSlashDropped=true}for(i=0,l=path.length;i=0&&proceed;--i){var route=routes[i];recognizer.add(routes,{as:route.handler});proceed=route.path==="/"||route.path===""||route.handler.slice(-6)===".index"}})},hasRoute:function(route){return this.recognizer.hasRoute(route)},queryParamsTransition:function(changelist,wasTransitioning,oldState,newState){var router=this;fireQueryParamDidChange(this,newState,changelist);if(!wasTransitioning&&this.activeTransition){return this.activeTransition}else{var newTransition=new Transition(this);newTransition.queryParamsOnly=true;oldState.queryParams=finalizeQueryParamChange(this,newState.handlerInfos,newState.queryParams,newTransition);newTransition.promise=newTransition.promise.then(function(result){updateURL(newTransition,oldState,true);if(router.didTransition){router.didTransition(router.currentHandlerInfos)}return result},null,promiseLabel("Transition complete"));return newTransition}},transitionByIntent:function(intent,isIntermediate){var wasTransitioning=!!this.activeTransition;var oldState=wasTransitioning?this.activeTransition.state:this.state;var newTransition;var router=this;try{var newState=intent.applyToState(oldState,this.recognizer,this.getHandler,isIntermediate);var queryParamChangelist=getChangelist(oldState.queryParams,newState.queryParams);if(handlerInfosEqual(newState.handlerInfos,oldState.handlerInfos)){if(queryParamChangelist){newTransition=this.queryParamsTransition(queryParamChangelist,wasTransitioning,oldState,newState);if(newTransition){return newTransition}}return new Transition(this)}if(isIntermediate){setupContexts(this,newState);return}newTransition=new Transition(this,intent,newState);if(this.activeTransition){this.activeTransition.abort()}this.activeTransition=newTransition;newTransition.promise=newTransition.promise.then(function(result){return finalizeTransition(newTransition,result.state)},null,promiseLabel("Settle transition promise when transition is finalized"));if(!wasTransitioning){notifyExistingHandlers(this,newState,newTransition)}fireQueryParamDidChange(this,newState,queryParamChangelist);return newTransition}catch(e){return new Transition(this,intent,null,e)}},reset:function(){if(this.state){forEach(this.state.handlerInfos,function(handlerInfo){var handler=handlerInfo.handler;if(handler.exit){handler.exit()}})}this.state=new TransitionState;this.currentHandlerInfos=null},activeTransition:null,handleURL:function(url){var args=slice.call(arguments);if(url.charAt(0)!=="/"){args[0]="/"+url}return doTransition(this,args).method(null)},updateURL:function(){throw new Error("updateURL is not implemented")},replaceURL:function(url){this.updateURL(url)},transitionTo:function(name){return doTransition(this,arguments)},intermediateTransitionTo:function(name){doTransition(this,arguments,true)},refresh:function(pivotHandler){var state=this.activeTransition?this.activeTransition.state:this.state;var handlerInfos=state.handlerInfos;var params={};for(var i=0,len=handlerInfos.length;i=0;--i){var handlerInfo=handlerInfos[i];merge(params,handlerInfo.params);if(handlerInfo.handler.inaccessibleByURL){urlMethod=null}}if(urlMethod){params.queryParams=transition._visibleQueryParams||state.queryParams;var url=router.recognizer.generate(handlerName,params);if(urlMethod==="replace"){router.replaceURL(url)}else{router.updateURL(url)}}}function finalizeTransition(transition,newState){try{log(transition.router,transition.sequence,"Resolved all models on destination route; finalizing transition.");var router=transition.router,handlerInfos=newState.handlerInfos,seq=transition.sequence;setupContexts(router,newState,transition);if(transition.isAborted){router.state.handlerInfos=router.currentHandlerInfos;return Promise.reject(logAbort(transition))}updateURL(transition,newState,transition.intent.url);transition.isActive=false;router.activeTransition=null;trigger(router,router.currentHandlerInfos,true,["didTransition"]);if(router.didTransition){router.didTransition(router.currentHandlerInfos)}log(router,transition.sequence,"TRANSITION COMPLETE.");return handlerInfos[handlerInfos.length-1].handler}catch(e){if(!(e instanceof TransitionAborted)){var infos=transition.state.handlerInfos;transition.trigger(true,"error",e,transition,infos[infos.length-1].handler);transition.abort()}throw e}}function doTransition(router,args,isIntermediate){var name=args[0]||"/";var lastArg=args[args.length-1];var queryParams={};if(lastArg&&lastArg.hasOwnProperty("queryParams")){queryParams=pop.call(args).queryParams}var intent;if(args.length===0){log(router,"Updating query params");var handlerInfos=router.state.handlerInfos;intent=new NamedTransitionIntent({name:handlerInfos[handlerInfos.length-1].name,contexts:[],queryParams:queryParams})}else if(name.charAt(0)==="/"){log(router,"Attempting URL transition to "+name);intent=new URLTransitionIntent({url:name})}else{log(router,"Attempting transition to "+name);intent=new NamedTransitionIntent({name:args[0],contexts:slice.call(args,1),queryParams:queryParams})}return router.transitionByIntent(intent,isIntermediate)}function handlerInfosEqual(handlerInfos,otherHandlerInfos){if(handlerInfos.length!==otherHandlerInfos.length){return false}for(var i=0,len=handlerInfos.length;i0){router._triggerWillChangeContext(changing,newTransition)}trigger(router,oldHandlers,true,["willTransition",newTransition])}__exports__["default"]=Router});define("router/transition-intent",["./utils","exports"],function(__dependency1__,__exports__){"use strict";var merge=__dependency1__.merge;function TransitionIntent(props){this.initialize(props);this.data=this.data||{}}TransitionIntent.prototype={initialize:null,applyToState:null};__exports__["default"]=TransitionIntent});define("router/transition-intent/named-transition-intent",["../transition-intent","../transition-state","../handler-info/factory","../utils","exports"],function(__dependency1__,__dependency2__,__dependency3__,__dependency4__,__exports__){"use strict";var TransitionIntent=__dependency1__["default"];var TransitionState=__dependency2__["default"];var handlerInfoFactory=__dependency3__["default"];var isParam=__dependency4__.isParam;var extractQueryParams=__dependency4__.extractQueryParams;var merge=__dependency4__.merge;var subclass=__dependency4__.subclass;__exports__["default"]=subclass(TransitionIntent,{name:null,pivotHandler:null,contexts:null,queryParams:null,initialize:function(props){this.name=props.name;this.pivotHandler=props.pivotHandler;this.contexts=props.contexts||[];this.queryParams=props.queryParams},applyToState:function(oldState,recognizer,getHandler,isIntermediate){var partitionedArgs=extractQueryParams([this.name].concat(this.contexts)),pureArgs=partitionedArgs[0],queryParams=partitionedArgs[1],handlers=recognizer.handlersFor(pureArgs[0]);var targetRouteName=handlers[handlers.length-1].handler;return this.applyToHandlers(oldState,handlers,getHandler,targetRouteName,isIntermediate)},applyToHandlers:function(oldState,handlers,getHandler,targetRouteName,isIntermediate,checkingIfActive){var i;var newState=new TransitionState;var objects=this.contexts.slice(0);var invalidateIndex=handlers.length;if(this.pivotHandler){for(i=0;i=0;--i){var result=handlers[i];var name=result.handler;var handler=getHandler(name);var oldHandlerInfo=oldState.handlerInfos[i];var newHandlerInfo=null;if(result.names.length>0){if(i>=invalidateIndex){newHandlerInfo=this.createParamHandlerInfo(name,handler,result.names,objects,oldHandlerInfo)}else{newHandlerInfo=this.getHandlerInfoForDynamicSegment(name,handler,result.names,objects,oldHandlerInfo,targetRouteName,i)}}else{newHandlerInfo=this.createParamHandlerInfo(name,handler,result.names,objects,oldHandlerInfo)}if(checkingIfActive){newHandlerInfo=newHandlerInfo.becomeResolved(null,newHandlerInfo.context);var oldContext=oldHandlerInfo&&oldHandlerInfo.context;if(result.names.length>0&&newHandlerInfo.context===oldContext){newHandlerInfo.params=oldHandlerInfo&&oldHandlerInfo.params}newHandlerInfo.context=oldContext}var handlerToUse=oldHandlerInfo;if(i>=invalidateIndex||newHandlerInfo.shouldSupercede(oldHandlerInfo)){invalidateIndex=Math.min(i,invalidateIndex);handlerToUse=newHandlerInfo}if(isIntermediate&&!checkingIfActive){handlerToUse=handlerToUse.becomeResolved(null,handlerToUse.context)}newState.handlerInfos.unshift(handlerToUse)}if(objects.length>0){throw new Error("More context objects were passed than there are dynamic segments for the route: "+targetRouteName)}if(!isIntermediate){this.invalidateChildren(newState.handlerInfos,invalidateIndex)}merge(newState.queryParams,this.queryParams||{});return newState},invalidateChildren:function(handlerInfos,invalidateIndex){for(var i=invalidateIndex,l=handlerInfos.length;i0){objectToUse=objects[objects.length-1];if(isParam(objectToUse)){return this.createParamHandlerInfo(name,handler,names,objects,oldHandlerInfo)}else{objects.pop()}}else if(oldHandlerInfo&&oldHandlerInfo.name===name){return oldHandlerInfo}else{if(this.preTransitionState){var preTransitionHandlerInfo=this.preTransitionState.handlerInfos[i];objectToUse=preTransitionHandlerInfo&&preTransitionHandlerInfo.context}else{return oldHandlerInfo}}return handlerInfoFactory("object",{name:name,handler:handler,context:objectToUse,names:names})},createParamHandlerInfo:function(name,handler,names,objects,oldHandlerInfo){var params={};var numNames=names.length;while(numNames--){var oldParams=oldHandlerInfo&&name===oldHandlerInfo.name&&oldHandlerInfo.params||{};var peek=objects[objects.length-1];var paramName=names[numNames];if(isParam(peek)){params[paramName]=""+objects.pop()}else{if(oldParams.hasOwnProperty(paramName)){params[paramName]=oldParams[paramName]}else{throw new Error("You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route "+name)}}}return handlerInfoFactory("param",{name:name,handler:handler,params:params})}})});define("router/transition-intent/url-transition-intent",["../transition-intent","../transition-state","../handler-info/factory","../utils","exports"],function(__dependency1__,__dependency2__,__dependency3__,__dependency4__,__exports__){"use strict";var TransitionIntent=__dependency1__["default"];var TransitionState=__dependency2__["default"];var handlerInfoFactory=__dependency3__["default"];var oCreate=__dependency4__.oCreate;var merge=__dependency4__.merge;var subclass=__dependency4__.subclass;__exports__["default"]=subclass(TransitionIntent,{url:null,initialize:function(props){this.url=props.url},applyToState:function(oldState,recognizer,getHandler){var newState=new TransitionState;var results=recognizer.recognize(this.url),queryParams={},i,len;if(!results){throw new UnrecognizedURLError(this.url)}var statesDiffer=false;for(i=0,len=results.length;i=handlerInfos.length?handlerInfos.length-1:payload.resolveIndex;return Promise.reject({error:error,handlerWithError:currentState.handlerInfos[errorHandlerIndex].handler,wasAborted:wasAborted,state:currentState})}function proceed(resolvedHandlerInfo){var wasAlreadyResolved=currentState.handlerInfos[payload.resolveIndex].isResolved;currentState.handlerInfos[payload.resolveIndex++]=resolvedHandlerInfo;if(!wasAlreadyResolved){var handler=resolvedHandlerInfo.handler;if(handler&&handler.redirect){handler.redirect(resolvedHandlerInfo.context,payload)}}return innerShouldContinue().then(resolveOneHandlerInfo,null,promiseLabel("Resolve handler"))}function resolveOneHandlerInfo(){if(payload.resolveIndex===currentState.handlerInfos.length){return{error:null,state:currentState}}var handlerInfo=currentState.handlerInfos[payload.resolveIndex];return handlerInfo.resolve(innerShouldContinue,payload).then(proceed,null,promiseLabel("Proceed"))}}};__exports__["default"]=TransitionState});define("router/transition",["rsvp/promise","./handler-info","./utils","exports"],function(__dependency1__,__dependency2__,__dependency3__,__exports__){"use strict";var Promise=__dependency1__["default"];var ResolvedHandlerInfo=__dependency2__.ResolvedHandlerInfo;var trigger=__dependency3__.trigger;var slice=__dependency3__.slice;var log=__dependency3__.log;var promiseLabel=__dependency3__.promiseLabel;function Transition(router,intent,state,error){var transition=this; +this.state=state||router.state;this.intent=intent;this.router=router;this.data=this.intent&&this.intent.data||{};this.resolvedModels={};this.queryParams={};if(error){this.promise=Promise.reject(error);return}if(state){this.params=state.params;this.queryParams=state.queryParams;this.handlerInfos=state.handlerInfos;var len=state.handlerInfos.length;if(len){this.targetName=state.handlerInfos[state.handlerInfos.length-1].name}for(var i=0;i0&&array[len-1]&&array[len-1].hasOwnProperty("queryParams")){queryParams=array[len-1].queryParams;head=slice.call(array,0,len-1);return[head,queryParams]}else{return[array,null]}}__exports__.extractQueryParams=extractQueryParams;function coerceQueryParamsToString(queryParams){for(var key in queryParams){if(typeof queryParams[key]==="number"){queryParams[key]=""+queryParams[key]}else if(isArray(queryParams[key])){for(var i=0,l=queryParams[key].length;i=0;i--){var handlerInfo=handlerInfos[i],handler=handlerInfo.handler;if(handler.events&&handler.events[name]){if(handler.events[name].apply(handler,args)===true){eventWasHandled=true}else{return}}}if(!eventWasHandled&&!ignoreFailure){throw new Error("Nothing handled the event '"+name+"'.")}}__exports__.trigger=trigger;function getChangelist(oldObject,newObject){var key;var results={all:{},changed:{},removed:{}};merge(results.all,newObject);var didChange=false;coerceQueryParamsToString(oldObject);coerceQueryParamsToString(newObject);for(key in oldObject){if(oldObject.hasOwnProperty(key)){if(!newObject.hasOwnProperty(key)){didChange=true;results.removed[key]=oldObject[key]}}}for(key in newObject){if(newObject.hasOwnProperty(key)){if(isArray(oldObject[key])&&isArray(newObject[key])){if(oldObject[key].length!==newObject[key].length){results.changed[key]=newObject[key];didChange=true}else{for(var i=0,l=oldObject[key].length;i