From c40aacb1b008173cf072f40cd0ade822f42b1114 Mon Sep 17 00:00:00 2001 From: Mathias Meyer Date: Tue, 1 Oct 2013 15:19:57 +0200 Subject: [PATCH 01/71] Update build email image. --- assets/images/getting-started/build-email.jpg | Bin 0 -> 55606 bytes assets/scripts/app/templates/no_owned_repos.hbs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/images/getting-started/build-email.jpg diff --git a/assets/images/getting-started/build-email.jpg b/assets/images/getting-started/build-email.jpg new file mode 100644 index 0000000000000000000000000000000000000000..706a57dc5f9b48acfd207122b29a3ad83303f323 GIT binary patch literal 55606 zcmeFYbzEJ`(l)p@d*klzPJ+9;yK8U_?h;&sYk=SuLV`mGEJ$O8&JS60*C+x^x_7n%q-kp#MRW4fd4j~ zPd`crfN#u?vi@7Pe_eoSY2|JKRZtCDY;Ngp;RwYa007)%;o{~20I-kpXuLgK9&r*B zR%fbdK2Mq-P;IN!5 z?VO<1D4CrgB+WGz-DIVf0JouX7e}vM_-^dp$$vf zxw|-+`TltuZGDl|TCevtjKf^T_i^zqWG!-QP#uY&~qX4VDWG&GD9r+J1{|Q58wL7bC9s>Y z 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 NFtUl6cIf`h{Xf#Uz}x@; literal 0 HcmV?d00001 diff --git a/assets/scripts/app/templates/no_owned_repos.hbs b/assets/scripts/app/templates/no_owned_repos.hbs index d6d567a3..c131c5b7 100644 --- a/assets/scripts/app/templates/no_owned_repos.hbs +++ b/assets/scripts/app/templates/no_owned_repos.hbs @@ -52,7 +52,7 @@

- +

From 3b3d36a98ac259b9cb10c159512d3031ce2ec1e9 Mon Sep 17 00:00:00 2001 From: Mathias Meyer Date: Wed, 2 Oct 2013 15:55:19 +0200 Subject: [PATCH 02/71] Make build email ever so slightly wider. --- assets/scripts/app/templates/no_owned_repos.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts/app/templates/no_owned_repos.hbs b/assets/scripts/app/templates/no_owned_repos.hbs index c131c5b7..666aa3e2 100644 --- a/assets/scripts/app/templates/no_owned_repos.hbs +++ b/assets/scripts/app/templates/no_owned_repos.hbs @@ -52,7 +52,7 @@

- +

From 2a83215fdccd314e9549faae2779ca7b7a79a690 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Thu, 3 Oct 2013 21:23:49 -0400 Subject: [PATCH 03/71] Fixed clearing log --- assets/scripts/app/views/log.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts/app/views/log.coffee b/assets/scripts/app/views/log.coffee index 5bd671a7..47503c34 100644 --- a/assets/scripts/app/views/log.coffee +++ b/assets/scripts/app/views/log.coffee @@ -34,7 +34,7 @@ Travis.reopen parts.removeArrayObserver(@, didChange: 'partsDidChange', willChange: 'noop') versionDidChange: (-> - @rerender() if @get('inDOM') + @rerender() if @get('state') == 'inDOM' ).observes('log.version') logDidChange: (-> From f6867e8a4cddfef041367a301d22d0969c6f886d Mon Sep 17 00:00:00 2001 From: Mathias Meyer Date: Fri, 4 Oct 2013 16:27:51 +0200 Subject: [PATCH 04/71] Move title attributes into

  • elements. The icon pictures are smaller than the li, reducing the surface to show the title as a tooltip. --- .../app/templates/repos/show/actions.hbs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/assets/scripts/app/templates/repos/show/actions.hbs b/assets/scripts/app/templates/repos/show/actions.hbs index 712bda33..181d23e6 100644 --- a/assets/scripts/app/templates/repos/show/actions.hbs +++ b/assets/scripts/app/templates/repos/show/actions.hbs @@ -1,43 +1,42 @@
      {{#if view.displayCancelBuild}} -
    • +
    • + {{bindAttr class="view.canCancelBuild::disabled"}}>
    • {{/if}} {{#if view.displayCancelJob}} -
    • +
    • + {{bindAttr class="view.canCancelJob::disabled"}}>
    • {{/if}} {{#if view.displayRequeueBuild}} -
    • +
    • + {{bindAttr class="view.canRequeueBuild::disabled"}}>
    • {{/if}} {{#if view.displayRequeueJob}} -
    • +
    • + {{bindAttr class="view.canRequeueJob::disabled"}}>
    • {{/if}} {{!TODO: for some reason showDownloadLog, which just delegates to jobIdForLog does not refresh 'if' properly, need further investigation}} {{#if view.jobIdForLog}} -
    • - +
    • +
    • {{/if}} {{#if view.displayCodeClimate}} -
    • +
    • - +
    • {{/if}} From 7218655cf41349214d9b6e98bfeb4209f10dfba5 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Fri, 4 Oct 2013 15:17:46 -0400 Subject: [PATCH 05/71] Added tests for clearing log when a job is started or requeued --- .../spec/integration/event_spec.coffee | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/assets/scripts/spec/integration/event_spec.coffee b/assets/scripts/spec/integration/event_spec.coffee index 9c2ba882..26b8eac3 100644 --- a/assets/scripts/spec/integration/event_spec.coffee +++ b/assets/scripts/spec/integration/event_spec.coffee @@ -35,6 +35,40 @@ test "event containing a repository, adds repository to repos list", -> row: 2 item: { slug: 'travis-ci/travis-support', build: { number: 4, url: '/travis-ci/travis-support/builds/10', duration: '1 min 30 sec', finishedAt: 'less than a minute ago' } } + +test "an event containing a created job, clears the job's log", -> + payload = + job: + id: 12 + repository_id: 1 + number: '1.4' + queue: 'build.linux' + + visit('/travis-ci/travis-core/').then -> + Em.run -> + logRendered() + Travis.receive 'build:created', payload + + wait().then -> + displaysLog [] + +test "an event containing a requeued job, clears the job's log", -> + payload = + job: + id: 12 + repository_id: 1 + number: '1.4' + queue: 'build.linux' + + visit('/travis-ci/travis-core').then -> + Em.run -> + logRendered() + Travis.receive 'build:requeued', payload + + wait().then -> + displaysLog [] + + test "an event with a build adds a build to a builds list", -> visit('/travis-ci/travis-core/builds').then -> payload = @@ -62,6 +96,7 @@ test "an event with a build adds a build to a builds list", -> row: 1 item: { id: 11, slug: 'travis-ci/travis-core', number: '3', sha: '1234567', branch: 'master', message: 'commit message 3', finishedAt: 'less than a minute ago', duration: '55 sec', color: 'red' } + #test "event containing a job, adds job to jobs list", -> # visit('travis-ci/travis-core').then -> # payload = From d0998a8fc511544e99943e6ca128e606def26848 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 7 Oct 2013 16:43:06 +0200 Subject: [PATCH 06/71] Remove unused observer log property in Travis.Job does not change - we create Log instance when it's accessed and don't refresh it on any occasion, that's why the observer on log is not needed. --- assets/scripts/app/views/log.coffee | 5 ----- 1 file changed, 5 deletions(-) diff --git a/assets/scripts/app/views/log.coffee b/assets/scripts/app/views/log.coffee index 47503c34..471bc06f 100644 --- a/assets/scripts/app/views/log.coffee +++ b/assets/scripts/app/views/log.coffee @@ -37,11 +37,6 @@ Travis.reopen @rerender() if @get('state') == 'inDOM' ).observes('log.version') - logDidChange: (-> - console.log 'log view: log did change: rerender' if Log.DEBUG - @rerender() if @get('inDOM') - ).observes('log') - createEngine: -> console.log 'log view: create engine' if Log.DEBUG @scroll = new Log.Scroll From 972fb6fb43b87515433954977948807056d4e240 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 7 Oct 2013 17:15:57 +0200 Subject: [PATCH 07/71] Always subscribe to log updates When the job is restart, we will not get any updates unless we're subscribed to job updates - that's why we need to subscribe even if the job is already finished. The other option to fix this would be to subscribe and unsubscribe also based on the status, but since subscribing to finished jobs does not cost us anything, I prefer the simpler solution. --- assets/scripts/app/views/log.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/assets/scripts/app/views/log.coffee b/assets/scripts/app/views/log.coffee index 471bc06f..2fccda0e 100644 --- a/assets/scripts/app/views/log.coffee +++ b/assets/scripts/app/views/log.coffee @@ -13,7 +13,7 @@ Travis.reopen job = @get('job') if job job.get('log').fetch() - job.subscribe() if !job.get('isFinished') + job.subscribe() willDestroyElement: -> job = @get('job') @@ -37,6 +37,11 @@ Travis.reopen @rerender() if @get('state') == 'inDOM' ).observes('log.version') + logDidChange: (-> + console.log 'log view: log did change: rerender' if Log.DEBUG + @rerender() if @get('state') == 'inDOM' + ).observes('log') + createEngine: -> console.log 'log view: create engine' if Log.DEBUG @scroll = new Log.Scroll From e4190d0610d4312db02bc17b2bc6b8c1de29e5bc Mon Sep 17 00:00:00 2001 From: Justine Arreche Date: Mon, 14 Oct 2013 12:20:59 -0400 Subject: [PATCH 08/71] changed http to https to fix font issue in chrome and firefox for master --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index 824aee17..36e17972 100644 --- a/public/index.html +++ b/public/index.html @@ -1,7 +1,7 @@ - + From 4abee5edca6db83e54c891a21445d9e5dee14492 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Mon, 14 Oct 2013 23:17:02 +0200 Subject: [PATCH 09/71] remove some duplicated content due to a bad merge --- .../scripts/app/templates/no_owned_repos.hbs | 61 ------------------- 1 file changed, 61 deletions(-) diff --git a/assets/scripts/app/templates/no_owned_repos.hbs b/assets/scripts/app/templates/no_owned_repos.hbs index 9b9bd393..ca925208 100644 --- a/assets/scripts/app/templates/no_owned_repos.hbs +++ b/assets/scripts/app/templates/no_owned_repos.hbs @@ -74,65 +74,4 @@ send us an email.

    - -

    - Hey, it looks like you're new around here and have yet to set up your first repository on Travis CI. -

    - -

    - We're here to help you get started, it's easy! -

    - -

    - -

    - -

    - Start by going to your {{#linkTo "profile.index"}}profile{{/linkTo}} and enable one of your projects. We've been - synchronizing all repositories you have administrative access to. Pick one and flip the switch next to it. -

    - -

    - -

    - -

    - Once you've enabled one of your projects, add a .travis.yml to your project, push some code, and we'll start processing your builds. Wait a - whee while and reload the page, and your newly setup and built project will show up on the right. -

    - -

    - We'll also send you an email once the build has finished. -

    - -

    - We use sensible defaults for most languages, but you can customize - both the build process and the build - environment to fit your project's needs. -

    - -

    - You can also configure how you want to be notified of build results. Email is only one channel you can use. We - support Campfire, HipChat, Flowdock, IRC, and webhooks. To avoid - exposing any private credentials, you can shield them from the public using encrypted - configuration settings. -

    - -

    - -

    - -

    - Should you have any questions or issues, have a look at our - documentation, open an issue or - send us an email. -

    ->>>>>>> f6867e8a4cddfef041367a301d22d0969c6f886d From c52719e9db8fd917b34e61c8e90ef4b416296525 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Mon, 14 Oct 2013 23:28:47 +0200 Subject: [PATCH 10/71] add rel='stylesheet' to the google fonts link --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index 36e17972..4f5bb6dc 100644 --- a/public/index.html +++ b/public/index.html @@ -1,7 +1,7 @@ - + From e5363fc64d7e926ced435cf36efcd96581d5e735 Mon Sep 17 00:00:00 2001 From: Justine Arreche Date: Mon, 14 Oct 2013 17:33:48 -0400 Subject: [PATCH 11/71] added stylesheet ref for google fonts and added backup helvetica and sans-serif to app sass and getting started --- assets/styles/app.sass | 2 +- assets/styles/getting_started.sass | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/styles/app.sass b/assets/styles/app.sass index b586643c..2bc86a61 100644 --- a/assets/styles/app.sass +++ b/assets/styles/app.sass @@ -1,7 +1,7 @@ @import "_mixins/all" html, body - font-family: 'Source Sans Pro', sans-serif + font-family: 'Source Sans Pro', Helvetica, sans-serif font-size: $font-size-small line-height: $line-height margin: 0 diff --git a/assets/styles/getting_started.sass b/assets/styles/getting_started.sass index fcc4cf9b..109f5a98 100644 --- a/assets/styles/getting_started.sass +++ b/assets/styles/getting_started.sass @@ -2,7 +2,7 @@ width: 850px margin: 0 auto padding-top: 30px - font-family: 'Source Sans Pro' + font-family: 'Source Sans Pro', Helvetica, sans-serif font-size: 17px line-height: 26px text-align: center From 14d7c0405c211f58cf054ba69566c8879d26f1cf Mon Sep 17 00:00:00 2001 From: Justine Arreche Date: Tue, 15 Oct 2013 13:58:46 -0400 Subject: [PATCH 12/71] adding white color for anchors in flash messages --- assets/styles/app/flash.sass | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/styles/app/flash.sass b/assets/styles/app/flash.sass index 86c880a8..071695ee 100644 --- a/assets/styles/app/flash.sass +++ b/assets/styles/app/flash.sass @@ -10,6 +10,7 @@ padding: 15px 60px 15px 30px a + color: #ffffff text-decoration: underline .success From af65c668d179a0a79565aef898d7e6723188e2d7 Mon Sep 17 00:00:00 2001 From: Justine Arreche Date: Wed, 16 Oct 2013 13:09:48 -0400 Subject: [PATCH 13/71] bringing in darker #left div bg color for master app.css --- assets/styles/layout.sass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/styles/layout.sass b/assets/styles/layout.sass index 041c5b56..b9609a2a 100644 --- a/assets/styles/layout.sass +++ b/assets/styles/layout.sass @@ -43,7 +43,7 @@ html, body max-width: 320px width: -webkit-calc(100% - 1000px) padding: 0 0 110px 0 - background-color: $color-bg-left + background-color: #fbfbfa border-right: 1px solid $color-border-normal @media screen and (max-width: 980px) From 9958de31913d9a4910cda61606c4d9591df96c8d Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 16 Oct 2013 12:25:47 +0200 Subject: [PATCH 14/71] Add record to record arrays in adapter Ember Model does not do it automatically. I had a patch, which was changing that, but after giving it more thought, I think it's not a good idea - this should be up to adapter if the records are going into record arrays. --- assets/scripts/lib/travis/adapter.coffee | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/assets/scripts/lib/travis/adapter.coffee b/assets/scripts/lib/travis/adapter.coffee index ca92965a..5dddab2b 100644 --- a/assets/scripts/lib/travis/adapter.coffee +++ b/assets/scripts/lib/travis/adapter.coffee @@ -15,6 +15,7 @@ Travis.Adapter = Ember.RESTAdapter.extend @sideload(klass, data) records.load(klass, dataToLoad) + @addToRecordArrays(records.get('content')) buildURL: -> @_super.apply(this, arguments).replace(/\.json$/, '') @@ -22,26 +23,38 @@ Travis.Adapter = Ember.RESTAdapter.extend didFind: (record, id, data) -> @sideload(record.constructor, data) @_super(record, id, data) + @addToRecordArrays(record) didFindAll: (klass, records, data) -> @sideload(klass, data) @_super(klass, records, data) + @addToRecordArrays(records.get('content')) didFindQuery: (klass, records, params, data) -> @sideload(klass, data) @_super(klass, records, params, data) + @addToRecordArrays(records.get('content')) didCreateRecord: (record, data) -> @sideload(record.constructor, data) @_super(record, data) + @addToRecordArrays(record) didSaveRecord: (record, data) -> @sideload(record.constructor, data) @_super(record, data) + @addToRecordArrays(record) didDeleteRecord: (record, data) -> @sideload(record.constructor, data) @_super(record, data) + @addToRecordArrays(record) + + addToRecordArrays: (records) -> + records = [records] unless Ember.isArray(records) + for record in records + record.constructor.addToRecordArrays(record) + sideload: (klass, data) -> for name, records of data From b3ce14cc22319d06441666b33e844e531f484739 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 18 Oct 2013 13:19:02 +0200 Subject: [PATCH 15/71] Update ember to 1.1.beta and ember-model to newest version --- assets/scripts/vendor/ember-model.js | 824 ++- assets/scripts/vendor/ember.js | 9719 ++++++++++++++++++-------- 2 files changed, 7342 insertions(+), 3201 deletions(-) diff --git a/assets/scripts/vendor/ember-model.js b/assets/scripts/vendor/ember-model.js index 4665d5d1..e4b05107 100644 --- a/assets/scripts/vendor/ember-model.js +++ b/assets/scripts/vendor/ember-model.js @@ -1,27 +1,41 @@ (function() { +var VERSION = '0.0.10'; + +if (Ember.libraries) { + Ember.libraries.register('Ember Model', VERSION); +} + + +})(); + +(function() { + function mustImplement(message) { var fn = function() { - throw new Error(message); + var className = this.constructor.toString(); + + throw new Error(message.replace('{{className}}', className)); }; fn.isUnimplemented = true; return fn; } Ember.Adapter = Ember.Object.extend({ - find: mustImplement('Ember.Adapter subclasses must implement find'), - findQuery: mustImplement('Ember.Adapter subclasses must implement findQuery'), - findMany: mustImplement('Ember.Adapter subclasses must implement findMany'), - findAll: mustImplement('Ember.Adapter subclasses must implement findAll'), - createRecord: mustImplement('Ember.Adapter subclasses must implement createRecord'), - saveRecord: mustImplement('Ember.Adapter subclasses must implement saveRecord'), - deleteRecord: mustImplement('Ember.Adapter subclasses must implement deleteRecord'), + find: mustImplement('{{className}} must implement find'), + findQuery: mustImplement('{{className}} must implement findQuery'), + findMany: mustImplement('{{className}} must implement findMany'), + findAll: mustImplement('{{className}} must implement findAll'), + createRecord: mustImplement('{{className}} must implement createRecord'), + saveRecord: mustImplement('{{className}} must implement saveRecord'), + deleteRecord: mustImplement('{{className}} must implement deleteRecord'), load: function(record, id, data) { record.load(id, data); } }); + })(); (function() { @@ -143,12 +157,23 @@ Ember.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { }, reload: function() { - var modelClass = this.get('modelClass'); - Ember.assert("Reload can only be called on findAll RecordArrays", - modelClass && modelClass._findAllRecordArray === this); + var modelClass = this.get('modelClass'), + self = this, + promises; set(this, 'isLoaded', false); - modelClass.adapter.findAll(modelClass, this); + if (modelClass._findAllRecordArray === this) { + modelClass.adapter.findAll(modelClass, this); + } else if (this._query) { + modelClass.adapter.findQuery(modelClass, this, this._query); + } else { + promises = this.map(function(record) { + return record.reload(); + }); + Ember.RSVP.all(promises).then(function(data) { + self.notifyLoaded(); + }); + } } }); @@ -171,13 +196,13 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ throw new Error('FilteredRecordArrays must be created with filterProperties'); } - this._registeredClientIds = Ember.A([]); - var modelClass = get(this, 'modelClass'); modelClass.registerRecordArray(this); this.registerObservers(); this.updateFilter(); + + this._super(); }, updateFilter: function() { @@ -186,20 +211,17 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ get(this, 'modelClass').forEachCachedRecord(function(record) { if (self.filterFunction(record)) { results.push(record); - } else { - results.removeObject(record); } }); this.set('content', Ember.A(results)); }, updateFilterForRecord: function(record) { - var results = get(this, 'content'); - if (this.filterFunction(record)) { - if(!results.contains(record)) { - results.pushObject(record); - } - } else { + var results = get(this, 'content'), + filterMatches = this.filterFunction(record); + if (filterMatches && !results.contains(record)) { + results.pushObject(record); + } else if(!filterMatches) { results.removeObject(record); } }, @@ -213,26 +235,44 @@ Ember.FilteredRecordArray = Ember.RecordArray.extend({ registerObserversOnRecord: function(record) { var self = this, - filterProperties = get(this, 'filterProperties'), - clientId = record._reference.clientId; + filterProperties = get(this, 'filterProperties'); - if(!this._registeredClientIds.contains(clientId)) { - for (var i = 0, l = get(filterProperties, 'length'); i < l; i++) { - record.addObserver(filterProperties[i], self, 'updateFilterForRecord'); - } - this._registeredClientIds.pushObject(clientId); + for (var i = 0, l = get(filterProperties, 'length'); i < l; i++) { + record.addObserver(filterProperties[i], self, 'updateFilterForRecord'); } } }); + })(); (function() { -var get = Ember.get; +var get = Ember.get, set = Ember.set; Ember.ManyArray = Ember.RecordArray.extend({ _records: null, + originalContent: null, + + isDirty: function() { + var originalContent = get(this, 'originalContent'), + originalContentLength = get(originalContent, 'length'), + content = get(this, 'content'), + contentLength = get(content, 'length'); + + if (originalContentLength !== contentLength) { return true; } + + var isDirty = false; + + for (var i = 0, l = contentLength; i < l; i++) { + if (!originalContent.contains(content[i])) { + isDirty = true; + break; + } + } + + return isDirty; + }.property('content.[]', 'originalContent'), objectAtContent: function(idx) { var content = get(this, 'content'); @@ -255,6 +295,48 @@ Ember.ManyArray = Ember.RecordArray.extend({ }, this); this._super(index, removed, added); + }, + + _contentWillChange: function() { + var content = get(this, 'content'); + if (content) { + content.removeArrayObserver(this); + this._setupOriginalContent(content); + } + }.observesBefore('content'), + + _contentDidChange: function() { + var content = get(this, 'content'); + if (content) { + content.addArrayObserver(this); + this.arrayDidChange(content, 0, 0, get(content, 'length')); + } + }.observes('content'), + + arrayWillChange: function(item, idx, removedCnt, addedCnt) {}, + + arrayDidChange: function(item, idx, removedCnt, addedCnt) { + var parent = get(this, 'parent'), relationshipKey = get(this, 'relationshipKey'), + isDirty = get(this, 'isDirty'); + + if (isDirty) { + parent._relationshipBecameDirty(relationshipKey); + } else { + parent._relationshipBecameClean(relationshipKey); + } + }, + + _setupOriginalContent: function(content) { + content = content || get(this, 'content'); + if (content) { + set(this, 'originalContent', content.slice()); + } + }, + + init: function() { + this._super(); + this._setupOriginalContent(); + this._contentDidChange(); } }); @@ -268,7 +350,7 @@ Ember.HasManyArray = Ember.ManyArray.extend({ if (reference.record) { record = reference.record; } else { - record = klass.findById(reference.id); + record = klass.find(reference.id); } return record; @@ -357,36 +439,6 @@ function hasCachedValue(object, key) { } } -function extractDirty(object, attrsOrRelations, dirtyAttributes) { - var key, desc, descMeta, type, dataValue, cachedValue, isDirty, dataType; - for (var i = 0, l = attrsOrRelations.length; i < l; i++) { - key = attrsOrRelations[i]; - if (!hasCachedValue(object, key)) { continue; } - cachedValue = object.cacheFor(key); - dataValue = get(object, '_data.' + object.dataKey(key)); - desc = meta(object).descs[key]; - descMeta = desc && desc.meta(); - type = descMeta.type; - dataType = Ember.Model.dataTypes[type]; - - if (type && type.isEqual) { - isDirty = !type.isEqual(dataValue, cachedValue); - } else if (dataType && dataType.isEqual) { - isDirty = !dataType.isEqual(dataValue, cachedValue); - } else if (dataValue && cachedValue instanceof Ember.Model) { // belongsTo case - isDirty = get(cachedValue, 'isDirty'); - } else if (dataValue !== cachedValue) { - isDirty = true; - } else { - isDirty = false; - } - - if (isDirty) { - dirtyAttributes.push(key); - } - } -} - Ember.run.queues.push('data'); Ember.Model = Ember.Object.extend(Ember.Evented, { @@ -407,24 +459,20 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { return value; }, - isDirty: Ember.computed(function() { - var attributes = this.attributes, - relationships = this.relationships, - dirtyAttributes = Ember.A(); // just for removeObject + isDirty: function() { + var dirtyAttributes = get(this, '_dirtyAttributes'); + return dirtyAttributes && dirtyAttributes.length !== 0 || false; + }.property('_dirtyAttributes.length'), - extractDirty(this, attributes, dirtyAttributes); - if (relationships) { - extractDirty(this, relationships, dirtyAttributes); - } + _relationshipBecameDirty: function(name) { + var dirtyAttributes = get(this, '_dirtyAttributes'); + if (!dirtyAttributes.contains(name)) { dirtyAttributes.pushObject(name); } + }, - if (dirtyAttributes.length) { - this._dirtyAttributes = dirtyAttributes; - return true; - } else { - this._dirtyAttributes = []; - return false; - } - }).property().volatile(), + _relationshipBecameClean: function(name) { + var dirtyAttributes = get(this, '_dirtyAttributes'); + dirtyAttributes.removeObject(name); + }, dataKey: function(key) { var camelizeKeys = get(this.constructor, 'camelizeKeys'); @@ -437,11 +485,10 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { init: function() { this._createReference(); + if (!this._dirtyAttributes) { + set(this, '_dirtyAttributes', []); + } this._super(); - - this.one('didLoad', function() { - this.constructor.addToRecordArrays(this); - }); }, _createReference: function() { @@ -449,9 +496,12 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { id = this.getPrimaryKey(); if (!reference) { - reference = this.constructor._referenceForId(id); + reference = this.constructor._getOrCreateReferenceForId(id); reference.record = this; this._reference = reference; + } else if (reference.id !== id) { + reference.id = id; + this.constructor._cacheReference(reference); } if (!reference.id) { @@ -469,6 +519,27 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { var data = {}; data[get(this.constructor, 'primaryKey')] = id; set(this, '_data', Ember.merge(data, hash)); + + // eagerly load embedded data + var relationships = this.constructor._relationships || [], meta = Ember.meta(this), relationshipKey, relationship, relationshipMeta, relationshipData, relationshipType; + for (var i = 0, l = relationships.length; i < l; i++) { + relationshipKey = relationships[i]; + relationship = meta.descs[relationshipKey]; + relationshipMeta = relationship.meta(); + + if (relationshipMeta.options.embedded) { + relationshipType = relationshipMeta.type; + if (typeof relationshipType === "string") { + relationshipType = Ember.get(Ember.lookup, relationshipType); + } + + relationshipData = data[relationshipKey]; + if (relationshipData) { + relationshipType.load(relationshipData); + } + } + } + set(this, 'isLoaded', true); set(this, 'isNew', false); this._createReference(); @@ -478,13 +549,15 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { didDefineProperty: function(proto, key, value) { if (value instanceof Ember.Descriptor) { var meta = value.meta(); + var klass = proto.constructor; if (meta.isAttribute) { - proto.attributes = proto.attributes ? proto.attributes.slice() : []; - proto.attributes.push(key); + if (!klass._attributes) { klass._attributes = []; } + klass._attributes.push(key); } else if (meta.isRelationship) { - proto.relationships = proto.relationships ? proto.relationships.slice() : []; - proto.relationships.push(key); + if (!klass._relationships) { klass._relationships = []; } + klass._relationships.push(key); + meta.relationshipKey = key; } } }, @@ -506,7 +579,9 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { toJSON: function() { var key, meta, json = {}, - properties = this.attributes ? this.getProperties(this.attributes) : {}, + attributes = this.constructor.getAttributes(), + relationships = this.constructor.getRelationships(), + properties = attributes ? this.getProperties(attributes) : {}, rootKey = get(this.constructor, 'rootKey'); for (key in properties) { @@ -520,11 +595,11 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { } } - if (this.relationships) { + if (relationships) { var data, relationshipKey; - for(var i = 0; i < this.relationships.length; i++) { - key = this.relationships[i]; + for(var i = 0; i < relationships.length; i++) { + key = relationships[i]; meta = this.constructor.metaForProperty(key); relationshipKey = meta.options.key || key; @@ -570,15 +645,8 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { }, revert: function() { - if (this.get('isDirty')) { - var data = get(this, '_data') || {}, - reverts = {}; - for (var i = 0; i < this._dirtyAttributes.length; i++) { - var attr = this._dirtyAttributes[i]; - reverts[attr] = data[attr]; - } - setProperties(this, reverts); - } + this.getWithDefault('_dirtyAttributes', []).clear(); + this.notifyPropertyChange('_data'); }, didCreateRecord: function() { @@ -587,10 +655,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { set(this, 'isNew', false); - if (!this.constructor.recordCache) this.constructor.recordCache = {}; - this.constructor.recordCache[id] = this; - - this._copyDirtyAttributesToData(); + set(this, '_dirtyAttributes', []); this.constructor.addToRecordArrays(this); this.trigger('didCreateRecord'); this.didSaveRecord(); @@ -627,7 +692,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { key = dirtyAttributes[i]; data[this.dataKey(key)] = this.cacheFor(key); } - this._dirtyAttributes = []; + set(this, '_dirtyAttributes', []); }, dataDidChange: Ember.observer(function() { @@ -642,11 +707,16 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { _reloadHasManys: function() { if (!this._hasManyArrays) { return; } - - var i; - for(i = 0; i < this._hasManyArrays.length; i++) { - var array = this._hasManyArrays[i]; - set(array, 'content', this._getHasManyContent(get(array, 'key'), get(array, 'modelClass'), get(array, 'embedded'))); + var i, j; + for (i = 0; i < this._hasManyArrays.length; i++) { + var array = this._hasManyArrays[i], + hasManyContent = this._getHasManyContent(get(array, 'key'), get(array, 'modelClass'), get(array, 'embedded')); + for (j = 0; j < array.get('length'); j++) { + if (array.objectAt(j).get('isNew')) { + hasManyContent.addObject(array.objectAt(j)._reference); + } + } + set(array, 'content', hasManyContent); } }, @@ -658,12 +728,12 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { if (embedded) { primaryKey = get(type, 'primaryKey'); mapFunction = function(attrs) { - reference = type._referenceForId(attrs[primaryKey]); + reference = type._getOrCreateReferenceForId(attrs[primaryKey]); reference.data = attrs; return reference; }; } else { - mapFunction = function(id) { return type._referenceForId(id); }; + mapFunction = function(id) { return type._getOrCreateReferenceForId(id); }; } content = Ember.EnumerableUtils.map(content, mapFunction); } @@ -679,26 +749,77 @@ Ember.Model.reopenClass({ _clientIdCounter: 1, - fetch: function() { - return Ember.loadPromise(this.find.apply(this, arguments)); + getAttributes: function() { + this.proto(); // force class "compilation" if it hasn't been done. + var attributes = this._attributes || []; + if (typeof this.superclass.getAttributes === 'function') { + attributes = this.superclass.getAttributes().concat(attributes); + } + return attributes; + }, + + getRelationships: function() { + this.proto(); // force class "compilation" if it hasn't been done. + var relationships = this._relationships || []; + if (typeof this.superclass.getRelationships === 'function') { + relationships = this.superclass.getRelationships().concat(relationships); + } + return relationships; + }, + + fetch: function(id) { + if (!arguments.length) { + return this._findFetchAll(true); + } else if (Ember.isArray(id)) { + return this._findFetchMany(id, true); + } else if (typeof id === 'object') { + return this._findFetchQuery(id, true); + } else { + return this._findFetchById(id, true); + } }, find: function(id) { if (!arguments.length) { - return this.findAll(); + return this._findFetchAll(false); } else if (Ember.isArray(id)) { - return this.findMany(id); + return this._findFetchMany(id, false); } else if (typeof id === 'object') { - return this.findQuery(id); + return this._findFetchQuery(id, false); } else { - return this.findById(id); + return this._findFetchById(id, false); } }, - findMany: function(ids) { - Ember.assert("findMany requires an array", Ember.isArray(ids)); + findQuery: function(params) { + return this._findFetchQuery(params, false); + }, - var records = Ember.RecordArray.create({_ids: ids}); + fetchQuery: function(params) { + return this._findFetchQuery(params, true); + }, + + _findFetchQuery: function(params, isFetch) { + var records = Ember.RecordArray.create({modelClass: this, _query: params}); + + var promise = this.adapter.findQuery(this, records, params); + + return isFetch ? promise : records; + }, + + findMany: function(ids) { + return this._findFetchMany(ids, false); + }, + + fetchMany: function(ids) { + return this._findFetchMany(ids, true); + }, + + _findFetchMany: function(ids, isFetch) { + Ember.assert("findFetchMany requires an array", Ember.isArray(ids)); + + var records = Ember.RecordArray.create({_ids: ids, modelClass: this}), + deferred; if (!this.recordArrays) { this.recordArrays = []; } this.recordArrays.push(records); @@ -711,43 +832,97 @@ Ember.Model.reopenClass({ this._currentBatchRecordArrays = [records]; } + if (isFetch) { + deferred = Ember.Deferred.create(); + Ember.set(deferred, 'resolveWith', records); + + if (!this._currentBatchDeferreds) { this._currentBatchDeferreds = []; } + this._currentBatchDeferreds.push(deferred); + } + Ember.run.scheduleOnce('data', this, this._executeBatch); - return records; + return isFetch ? deferred : records; }, findAll: function() { - if (this._findAllRecordArray) { return this._findAllRecordArray; } + return this._findFetchAll(false); + }, + + fetchAll: function() { + return this._findFetchAll(true); + }, + + _findFetchAll: function(isFetch) { + var self = this; + + if (this._findAllRecordArray) { + if (isFetch) { + return new Ember.RSVP.Promise(function(resolve) { + resolve(self._findAllRecordArray); + }); + } else { + return this._findAllRecordArray; + } + } var records = this._findAllRecordArray = Ember.RecordArray.create({modelClass: this}); - this.adapter.findAll(this, records); + var promise = this.adapter.findAll(this, records); - return records; + // Remove the cached record array if the promise is rejected + if (promise.then) { + promise.then(null, function() { + self._findAllRecordArray = null; + return Ember.RSVP.reject.apply(null, arguments); + }); + } + + return isFetch ? promise : records; + }, + + findById: function(id) { + return this._findFetchById(id, false); + }, + + fetchById: function(id) { + return this._findFetchById(id, true); + }, + + _findFetchById: function(id, isFetch) { + var record = this.cachedRecordForId(id), + isLoaded = get(record, 'isLoaded'), + adapter = get(this, 'adapter'), + deferredOrPromise; + + if (isLoaded) { + if (isFetch) { + return new Ember.RSVP.Promise(function(resolve, reject) { + resolve(record); + }); + } else { + return record; + } + } + + deferredOrPromise = this._fetchById(record, id); + + return isFetch ? deferredOrPromise : record; }, _currentBatchIds: null, _currentBatchRecordArrays: null, - - findById: function(id) { - var record = this.cachedRecordForId(id); - - if (!get(record, 'isLoaded')) { - this._fetchById(record, id); - } - return record; - }, + _currentBatchDeferreds: null, reload: function(id) { var record = this.cachedRecordForId(id); - - this._fetchById(record, id); - - return record; + record.set('isLoaded', false); + return this._fetchById(record, id); }, _fetchById: function(record, id) { - var adapter = get(this, 'adapter'); + var adapter = get(this, 'adapter'), + deferred; if (adapter.findMany && !adapter.findMany.isUnimplemented) { if (this._currentBatchIds) { @@ -757,8 +932,17 @@ Ember.Model.reopenClass({ this._currentBatchRecordArrays = []; } + deferred = Ember.Deferred.create(); + + //Attached the record to the deferred so we can resolove it later. + Ember.set(deferred, 'resolveWith', record); + + if (!this._currentBatchDeferreds) { this._currentBatchDeferreds = []; } + this._currentBatchDeferreds.push(deferred); + Ember.run.scheduleOnce('data', this, this._executeBatch); - // TODO: return a promise here + + return deferred; } else { return adapter.find(record, id); } @@ -767,6 +951,7 @@ Ember.Model.reopenClass({ _executeBatch: function() { var batchIds = this._currentBatchIds, batchRecordArrays = this._currentBatchRecordArrays, + batchDeferreds = this._currentBatchDeferreds, self = this, requestIds = [], promise, @@ -774,41 +959,60 @@ Ember.Model.reopenClass({ this._currentBatchIds = null; this._currentBatchRecordArrays = null; + this._currentBatchDeferreds = null; + + for (i = 0; i < batchIds.length; i++) { + if (!this.cachedRecordForId(batchIds[i]).get('isLoaded')) { + requestIds.push(batchIds[i]); + } + } if (batchIds.length === 1) { promise = get(this, 'adapter').find(this.cachedRecordForId(batchIds[0]), batchIds[0]); } else { var recordArray = Ember.RecordArray.create({_ids: batchIds}); - promise = get(this, 'adapter').findMany(this, recordArray, batchIds); + if (requestIds.length === 0) { + promise = new Ember.RSVP.Promise(function(resolve, reject) { resolve(recordArray); }); + recordArray.notifyLoaded(); + } else { + promise = get(this, 'adapter').findMany(this, recordArray, requestIds); + } } promise.then(function() { for (var i = 0, l = batchRecordArrays.length; i < l; i++) { batchRecordArrays[i].loadForFindMany(self); } + + if(batchDeferreds) { + for (i = 0, l = batchDeferreds.length; i < l; i++) { + var resolveWith = Ember.get(batchDeferreds[i], 'resolveWith'); + batchDeferreds[i].resolve(resolveWith); + } + } + }).then(null, function(errorXHR) { + if (batchDeferreds) { + for (var i = 0, l = batchDeferreds.length; i < l; i++) { + batchDeferreds[i].reject(errorXHR); + } + } }); }, - findQuery: function(params) { - var records = Ember.RecordArray.create(); - - this.adapter.findQuery(this, records, params); - - return records; + getCachedReferenceRecord: function(id){ + var ref = this._getReferenceById(id); + if(ref) return ref.record; + return undefined; }, cachedRecordForId: function(id) { - if (!this.recordCache) { this.recordCache = {}; } - var record; + var record = this.getCachedReferenceRecord(id); - if (this.recordCache[id]) { - record = this.recordCache[id]; - } else { + if (!record) { var primaryKey = get(this, 'primaryKey'), - attrs = {isLoaded: false}; + attrs = {isLoaded: false}; attrs[primaryKey] = id; record = this.create(attrs); - this.recordCache[id] = record; var sideloadedData = this.sideloadedData && this.sideloadedData[id]; if (sideloadedData) { record.load(id, sideloadedData); @@ -818,6 +1022,7 @@ Ember.Model.reopenClass({ return record; }, + addToRecordArrays: function(record) { if (this._findAllRecordArray) { this._findAllRecordArray.pushObject(record); @@ -834,6 +1039,26 @@ Ember.Model.reopenClass({ } }, + unload: function (record) { + this.removeFromRecordArrays(record); + var primaryKey = record.get(get(this, 'primaryKey')); + this.removeFromCache(primaryKey); + }, + + clearCache: function () { + this.sideloadedData = undefined; + this._referenceCache = undefined; + }, + + removeFromCache: function (key) { + if (this.sideloadedData && this.sideloadedData[key]) { + delete this.sideloadedData[key]; + } + if(this._referenceCache && this._referenceCache[key]) { + delete this._referenceCache[key]; + } + }, + removeFromRecordArrays: function(record) { if (this._findAllRecordArray) { this._findAllRecordArray.removeObject(record); @@ -869,25 +1094,39 @@ Ember.Model.reopenClass({ }, forEachCachedRecord: function(callback) { - if (!this.recordCache) { return Ember.A([]); } - var ids = Object.keys(this.recordCache); + if (!this._referenceCache) { this._referenceCache = {}; } + var ids = Object.keys(this._referenceCache); ids.map(function(id) { - return this.recordCache[id]; + return this._getReferenceById(id).record; }, this).forEach(callback); }, load: function(hashes) { + if (Ember.typeOf(hashes) !== 'array') { hashes = [hashes]; } + if (!this.sideloadedData) { this.sideloadedData = {}; } + for (var i = 0, l = hashes.length; i < l; i++) { - var hash = hashes[i]; - this.sideloadedData[hash[get(this, 'primaryKey')]] = hash; + var hash = hashes[i], + primaryKey = hash[get(this, 'primaryKey')], + record = this.getCachedReferenceRecord(primaryKey); + + if (record) { + record.load(primaryKey, hash); + } else { + this.sideloadedData[primaryKey] = hash; + } } }, - _referenceForId: function(id) { - if (!this._idToReference) { this._idToReference = {}; } + _getReferenceById: function(id) { + if (!this._referenceCache) { this._referenceCache = {}; } + return this._referenceCache[id]; + }, + + _getOrCreateReferenceForId: function(id) { + var reference = this._getReferenceById(id); - var reference = this._idToReference[id]; if (!reference) { reference = this._createReference(id); } @@ -896,32 +1135,26 @@ Ember.Model.reopenClass({ }, _createReference: function(id) { - if (!this._idToReference) { this._idToReference = {}; } + if (!this._referenceCache) { this._referenceCache = {}; } - Ember.assert('The id ' + id + ' has alread been used with another record of type ' + this.toString() + '.', !id || !this._idToReference[id]); + Ember.assert('The id ' + id + ' has alread been used with another record of type ' + this.toString() + '.', !id || !this._referenceCache[id]); var reference = { id: id, clientId: this._clientIdCounter++ }; - // if we're creating an item, this process will be done - // later, once the object has been persisted. - if (id) { - this._idToReference[id] = reference; - } + this._cacheReference(reference); return reference; }, - resetData: function() { - this._idToReference = null; - this.sideloadedData = null; - this.recordCache = null; - this.recordArrays = null; - this._currentBatchIds = null; - this._hasManyArrays = null; - this._findAllRecordArray = null; + _cacheReference: function(reference) { + // if we're creating an item, this process will be done + // later, once the object has been persisted. + if (reference.id) { + this._referenceCache[reference.id] = reference; + } } }); @@ -957,7 +1190,8 @@ Ember.Model.reopen({ modelClass: type, content: this._getHasManyContent(key, type, embedded), embedded: embedded, - key: key + key: key, + relationshipKey: meta.relationshipKey }); this._registerHasManyArray(collection); @@ -971,7 +1205,8 @@ Ember.Model.reopen({ (function() { -var get = Ember.get; +var get = Ember.get, + set = Ember.set; function getType() { if (typeof this.type === "string") { @@ -986,14 +1221,32 @@ Ember.belongsTo = function(type, options) { var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo', getType: getType }, relationshipKey = options.key; - return Ember.computed(function(key, value) { + return Ember.computed(function(key, value, oldValue) { type = meta.getType(); - if (arguments.length === 2) { + var dirtyAttributes = get(this, '_dirtyAttributes'), + createdDirtyAttributes = false; + + if (!dirtyAttributes) { + dirtyAttributes = []; + createdDirtyAttributes = true; + } + + if (arguments.length > 1) { if (value) { Ember.assert(Ember.String.fmt('Attempted to set property of type: %@ with a value of type: %@', [value.constructor, type]), value instanceof type); + + if (oldValue !== value) { + dirtyAttributes.pushObject(key); + } else { + dirtyAttributes.removeObject(key); + } + + if (createdDirtyAttributes) { + set(this, '_dirtyAttributes', dirtyAttributes); + } } return value === undefined ? null : value; } else { @@ -1012,11 +1265,12 @@ Ember.Model.reopen({ } if (meta.options.embedded) { - var primaryKey = get(type, 'primaryKey'); - record = type.create({ isLoaded: false }); - record.load(idOrAttrs[primaryKey], idOrAttrs); + var primaryKey = get(type, 'primaryKey'), + id = idOrAttrs[primaryKey]; + record = type.create({ isLoaded: false, id: id }); + record.load(id, idOrAttrs); } else { - record = type.findById(idOrAttrs); + record = type.find(idOrAttrs); } return record; @@ -1032,41 +1286,15 @@ var get = Ember.get, set = Ember.set, meta = Ember.meta; -function wrapObject(value) { - if (Ember.isArray(value)) { - var clonedArray = value.slice(); - - // TODO: write test for recursive cloning - for (var i = 0, l = clonedArray.length; i < l; i++) { - clonedArray[i] = wrapObject(clonedArray[i]); - } - - return Ember.A(clonedArray); - } else if (value && value.constructor === Date) { - return new Date(value.toISOString()); - } else if (value && typeof value === "object") { - var clone = Ember.create(value), property; - - for (property in value) { - if (value.hasOwnProperty(property) && typeof value[property] === "object") { - clone[property] = wrapObject(value[property]); - } - } - return clone; - } else { - return value; - } -} - Ember.Model.dataTypes = {}; Ember.Model.dataTypes[Date] = { deserialize: function(string) { - if(!string) { return null; } + if (!string) { return null; } return new Date(string); }, serialize: function (date) { - if(!date) { return null; } + if (!date) { return null; } return date.toISOString(); }, isEqual: function(obj1, obj2) { @@ -1093,7 +1321,7 @@ function deserialize(value, type) { } else if (type && Ember.Model.dataTypes[type]) { return Ember.Model.dataTypes[type].deserialize(value); } else { - return wrapObject(value); + return value; } } @@ -1103,15 +1331,35 @@ Ember.attr = function(type, options) { var data = get(this, '_data'), dataKey = this.dataKey(key), dataValue = data && get(data, dataKey), - beingCreated = meta(this).proto === this; + beingCreated = meta(this).proto === this, + dirtyAttributes = get(this, '_dirtyAttributes'), + createdDirtyAttributes = false; + + if (!dirtyAttributes) { + dirtyAttributes = []; + createdDirtyAttributes = true; + } if (arguments.length === 2) { - if (beingCreated && !data) { - data = {}; - set(this, '_data', data); - data[dataKey] = value; + if (beingCreated) { + if (!data) { + data = {}; + set(this, '_data', data); + } + dataValue = data[dataKey] = value; } - return wrapObject(value); + + if (dataValue !== value) { + dirtyAttributes.pushObject(key); + } else { + dirtyAttributes.removeObject(key); + } + + if (createdDirtyAttributes) { + set(this, '_dirtyAttributes', dirtyAttributes); + } + + return value; } return this.getAttr(key, deserialize(dataValue, type)); @@ -1132,6 +1380,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url).then(function(data) { self.didFind(record, id, data); + return record; }); }, @@ -1148,6 +1397,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url).then(function(data) { self.didFindAll(klass, records, data); + return records; }); }, @@ -1164,6 +1414,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url, params).then(function(data) { self.didFindQuery(klass, records, params, data); + return records; }); }, @@ -1180,6 +1431,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url, record.toJSON(), "POST").then(function(data) { self.didCreateRecord(record, data); + return record; }); }, @@ -1187,7 +1439,6 @@ Ember.RESTAdapter = Ember.Adapter.extend({ var rootKey = get(record.constructor, 'rootKey'), primaryKey = get(record.constructor, 'primaryKey'), dataToLoad = rootKey ? data[rootKey] : data; - record.load(dataToLoad[primaryKey], dataToLoad); record.didCreateRecord(); }, @@ -1199,6 +1450,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ return this.ajax(url, record.toJSON(), "PUT").then(function(data) { // TODO: Some APIs may or may not return data self.didSaveRecord(record, data); + return record; }); }, @@ -1228,7 +1480,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({ var urlRoot = get(klass, 'url'); if (!urlRoot) { throw new Error('Ember.RESTAdapter requires a `url` property to be specified'); } - if (id) { + if (!Ember.isEmpty(id)) { return urlRoot + "/" + id + ".json"; } else { return urlRoot + ".json"; @@ -1243,8 +1495,10 @@ Ember.RESTAdapter = Ember.Adapter.extend({ }; }, - _ajax: function(url, params, method) { - var settings = this.ajaxSettings(url, method); + _ajax: function(url, params, method, settings) { + if (!settings) { + settings = this.ajaxSettings(url, method); + } return new Ember.RSVP.Promise(function(resolve, reject) { if (params) { @@ -1261,6 +1515,11 @@ Ember.RESTAdapter = Ember.Adapter.extend({ }; settings.error = function(jqXHR, textStatus, errorThrown) { + // https://github.com/ebryn/ember-model/issues/202 + if (jqXHR) { + jqXHR.then = null; + } + Ember.run(null, reject, jqXHR); }; @@ -1304,4 +1563,123 @@ Ember.loadPromise = function(target) { }; +})(); + +(function() { + +// This is a debug adapter for the Ember Extension, don't let the fact this is called an "adapter" confuse you. +// Most copied from: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/debug/debug_adapter.js + +if (!Ember.DataAdapter) { return; } + +var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.String.underscore; + +var DebugAdapter = Ember.DataAdapter.extend({ + getFilters: function() { + return [ + { name: 'isNew', desc: 'New' }, + { name: 'isModified', desc: 'Modified' }, + { name: 'isClean', desc: 'Clean' } + ]; + }, + + detect: function(klass) { + return klass !== Ember.Model && Ember.Model.detect(klass); + }, + + columnsForType: function(type) { + var columns = [], count = 0, self = this; + Ember.A(get(type.proto(), 'attributes')).forEach(function(name, meta) { + if (count++ > self.attributeLimit) { return false; } + var desc = capitalize(underscore(name).replace('_', ' ')); + columns.push({ name: name, desc: desc }); + }); + return columns; + }, + + getRecords: function(type) { + var records = []; + type.forEachCachedRecord(function(record) { records.push(record); }); + return records; + }, + + getRecordColumnValues: function(record) { + var self = this, count = 0, + columnValues = { id: get(record, 'id') }; + + record.get('attributes').forEach(function(key) { + if (count++ > self.attributeLimit) { + return false; + } + var value = get(record, key); + columnValues[key] = value; + }); + return columnValues; + }, + + getRecordKeywords: function(record) { + var keywords = [], keys = Ember.A(['id']); + record.get('attributes').forEach(function(key) { + keys.push(key); + }); + keys.forEach(function(key) { + keywords.push(get(record, key)); + }); + return keywords; + }, + + getRecordFilterValues: function(record) { + return { + isNew: record.get('isNew'), + isModified: record.get('isDirty') && !record.get('isNew'), + isClean: !record.get('isDirty') + }; + }, + + getRecordColor: function(record) { + var color = 'black'; + if (record.get('isNew')) { + color = 'green'; + } else if (record.get('isDirty')) { + color = 'blue'; + } + return color; + }, + + observeRecord: function(record, recordUpdated) { + var releaseMethods = Ember.A(), self = this, + keysToObserve = Ember.A(['id', 'isNew', 'isDirty']); + + record.get('attributes').forEach(function(key) { + keysToObserve.push(key); + }); + + keysToObserve.forEach(function(key) { + var handler = function() { + recordUpdated(self.wrapRecord(record)); + }; + Ember.addObserver(record, key, handler); + releaseMethods.push(function() { + Ember.removeObserver(record, key, handler); + }); + }); + + var release = function() { + releaseMethods.forEach(function(fn) { fn(); } ); + }; + + return release; + } +}); + +Ember.onLoad('Ember.Application', function(Application) { + Application.initializer({ + name: "dataAdapter", + + initialize: function(container, application) { + application.register('dataAdapter:main', DebugAdapter); + } + }); +}); + })(); diff --git a/assets/scripts/vendor/ember.js b/assets/scripts/vendor/ember.js index 33248fc3..9a26a814 100644 --- a/assets/scripts/vendor/ember.js +++ b/assets/scripts/vendor/ember.js @@ -1,5 +1,5 @@ -// Version: v1.0.0-rc.7-59-g4048275 -// Last commit: 4048275 (2013-08-25 00:57:38 -0400) +// Version: v1.1.0-beta.4 +// Last commit: 89513c5 (2013-10-11 15:24:11 -0700) (function() { @@ -55,7 +55,7 @@ Ember.assert = function(desc, test) { if (Ember.testing && !test) { // when testing, ensure test failures when assertions fail - throw new Error("Assertion Failed: " + desc); + throw new Ember.Error("Assertion Failed: " + desc); } }; @@ -107,7 +107,7 @@ Ember.deprecate = function(message, test) { if (arguments.length === 1) { test = false; } if (test) { return; } - if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new Error(message); } + if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new Ember.Error(message); } var error; @@ -138,15 +138,21 @@ Ember.deprecate = function(message, test) { /** + Alias an old, deprecated method with its new counterpart. + Display a deprecation warning with the provided message and a stack trace - (Chrome and Firefox only) when the wrapped method is called. + (Chrome and Firefox only) when the assigned method is called. Ember build tools will not remove calls to `Ember.deprecateFunc()`, though no warnings will be shown in production. + ```javascript + Ember.oldMethod = Ember.deprecateFunc("Please use the new, updated method", Ember.newMethod); + ``` + @method deprecateFunc @param {String} message A description of the deprecation. - @param {Function} func The function to be deprecated. + @param {Function} func The new function called to replace its deprecated counterpart. @return {Function} a new function that wrapped the original function with a deprecation warning */ Ember.deprecateFunc = function(message, func) { @@ -156,10 +162,22 @@ Ember.deprecateFunc = function(message, func) { }; }; + +// Inform the developer about the Ember Inspector if not installed. +if (!Ember.testing) { + if (typeof window !== 'undefined' && window.chrome && window.addEventListener) { + window.addEventListener("load", function() { + if (document.body && document.body.dataset && !document.body.dataset.emberExtension) { + Ember.debug('For more advanced debugging, install the Ember Inspector from https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi'); + } + }, false); + } +} + })(); -// Version: v1.0.0-rc.7-59-g4048275 -// Last commit: 4048275 (2013-08-25 00:57:38 -0400) +// Version: v1.1.0-beta.4 +// Last commit: 89513c5 (2013-10-11 15:24:11 -0700) (function() { @@ -225,7 +243,7 @@ var define, requireModule; @class Ember @static - @version 1.0.0-rc.7 + @version 1.1.0-beta.4 */ if ('undefined' === typeof Ember) { @@ -252,10 +270,10 @@ Ember.toString = function() { return "Ember"; }; /** @property VERSION @type String - @default '1.0.0-rc.7' + @default '1.1.0-beta.4' @final */ -Ember.VERSION = '1.0.0-rc.7'; +Ember.VERSION = '1.1.0-beta.4'; /** Standard environmental variables. You can define these in a global `ENV` @@ -280,6 +298,27 @@ Ember.ENV = Ember.ENV || ENV; Ember.config = Ember.config || {}; +/** + Hash of enabled Canary features. Add to before creating your application. + + @property FEATURES + @type Hash +*/ + +Ember.FEATURES = {}; + +/** + Test that a feature is enabled. Parsed by Ember's build tools to leave + experimental features out of beta/stable builds. + + @method isEnabled + @param {string} feature +*/ + +Ember.FEATURES.isEnabled = function(feature) { + return Ember.FEATURES[feature]; +}; + // .......................................................... // BOOTSTRAP // @@ -332,7 +371,7 @@ Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPE Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true; /** - Empty function. Useful for some operations. + Empty function. Useful for some operations. Always returns `this`. @method K @private @@ -411,11 +450,87 @@ function assertPolyfill(test, message) { @namespace Ember */ Ember.Logger = { + /** + Logs the arguments to the console. + You can pass as many arguments as you want and they will be joined together with a space. + + ```javascript + var foo = 1; + Ember.Logger.log('log value of foo:', foo); // "log value of foo: 1" will be printed to the console + ``` + + @method log + @for Ember.Logger + @param {*} arguments + */ log: consoleMethod('log') || Ember.K, + /** + Prints the arguments to the console with a warning icon. + You can pass as many arguments as you want and they will be joined together with a space. + + ```javascript + Ember.Logger.warn('Something happened!'); // "Something happened!" will be printed to the console with a warning icon. + ``` + + @method warn + @for Ember.Logger + @param {*} arguments + */ warn: consoleMethod('warn') || Ember.K, + /** + Prints the arguments to the console with an error icon, red text and a stack race. + You can pass as many arguments as you want and they will be joined together with a space. + + ```javascript + Ember.Logger.error('Danger! Danger!'); // "Danger! Danger!" will be printed to the console in red text. + ``` + + @method error + @for Ember.Logger + @param {*} arguments + */ error: consoleMethod('error') || Ember.K, + /** + Logs the arguments to the console. + You can pass as many arguments as you want and they will be joined together with a space. + + ```javascript + var foo = 1; + Ember.Logger.info('log value of foo:', foo); // "log value of foo: 1" will be printed to the console + ``` + + @method info + @for Ember.Logger + @param {*} arguments + */ info: consoleMethod('info') || Ember.K, + /** + Logs the arguments to the console in blue text. + You can pass as many arguments as you want and they will be joined together with a space. + + ```javascript + var foo = 1; + Ember.Logger.debug('log value of foo:', foo); // "log value of foo: 1" will be printed to the console + ``` + + @method debug + @for Ember.Logger + @param {*} arguments + */ debug: consoleMethod('debug') || consoleMethod('info') || Ember.K, + /** + + If the value passed into Ember.Logger.assert is not truthy it will throw an error with a stack trace. + + ```javascript + Ember.Logger.assert(true); // undefined + Ember.Logger.assert(true === false); // Throws an Assertion failed error. + ``` + + @method assert + @for Ember.Logger + @param {Boolean} bool Value to test + */ assert: consoleMethod('assert') || assertPolyfill }; @@ -429,6 +544,15 @@ Ember.Logger = { internals encounter an error. This is useful for specialized error handling and reporting code. + ```javascript + Ember.onerror = function(error) { + Em.$.ajax('/report-error', 'POST', { + stack: error.stack, + otherInformation: 'whatever app state you want to provide' + }); + }; + ``` + @event onerror @for Ember @param {Exception} error the error object @@ -459,6 +583,21 @@ Ember.handleErrors = function(func, context) { } }; +/** + Merge the contents of two objects together into the first object. + + ```javascript + Ember.merge({first: 'Tom'}, {last: 'Dale'}); // {first: 'Tom', last: 'Dale'} + var a = {first: 'Yehuda'}, b = {last: 'Katz'}; + Ember.merge(a, b); // a == {first: 'Yehuda', last: 'Katz'}, b == {last: 'Katz'} + ``` + + @method merge + @for Ember + @param {Object} original The object to merge into + @param {Object} updates The object to copy properties from + @return {Object} +*/ Ember.merge = function(original, updates) { for (var prop in updates) { if (!updates.hasOwnProperty(prop)) { continue; } @@ -761,6 +900,12 @@ var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.index return -1; }; +/** + Array polyfills to support ES5 features in older browsers. + + @namespace Ember + @property ArrayPolyfills +*/ Ember.ArrayPolyfills = { map: arrayMap, forEach: arrayForEach, @@ -1174,10 +1319,18 @@ function canInvoke(obj, methodName) { /** Checks to see if the `methodName` exists on the `obj`. + ```javascript + var foo = {bar: Ember.K, baz: null}; + Ember.canInvoke(foo, 'bar'); // true + Ember.canInvoke(foo, 'baz'); // false + Ember.canInvoke(foo, 'bat'); // false + ``` + @method canInvoke @for Ember @param {Object} obj The object to check for the method @param {String} methodName The method name to check for + @return {Boolean} */ Ember.canInvoke = canInvoke; @@ -1185,6 +1338,13 @@ Ember.canInvoke = canInvoke; Checks to see if the `methodName` exists on the `obj`, and if it does, invokes it with the arguments passed. + ```javascript + var d = new Date('03/15/2013'); + Ember.tryInvoke(d, 'getTime'); // 1363320000000 + Ember.tryInvoke(d, 'setFullYear', [2014]); // 1394856000000 + Ember.tryInvoke(d, 'noSuchMethod', [2014]); // undefined + ``` + @method tryInvoke @for Ember @param {Object} obj The object to check for the method @@ -1216,6 +1376,17 @@ var needsFinallyFix = (function() { Provides try { } finally { } functionality, while working around Safari's double finally bug. + ```javascript + var tryable = function() { + someResource.lock(); + runCallback(); // May throw error. + }; + var finalizer = function() { + someResource.unlock(); + }; + Ember.tryFinally(tryable, finalizer); + ``` + @method tryFinally @for Ember @param {Function} tryable The function to run the try callback @@ -1266,6 +1437,30 @@ if (needsFinallyFix) { Provides try { } catch finally { } functionality, while working around Safari's double finally bug. + ```javascript + var tryable = function() { + for (i=0, l=listeners.length; i size ? size : ends; + if (count <= 0) { count = 0; } + + chunk = args.splice(0, size); + chunk = [start, count].concat(chunk); + + start += size; + ends -= count; + + ret = ret.concat(splice.apply(array, chunk)); + } + return ret; + }, + replace: function(array, idx, amt, objects) { if (array.replace) { return array.replace(idx, amt, objects); } else { - var args = concat.apply([idx, amt], objects); - return array.splice.apply(array, args); + return utils._replace(array, idx, amt, objects); } }, @@ -2016,7 +2262,7 @@ function suspendListener(obj, eventName, target, method, callback) { Suspends multiple listeners during a callback. - + @method suspendListeners @for Ember @param obj @@ -2032,6 +2278,7 @@ function suspendListeners(obj, eventNames, target, method, callback) { } var suspendedActions = [], + actionsList = [], eventName, actions, i, l; for (i=0, l=eventNames.length; i 0 || keyName === 'length', proto = m.proto, @@ -2308,7 +2556,8 @@ var propertyWillChange = Ember.propertyWillChange = function(obj, keyName) { dependentKeysWillChange(obj, keyName, m); chainsWillChange(obj, keyName, m); notifyBeforeObservers(obj, keyName); -}; +} +Ember.propertyWillChange = propertyWillChange; /** This function is called just after an object property has changed. @@ -2316,7 +2565,7 @@ var propertyWillChange = Ember.propertyWillChange = function(obj, keyName) { Normally you will not need to call this method directly but if for some reason you can't directly watch a property you can invoke this method - manually along with `Ember.propertyWilLChange()` which you should call just + manually along with `Ember.propertyWillChange()` which you should call just before the property value changes. @method propertyDidChange @@ -2325,7 +2574,7 @@ var propertyWillChange = Ember.propertyWillChange = function(obj, keyName) { @param {String} keyName The property key (or path) that will change. @return {void} */ -var propertyDidChange = Ember.propertyDidChange = function(obj, keyName) { +function propertyDidChange(obj, keyName) { var m = metaFor(obj, false), watching = m.watching[keyName] > 0 || keyName === 'length', proto = m.proto, @@ -2338,9 +2587,10 @@ var propertyDidChange = Ember.propertyDidChange = function(obj, keyName) { if (!watching && keyName !== 'length') { return; } dependentKeysDidChange(obj, keyName, m); - chainsDidChange(obj, keyName, m); + chainsDidChange(obj, keyName, m, false); notifyObservers(obj, keyName); -}; +} +Ember.propertyDidChange = propertyDidChange; var WILL_SEEN, DID_SEEN; @@ -2381,35 +2631,47 @@ function iterDeps(method, obj, depKey, seen, meta) { } } -var chainsWillChange = function(obj, keyName, m, arg) { - if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do - - var nodes = m.chainWatchers; - - nodes = nodes[keyName]; - if (!nodes) { return; } - - nodes = nodes.slice(); - - for(var i = 0, l = nodes.length; i < l; i++) { - nodes[i].willChange(arg); +function chainsWillChange(obj, keyName, m) { + if (!(m.hasOwnProperty('chainWatchers') && + m.chainWatchers[keyName])) { + return; } -}; -var chainsDidChange = function(obj, keyName, m, arg) { - if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do + var nodes = m.chainWatchers[keyName], + events = [], + i, l; - var nodes = m.chainWatchers; - - nodes = nodes[keyName]; - if (!nodes) { return; } - - nodes = nodes.slice(); - - for(var i = 0, l = nodes.length; i < l; i++) { - nodes[i].didChange(arg); + for(i = 0, l = nodes.length; i < l; i++) { + nodes[i].willChange(events); } -}; + + for (i = 0, l = events.length; i < l; i += 2) { + propertyWillChange(events[i], events[i+1]); + } +} + +function chainsDidChange(obj, keyName, m, suppressEvents) { + if (!(m.hasOwnProperty('chainWatchers') && + m.chainWatchers[keyName])) { + return; + } + + var nodes = m.chainWatchers[keyName], + events = suppressEvents ? null : [], + i, l; + + for(i = 0, l = nodes.length; i < l; i++) { + nodes[i].didChange(events); + } + + if (suppressEvents) { + return; + } + + for (i = 0, l = events.length; i < l; i += 2) { + propertyDidChange(events[i], events[i+1]); + } +} Ember.overrideChains = function(obj, keyName, m) { chainsDidChange(obj, keyName, m, true); @@ -2419,20 +2681,24 @@ Ember.overrideChains = function(obj, keyName, m) { @method beginPropertyChanges @chainable */ -var beginPropertyChanges = Ember.beginPropertyChanges = function() { +function beginPropertyChanges() { deferred++; -}; +} + +Ember.beginPropertyChanges = beginPropertyChanges; /** @method endPropertyChanges */ -var endPropertyChanges = Ember.endPropertyChanges = function() { +function endPropertyChanges() { deferred--; if (deferred<=0) { beforeObserverSet.clear(); observerSet.flush(); } -}; +} + +Ember.endPropertyChanges = endPropertyChanges; /** Make a series of property changes together in an @@ -2454,7 +2720,7 @@ Ember.changeProperties = function(cb, binding) { tryFinally(cb, endPropertyChanges, binding); }; -var notifyBeforeObservers = function(obj, keyName) { +function notifyBeforeObservers(obj, keyName) { if (obj.isDestroying) { return; } var eventName = keyName + ':before', listeners, diff; @@ -2465,9 +2731,9 @@ var notifyBeforeObservers = function(obj, keyName) { } else { sendEvent(obj, eventName, [obj, keyName]); } -}; +} -var notifyObservers = function(obj, keyName) { +function notifyObservers(obj, keyName) { if (obj.isDestroying) { return; } var eventName = keyName + ':change', listeners; @@ -2477,7 +2743,7 @@ var notifyObservers = function(obj, keyName) { } else { sendEvent(obj, eventName, [obj, keyName]); } -}; +} })(); @@ -2496,7 +2762,7 @@ var META_KEY = Ember.META_KEY, /** Sets the value of a property on an object, respecting computed properties and notifying observers and other listeners of the change. If the - property is not defined but the object implements the `unknownProperty` + property is not defined but the object implements the `setUnknownProperty` method then that will be invoked as well. If you plan to run on IE8 and older browsers then you should use this @@ -2506,7 +2772,7 @@ var META_KEY = Ember.META_KEY, On all newer browsers, you only need to use this method to set properties if the property might not be defined on the object and you want - to respect the `unknownProperty` handler. Otherwise you can ignore this + to respect the `setUnknownProperty` handler. Otherwise you can ignore this method. @method set @@ -2837,14 +3103,14 @@ Map.create = function() { Map.prototype = { /** This property will change as the number of objects in the map changes. - + @property length @type number @default 0 */ length: 0, - - + + /** Retrieve the value associated with a given key. @@ -3165,6 +3431,47 @@ Ember.defineProperty = function(obj, keyName, desc, data, meta) { +(function() { +var get = Ember.get; + +/** + To get multiple properties at once, call `Ember.getProperties` + with an object followed by a list of strings or an array: + + ```javascript + Ember.getProperties(record, 'firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } + ``` + + is equivalent to: + + ```javascript + Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } + ``` + + @method getProperties + @param obj + @param {String...|Array} list of keys to get + @return {Hash} +*/ +Ember.getProperties = function(obj) { + var ret = {}, + propertyNames = arguments, + i = 1; + + if (arguments.length === 2 && Ember.typeOf(arguments[1]) === 'array') { + i = 0; + propertyNames = arguments[1]; + } + for(var len = propertyNames.length; i < len; i++) { + ret[propertyNames[i]] = get(obj, propertyNames[i]); + } + return ret; +}; + +})(); + + + (function() { var changeProperties = Ember.changeProperties, set = Ember.set; @@ -3174,6 +3481,14 @@ var changeProperties = Ember.changeProperties, a single `beginPropertyChanges` and `endPropertyChanges` batch, so observers will be buffered. + ```javascript + anObject.setProperties({ + firstName: "Stanley", + lastName: "Stuart", + age: "21" + }) + ``` + @method setProperties @param self @param {Object} hash @@ -3263,8 +3578,6 @@ var metaFor = Ember.meta, // utils.js warn = Ember.warn, watchKey = Ember.watchKey, unwatchKey = Ember.unwatchKey, - propertyWillChange = Ember.propertyWillChange, - propertyDidChange = Ember.propertyDidChange, FIRST_KEY = /^([^\.\*]+)/; function firstKey(path) { @@ -3498,42 +3811,50 @@ ChainNodePrototype.unchain = function(key, path) { }; -ChainNodePrototype.willChange = function() { +ChainNodePrototype.willChange = function(events) { var chains = this._chains; if (chains) { for(var key in chains) { if (!chains.hasOwnProperty(key)) { continue; } - chains[key].willChange(); + chains[key].willChange(events); } } - if (this._parent) { this._parent.chainWillChange(this, this._key, 1); } + if (this._parent) { this._parent.chainWillChange(this, this._key, 1, events); } }; -ChainNodePrototype.chainWillChange = function(chain, path, depth) { +ChainNodePrototype.chainWillChange = function(chain, path, depth, events) { if (this._key) { path = this._key + '.' + path; } if (this._parent) { - this._parent.chainWillChange(this, path, depth+1); + this._parent.chainWillChange(this, path, depth+1, events); } else { - if (depth > 1) { propertyWillChange(this.value(), path); } + if (depth > 1) { + events.push(this.value(), path); + } path = 'this.' + path; - if (this._paths[path] > 0) { propertyWillChange(this.value(), path); } + if (this._paths[path] > 0) { + events.push(this.value(), path); + } } }; -ChainNodePrototype.chainDidChange = function(chain, path, depth) { +ChainNodePrototype.chainDidChange = function(chain, path, depth, events) { if (this._key) { path = this._key + '.' + path; } if (this._parent) { - this._parent.chainDidChange(this, path, depth+1); + this._parent.chainDidChange(this, path, depth+1, events); } else { - if (depth > 1) { propertyDidChange(this.value(), path); } + if (depth > 1) { + events.push(this.value(), path); + } path = 'this.' + path; - if (this._paths[path] > 0) { propertyDidChange(this.value(), path); } + if (this._paths[path] > 0) { + events.push(this.value(), path); + } } }; -ChainNodePrototype.didChange = function(suppressEvent) { +ChainNodePrototype.didChange = function(events) { // invalidate my own value first. if (this._watching) { var obj = this._parent.value(); @@ -3555,14 +3876,15 @@ ChainNodePrototype.didChange = function(suppressEvent) { if (chains) { for(var key in chains) { if (!chains.hasOwnProperty(key)) { continue; } - chains[key].didChange(suppressEvent); + chains[key].didChange(events); } } - if (suppressEvent) { return; } + // if no events are passed in then we only care about the above wiring update + if (events === null) { return; } // and finally tell parent about my path changing... - if (this._parent) { this._parent.chainDidChange(this, this._key, 1); } + if (this._parent) { this._parent.chainDidChange(this, this._key, 1, events); } }; Ember.finishChains = function(obj) { @@ -3571,9 +3893,10 @@ Ember.finishChains = function(obj) { if (chains.value() !== obj) { m.chains = chains = chains.copy(obj); } - chains.didChange(true); + chains.didChange(null); } }; + })(); @@ -3854,6 +4177,81 @@ function removeDependentKeys(desc, obj, keyName, meta) { // /** + A computed property transforms an objects function into a property. + + By default the function backing the computed property will only be called + once and the result will be cached. You can specify various properties + that your computed property is dependent on. This will force the cached + result to be recomputed if the dependencies are modified. + + In the following example we declare a computed property (by calling + `.property()` on the fullName function) and setup the properties + dependencies (depending on firstName and lastName). The fullName function + will be called once (regardless of how many times it is accessed) as long + as it's dependencies have not been changed. Once firstName or lastName are updated + any future calls (or anything bound) to fullName will incorporate the new + values. + + ```javascript + Person = Ember.Object.extend({ + // these will be supplied by `create` + firstName: null, + lastName: null, + + fullName: function() { + var firstName = this.get('firstName'); + var lastName = this.get('lastName'); + + return firstName + ' ' + lastName; + }.property('firstName', 'lastName') + }); + + var tom = Person.create({ + firstName: "Tom", + lastName: "Dale" + }); + + tom.get('fullName') // "Tom Dale" + ``` + + You can also define what Ember should do when setting a computed property. + If you try to set a computed property, it will be invoked with the key and + value you want to set it to. You can also accept the previous value as the + third parameter. + + ```javascript + + Person = Ember.Object.extend({ + // these will be supplied by `create` + firstName: null, + lastName: null, + + fullName: function(key, value, oldValue) { + // getter + if (arguments.length === 1) { + var firstName = this.get('firstName'); + var lastName = this.get('lastName'); + + return firstName + ' ' + lastName; + + // setter + } else { + var name = value.split(" "); + + this.set('firstName', name[0]); + this.set('lastName', name[1]); + + return value; + } + }.property('firstName', 'lastName') + }); + + var person = Person.create(); + person.set('fullName', "Peter Wagenet"); + person.get('firstName') // Peter + person.get('lastName') // Wagenet + ``` + @class ComputedProperty @namespace Ember @extends Ember.Descriptor @@ -3872,7 +4270,7 @@ ComputedProperty.prototype = new Ember.Descriptor(); var ComputedPropertyPrototype = ComputedProperty.prototype; -/* +/** Properties are cacheable by default. Computed property will automatically cache the return value of your function until one of the dependent keys changes. @@ -4014,11 +4412,37 @@ ComputedPropertyPrototype.didChange = function(obj, keyName) { function finishChains(chainNodes) { for (var i=0, l=chainNodes.length; i= 0) || + if ((concats && a_indexOf.call(concats, key) >= 0) || key === 'concatenatedProperties' || key === 'mergedProperties') { value = applyConcatenatedProperties(base, key, value, values); } else if ((mergings && a_indexOf.call(mergings, key) >= 0)) { value = applyMergedProperties(base, key, value, values); + } else if (isMethod(value)) { + value = giveMethodSuper(base, key, value, values, descs); } descs[key] = undefined; @@ -6370,6 +7082,7 @@ function mergeMixins(mixins, m, descs, values, base, keys) { if (props) { meta = Ember.meta(base); + if (base.willMergeMixin) { base.willMergeMixin(props); } concats = concatenatedMixinProperties('concatenatedProperties', props, values, base); mergings = concatenatedMixinProperties('mergedProperties', props, values, base); @@ -6543,7 +7256,7 @@ Ember.mixin = function(obj) { // Mix mixins into classes by passing them as the first arguments to // .extend. App.CommentView = Ember.View.extend(App.Editable, { - template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}') + template: Ember.Handlebars.compile('{{#if view.isEditing}}...{{else}}...{{/if}}') }); commentView = App.CommentView.create(); @@ -6553,6 +7266,31 @@ Ember.mixin = function(obj) { Note that Mixins are created with `Ember.Mixin.create`, not `Ember.Mixin.extend`. + Note that mixins extend a constructor's prototype so arrays and object literals + defined as properties will be shared amongst objects that implement the mixin. + If you want to define an property in a mixin that is not shared, you can define + it either as a computed property or have it be created on initialization of the object. + + ```javascript + //filters array will be shared amongst any object implementing mixin + App.Filterable = Ember.Mixin.create({ + filters: Ember.A() + }); + + //filters will be a separate array for every object implementing the mixin + App.Filterable = Ember.Mixin.create({ + filters: Ember.computed(function(){return Ember.A();}) + }); + + //filters will be created as a separate array during the object's initialization + App.Filterable = Ember.Mixin.create({ + init: function() { + this._super(); + this.set("filters", Ember.A()); + } + }); + ``` + @class Mixin @namespace Ember */ @@ -6755,11 +7493,10 @@ Alias.prototype = new Ember.Descriptor(); @deprecated Use `Ember.aliasMethod` or `Ember.computed.alias` instead */ Ember.alias = function(methodName) { + Ember.deprecate("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead."); return new Alias(methodName); }; -Ember.alias = Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.", Ember.alias); - /** Makes a method available via an additional name. @@ -6788,6 +7525,22 @@ Ember.aliasMethod = function(methodName) { // /** + Specify a method that observes property changes. + + ```javascript + Ember.Object.extend({ + valueObserver: Ember.observer(function() { + // Executes whenever the "value" property changes + }, 'value') + }); + ``` + + In the future this method may become asynchronous. If you want to ensure + synchronous behavior, use `immediateObserver`. + + Also available as `Function.prototype.observes` if prototype extensions are + enabled. + @method observer @for Ember @param {Function} func @@ -6800,9 +7553,23 @@ Ember.observer = function(func) { return func; }; -// If observers ever become asynchronous, Ember.immediateObserver -// must remain synchronous. /** + Specify a method that observes property changes. + + ```javascript + Ember.Object.extend({ + valueObserver: Ember.immediateObserver(function() { + // Executes whenever the "value" property changes + }, 'value') + }); + ``` + + In the future, `Ember.observer` may become asynchronous. In this event, + `Ember.immediateObserver` will maintain the synchronous behavior. + + Also available as `Function.prototype.observesImmediately` if prototype extensions are + enabled. + @method immediateObserver @for Ember @param {Function} func @@ -6830,24 +7597,31 @@ Ember.immediateObserver = function() { ```javascript App.PersonView = Ember.View.extend({ + friends: [{ name: 'Tom' }, { name: 'Stefan' }, { name: 'Kris' }], - valueWillChange: function (obj, keyName) { + + valueWillChange: Ember.beforeObserver(function(obj, keyName) { this.changingFrom = obj.get(keyName); - }.observesBefore('content.value'), - valueDidChange: function(obj, keyName) { + }, 'content.value'), + + valueDidChange: Ember.observer(function(obj, keyName) { // only run if updating a value already in the DOM if (this.get('state') === 'inDOM') { - var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red'; - // logic + var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red'; + // logic } - }.observes('content.value'), - friendsDidChange: function(obj, keyName) { + }, 'content.value'), + + friendsDidChange: Ember.observer(function(obj, keyName) { // some logic // obj.get(keyName) returns friends array - }.observes('friends.@each.name') + }, 'friends.@each.name') }); ``` + Also available as `Function.prototype.observesBefore` if prototype extensions are + enabled. + @method beforeObserver @for Ember @param {Function} func @@ -6864,6 +7638,55 @@ Ember.beforeObserver = function(func) { +(function() { +// Provides a way to register library versions with ember. +var forEach = Ember.EnumerableUtils.forEach, + indexOf = Ember.EnumerableUtils.indexOf; + +Ember.libraries = function() { + var libraries = []; + var coreLibIndex = 0; + + var getLibrary = function(name) { + for (var i = 0; i < libraries.length; i++) { + if (libraries[i].name === name) { + return libraries[i]; + } + } + }; + + libraries.register = function(name, version) { + if (!getLibrary(name)) { + libraries.push({name: name, version: version}); + } + }; + + libraries.registerCoreLibrary = function(name, version) { + if (!getLibrary(name)) { + libraries.splice(coreLibIndex++, 0, {name: name, version: version}); + } + }; + + libraries.deRegister = function(name) { + var lib = getLibrary(name); + if (lib) libraries.splice(indexOf(libraries, lib), 1); + }; + + libraries.each = function (callback) { + forEach(libraries, function(lib) { + callback(lib.name, lib.version); + }); + }; + + return libraries; +}(); + +Ember.libraries.registerCoreLibrary('Ember', Ember.VERSION); + +})(); + + + (function() { /** Ember Metal @@ -6931,6 +7754,7 @@ define("rsvp/async", var browserGlobal = (typeof window !== 'undefined') ? window : {}; var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; var async; + var local = (typeof global !== 'undefined') ? global : this; // old node function useNextTick() { @@ -6981,7 +7805,7 @@ define("rsvp/async", function useSetTimeout() { return function(callback, arg) { - setTimeout(function() { + local.setTimeout(function() { callback(arg); }, 1); }; @@ -7364,6 +8188,10 @@ define("rsvp/promise", }); return thenPromise; + }, + + fail: function(fail) { + return this.then(null, fail); } }; @@ -7466,19 +8294,36 @@ define("rsvp/resolve", __exports__.resolve = resolve; }); +define("rsvp/rethrow", + ["exports"], + function(__exports__) { + "use strict"; + var local = (typeof global === "undefined") ? this : global; + + function rethrow(reason) { + local.setTimeout(function() { + throw reason; + }); + throw reason; + } + + + __exports__.rethrow = rethrow; + }); define("rsvp", - ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { + ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/rethrow","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { "use strict"; var EventTarget = __dependency1__.EventTarget; var Promise = __dependency2__.Promise; var denodeify = __dependency3__.denodeify; var all = __dependency4__.all; var hash = __dependency5__.hash; - var defer = __dependency6__.defer; - var config = __dependency7__.config; - var resolve = __dependency8__.resolve; - var reject = __dependency9__.reject; + var rethrow = __dependency6__.rethrow; + var defer = __dependency7__.defer; + var config = __dependency8__.config; + var resolve = __dependency9__.resolve; + var reject = __dependency10__.reject; function configure(name, value) { config[name] = value; @@ -7489,25 +8334,35 @@ define("rsvp", __exports__.EventTarget = EventTarget; __exports__.all = all; __exports__.hash = hash; + __exports__.rethrow = rethrow; __exports__.defer = defer; __exports__.denodeify = denodeify; __exports__.configure = configure; __exports__.resolve = resolve; __exports__.reject = reject; }); - })(); (function() { +/** +@private +Public api for the container is still in flux. +The public api, specified on the application namespace should be considered the stable api. +// @module container +*/ + +/* + Flag to enable/disable model factory injections (disabled by default) + If model factory injections are enabled, models should not be + accessed globally (only through `container.lookupFactory('model:modelName'))`); +*/ +Ember.MODEL_FACTORY_INJECTIONS = false || !!Ember.ENV.MODEL_FACTORY_INJECTIONS; + define("container", [], function() { - /** - A safe and simple inheriting object. - - @class InheritingDict - */ + // A safe and simple inheriting object. function InheritingDict(parent) { this.parent = parent; this.dict = {}; @@ -7580,7 +8435,7 @@ define("container", @method has @param {String} key - @returns {Boolean} + @return {Boolean} */ has: function(key) { var dict = this.dict; @@ -7614,11 +8469,10 @@ define("container", } }; - /** - A lightweight container that helps to assemble and decouple components. - @class Container - */ + // 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 = []; @@ -7707,7 +8561,7 @@ define("container", to correctly inherit from the current container. @method child - @returns {Container} + @return {Container} */ child: function() { var container = new Container(this); @@ -7743,25 +8597,25 @@ define("container", ``` @method register - @param {String} type - @param {String} name + @param {String} fullName @param {Function} factory @param {Object} options */ - register: function(type, name, factory, options) { - var fullName; + register: function(fullName, factory, options) { + if (fullName.indexOf(':') === -1) { + throw new TypeError("malformed fullName, expected: `type:name` got: " + fullName + ""); + } - if (type.indexOf(':') !== -1) { - options = factory; - factory = name; - fullName = type; - } else { - Ember.deprecate('register("'+type +'", "'+ name+'") is now deprecated in-favour of register("'+type+':'+name+'");', false); - fullName = type + ":" + name; + 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 || {}); }, @@ -7777,6 +8631,7 @@ define("container", container.unregister('model:user') container.lookup('model:user') === undefined //=> true + ``` @method unregister @param {String} fullName @@ -7786,6 +8641,7 @@ define("container", this.registry.remove(normalizedName); this.cache.remove(normalizedName); + this.factoryCache.remove(normalizedName); this._options.remove(normalizedName); }, @@ -7819,7 +8675,7 @@ define("container", @method resolve @param {String} fullName - @returns {Function} fullName's factory + @return {Function} fullName's factory */ resolve: function(fullName) { return this.resolver(fullName) || this.registry.get(fullName); @@ -7850,6 +8706,17 @@ define("container", 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. @@ -7900,7 +8767,7 @@ define("container", var value = instantiate(this, fullName); - if (!value) { return; } + if (value === undefined) { return; } if (isSingleton(this, fullName) && options.singleton !== false) { this.cache.set(fullName, value); @@ -7978,7 +8845,7 @@ define("container", this.optionsForType(type, options); }, - /* + /** @private Used only via `injection`. @@ -8020,7 +8887,7 @@ define("container", addTypeInjection(this.typeInjections, type, property, fullName); }, - /* + /** Defines injection rules. These rules are used to inject dependencies onto objects when they @@ -8028,8 +8895,8 @@ define("container", Two forms of injections are possible: - * Injecting one fullName on another fullName - * Injecting one fullName on a type + * Injecting one fullName on another fullName + * Injecting one fullName on a type Example: @@ -8075,7 +8942,7 @@ define("container", }, - /* + /** @private Used only via `factoryInjection`. @@ -8112,7 +8979,7 @@ define("container", addTypeInjection(this.factoryTypeInjections, type, property, fullName); }, - /* + /** Defines factory injection rules. Similar to regular injection rules, but are run against factories, via @@ -8123,8 +8990,8 @@ define("container", Two forms of injections are possible: - * Injecting one fullName on another fullName - * Injecting one fullName on a type + * Injecting one fullName on another fullName + * Injecting one fullName on a type Example: @@ -8226,7 +9093,7 @@ define("container", injection = injections[i]; lookup = container.lookup(injection.fullName); - if (lookup) { + if (lookup !== undefined) { hash[injection.property] = lookup; } else { throw new Error('Attempting to inject an unknown injection: `' + injection.fullName + '`'); @@ -8256,20 +9123,27 @@ define("container", var factory = container.resolve(name); var injectedFactory; var cache = container.factoryCache; + var type = fullName.split(":")[0]; - if (!factory) { return; } + if (factory === undefined) { return; } if (cache.has(fullName)) { return cache.get(fullName); } - if (typeof factory.extend !== 'function') { + 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 { - injectedFactory = factory.extend(injectionsFor(container, fullName)); - injectedFactory.reopenClass(factoryInjectionsFor(container, fullName)); + + var injections = injectionsFor(container, fullName); + var factoryInjections = factoryInjectionsFor(container, fullName); + + factoryInjections._toString = container.makeToString(factory, fullName); + + injectedFactory = factory.extend(injections); + injectedFactory.reopenClass(factoryInjections); cache.set(fullName, injectedFactory); @@ -8633,16 +9507,39 @@ Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [ Ember.keys = Object.keys; if (!Ember.keys || Ember.create.isSimulated) { - Ember.keys = function(obj) { - var ret = []; - for(var key in obj) { - // Prevents browsers that don't respect non-enumerability from - // copying internal Ember properties - if (key.substring(0,2) === '__') continue; - if (key === '_super') continue; + var prototypeProperties = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'valueOf', + 'toLocaleString', + 'toString' + ], + pushPropertyName = function(obj, array, key) { + // Prevents browsers that don't respect non-enumerability from + // copying internal Ember properties + if (key.substring(0,2) === '__') return; + if (key === '_super') return; + if (indexOf(array, key) >= 0) return; + if (!obj.hasOwnProperty(key)) return; - if (obj.hasOwnProperty(key)) { ret.push(key); } + array.push(key); + }; + + Ember.keys = function(obj) { + var ret = [], key; + for (key in obj) { + pushPropertyName(obj, ret, key); } + + // IE8 doesn't enumerate property that named the same as prototype properties. + for (var i = 0, l = prototypeProperties.length; i < l; i++) { + key = prototypeProperties[i]; + + pushPropertyName(obj, ret, key); + } + return ret; }; } @@ -8676,557 +9573,6 @@ Ember.Error.prototype = Ember.create(Error.prototype); -(function() { -/** - Expose RSVP implementation - - Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md - - @class RSVP - @namespace Ember - @constructor -*/ -Ember.RSVP = requireModule('rsvp'); - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var STRING_DASHERIZE_REGEXP = (/[ _]/g); -var STRING_DASHERIZE_CACHE = {}; -var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g); -var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); -var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); -var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); - -/** - Defines the hash of localized strings for the current language. Used by - the `Ember.String.loc()` helper. To localize, add string values to this - hash. - - @property STRINGS - @for Ember - @type Hash -*/ -Ember.STRINGS = {}; - -/** - Defines string helper methods including string formatting and localization. - Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be - added to the `String.prototype` as well. - - @class String - @namespace Ember - @static -*/ -Ember.String = { - - /** - Apply formatting options to the string. This will look for occurrences - of "%@" in your string and substitute them with the arguments you pass into - this method. If you want to control the specific order of replacement, - you can add a number after the key as well to indicate which argument - you want to insert. - - Ordered insertions are most useful when building loc strings where values - you need to insert may appear in different orders. - - ```javascript - "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe" - "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John" - ``` - - @method fmt - @param {String} str The string to format - @param {Array} formats An array of parameters to interpolate into string. - @return {String} formatted string - */ - fmt: function(str, formats) { - // first, replace any ORDERED replacements. - var idx = 0; // the current index for non-numerical replacements - return str.replace(/%@([0-9]+)?/g, function(s, argIndex) { - argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++; - s = formats[argIndex]; - return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); - }) ; - }, - - /** - Formats the passed string, but first looks up the string in the localized - strings hash. This is a convenient way to localize text. See - `Ember.String.fmt()` for more information on formatting. - - Note that it is traditional but not required to prefix localized string - keys with an underscore or other character so you can easily identify - localized strings. - - ```javascript - Ember.STRINGS = { - '_Hello World': 'Bonjour le monde', - '_Hello %@ %@': 'Bonjour %@ %@' - }; - - Ember.String.loc("_Hello World"); // 'Bonjour le monde'; - Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith"; - ``` - - @method loc - @param {String} str The string to format - @param {Array} formats Optional array of parameters to interpolate into string. - @return {String} formatted string - */ - loc: function(str, formats) { - str = Ember.STRINGS[str] || str; - return Ember.String.fmt(str, formats) ; - }, - - /** - Splits a string into separate units separated by spaces, eliminating any - empty strings in the process. This is a convenience method for split that - is mostly useful when applied to the `String.prototype`. - - ```javascript - Ember.String.w("alpha beta gamma").forEach(function(key) { - console.log(key); - }); - - // > alpha - // > beta - // > gamma - ``` - - @method w - @param {String} str The string to split - @return {String} split string - */ - w: function(str) { return str.split(/\s+/); }, - - /** - Converts a camelized string into all lower case separated by underscores. - - ```javascript - 'innerHTML'.decamelize(); // 'inner_html' - 'action_name'.decamelize(); // 'action_name' - 'css-class-name'.decamelize(); // 'css-class-name' - 'my favorite items'.decamelize(); // 'my favorite items' - ``` - - @method decamelize - @param {String} str The string to decamelize. - @return {String} the decamelized string. - */ - decamelize: function(str) { - return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase(); - }, - - /** - Replaces underscores or spaces with dashes. - - ```javascript - 'innerHTML'.dasherize(); // 'inner-html' - 'action_name'.dasherize(); // 'action-name' - 'css-class-name'.dasherize(); // 'css-class-name' - 'my favorite items'.dasherize(); // 'my-favorite-items' - ``` - - @method dasherize - @param {String} str The string to dasherize. - @return {String} the dasherized string. - */ - dasherize: function(str) { - var cache = STRING_DASHERIZE_CACHE, - hit = cache.hasOwnProperty(str), - ret; - - if (hit) { - return cache[str]; - } else { - ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-'); - cache[str] = ret; - } - - return ret; - }, - - /** - Returns the lowerCamelCase form of a string. - - ```javascript - 'innerHTML'.camelize(); // 'innerHTML' - 'action_name'.camelize(); // 'actionName' - 'css-class-name'.camelize(); // 'cssClassName' - 'my favorite items'.camelize(); // 'myFavoriteItems' - 'My Favorite Items'.camelize(); // 'myFavoriteItems' - ``` - - @method camelize - @param {String} str The string to camelize. - @return {String} the camelized string. - */ - camelize: function(str) { - return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) { - return chr ? chr.toUpperCase() : ''; - }).replace(/^([A-Z])/, function(match, separator, chr) { - return match.toLowerCase(); - }); - }, - - /** - Returns the UpperCamelCase form of a string. - - ```javascript - 'innerHTML'.classify(); // 'InnerHTML' - 'action_name'.classify(); // 'ActionName' - 'css-class-name'.classify(); // 'CssClassName' - 'my favorite items'.classify(); // 'MyFavoriteItems' - ``` - - @method classify - @param {String} str the string to classify - @return {String} the classified string - */ - classify: function(str) { - var parts = str.split("."), - out = []; - - for (var i=0, l=parts.length; i Ember.TrackedArray instances. We use + // this to lazily recompute indexes for item property observers. + this.trackedArraysByGuid = {}; + + // This is used to coalesce item changes from property observers. + this.changedItems = {}; +} + +function ItemPropertyObserverContext (dependentArray, index, trackedArray) { + Ember.assert("Internal error: trackedArray is null or undefined", trackedArray); + + this.dependentArray = dependentArray; + this.index = index; + this.item = dependentArray.objectAt(index); + this.trackedArray = trackedArray; + this.beforeObserver = null; + this.observer = null; + + this.destroyed = false; +} + +DependentArraysObserver.prototype = { + setValue: function (newValue) { + this.instanceMeta.setValue(newValue, true); + }, + getValue: function () { + return this.instanceMeta.getValue(); + }, + + setupObservers: function (dependentArray, dependentKey) { + Ember.assert("dependent array must be an `Ember.Array`", Ember.Array.detect(dependentArray)); + + this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey; + + dependentArray.addArrayObserver(this, { + willChange: 'dependentArrayWillChange', + didChange: 'dependentArrayDidChange' + }); + + if (this.cp._itemPropertyKeys[dependentKey]) { + this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]); + } + }, + + teardownObservers: function (dependentArray, dependentKey) { + var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || []; + + delete this.dependentKeysByGuid[guidFor(dependentArray)]; + + this.teardownPropertyObservers(dependentKey, itemPropertyKeys); + + dependentArray.removeArrayObserver(this, { + willChange: 'dependentArrayWillChange', + didChange: 'dependentArrayDidChange' + }); + }, + + setupPropertyObservers: function (dependentKey, itemPropertyKeys) { + var dependentArray = get(this.instanceMeta.context, dependentKey), + length = get(dependentArray, 'length'), + observerContexts = new Array(length); + + this.resetTransformations(dependentKey, observerContexts); + + forEach(dependentArray, function (item, index) { + var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]); + observerContexts[index] = observerContext; + + forEach(itemPropertyKeys, function (propertyKey) { + addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); + addObserver(item, propertyKey, this, observerContext.observer); + }, this); + }, this); + }, + + teardownPropertyObservers: function (dependentKey, itemPropertyKeys) { + var dependentArrayObserver = this, + trackedArray = this.trackedArraysByGuid[dependentKey], + beforeObserver, + observer, + item; + + if (!trackedArray) { return; } + + trackedArray.apply(function (observerContexts, offset, operation) { + if (operation === Ember.TrackedArray.DELETE) { return; } + + forEach(observerContexts, function (observerContext) { + observerContext.destroyed = true; + beforeObserver = observerContext.beforeObserver; + observer = observerContext.observer; + item = observerContext.item; + + forEach(itemPropertyKeys, function (propertyKey) { + removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver); + removeObserver(item, propertyKey, dependentArrayObserver, observer); + }); + }); + }); + }, + + createPropertyObserverContext: function (dependentArray, index, trackedArray) { + var observerContext = new ItemPropertyObserverContext(dependentArray, index, trackedArray); + + this.createPropertyObserver(observerContext); + + return observerContext; + }, + + createPropertyObserver: function (observerContext) { + var dependentArrayObserver = this; + + observerContext.beforeObserver = function (obj, keyName) { + return dependentArrayObserver.itemPropertyWillChange(obj, keyName, observerContext.dependentArray, observerContext); + }; + observerContext.observer = function (obj, keyName) { + return dependentArrayObserver.itemPropertyDidChange(obj, keyName, observerContext.dependentArray, observerContext); + }; + }, + + resetTransformations: function (dependentKey, observerContexts) { + this.trackedArraysByGuid[dependentKey] = new Ember.TrackedArray(observerContexts); + }, + + trackAdd: function (dependentKey, index, newItems) { + var trackedArray = this.trackedArraysByGuid[dependentKey]; + if (trackedArray) { + trackedArray.addItems(index, newItems); + } + }, + + trackRemove: function (dependentKey, index, removedCount) { + var trackedArray = this.trackedArraysByGuid[dependentKey]; + + if (trackedArray) { + return trackedArray.removeItems(index, removedCount); + } + + return []; + }, + + updateIndexes: function (trackedArray, array) { + var length = get(array, 'length'); + // OPTIMIZE: we could stop updating once we hit the object whose observer + // fired; ie partially apply the transformations + trackedArray.apply(function (observerContexts, offset, operation) { + // we don't even have observer contexts for removed items, even if we did, + // they no longer have any index in the array + if (operation === Ember.TrackedArray.DELETE) { return; } + if (operation === Ember.TrackedArray.RETAIN && observerContexts.length === length && offset === 0) { + // If we update many items we don't want to walk the array each time: we + // only need to update the indexes at most once per run loop. + return; + } + + forEach(observerContexts, function (context, index) { + context.index = index + offset; + }); + }); + }, + + dependentArrayWillChange: function (dependentArray, index, removedCount, addedCount) { + var removedItem = this.callbacks.removedItem, + changeMeta, + guid = guidFor(dependentArray), + dependentKey = this.dependentKeysByGuid[guid], + itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [], + item, + itemIndex, + sliceIndex, + observerContexts; + + observerContexts = this.trackRemove(dependentKey, index, removedCount); + + function removeObservers(propertyKey) { + observerContexts[sliceIndex].destroyed = true; + removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver); + removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer); + } + + for (sliceIndex = removedCount - 1; sliceIndex >= 0; --sliceIndex) { + itemIndex = index + sliceIndex; + item = dependentArray.objectAt(itemIndex); + + forEach(itemPropertyKeys, removeObservers, this); + + changeMeta = createChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp); + this.setValue( removedItem.call( + this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); + } + }, + + dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) { + var addedItem = this.callbacks.addedItem, + guid = guidFor(dependentArray), + dependentKey = this.dependentKeysByGuid[guid], + observerContexts = new Array(addedCount), + itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey], + changeMeta, + observerContext; + + forEach(dependentArray.slice(index, index + addedCount), function (item, sliceIndex) { + if (itemPropertyKeys) { + observerContext = + observerContexts[sliceIndex] = + this.createPropertyObserverContext(dependentArray, index + sliceIndex, this.trackedArraysByGuid[dependentKey]); + forEach(itemPropertyKeys, function (propertyKey) { + addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); + addObserver(item, propertyKey, this, observerContext.observer); + }, this); + } + + changeMeta = createChangeMeta(dependentArray, item, index + sliceIndex, this.instanceMeta.propertyName, this.cp); + this.setValue( addedItem.call( + this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); + }, this); + + this.trackAdd(dependentKey, index, observerContexts); + }, + + itemPropertyWillChange: function (obj, keyName, array, observerContext) { + var guid = guidFor(obj); + + if (!this.changedItems[guid]) { + this.changedItems[guid] = { + array: array, + observerContext: observerContext, + obj: obj, + previousValues: {} + }; + } + + this.changedItems[guid].previousValues[keyName] = get(obj, keyName); + }, + + itemPropertyDidChange: function(obj, keyName, array, observerContext) { + this.flushChanges(); + }, + + flushChanges: function() { + var changedItems = this.changedItems, key, c, changeMeta; + + for (key in changedItems) { + c = changedItems[key]; + if (c.observerContext.destroyed) { continue; } + + this.updateIndexes(c.observerContext.trackedArray, c.observerContext.dependentArray); + + changeMeta = createChangeMeta(c.array, c.obj, c.observerContext.index, this.instanceMeta.propertyName, this.cp, c.previousValues); + this.setValue( + this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta)); + this.setValue( + this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta)); + } + this.changedItems = {}; + } +}; + +function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) { + var meta = { + arrayChanged: dependentArray, + index: index, + item: item, + propertyName: propertyName, + property: property + }; + + if (previousValues) { + // previous values only available for item property changes + meta.previousValues = previousValues; + } + + return meta; +} + +function addItems (dependentArray, callbacks, cp, propertyName, meta) { + forEach(dependentArray, function (item, index) { + meta.setValue( callbacks.addedItem.call( + this, meta.getValue(), item, createChangeMeta(dependentArray, item, index, propertyName, cp), meta.sugarMeta)); + }, this); +} + +function reset(cp, propertyName) { + var callbacks = cp._callbacks(), + meta; + + if (cp._hasInstanceMeta(this, propertyName)) { + meta = cp._instanceMeta(this, propertyName); + meta.setValue(cp.resetValue(meta.getValue())); + } else { + meta = cp._instanceMeta(this, propertyName); + } + + if (cp.options.initialize) { + cp.options.initialize.call(this, meta.getValue(), { property: cp, propertyName: propertyName }, meta.sugarMeta); + } +} + +function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) { + this.context = context; + this.propertyName = propertyName; + this.cache = metaFor(context).cache; + + this.dependentArrays = {}; + this.sugarMeta = {}; + + this.initialValue = initialValue; +} + +ReduceComputedPropertyInstanceMeta.prototype = { + getValue: function () { + if (this.propertyName in this.cache) { + return this.cache[this.propertyName]; + } else { + return this.initialValue; + } + }, + + setValue: function(newValue, triggerObservers) { + // This lets sugars force a recomputation, handy for very simple + // implementations of eg max. + if (newValue !== undefined) { + var fireObservers = triggerObservers && (newValue !== this.cache[this.propertyName]); + + if (fireObservers) { + propertyWillChange(this.context, this.propertyName); + } + + this.cache[this.propertyName] = newValue; + + if (fireObservers) { + propertyDidChange(this.context, this.propertyName); + } + } else { + delete this.cache[this.propertyName]; + } + } +}; + +/** + A computed property whose dependent keys are arrays and which is updated with + "one at a time" semantics. + + @class ReduceComputedProperty + @namespace Ember + @extends Ember.ComputedProperty + @constructor +*/ +function ReduceComputedProperty(options) { + var cp = this; + + this.options = options; + this._instanceMetas = {}; + + this._dependentKeys = null; + // A map of dependentKey -> [itemProperty, ...] that tracks what properties of + // items in the array we must track to update this property. + this._itemPropertyKeys = {}; + this._previousItemPropertyKeys = {}; + + this.readOnly(); + this.cacheable(); + + this.recomputeOnce = function(propertyName) { + // What we really want to do is coalesce by . + // We need a form of `scheduleOnce` that accepts an arbitrary token to + // coalesce by, in addition to the target and method. + Ember.run.once(this, recompute, propertyName); + }; + var recompute = function(propertyName) { + var dependentKeys = cp._dependentKeys, + meta = cp._instanceMeta(this, propertyName), + callbacks = cp._callbacks(); + + reset.call(this, cp, propertyName); + + forEach(cp._dependentKeys, function (dependentKey) { + var dependentArray = get(this, dependentKey), + previousDependentArray = meta.dependentArrays[dependentKey]; + + if (dependentArray === previousDependentArray) { + // The array may be the same, but our item property keys may have + // changed, so we set them up again. We can't easily tell if they've + // changed: the array may be the same object, but with different + // contents. + if (cp._previousItemPropertyKeys[dependentKey]) { + delete cp._previousItemPropertyKeys[dependentKey]; + meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]); + } + } else { + meta.dependentArrays[dependentKey] = dependentArray; + + if (previousDependentArray) { + meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey); + } + + if (dependentArray) { + meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey); + } + } + }, this); + + forEach(cp._dependentKeys, function(dependentKey) { + var dependentArray = get(this, dependentKey); + if (dependentArray) { + addItems.call(this, dependentArray, callbacks, cp, propertyName, meta); + } + }, this); + }; + + this.func = function (propertyName) { + Ember.assert("Computed reduce values require at least one dependent key", cp._dependentKeys); + + recompute.call(this, propertyName); + + return cp._instanceMeta(this, propertyName).getValue(); + }; +} + +Ember.ReduceComputedProperty = ReduceComputedProperty; +ReduceComputedProperty.prototype = o_create(ComputedProperty.prototype); + +function defaultCallback(computedValue) { + return computedValue; +} + +ReduceComputedProperty.prototype._callbacks = function () { + if (!this.callbacks) { + var options = this.options; + this.callbacks = { + removedItem: options.removedItem || defaultCallback, + addedItem: options.addedItem || defaultCallback + }; + } + return this.callbacks; +}; + +ReduceComputedProperty.prototype._hasInstanceMeta = function (context, propertyName) { + var guid = guidFor(context), + key = guid + ':' + propertyName; + + return !!this._instanceMetas[key]; +}; + +ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) { + var guid = guidFor(context), + key = guid + ':' + propertyName, + meta = this._instanceMetas[key]; + + if (!meta) { + meta = this._instanceMetas[key] = new ReduceComputedPropertyInstanceMeta(context, propertyName, this.initialValue()); + meta.dependentArraysObserver = new DependentArraysObserver(this._callbacks(), this, meta, context, propertyName, meta.sugarMeta); + } + + return meta; +}; + +ReduceComputedProperty.prototype.initialValue = function () { + if (typeof this.options.initialValue === 'function') { + return this.options.initialValue(); + } + else { + return this.options.initialValue; + } +}; + +ReduceComputedProperty.prototype.resetValue = function (value) { + return this.initialValue(); +}; + +ReduceComputedProperty.prototype.itemPropertyKey = function (dependentArrayKey, itemPropertyKey) { + this._itemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey] || []; + this._itemPropertyKeys[dependentArrayKey].push(itemPropertyKey); +}; + +ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArrayKey) { + if (this._itemPropertyKeys[dependentArrayKey]) { + this._previousItemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey]; + this._itemPropertyKeys[dependentArrayKey] = []; + } +}; + +ReduceComputedProperty.prototype.property = function () { + var cp = this, + args = a_slice.call(arguments), + propertyArgs = new Ember.Set(), + match, + dependentArrayKey, + itemPropertyKey; + + forEach(a_slice.call(arguments), function (dependentKey) { + if (doubleEachPropertyPattern.test(dependentKey)) { + throw new Ember.Error("Nested @each properties not supported: " + dependentKey); + } else if (match = eachPropertyPattern.exec(dependentKey)) { + dependentArrayKey = match[1]; + itemPropertyKey = match[2]; + cp.itemPropertyKey(dependentArrayKey, itemPropertyKey); + propertyArgs.add(dependentArrayKey); + } else { + propertyArgs.add(dependentKey); + } + }); + + return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray()); +}; + +/** + Creates a computed property which operates on dependent arrays and + is updated with "one at a time" semantics. When items are added or + removed from the dependent array(s) a reduce computed only operates + on the change instead of re-evaluating the entire array. + + If there are more than one arguments the first arguments are + considered to be dependent property keys. The last argument is + required to be an options object. The options object can have the + following four properties: + + `initialValue` - A value or function that will be used as the initial + value for the computed. If this property is a function the result of calling + the function will be used as the initial value. This property is required. + + `initialize` - An optional initialize function. Typically this will be used + to set up state on the instanceMeta object. + + `removedItem` - A function that is called each time an element is removed + from the array. + + `addedItem` - A function that is called each time an element is added to + the array. + + + The `initialize` function has the following signature: + + ```javascript + function (initialValue, changeMeta, instanceMeta) + ``` + + `initialValue` - The value of the `initialValue` property from the + options object. + + `changeMeta` - An object which contains meta information about the + computed. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + + The `removedItem` and `addedItem` functions both have the following signature: + + ```javascript + function (accumulatedValue, item, changeMeta, instanceMeta) + ``` + + `accumulatedValue` - The value returned from the last time + `removedItem` or `addedItem` was called or `initialValue`. + + `item` - the element added or removed from the array + + `changeMeta` - An object which contains meta information about the + change. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + - `index` the index of the added or removed item + - `item` the added or removed item: this is exactly the same as + the second arg + - `arrayChanged` the array that triggered the change. Can be + useful when depending on multiple arrays. + + For property changes triggered on an item property change (when + depKey is something like `someArray.@each.someProperty`), + `changeMeta` will also contain the following property: + + - `previousValues` an object whose keys are the properties that changed on + the item, and whose values are the item's previous values. + + `previousValues` is important Ember coalesces item property changes via + Ember.run.once. This means that by the time removedItem gets called, item has + the new values, but you may need the previous value (eg for sorting & + filtering). + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + The `removedItem` and `addedItem` functions should return the accumulated + value. It is acceptable to not return anything (ie return undefined) + to invalidate the computation. This is generally not a good idea for + arrayComputed but it's used in eg max and min. + + Note that observers will be fired if either of these functions return a value + that differs from the accumulated value. When returning an object that + mutates in response to array changes, for example an array that maps + everything from some other array (see `Ember.computed.map`), it is usually + important that the *same* array be returned to avoid accidentally triggering observers. + + Example + + ```javascript + Ember.computed.max = function (dependentKey) { + return Ember.reduceComputed.call(null, dependentKey, { + initialValue: -Infinity, + + addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + return Math.max(accumulatedValue, item); + }, + + removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + if (item < accumulatedValue) { + return accumulatedValue; + } + } + }); + }; + ``` + + @method reduceComputed + @for Ember + @param {String} [dependentKeys*] + @param {Object} options + @return {Ember.ComputedProperty} +*/ +Ember.reduceComputed = function (options) { + var args; + + if (arguments.length > 1) { + args = a_slice.call(arguments, 0, -1); + options = a_slice.call(arguments, -1)[0]; + } + + if (typeof options !== "object") { + throw new Ember.Error("Reduce Computed Property declared without an options hash"); + } + + if (!('initialValue' in options)) { + throw new Ember.Error("Reduce Computed Property declared without an initial value"); + } + + var cp = new ReduceComputedProperty(options); + + if (args) { + cp.property.apply(cp, args); + } + + return cp; +}; + +})(); + + + +(function() { +var ReduceComputedProperty = Ember.ReduceComputedProperty, + a_slice = [].slice, + o_create = Ember.create, + forEach = Ember.EnumerableUtils.forEach; + +function ArrayComputedProperty() { + var cp = this; + + ReduceComputedProperty.apply(this, arguments); + + this.func = (function(reduceFunc) { + return function (propertyName) { + if (!cp._hasInstanceMeta(this, propertyName)) { + // When we recompute an array computed property, we need already + // retrieved arrays to be updated; we can't simply empty the cache and + // hope the array is re-retrieved. + forEach(cp._dependentKeys, function(dependentKey) { + Ember.addObserver(this, dependentKey, function() { + cp.recomputeOnce.call(this, propertyName); + }); + }, this); + } + + return reduceFunc.apply(this, arguments); + }; + })(this.func); + + return this; +} +Ember.ArrayComputedProperty = ArrayComputedProperty; +ArrayComputedProperty.prototype = o_create(ReduceComputedProperty.prototype); +ArrayComputedProperty.prototype.initialValue = function () { + return Ember.A(); +}; +ArrayComputedProperty.prototype.resetValue = function (array) { + array.clear(); + return array; +}; + +/** + Creates a computed property which operates on dependent arrays and + is updated with "one at a time" semantics. When items are added or + removed from the dependent array(s) an array computed only operates + on the change instead of re-evaluating the entire array. This should + return an array, if you'd like to use "one at a time" semantics and + compute some value other then an array look at + `Ember.reduceComputed`. + + If there are more than one arguments the first arguments are + considered to be dependent property keys. The last argument is + required to be an options object. The options object can have the + following three properties. + + `initialize` - An optional initialize function. Typically this will be used + to set up state on the instanceMeta object. + + `removedItem` - A function that is called each time an element is + removed from the array. + + `addedItem` - A function that is called each time an element is + added to the array. + + + The `initialize` function has the following signature: + + ```javascript + function (array, changeMeta, instanceMeta) + ``` + + `array` - The initial value of the arrayComputed, an empty array. + + `changeMeta` - An object which contains meta information about the + computed. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + + The `removedItem` and `addedItem` functions both have the following signature: + + ```javascript + function (accumulatedValue, item, changeMeta, instanceMeta) + ``` + + `accumulatedValue` - The value returned from the last time + `removedItem` or `addedItem` was called or an empty array. + + `item` - the element added or removed from the array + + `changeMeta` - An object which contains meta information about the + change. It contains the following properties: + + - `property` the computed property + - `propertyName` the name of the property on the object + - `index` the index of the added or removed item + - `item` the added or removed item: this is exactly the same as + the second arg + - `arrayChanged` the array that triggered the change. Can be + useful when depending on multiple arrays. + + For property changes triggered on an item property change (when + depKey is something like `someArray.@each.someProperty`), + `changeMeta` will also contain the following property: + + - `previousValues` an object whose keys are the properties that changed on + the item, and whose values are the item's previous values. + + `previousValues` is important Ember coalesces item property changes via + Ember.run.once. This means that by the time removedItem gets called, item has + the new values, but you may need the previous value (eg for sorting & + filtering). + + `instanceMeta` - An object that can be used to store meta + information needed for calculating your computed. For example a + unique computed might use this to store the number of times a given + element is found in the dependent array. + + The `removedItem` and `addedItem` functions should return the accumulated + value. It is acceptable to not return anything (ie return undefined) + to invalidate the computation. This is generally not a good idea for + arrayComputed but it's used in eg max and min. + + Example + + ```javascript + Ember.computed.map = function(dependentKey, callback) { + var options = { + addedItem: function(array, item, changeMeta, instanceMeta) { + var mapped = callback(item); + array.insertAt(changeMeta.index, mapped); + return array; + }, + removedItem: function(array, item, changeMeta, instanceMeta) { + array.removeAt(changeMeta.index, 1); + return array; + } + }; + + return Ember.arrayComputed(dependentKey, options); + }; + ``` + + @method arrayComputed + @for Ember + @param {String} [dependentKeys*] + @param {Object} options + @return {Ember.ComputedProperty} +*/ +Ember.arrayComputed = function (options) { + var args; + + if (arguments.length > 1) { + args = a_slice.call(arguments, 0, -1); + options = a_slice.call(arguments, -1)[0]; + } + + if (typeof options !== "object") { + throw new Ember.Error("Array Computed Property declared without an options hash"); + } + + var cp = new ArrayComputedProperty(options); + + if (args) { + cp.property.apply(cp, args); + } + + return cp; +}; + +})(); + + + +(function() { +/** +@module ember +@submodule ember-runtime +*/ + +var get = Ember.get, + set = Ember.set, + guidFor = Ember.guidFor, + merge = Ember.merge, + a_slice = [].slice, + forEach = Ember.EnumerableUtils.forEach, + map = Ember.EnumerableUtils.map; + +/** + A computed property that calculates the maximum value in the + dependent array. This will return `-Infinity` when the dependent + array is empty. + + Example + + ```javascript + App.Person = Ember.Object.extend({ + childAges: Ember.computed.mapBy('children', 'age'), + maxChildAge: Ember.computed.max('childAges') + }); + + var lordByron = App.Person.create({children: []}); + lordByron.get('maxChildAge'); // -Infinity + lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); + lordByron.get('maxChildAge'); // 7 + lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); + lordByron.get('maxChildAge'); // 8 + ``` + + @method computed.max + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array +*/ +Ember.computed.max = function (dependentKey) { + return Ember.reduceComputed.call(null, dependentKey, { + initialValue: -Infinity, + + addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + return Math.max(accumulatedValue, item); + }, + + removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + if (item < accumulatedValue) { + return accumulatedValue; + } + } + }); +}; + +/** + A computed property that calculates the minimum value in the + dependent array. This will return `Infinity` when the dependent + array is empty. + + Example + + ```javascript + App.Person = Ember.Object.extend({ + childAges: Ember.computed.mapBy('children', 'age'), + minChildAge: Ember.computed.min('childAges') + }); + + var lordByron = App.Person.create({children: []}); + lordByron.get('minChildAge'); // Infinity + lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); + lordByron.get('minChildAge'); // 7 + lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); + lordByron.get('minChildAge'); // 5 + ``` + + @method computed.min + @for Ember + @param {String} dependentKey + @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array +*/ +Ember.computed.min = function (dependentKey) { + return Ember.reduceComputed.call(null, dependentKey, { + initialValue: Infinity, + + addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + return Math.min(accumulatedValue, item); + }, + + removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) { + if (item > accumulatedValue) { + return accumulatedValue; + } + } + }); +}; + +/** + Returns an array mapped via the callback + + The callback method you provide should have the following signature: + + ```javascript + function(item); + ``` + + - `item` is the current item in the iteration. + + Example + + ```javascript + App.Hampster = Ember.Object.extend({ + excitingChores: Ember.computed.map('chores', function(chore) { + return chore.toUpperCase() + '!'; + }) + }); + + var hampster = App.Hampster.create({chores: ['cook', 'clean', 'write more unit tests']}); + hampster.get('excitingChores'); // ['COOK!', 'CLEAN!', 'WRITE MORE UNIT TESTS!'] + ``` + + @method computed.map + @for Ember + @param {String} dependentKey + @param {Function} callback + @return {Ember.ComputedProperty} an array mapped via the callback +*/ +Ember.computed.map = function(dependentKey, callback) { + var options = { + addedItem: function(array, item, changeMeta, instanceMeta) { + var mapped = callback.call(this, item); + array.insertAt(changeMeta.index, mapped); + return array; + }, + removedItem: function(array, item, changeMeta, instanceMeta) { + array.removeAt(changeMeta.index, 1); + return array; + } + }; + + return Ember.arrayComputed(dependentKey, options); +}; + +/** + Returns an array mapped to the specified key. + + Example + + ```javascript + App.Person = Ember.Object.extend({ + childAges: Ember.computed.mapBy('children', 'age') + }); + + var lordByron = App.Person.create({children: []}); + lordByron.get('childAges'); // [] + lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7}); + lordByron.get('childAges'); // [7] + lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]); + lordByron.get('childAges'); // [7, 5, 8] + ``` + + @method computed.mapBy + @for Ember + @param {String} dependentKey + @param {String} propertyKey + @return {Ember.ComputedProperty} an array mapped to the specified key +*/ +Ember.computed.mapBy = function(dependentKey, propertyKey) { + var callback = function(item) { return get(item, propertyKey); }; + return Ember.computed.map(dependentKey + '.@each.' + propertyKey, callback); +}; + +/** + @method computed.mapProperty + @for Ember + @deprecated Use `Ember.computed.mapBy` instead + @param dependentKey + @param propertyKey +*/ +Ember.computed.mapProperty = Ember.computed.mapBy; + +/** + Filters the array by the callback. + + The callback method you provide should have the following signature: + + ```javascript + function(item); + ``` + + - `item` is the current item in the iteration. + + Example + + ```javascript + App.Hampster = Ember.Object.extend({ + remainingChores: Ember.computed.filter('chores', function(chore) { + return !chore.done; + }) + }); + + var hampster = App.Hampster.create({chores: [ + {name: 'cook', done: true}, + {name: 'clean', done: true}, + {name: 'write more unit tests', done: false} + ]}); + hampster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] + ``` + + @method computed.filter + @for Ember + @param {String} dependentKey + @param {Function} callback + @return {Ember.ComputedProperty} the filtered array +*/ +Ember.computed.filter = function(dependentKey, callback) { + var options = { + initialize: function (array, changeMeta, instanceMeta) { + instanceMeta.filteredArrayIndexes = new Ember.SubArray(); + }, + + addedItem: function(array, item, changeMeta, instanceMeta) { + var match = !!callback.call(this, item), + filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match); + + if (match) { + array.insertAt(filterIndex, item); + } + + return array; + }, + + removedItem: function(array, item, changeMeta, instanceMeta) { + var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index); + + if (filterIndex > -1) { + array.removeAt(filterIndex); + } + + return array; + } + }; + + return Ember.arrayComputed(dependentKey, options); +}; + +/** + Filters the array by the property and value + + Example + + ```javascript + App.Hampster = Ember.Object.extend({ + remainingChores: Ember.computed.filterBy('chores', 'done', false) + }); + + var hampster = App.Hampster.create({chores: [ + {name: 'cook', done: true}, + {name: 'clean', done: true}, + {name: 'write more unit tests', done: false} + ]}); + hampster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] + ``` + + @method computed.filterBy + @for Ember + @param {String} dependentKey + @param {String} propertyKey + @param {String} value + @return {Ember.ComputedProperty} the filtered array +*/ +Ember.computed.filterBy = function(dependentKey, propertyKey, value) { + var callback; + + if (arguments.length === 2) { + callback = function(item) { + return get(item, propertyKey); + }; + } else { + callback = function(item) { + return get(item, propertyKey) === value; + }; + } + + return Ember.computed.filter(dependentKey + '.@each.' + propertyKey, callback); +}; + +/** + @method computed.filterProperty + @for Ember + @param dependentKey + @param propertyKey + @param value + @deprecated Use `Ember.computed.filterBy` instead +*/ +Ember.computed.filterProperty = Ember.computed.filterBy; + +/** + A computed property which returns a new array with all the unique + elements from one or more dependent arrays. + + Example + + ```javascript + App.Hampster = Ember.Object.extend({ + uniqueFruits: Ember.computed.uniq('fruits') + }); + + var hampster = App.Hampster.create({fruits: [ + 'banana', + 'grape', + 'kale', + 'banana' + ]}); + hampster.get('uniqueFruits'); // ['banana', 'grape', 'kale'] + ``` + + @method computed.uniq + @for Ember + @param {String} propertyKey* + @return {Ember.ComputedProperty} computes a new array with all the + unique elements from the dependent array +*/ +Ember.computed.uniq = function() { + var args = a_slice.call(arguments); + args.push({ + initialize: function(array, changeMeta, instanceMeta) { + instanceMeta.itemCounts = {}; + }, + + addedItem: function(array, item, changeMeta, instanceMeta) { + var guid = guidFor(item); + + if (!instanceMeta.itemCounts[guid]) { + instanceMeta.itemCounts[guid] = 1; + } else { + ++instanceMeta.itemCounts[guid]; + } + array.addObject(item); + return array; + }, + removedItem: function(array, item, _, instanceMeta) { + var guid = guidFor(item), + itemCounts = instanceMeta.itemCounts; + + if (--itemCounts[guid] === 0) { + array.removeObject(item); + } + return array; + } + }); + return Ember.arrayComputed.apply(null, args); +}; + +/** + Alias for [Ember.computed.uniq](/api/#method_computed_uniq). + + @method computed.union + @for Ember + @param {String} propertyKey* + @return {Ember.ComputedProperty} computes a new array with all the + unique elements from the dependent array +*/ +Ember.computed.union = Ember.computed.uniq; + +/** + A computed property which returns a new array with all the duplicated + elements from two or more dependeny arrays. + + Example + + ```javascript + var obj = Ember.Object.createWithMixins({ + adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'], + charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'], + friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends') + }); + + obj.get('friendsInCommon'); // ['William King', 'Mary Somerville'] + ``` + + @method computed.intersect + @for Ember + @param {String} propertyKey* + @return {Ember.ComputedProperty} computes a new array with all the + duplicated elements from the dependent arrays +*/ +Ember.computed.intersect = function () { + var getDependentKeyGuids = function (changeMeta) { + return map(changeMeta.property._dependentKeys, function (dependentKey) { + return guidFor(dependentKey); + }); + }; + + var args = a_slice.call(arguments); + args.push({ + initialize: function (array, changeMeta, instanceMeta) { + instanceMeta.itemCounts = {}; + }, + + addedItem: function(array, item, changeMeta, instanceMeta) { + var itemGuid = guidFor(item), + dependentGuids = getDependentKeyGuids(changeMeta), + dependentGuid = guidFor(changeMeta.arrayChanged), + numberOfDependentArrays = changeMeta.property._dependentKeys.length, + itemCounts = instanceMeta.itemCounts; + + if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; } + if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } + + if (++itemCounts[itemGuid][dependentGuid] === 1 && + numberOfDependentArrays === Ember.keys(itemCounts[itemGuid]).length) { + + array.addObject(item); + } + return array; + }, + removedItem: function(array, item, changeMeta, instanceMeta) { + var itemGuid = guidFor(item), + dependentGuids = getDependentKeyGuids(changeMeta), + dependentGuid = guidFor(changeMeta.arrayChanged), + numberOfDependentArrays = changeMeta.property._dependentKeys.length, + numberOfArraysItemAppearsIn, + itemCounts = instanceMeta.itemCounts; + + if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } + if (--itemCounts[itemGuid][dependentGuid] === 0) { + delete itemCounts[itemGuid][dependentGuid]; + numberOfArraysItemAppearsIn = Ember.keys(itemCounts[itemGuid]).length; + + if (numberOfArraysItemAppearsIn === 0) { + delete itemCounts[itemGuid]; + } + array.removeObject(item); + } + return array; + } + }); + return Ember.arrayComputed.apply(null, args); +}; + +/** + A computed property which returns a new array with all the + properties from the first dependent array that are not in the second + dependent array. + + Example + + ```javascript + App.Hampster = Ember.Object.extend({ + likes: ['banana', 'grape', 'kale'], + wants: Ember.computed.setDiff('likes', 'fruits') + }); + + var hampster = App.Hampster.create({fruits: [ + 'grape', + 'kale', + ]}); + hampster.get('wants'); // ['banana'] + ``` + + @method computed.setDiff + @for Ember + @param {String} setAProperty + @param {String} setBProperty + @return {Ember.ComputedProperty} computes a new array with all the + items from the first dependent array that are not in the second + dependent array +*/ +Ember.computed.setDiff = function (setAProperty, setBProperty) { + if (arguments.length !== 2) { + throw new Ember.Error("setDiff requires exactly two dependent arrays."); + } + return Ember.arrayComputed.call(null, setAProperty, setBProperty, { + addedItem: function (array, item, changeMeta, instanceMeta) { + var setA = get(this, setAProperty), + setB = get(this, setBProperty); + + if (changeMeta.arrayChanged === setA) { + if (!setB.contains(item)) { + array.addObject(item); + } + } else { + array.removeObject(item); + } + return array; + }, + + removedItem: function (array, item, changeMeta, instanceMeta) { + var setA = get(this, setAProperty), + setB = get(this, setBProperty); + + if (changeMeta.arrayChanged === setB) { + if (setA.contains(item)) { + array.addObject(item); + } + } else { + array.removeObject(item); + } + return array; + } + }); +}; + +function binarySearch(array, item, low, high) { + var mid, midItem, res, guidMid, guidItem; + + if (arguments.length < 4) { high = get(array, 'length'); } + if (arguments.length < 3) { low = 0; } + + if (low === high) { + return low; + } + + mid = low + Math.floor((high - low) / 2); + midItem = array.objectAt(mid); + + guidMid = _guidFor(midItem); + guidItem = _guidFor(item); + + if (guidMid === guidItem) { + return mid; + } + + res = this.order(midItem, item); + if (res === 0) { + res = guidMid < guidItem ? -1 : 1; + } + + + if (res < 0) { + return this.binarySearch(array, item, mid+1, high); + } else if (res > 0) { + return this.binarySearch(array, item, low, mid); + } + + return mid; + + function _guidFor(item) { + if (Ember.ObjectProxy.detectInstance(item)) { + return guidFor(get(item, 'content')); + } + return guidFor(item); + } +} + +/** + A computed property which returns a new array with all the + properties from the first dependent array sorted based on a property + or sort function. + + The callback method you provide should have the following signature: + + ```javascript + function(itemA, itemB); + ``` + + - `itemA` the first item to compare. + - `itemB` the second item to compare. + + This function should return `-1` when `itemA` should come before + `itemB`. It should return `1` when `itemA` should come after + `itemB`. If the `itemA` and `itemB` are equal this function should return `0`. + + Example + + ```javascript + var ToDoList = Ember.Object.extend({ + todosSorting: ['name'], + sortedTodos: Ember.computed.sort('todos', 'todosSorting'), + priorityTodos: Ember.computed.sort('todos', function(a, b){ + if (a.priority > b.priority) { + return 1; + } else if (a.priority < b.priority) { + return -1; + } + return 0; + }), + }); + var todoList = ToDoList.create({todos: [ + {name: 'Unit Test', priority: 2}, + {name: 'Documentation', priority: 3}, + {name: 'Release', priority: 1} + ]}); + + todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}] + todoList.get('priorityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}] + ``` + + @method computed.sort + @for Ember + @param {String} dependentKey + @param {String or Function} sortDefinition a dependent key to an + array of sort properties or a function to use when sorting + @return {Ember.ComputedProperty} computes a new sorted array based + on the sort property array or callback function +*/ +Ember.computed.sort = function (itemsKey, sortDefinition) { + Ember.assert("Ember.computed.sort requires two arguments: an array key to sort and either a sort properties key or sort function", arguments.length === 2); + + var initFn, sortPropertiesKey; + + if (typeof sortDefinition === 'function') { + initFn = function (array, changeMeta, instanceMeta) { + instanceMeta.order = sortDefinition; + instanceMeta.binarySearch = binarySearch; + }; + } else { + sortPropertiesKey = sortDefinition; + initFn = function (array, changeMeta, instanceMeta) { + function setupSortProperties() { + var sortPropertyDefinitions = get(this, sortPropertiesKey), + sortProperty, + sortProperties = instanceMeta.sortProperties = [], + sortPropertyAscending = instanceMeta.sortPropertyAscending = {}, + idx, + asc; + + Ember.assert("Cannot sort: '" + sortPropertiesKey + "' is not an array.", Ember.isArray(sortPropertyDefinitions)); + + changeMeta.property.clearItemPropertyKeys(itemsKey); + + forEach(sortPropertyDefinitions, function (sortPropertyDefinition) { + if ((idx = sortPropertyDefinition.indexOf(':')) !== -1) { + sortProperty = sortPropertyDefinition.substring(0, idx); + asc = sortPropertyDefinition.substring(idx+1).toLowerCase() !== 'desc'; + } else { + sortProperty = sortPropertyDefinition; + asc = true; + } + + sortProperties.push(sortProperty); + sortPropertyAscending[sortProperty] = asc; + changeMeta.property.itemPropertyKey(itemsKey, sortProperty); + }); + + sortPropertyDefinitions.addObserver('@each', this, updateSortPropertiesOnce); + } + + function updateSortPropertiesOnce() { + Ember.run.once(this, updateSortProperties, changeMeta.propertyName); + } + + function updateSortProperties(propertyName) { + setupSortProperties.call(this); + changeMeta.property.recomputeOnce.call(this, propertyName); + } + + Ember.addObserver(this, sortPropertiesKey, updateSortPropertiesOnce); + + setupSortProperties.call(this); + + + instanceMeta.order = function (itemA, itemB) { + var sortProperty, result, asc; + for (var i = 0; i < this.sortProperties.length; ++i) { + sortProperty = this.sortProperties[i]; + result = Ember.compare(get(itemA, sortProperty), get(itemB, sortProperty)); + + if (result !== 0) { + asc = this.sortPropertyAscending[sortProperty]; + return asc ? result : (-1 * result); + } + } + + return 0; + }; + + instanceMeta.binarySearch = binarySearch; + }; + } + + return Ember.arrayComputed.call(null, itemsKey, { + initialize: initFn, + + addedItem: function (array, item, changeMeta, instanceMeta) { + var index = instanceMeta.binarySearch(array, item); + array.insertAt(index, item); + return array; + }, + + removedItem: function (array, item, changeMeta, instanceMeta) { + var proxyProperties, index, searchItem; + + if (changeMeta.previousValues) { + proxyProperties = merge({ content: item }, changeMeta.previousValues); + + searchItem = Ember.ObjectProxy.create(proxyProperties); + } else { + searchItem = item; + } + + index = instanceMeta.binarySearch(array, searchItem); + array.removeAt(index); + return array; + } + }); +}; + +})(); + + + +(function() { +/** + Expose RSVP implementation + + Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md + + @class RSVP + @namespace Ember + @constructor +*/ +Ember.RSVP = requireModule('rsvp'); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-runtime +*/ + +var STRING_DASHERIZE_REGEXP = (/[ _]/g); +var STRING_DASHERIZE_CACHE = {}; +var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g); +var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); +var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); +var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); + +/** + Defines the hash of localized strings for the current language. Used by + the `Ember.String.loc()` helper. To localize, add string values to this + hash. + + @property STRINGS + @for Ember + @type Hash +*/ +Ember.STRINGS = {}; + +/** + Defines string helper methods including string formatting and localization. + Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be + added to the `String.prototype` as well. + + @class String + @namespace Ember + @static +*/ +Ember.String = { + + /** + Apply formatting options to the string. This will look for occurrences + of "%@" in your string and substitute them with the arguments you pass into + this method. If you want to control the specific order of replacement, + you can add a number after the key as well to indicate which argument + you want to insert. + + Ordered insertions are most useful when building loc strings where values + you need to insert may appear in different orders. + + ```javascript + "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe" + "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John" + ``` + + @method fmt + @param {String} str The string to format + @param {Array} formats An array of parameters to interpolate into string. + @return {String} formatted string + */ + fmt: function(str, formats) { + // first, replace any ORDERED replacements. + var idx = 0; // the current index for non-numerical replacements + return str.replace(/%@([0-9]+)?/g, function(s, argIndex) { + argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++; + s = formats[argIndex]; + return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); + }) ; + }, + + /** + Formats the passed string, but first looks up the string in the localized + strings hash. This is a convenient way to localize text. See + `Ember.String.fmt()` for more information on formatting. + + Note that it is traditional but not required to prefix localized string + keys with an underscore or other character so you can easily identify + localized strings. + + ```javascript + Ember.STRINGS = { + '_Hello World': 'Bonjour le monde', + '_Hello %@ %@': 'Bonjour %@ %@' + }; + + Ember.String.loc("_Hello World"); // 'Bonjour le monde'; + Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith"; + ``` + + @method loc + @param {String} str The string to format + @param {Array} formats Optional array of parameters to interpolate into string. + @return {String} formatted string + */ + loc: function(str, formats) { + str = Ember.STRINGS[str] || str; + return Ember.String.fmt(str, formats) ; + }, + + /** + Splits a string into separate units separated by spaces, eliminating any + empty strings in the process. This is a convenience method for split that + is mostly useful when applied to the `String.prototype`. + + ```javascript + Ember.String.w("alpha beta gamma").forEach(function(key) { + console.log(key); + }); + + // > alpha + // > beta + // > gamma + ``` + + @method w + @param {String} str The string to split + @return {String} split string + */ + w: function(str) { return str.split(/\s+/); }, + + /** + Converts a camelized string into all lower case separated by underscores. + + ```javascript + 'innerHTML'.decamelize(); // 'inner_html' + 'action_name'.decamelize(); // 'action_name' + 'css-class-name'.decamelize(); // 'css-class-name' + 'my favorite items'.decamelize(); // 'my favorite items' + ``` + + @method decamelize + @param {String} str The string to decamelize. + @return {String} the decamelized string. + */ + decamelize: function(str) { + return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase(); + }, + + /** + Replaces underscores, spaces, or camelCase with dashes. + + ```javascript + 'innerHTML'.dasherize(); // 'inner-html' + 'action_name'.dasherize(); // 'action-name' + 'css-class-name'.dasherize(); // 'css-class-name' + 'my favorite items'.dasherize(); // 'my-favorite-items' + ``` + + @method dasherize + @param {String} str The string to dasherize. + @return {String} the dasherized string. + */ + dasherize: function(str) { + var cache = STRING_DASHERIZE_CACHE, + hit = cache.hasOwnProperty(str), + ret; + + if (hit) { + return cache[str]; + } else { + ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-'); + cache[str] = ret; + } + + return ret; + }, + + /** + Returns the lowerCamelCase form of a string. + + ```javascript + 'innerHTML'.camelize(); // 'innerHTML' + 'action_name'.camelize(); // 'actionName' + 'css-class-name'.camelize(); // 'cssClassName' + 'my favorite items'.camelize(); // 'myFavoriteItems' + 'My Favorite Items'.camelize(); // 'myFavoriteItems' + ``` + + @method camelize + @param {String} str The string to camelize. + @return {String} the camelized string. + */ + camelize: function(str) { + return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) { + return chr ? chr.toUpperCase() : ''; + }).replace(/^([A-Z])/, function(match, separator, chr) { + return match.toLowerCase(); + }); + }, + + /** + Returns the UpperCamelCase form of a string. + + ```javascript + 'innerHTML'.classify(); // 'InnerHTML' + 'action_name'.classify(); // 'ActionName' + 'css-class-name'.classify(); // 'CssClassName' + 'my favorite items'.classify(); // 'MyFavoriteItems' + ``` + + @method classify + @param {String} str the string to classify + @return {String} the classified string + */ + classify: function(str) { + var parts = str.split("."), + out = []; + + for (var i=0, l=parts.length; i get(this, 'length')) throw new Error(OUT_OF_RANGE_EXCEPTION) ; + if (idx > get(this, 'length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION) ; this.replace(idx, 0, [object]) ; return this ; }, @@ -11060,7 +13586,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,/** if ('number' === typeof start) { if ((start < 0) || (start >= get(this, 'length'))) { - throw new Error(OUT_OF_RANGE_EXCEPTION); + throw new Ember.Error(OUT_OF_RANGE_EXCEPTION); } // fast case @@ -11260,7 +13786,10 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,/** @submodule ember-runtime */ -var get = Ember.get, set = Ember.set; +var get = Ember.get, + set = Ember.set, + slice = Array.prototype.slice, + getProperties = Ember.getProperties; /** ## Overview @@ -11390,15 +13919,7 @@ Ember.Observable = Ember.Mixin.create({ @return {Hash} */ getProperties: function() { - var ret = {}; - var propertyNames = arguments; - if (arguments.length === 1 && Ember.typeOf(arguments[0]) === 'array') { - propertyNames = arguments[0]; - } - for(var i = 0; i < propertyNames.length; i++) { - ret[propertyNames[i]] = get(this, propertyNames[i]); - } - return ret; + return getProperties.apply(null, [this].concat(slice.call(arguments))); }, /** @@ -11406,7 +13927,7 @@ Ember.Observable = Ember.Mixin.create({ This method is generally very similar to calling `object[key] = value` or `object.key = value`, except that it provides support for computed - properties, the `unknownProperty()` method and property observers. + properties, the `setUnknownProperty()` method and property observers. ### Computed Properties @@ -11420,9 +13941,9 @@ Ember.Observable = Ember.Mixin.create({ ### Unknown Properties If you try to set a value on a key that is undefined in the target - object, then the `unknownProperty()` handler will be called instead. This + object, then the `setUnknownProperty()` handler will be called instead. This gives you an opportunity to implement complex "virtual" properties that - are not predefined on the object. If `unknownProperty()` returns + are not predefined on the object. If `setUnknownProperty()` returns undefined, then `set()` will simply set the value on the object. ### Property Observers @@ -11878,18 +14399,29 @@ Ember.TargetActionSupport = Ember.Mixin.create({ */ triggerAction: function(opts) { opts = opts || {}; - var action = opts['action'] || get(this, 'action'), - target = opts['target'] || get(this, 'targetObject'), - actionContext = opts['actionContext'] || get(this, 'actionContextObject') || this; + var action = opts.action || get(this, 'action'), + target = opts.target || get(this, 'targetObject'), + actionContext = opts.actionContext; + + function args(options, actionName) { + var ret = []; + if (actionName) { ret.push(actionName); } + + return ret.concat(options); + } + + if (typeof actionContext === 'undefined') { + actionContext = get(this, 'actionContextObject') || this; + } if (target && action) { var ret; if (target.send) { - ret = target.send.apply(target, [action, actionContext]); + ret = target.send.apply(target, args(actionContext, action)); } else { Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function'); - ret = target[action].apply(target, [actionContext]); + ret = target[action].apply(target, args(actionContext)); } if (ret !== false) ret = true; @@ -12024,11 +14556,6 @@ Ember.Evented = Ember.Mixin.create({ Ember.sendEvent(this, name, args); }, - fire: function(name) { - Ember.deprecate("Ember.Evented#fire() has been deprecated in favor of trigger() for compatibility with jQuery. It will be removed in 1.0. Please update your code to call trigger() instead."); - this.trigger.apply(this, arguments); - }, - /** Cancels subscription for given name, target, and method. @@ -12082,8 +14609,8 @@ Ember.DeferredMixin = Ember.Mixin.create({ Add handlers to be called when the Deferred object is resolved or rejected. @method then - @param {Function} doneCallback a callback function to be called when done - @param {Function} failCallback a callback function to be called when failed + @param {Function} resolve a callback function to be called when done + @param {Function} reject a callback function to be called when failed */ then: function(resolve, reject) { var deferred, promise, entity; @@ -12141,6 +14668,699 @@ Ember.DeferredMixin = Ember.Mixin.create({ (function() { +/** +@module ember +@submodule ember-runtime +*/ + +var get = Ember.get; + +/** + The `Ember.ActionHandler` mixin implements support for moving an `actions` + property to an `_actions` property at extend time, and adding `_actions` + to the object's mergedProperties list. + + `Ember.ActionHandler` is used internally by Ember in `Ember.View`, + `Ember.Controller`, and `Ember.Route`. + + @class ActionHandler + @namespace Ember +*/ +Ember.ActionHandler = Ember.Mixin.create({ + mergedProperties: ['_actions'], + + /** + @private + + Moves `actions` to `_actions` at extend time. Note that this currently + modifies the mixin themselves, which is technically dubious but + is practically of little consequence. This may change in the future. + + @method willMergeMixin + */ + willMergeMixin: function(props) { + if (props.actions && !props._actions) { + props._actions = Ember.merge(props._actions || {}, props.actions); + delete props.actions; + } + }, + + send: function(actionName) { + var args = [].slice.call(arguments, 1), target; + + if (this._actions && this._actions[actionName]) { + if (this._actions[actionName].apply(this, args) === true) { + // handler returned true, so this action will bubble + } else { + return; + } + } else if (this.deprecatedSend && this.deprecatedSendHandles && this.deprecatedSendHandles(actionName)) { + if (this.deprecatedSend.apply(this, [].slice.call(arguments)) === true) { + // handler return true, so this action will bubble + } else { + return; + } + } + + if (target = get(this, 'target')) { + Ember.assert("The `target` for " + this + " (" + target + ") does not have a `send` method", typeof target.send === 'function'); + target.send.apply(target, arguments); + } + } + +}); + +})(); + + + +(function() { +var set = Ember.set, get = Ember.get, + resolve = Ember.RSVP.resolve, + rethrow = Ember.RSVP.rethrow, + not = Ember.computed.not, + or = Ember.computed.or; + +/** + @module ember + @submodule ember-runtime + */ + +function installPromise(proxy, promise) { + promise.then(function(value) { + set(proxy, 'isFulfilled', true); + set(proxy, 'content', value); + + return value; + }, function(reason) { + set(proxy, 'isRejected', true); + set(proxy, 'reason', reason); + }).fail(rethrow); +} + +/** + A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware. + + ```javascript + var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin); + + var controller = ObjectPromiseController.create({ + promise: $.getJSON('/some/remote/data.json') + }); + + controller.then(function(json){ + // the json + }, function(reason) { + // the reason why you have no json + }); + ``` + + the controller has bindable attributes which + track the promises life cycle + + ```javascript + controller.get('isPending') //=> true + controller.get('isSettled') //=> false + controller.get('isRejected') //=> false + controller.get('isFulfilled') //=> false + ``` + + When the the $.getJSON completes, and the promise is fulfilled + with json, the life cycle attributes will update accordingly. + + ```javascript + controller.get('isPending') //=> false + controller.get('isSettled') //=> true + controller.get('isRejected') //=> false + controller.get('isFulfilled') //=> true + ``` + + As the controller is an ObjectController, and the json now its content, + all the json properties will be available directly from the controller. + + ```javascript + // Assuming the following json: + { + firstName: 'Stefan', + lastName: 'Penner' + } + + // both properties will accessible on the controller + controller.get('firstName') //=> 'Stefan' + controller.get('lastName') //=> 'Penner' + ``` + + If the controller is backing a template, the attributes are + bindable from within that template + + ```handlebars + {{#if isPending}} + loading... + {{else}} + firstName: {{firstName}} + lastName: {{lastName}} + {{/if}} + ``` + @class Ember.PromiseProxyMixin +*/ +Ember.PromiseProxyMixin = Ember.Mixin.create({ + reason: null, + isPending: not('isSettled').readOnly(), + isSettled: or('isRejected', 'isFulfilled').readOnly(), + isRejected: false, + isFulfilled: false, + + promise: Ember.computed(function(key, promise) { + if (arguments.length === 2) { + promise = resolve(promise); + installPromise(this, promise); + return promise; + } else { + throw new Ember.Error("PromiseProxy's promise must be set"); + } + }), + + then: function(fulfill, reject) { + return get(this, 'promise').then(fulfill, reject); + } +}); + + +})(); + + + +(function() { + +})(); + + + +(function() { +var get = Ember.get, + forEach = Ember.EnumerableUtils.forEach, + RETAIN = 'r', + INSERT = 'i', + DELETE = 'd'; + +/** + An `Ember.TrackedArray` tracks array operations. It's useful when you want to + lazily compute the indexes of items in an array after they've been shifted by + subsequent operations. + + @class TrackedArray + @namespace Ember + @param {array} [items=[]] The array to be tracked. This is used just to get + the initial items for the starting state of retain:n. +*/ +Ember.TrackedArray = function (items) { + if (arguments.length < 1) { items = []; } + + var length = get(items, 'length'); + + if (length) { + this._operations = [new ArrayOperation(RETAIN, length, items)]; + } else { + this._operations = []; + } +}; + +Ember.TrackedArray.RETAIN = RETAIN; +Ember.TrackedArray.INSERT = INSERT; +Ember.TrackedArray.DELETE = DELETE; + +Ember.TrackedArray.prototype = { + + /** + Track that `newItems` were added to the tracked array at `index`. + + @method addItems + @param index + @param newItems + */ + addItems: function (index, newItems) { + var count = get(newItems, 'length'); + if (count < 1) { return; } + + var match = this._findArrayOperation(index), + arrayOperation = match.operation, + arrayOperationIndex = match.index, + arrayOperationRangeStart = match.rangeStart, + composeIndex, + splitIndex, + splitItems, + splitArrayOperation, + newArrayOperation; + + newArrayOperation = new ArrayOperation(INSERT, count, newItems); + + if (arrayOperation) { + if (!match.split) { + // insert left of arrayOperation + this._operations.splice(arrayOperationIndex, 0, newArrayOperation); + composeIndex = arrayOperationIndex; + } else { + this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); + composeIndex = arrayOperationIndex + 1; + } + } else { + // insert at end + this._operations.push(newArrayOperation); + composeIndex = arrayOperationIndex; + } + + this._composeInsert(composeIndex); + }, + + /** + Track that `count` items were removed at `index`. + + @method removeItems + @param index + @param count + */ + removeItems: function (index, count) { + if (count < 1) { return; } + + var match = this._findArrayOperation(index), + arrayOperation = match.operation, + arrayOperationIndex = match.index, + arrayOperationRangeStart = match.rangeStart, + newArrayOperation, + composeIndex; + + newArrayOperation = new ArrayOperation(DELETE, count); + if (!match.split) { + // insert left of arrayOperation + this._operations.splice(arrayOperationIndex, 0, newArrayOperation); + composeIndex = arrayOperationIndex; + } else { + this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); + composeIndex = arrayOperationIndex + 1; + } + + return this._composeDelete(composeIndex); + }, + + /** + Apply all operations, reducing them to retain:n, for `n`, the number of + items in the array. + + `callback` will be called for each operation and will be passed the following arguments: + * {array} items The items for the given operation + * {number} offset The computed offset of the items, ie the index in the + array of the first item for this operation. + * {string} operation The type of the operation. One of + `Ember.TrackedArray.{RETAIN, DELETE, INSERT}` + + @method apply + @param {function} callback + */ + apply: function (callback) { + var items = [], + offset = 0; + + forEach(this._operations, function (arrayOperation) { + callback(arrayOperation.items, offset, arrayOperation.type); + + if (arrayOperation.type !== DELETE) { + offset += arrayOperation.count; + items = items.concat(arrayOperation.items); + } + }); + + this._operations = [new ArrayOperation(RETAIN, items.length, items)]; + }, + + /** + Return an ArrayOperationMatch for the operation that contains the item at `index`. + + @method _findArrayOperation + + @param {number} index the index of the item whose operation information + should be returned. + @private + */ + _findArrayOperation: function (index) { + var arrayOperationIndex, + len, + split = false, + arrayOperation, + arrayOperationRangeStart, + arrayOperationRangeEnd; + + // OPTIMIZE: we could search these faster if we kept a balanced tree. + // find leftmost arrayOperation to the right of `index` + for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) { + arrayOperation = this._operations[arrayOperationIndex]; + + if (arrayOperation.type === DELETE) { continue; } + + arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1; + + if (index === arrayOperationRangeStart) { + break; + } else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) { + split = true; + break; + } else { + arrayOperationRangeStart = arrayOperationRangeEnd + 1; + } + } + + return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart); + }, + + _split: function (arrayOperationIndex, splitIndex, newArrayOperation) { + var arrayOperation = this._operations[arrayOperationIndex], + splitItems = arrayOperation.items.slice(splitIndex), + splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems); + + // truncate LHS + arrayOperation.count = splitIndex; + arrayOperation.items = arrayOperation.items.slice(0, splitIndex); + + this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation); + }, + + // see SubArray for a better implementation. + _composeInsert: function (index) { + var newArrayOperation = this._operations[index], + leftArrayOperation = this._operations[index-1], // may be undefined + rightArrayOperation = this._operations[index+1], // may be undefined + leftOp = leftArrayOperation && leftArrayOperation.type, + rightOp = rightArrayOperation && rightArrayOperation.type; + + if (leftOp === INSERT) { + // merge left + leftArrayOperation.count += newArrayOperation.count; + leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items); + + if (rightOp === INSERT) { + // also merge right (we have split an insert with an insert) + leftArrayOperation.count += rightArrayOperation.count; + leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items); + this._operations.splice(index, 2); + } else { + // only merge left + this._operations.splice(index, 1); + } + } else if (rightOp === INSERT) { + // merge right + newArrayOperation.count += rightArrayOperation.count; + newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items); + this._operations.splice(index + 1, 1); + } + }, + + _composeDelete: function (index) { + var arrayOperation = this._operations[index], + deletesToGo = arrayOperation.count, + leftArrayOperation = this._operations[index-1], // may be undefined + leftOp = leftArrayOperation && leftArrayOperation.type, + nextArrayOperation, + nextOp, + nextCount, + removeNewAndNextOp = false, + removedItems = []; + + if (leftOp === DELETE) { + arrayOperation = leftArrayOperation; + index -= 1; + } + + for (var i = index + 1; deletesToGo > 0; ++i) { + nextArrayOperation = this._operations[i]; + nextOp = nextArrayOperation.type; + nextCount = nextArrayOperation.count; + + if (nextOp === DELETE) { + arrayOperation.count += nextCount; + continue; + } + + if (nextCount > deletesToGo) { + // d:2 {r,i}:5 we reduce the retain or insert, but it stays + removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo)); + nextArrayOperation.count -= deletesToGo; + + // In the case where we truncate the last arrayOperation, we don't need to + // remove it; also the deletesToGo reduction is not the entirety of + // nextCount + i -= 1; + nextCount = deletesToGo; + + deletesToGo = 0; + } else { + if (nextCount === deletesToGo) { + // Handle edge case of d:2 i:2 in which case both operations go away + // during composition. + removeNewAndNextOp = true; + } + removedItems = removedItems.concat(nextArrayOperation.items); + deletesToGo -= nextCount; + } + + if (nextOp === INSERT) { + // d:2 i:3 will result in delete going away + arrayOperation.count -= nextCount; + } + } + + if (arrayOperation.count > 0) { + // compose our new delete with possibly several operations to the right of + // disparate types + this._operations.splice(index+1, i-1-index); + } else { + // The delete operation can go away; it has merely reduced some other + // operation, as in d:3 i:4; it may also have eliminated that operation, + // as in d:3 i:3. + this._operations.splice(index, removeNewAndNextOp ? 2 : 1); + } + + return removedItems; + }, + + toString: function () { + var str = ""; + forEach(this._operations, function (operation) { + str += " " + operation.type + ":" + operation.count; + }); + return str.substring(1); + } +}; + +/** + Internal data structure to represent an array operation. + + @method ArrayOperation + @private + @property {string} type The type of the operation. One of + `Ember.TrackedArray.{RETAIN, INSERT, DELETE}` + @property {number} count The number of items in this operation. + @property {array} items The items of the operation, if included. RETAIN and + INSERT include their items, DELETE does not. +*/ +function ArrayOperation (operation, count, items) { + this.type = operation; // RETAIN | INSERT | DELETE + this.count = count; + this.items = items; +} + +/** + Internal data structure used to include information when looking up operations + by item index. + + @method ArrayOperationMatch + @private + @property {ArrayOperation} operation + @property {number} index The index of `operation` in the array of operations. + @property {boolean} split Whether or not the item index searched for would + require a split for a new operation type. + @property {number} rangeStart The index of the first item in the operation, + with respect to the tracked array. The index of the last item can be computed + from `rangeStart` and `operation.count`. +*/ +function ArrayOperationMatch(operation, index, split, rangeStart) { + this.operation = operation; + this.index = index; + this.split = split; + this.rangeStart = rangeStart; +} + +})(); + + + +(function() { +var get = Ember.get, + forEach = Ember.EnumerableUtils.forEach, + RETAIN = 'r', + FILTER = 'f'; + +function Operation (type, count) { + this.type = type; + this.count = count; +} + +/** + An `Ember.SubArray` tracks an array in a way similar to, but more specialized + than, `Ember.TrackedArray`. It is useful for keeping track of the indexes of + items within a filtered array. + + @class SubArray + @namespace Ember +*/ +Ember.SubArray = function (length) { + if (arguments.length < 1) { length = 0; } + + if (length > 0) { + this._operations = [new Operation(RETAIN, length)]; + } else { + this._operations = []; + } +}; + +Ember.SubArray.prototype = { + /** + Track that an item was added to the tracked array. + + @method addItem + + @param {number} index The index of the item in the tracked array. + @param {boolean} match `true` iff the item is included in the subarray. + + @return {number} The index of the item in the subarray. + */ + addItem: function(index, match) { + var returnValue = -1, + itemType = match ? RETAIN : FILTER, + self = this; + + this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { + var newOperation, splitOperation; + + if (itemType === operation.type) { + ++operation.count; + } else if (index === rangeStart) { + // insert to the left of `operation` + self._operations.splice(operationIndex, 0, new Operation(itemType, 1)); + } else { + newOperation = new Operation(itemType, 1); + splitOperation = new Operation(operation.type, rangeEnd - index + 1); + operation.count = index - rangeStart; + + self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation); + } + + if (match) { + if (operation.type === RETAIN) { + returnValue = seenInSubArray + (index - rangeStart); + } else { + returnValue = seenInSubArray; + } + } + + self._composeAt(operationIndex); + }, function(seenInSubArray) { + self._operations.push(new Operation(itemType, 1)); + + if (match) { + returnValue = seenInSubArray; + } + + self._composeAt(self._operations.length-1); + }); + + return returnValue; + }, + + /** + Track that an item was removed from the tracked array. + + @method removeItem + + @param {number} index The index of the item in the tracked array. + + @return {number} The index of the item in the subarray, or `-1` if the item + was not in the subarray. + */ + removeItem: function(index) { + var returnValue = -1, + self = this; + + this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) { + if (operation.type === RETAIN) { + returnValue = seenInSubArray + (index - rangeStart); + } + + if (operation.count > 1) { + --operation.count; + } else { + self._operations.splice(operationIndex, 1); + self._composeAt(operationIndex); + } + }, function() { + throw new Ember.Error("Can't remove an item that has never been added."); + }); + + return returnValue; + }, + + + _findOperation: function (index, foundCallback, notFoundCallback) { + var operationIndex, + len, + operation, + rangeStart, + rangeEnd, + seenInSubArray = 0; + + // OPTIMIZE: change to balanced tree + // find leftmost operation to the right of `index` + for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) { + operation = this._operations[operationIndex]; + rangeEnd = rangeStart + operation.count - 1; + + if (index >= rangeStart && index <= rangeEnd) { + foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray); + return; + } else if (operation.type === RETAIN) { + seenInSubArray += operation.count; + } + } + + notFoundCallback(seenInSubArray); + }, + + _composeAt: function(index) { + var op = this._operations[index], + otherOp; + + if (!op) { + // Composing out of bounds is a no-op, as when removing the last operation + // in the list. + return; + } + + if (index > 0) { + otherOp = this._operations[index-1]; + if (otherOp.type === op.type) { + op.count += otherOp.count; + this._operations.splice(index-1, 1); + --index; + } + } + + if (index < this._operations.length-1) { + otherOp = this._operations[index+1]; + if (otherOp.type === op.type) { + op.count += otherOp.count; + this._operations.splice(index+1, 1); + } + } + } +}; })(); @@ -12225,7 +15445,11 @@ function makeCtor() { Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin)); - for (var keyName in properties) { + if (Ember.typeOf(properties) !== 'object') { continue; } + + var keyNames = Ember.keys(properties); + for (var j = 0, ll = keyNames.length; j < ll; j++) { + var keyName = keyNames[j]; if (!properties.hasOwnProperty(keyName)) { continue; } var value = properties[keyName], @@ -12245,6 +15469,7 @@ function makeCtor() { Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty)); Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1)); + Ember.assert("`actions` must be provided at extend time, not at create time, when Ember.ActionHandler is used (i.e. views, controllers & routes).", !((keyName === 'actions') && Ember.ActionHandler.detect(this))); if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) { var baseValue = this[keyName]; @@ -12275,9 +15500,9 @@ function makeCtor() { } } finishPartial(this, m); + this.init.apply(this, arguments); m.proto = proto; finishChains(this); - this.init.apply(this, arguments); sendEvent(this, "init"); }; @@ -12410,7 +15635,10 @@ CoreObject.PrototypeMixin = Mixin.create({ are also concatenated, in addition to `classNames`. This feature is available for you to use throughout the Ember object model, - although typical app developers are likely to use it infrequently. + although typical app developers are likely to use it infrequently. Since + it changes expectations about behavior of properties, you should properly + document its usage in each individual concatenated property (to not + mislead your users to think they can override the property in a subclass). @property concatenatedProperties @type Array @@ -12550,6 +15778,86 @@ var ClassMixin = Mixin.create({ isMethod: false, + /** + Creates a new subclass. + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + alert(thing); + } + }); + ``` + + This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`. + + You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class: + + ```javascript + App.PersonView = Ember.View.extend({ + tagName: 'li', + classNameBindings: ['isAdministrator'] + }); + ``` + + When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method: + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + var name = this.get('name'); + alert(name + ' says: ' + thing); + } + }); + + App.Soldier = App.Person.extend({ + say: function(thing) { + this._super(thing + ", sir!"); + }, + march: function(numberOfHours) { + alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.') + } + }); + + var yehuda = App.Soldier.create({ + name: "Yehuda Katz" + }); + + yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!" + ``` + + The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method. + + You can also pass `Ember.Mixin` classes to add additional properties to the subclass. + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + alert(this.get('name') + ' says: ' + thing); + } + }); + + App.SingingMixin = Ember.Mixin.create({ + sing: function(thing){ + alert(this.get('name') + ' sings: la la la ' + thing); + } + }); + + App.BroadwayStar = App.Person.extend(App.SingingMixin, { + dance: function() { + alert(this.get('name') + ' dances: tap tap tap tap '); + } + }); + ``` + + The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`. + + @method extend + @static + + @param {Ember.Mixin} [mixins]* One or more Ember.Mixin classes + @param {Object} [arguments]* Object containing values to use within the new class + */ extend: function() { var Class = makeCtor(), proto; Class.ClassMixin = Mixin.create(this.ClassMixin); @@ -12629,12 +15937,98 @@ var ClassMixin = Mixin.create({ return new C(); }, + /** + + Augments a constructor's prototype with additional + properties and functions: + + ```javascript + MyObject = Ember.Object.extend({ + name: 'an object' + }); + + o = MyObject.create(); + o.get('name'); // 'an object' + + MyObject.reopen({ + say: function(msg){ + console.log(msg); + } + }) + + o2 = MyObject.create(); + o2.say("hello"); // logs "hello" + + o.say("goodbye"); // logs "goodbye" + ``` + + To add functions and properties to the constructor itself, + see `reopenClass` + + @method reopen + */ reopen: function() { this.willReopen(); reopen.apply(this.PrototypeMixin, arguments); return this; }, + /** + Augments a constructor's own properties and functions: + + ```javascript + MyObject = Ember.Object.extend({ + name: 'an object' + }); + + + MyObject.reopenClass({ + canBuild: false + }); + + MyObject.canBuild; // false + o = MyObject.create(); + ``` + + In other words, this creates static properties and functions for the class. These are only available on the class + and not on any instance of that class. + + ```javascript + App.Person = Ember.Object.extend({ + name : "", + sayHello : function(){ + alert("Hello. My name is " + this.get('name')); + } + }); + + App.Person.reopenClass({ + species : "Homo sapiens", + createPerson: function(newPersonsName){ + return App.Person.create({ + name:newPersonsName + }); + } + }); + + var tom = App.Person.create({ + name : "Tom Dale" + }); + var yehuda = App.Person.createPerson("Yehuda Katz"); + + tom.sayHello(); // "Hello. My name is Tom Dale" + yehuda.sayHello(); // "Hello. My name is Yehuda Katz" + alert(App.Person.species); // "Homo sapiens" + ``` + + Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda` + variables. They are only valid on `App.Person`. + + To add functions and properties to instances of + a constructor by extending the constructor's prototype + see `reopen` + + @method reopenClass + */ reopenClass: function() { reopen.apply(this.ClassMixin, arguments); applyMixin(this, arguments, false); @@ -12913,6 +16307,8 @@ function classToString() { if (this[NAME_KEY]) { ret = this[NAME_KEY]; + } else if (this._toString) { + ret = this._toString; } else { var str = superClassString(this); if (str) { @@ -13191,7 +16587,7 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.Array }, _insertAt: function(idx, object) { - if (idx > get(this, 'content.length')) throw new Error(OUT_OF_RANGE_EXCEPTION); + if (idx > get(this, 'content.length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION); this._replace(idx, 0, [object]); return this; }, @@ -13211,7 +16607,7 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.Array indices = [], i; if ((start < 0) || (start >= get(this, 'length'))) { - throw new Error(OUT_OF_RANGE_EXCEPTION); + throw new Ember.Error(OUT_OF_RANGE_EXCEPTION); } if (len === undefined) len = 1; @@ -13309,7 +16705,9 @@ var get = Ember.get, removeBeforeObserver = Ember.removeBeforeObserver, removeObserver = Ember.removeObserver, propertyWillChange = Ember.propertyWillChange, - propertyDidChange = Ember.propertyDidChange; + propertyDidChange = Ember.propertyDidChange, + meta = Ember.meta, + defineProperty = Ember.defineProperty; function contentPropertyWillChange(content, contentKey) { var key = contentKey.slice(8); // remove "content." @@ -13427,6 +16825,14 @@ Ember.ObjectProxy = Ember.Object.extend(/** @scope Ember.ObjectProxy.prototype * }, setUnknownProperty: function (key, value) { + var m = meta(this); + if (m.proto === this) { + // if marked as prototype then just defineProperty + // rather than delegate + defineProperty(this, key, null, value); + return value; + } + var content = get(this, 'content'); Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content); return set(content, key, value); @@ -13434,25 +16840,6 @@ Ember.ObjectProxy = Ember.Object.extend(/** @scope Ember.ObjectProxy.prototype * }); -Ember.ObjectProxy.reopenClass({ - create: function () { - var mixin, prototype, i, l, properties, keyName; - if (arguments.length) { - prototype = this.proto(); - for (i = 0, l = arguments.length; i < l; i++) { - properties = arguments[i]; - for (keyName in properties) { - if (!properties.hasOwnProperty(keyName) || keyName in prototype) { continue; } - if (!mixin) mixin = {}; - mixin[keyName] = null; - } - } - if (mixin) this._initMixins([mixin]); - } - return this._super.apply(this, arguments); - } -}); - })(); @@ -13465,7 +16852,8 @@ Ember.ObjectProxy.reopenClass({ var set = Ember.set, get = Ember.get, guidFor = Ember.guidFor; -var forEach = Ember.EnumerableUtils.forEach; +var forEach = Ember.EnumerableUtils.forEach, + indexOf = Ember.ArrayPolyfills.indexOf; var EachArray = Ember.Object.extend(Ember.Array, { @@ -13523,7 +16911,7 @@ function removeObserverForContentKey(content, keyName, proxy, idx, loc) { guid = guidFor(item); indicies = objects[guid]; - indicies[indicies.indexOf(loc)] = null; + indicies[indexOf.call(indicies, loc)] = null; } } } @@ -13672,7 +17060,7 @@ Ember.EachProxy = Ember.Object.extend({ */ -var get = Ember.get, set = Ember.set; +var get = Ember.get, set = Ember.set, replace = Ember.EnumerableUtils._replace; // Add Ember.Array to Array.prototype. Remove methods with native // implementations and supply some more optimized versions of generic methods @@ -13694,7 +17082,7 @@ var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember // primitive for array support. replace: function(idx, amt, objects) { - if (this.isFrozen) throw Ember.FROZEN_ERROR ; + if (this.isFrozen) throw Ember.FROZEN_ERROR; // if we replaced exactly the same number of items, then pass only the // replaced range. Otherwise, pass the full remaining array length @@ -13703,14 +17091,13 @@ var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember this.arrayContentWillChange(idx, amt, len); if (!objects || objects.length === 0) { - this.splice(idx, amt) ; + this.splice(idx, amt); } else { - var args = [idx, amt].concat(objects) ; - this.splice.apply(this,args) ; + replace(this, idx, amt, objects); } this.arrayContentDidChange(idx, amt, len); - return this ; + return this; }, // If you ask for an unknown property, then try to collect the value @@ -13787,7 +17174,26 @@ Ember.NativeArray = NativeArray; /** Creates an `Ember.NativeArray` from an Array like object. - Does not modify the original object. + Does not modify the original object. Ember.A is not needed if + `Ember.EXTEND_PROTOTYPES` is `true` (the default value). However, + it is recommended that you use Ember.A when creating addons for + ember or when you can not garentee that `Ember.EXTEND_PROTOTYPES` + will be `true`. + + Example + + ```js + var Pagination = Ember.CollectionView.extend({ + tagName: 'ul', + classNames: ['pagination'], + init: function() { + this._super(); + if (!this.get('content')) { + this.set('content', Ember.A([])); + } + } + }); + ``` @method A @for Ember @@ -13800,7 +17206,17 @@ Ember.A = function(arr) { /** Activates the mixin on the Array.prototype if not already applied. Calling - this method more than once is safe. + this method more than once is safe. This will be called when ember is loaded + unless you have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` + set to `false`. + + Example + + ```js + if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) { + Ember.NativeArray.activate(); + } + ``` @method activate @for Ember.NativeArray @@ -13895,8 +17311,8 @@ var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, isNone = Ember.is When using `Ember.Set`, you can observe the `"[]"` property to be alerted whenever the content changes. You can also add an enumerable observer to the set to be notified of specific objects that are added and - removed from the set. See `Ember.Enumerable` for more information on - enumerables. + removed from the set. See [Ember.Enumerable](/api/classes/Ember.Enumerable.html) + for more information on enumerables. This is often unhelpful. If you are filtering sets of objects, for instance, it is very inefficient to re-filter all of the items each time the set @@ -13958,7 +17374,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb @return {Ember.Set} An empty Set */ clear: function() { - if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); } + if (this.isFrozen) { throw new Ember.Error(Ember.FROZEN_ERROR); } var len = get(this, 'length'); if (len === 0) { return this; } @@ -14068,7 +17484,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb @return {Object} The removed object from the set or null. */ pop: function() { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR); var obj = this.length > 0 ? this[this.length-1] : null; this.remove(obj); return obj; @@ -14185,7 +17601,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb // implements Ember.MutableEnumerable addObject: function(obj) { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR); if (isNone(obj)) return this; // nothing to do var guid = guidFor(obj), @@ -14213,7 +17629,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb // implements Ember.MutableEnumerable removeObject: function(obj) { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR); if (isNone(obj)) return this; // nothing to do var guid = guidFor(obj), @@ -14311,6 +17727,20 @@ var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {}; var loaded = {}; /** + +Detects when a specific package of Ember (e.g. 'Ember.Handlebars') +has fully loaded and is available for extension. + +The provided `callback` will be called with the `name` passed +resolved from a string into the object: + +```javascript +Ember.onLoad('Ember.Handlebars' function(hbars){ + hbars.registerHelper(...); +}); +``` + + @method onLoad @for Ember @param name {String} name of hook @@ -14328,6 +17758,10 @@ Ember.onLoad = function(name, callback) { }; /** + +Called when an Ember.js package (e.g Ember.Handlebars) has finished +loading. Triggers any callbacks registered for this event. + @method runLoadHooks @for Ember @param name {String} name of hook @@ -14366,39 +17800,18 @@ var get = Ember.get; compose Ember's controller layer: `Ember.Controller`, `Ember.ArrayController`, and `Ember.ObjectController`. - Within an `Ember.Router`-managed application single shared instaces of every - Controller object in your application's namespace will be added to the - application's `Ember.Router` instance. See `Ember.Application#initialize` - for additional information. - - ## Views - - By default a controller instance will be the rendering context - for its associated `Ember.View.` This connection is made during calls to - `Ember.ControllerMixin#connectOutlet`. - - Within the view's template, the `Ember.View` instance can be accessed - through the controller with `{{view}}`. - - ## Target Forwarding - - By default a controller will target your application's `Ember.Router` - instance. Calls to `{{action}}` within the template of a controller's view - are forwarded to the router. See `Ember.Handlebars.helpers.action` for - additional information. - @class ControllerMixin @namespace Ember */ -Ember.ControllerMixin = Ember.Mixin.create({ +Ember.ControllerMixin = Ember.Mixin.create(Ember.ActionHandler, { /* ducktype as a controller */ isController: true, /** - The object to which events from the view should be sent. + The object to which actions from the view should be sent. For example, when a Handlebars template uses the `{{action}}` helper, - it will attempt to send the event to the view's controller's `target`. + it will attempt to send the action to the view's controller's `target`. By default, a controller's `target` is set to the router after it is instantiated by `Ember.Application#initialize`. @@ -14416,16 +17829,16 @@ Ember.ControllerMixin = Ember.Mixin.create({ model: Ember.computed.alias('content'), - send: function(actionName) { - var args = [].slice.call(arguments, 1), target; + deprecatedSendHandles: function(actionName) { + return !!this[actionName]; + }, - if (this[actionName]) { - Ember.assert("The controller " + this + " does not have the action " + actionName, typeof this[actionName] === 'function'); - this[actionName].apply(this, args); - } else if (target = get(this, 'target')) { - Ember.assert("The target for controller " + this + " (" + target + ") did not define a `send` method", typeof target.send === 'function'); - target.send.apply(target, arguments); - } + deprecatedSend: function(actionName) { + var args = [].slice.call(arguments, 1); + Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); + Ember.deprecate('Action handlers implemented directly on controllers are deprecated in favor of action handlers on an `actions` object (' + actionName + ' on ' + this + ')', false); + this[actionName].apply(this, args); + return; } }); @@ -14474,6 +17887,29 @@ var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach; songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'} ``` + If you add or remove the properties to sort by or change the sort direction the content + sort order will be automatically updated. + + ```javascript + songsController.set('sortProperties', ['title']); + songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'} + + songsController.toggleProperty('sortAscending'); + songsController.get('firstObject'); // {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'} + ``` + + SortableMixin works by sorting the arrangedContent array, which is the array that + arrayProxy displays. Due to the fact that the underlying 'content' array is not changed, that + array will not display the sorted list: + + ```javascript + songsController.get('content').get('firstObject'); // Returns the unsorted original content + songsController.get('firstObject'); // Returns the sorted content. + ``` + + Although the sorted content can also be accessed through the arrangedContent property, + it is preferable to use the proxied class and not the arrangedContent array directly. + @class SortableMixin @namespace Ember @uses Ember.MutableEnumerable @@ -14483,6 +17919,9 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { /** Specifies which properties dictate the arrangedContent's sort order. + When specifying multiple properties the sorting will use properties + from the `sortProperties` array prioritized from first to last. + @property {Array} sortProperties */ sortProperties: null, @@ -14496,7 +17935,7 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { /** The function used to compare two values. You can override this if you - want to do custom comparisons.Functions must be of the type expected by + want to do custom comparisons. Functions must be of the type expected by Array#sort, i.e. return 0 if the two parameters are equal, return a negative value if the first parameter is smaller than the second or @@ -14553,6 +17992,13 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { isSorted: Ember.computed.bool('sortProperties'), + /** + Overrides the default arrangedContent from arrayProxy in order to sort by sortFunction. + Also sets up observers for each sortProperty on each item in the content Array. + + @property arrangedContent + */ + arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) { var content = get(this, 'content'), isSorted = get(this, 'isSorted'), @@ -14870,11 +18316,15 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, }, init: function() { - if (!this.get('content')) { Ember.defineProperty(this, 'content', undefined, Ember.A()); } this._super(); + this.set('_subControllers', Ember.A()); }, + content: Ember.computed(function () { + return Ember.A(); + }), + controllerAt: function(idx, object, controllerClass) { var container = get(this, 'container'), subControllers = get(this, '_subControllers'), @@ -14886,7 +18336,7 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, fullName = "controller:" + controllerClass; if (!container.has(fullName)) { - throw new Error('Could not resolve itemController: "' + controllerClass + '"'); + throw new Ember.Error('Could not resolve itemController: "' + controllerClass + '"'); } subController = container.lookupFactory(fullName).create({ @@ -14925,9 +18375,11 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, */ /** - `Ember.ObjectController` is part of Ember's Controller layer. + `Ember.ObjectController` is part of Ember's Controller layer. It is intended + to wrap a single object, proxying unhandled attempts to `get` and `set` to the underlying + content object, and to forward unhandled action attempts to its `target`. - `Ember.ObjectController` derives its functionality from its superclass + `Ember.ObjectController` derives this functionality from its superclass `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin. @class ObjectController @@ -15206,14 +18658,32 @@ function escapeAttribute(value) { return string.replace(BAD_CHARS_REGEXP, escapeChar); } +// IE 6/7 have bugs arond setting names on inputs during creation. +// From http://msdn.microsoft.com/en-us/library/ie/ms536389(v=vs.85).aspx: +// "To include the NAME attribute at run time on objects created with the createElement method, use the eTag." +var canSetNameOnInputs = (function() { + var div = document.createElement('div'), + el = document.createElement('input'); + + el.setAttribute('name', 'foo'); + div.appendChild(el); + + return !!div.innerHTML.match('foo'); +})(); + /** `Ember.RenderBuffer` gathers information regarding the a view and generates the final representation. `Ember.RenderBuffer` will generate HTML which can be pushed to the DOM. + ```javascript + var buffer = Ember.RenderBuffer('div'); + ``` + @class RenderBuffer @namespace Ember @constructor + @param {String} tagName tag name (such as 'div' or 'p') used for the buffer */ Ember.RenderBuffer = function(tagName) { return new Ember._RenderBuffer(tagName); @@ -15560,14 +19030,22 @@ Ember._RenderBuffer.prototype = generateElement: function() { var tagName = this.tagNames.pop(), // pop since we don't need to close - element = document.createElement(tagName), - $element = Ember.$(element), id = this.elementId, classes = this.classes, attrs = this.elementAttributes, props = this.elementProperties, style = this.elementStyle, - styleBuffer = '', attr, prop; + styleBuffer = '', attr, prop, tagString; + + if (attrs && attrs.name && !canSetNameOnInputs) { + // IE allows passing a tag to createElement. See note on `canSetNameOnInputs` above as well. + tagString = '<'+stripTagName(tagName)+' name="'+escapeAttribute(attrs.name)+'">'; + } else { + tagString = tagName; + } + + var element = document.createElement(tagString), + $element = Ember.$(element); if (id) { $element.attr('id', id); @@ -15962,6 +19440,7 @@ var get = Ember.get, set = Ember.set; var guidFor = Ember.guidFor; var a_forEach = Ember.EnumerableUtils.forEach; var a_addObject = Ember.EnumerableUtils.addObject; +var meta = Ember.meta; var childViewsProperty = Ember.computed(function() { var childViews = this._childViews, ret = Ember.A(), view = this; @@ -15982,7 +19461,7 @@ var childViewsProperty = Ember.computed(function() { Ember.deprecate("Manipulating an Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray."); return view.replace(idx, removedCount, addedViews); } - throw new Error("childViews is immutable"); + throw new Ember.Error("childViews is immutable"); }; return ret; @@ -16002,7 +19481,13 @@ Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionali Ember.TEMPLATES = {}; /** - `Ember.CoreView` is + `Ember.CoreView` is an abstract class that exists to give view-like behavior + to both Ember's main view class `Ember.View` and other classes like + `Ember._SimpleMetamorphView` that don't need the fully functionaltiy of + `Ember.View`. + + Unless you have specific needs for `CoreView`, you will use `Ember.View` + in your applications. @class CoreView @namespace Ember @@ -16010,7 +19495,7 @@ Ember.TEMPLATES = {}; @uses Ember.Evented */ -Ember.CoreView = Ember.Object.extend(Ember.Evented, { +Ember.CoreView = Ember.Object.extend(Ember.Evented, Ember.ActionHandler, { isView: true, states: states, @@ -16125,6 +19610,18 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, { } }, + deprecatedSendHandles: function(actionName) { + return !!this[actionName]; + }, + + deprecatedSend: function(actionName) { + var args = [].slice.call(arguments, 1); + Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function'); + Ember.deprecate('Action handlers implemented directly on views are deprecated in favor of action handlers on an `actions` object (' + actionName + ' on ' + this + ')', false); + this[actionName].apply(this, args); + return; + }, + has: function(name) { return Ember.typeOf(this[name]) === 'function' || this._super(name); }, @@ -16226,7 +19723,7 @@ var EMPTY_ARRAY = []; The default HTML tag name used for a view's DOM representation is `div`. This can be customized by setting the `tagName` property. The following view -class: + class: ```javascript ParagraphView = Ember.View.extend({ @@ -16400,8 +19897,8 @@ class: will be removed. Both `classNames` and `classNameBindings` are concatenated properties. See - `Ember.Object` documentation for more information about concatenated - properties. + [Ember.Object](/api/classes/Ember.Object.html) documentation for more + information about concatenated properties. ## HTML Attributes @@ -16461,7 +19958,7 @@ class: Updates to the the property of an attribute binding will result in automatic update of the HTML attribute in the view's rendered HTML representation. - `attributeBindings` is a concatenated property. See `Ember.Object` + `attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html) documentation for more information about concatenated properties. ## Templates @@ -16504,9 +20001,6 @@ class: Using a value for `templateName` that does not have a Handlebars template with a matching `data-template-name` attribute will throw an error. - Assigning a value to both `template` and `templateName` properties will throw - an error. - For views classes that may have a template later defined (e.g. as the block portion of a `{{view}}` Handlebars helper call in another template or in a subclass), you can provide a `defaultTemplate` property set to compiled @@ -16612,7 +20106,8 @@ class: ``` - See `Handlebars.helpers.yield` for more information. + See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield) + for more information. ## Responding to Browser Events @@ -16709,7 +20204,7 @@ class: ### Handlebars `{{action}}` Helper - See `Handlebars.helpers.action`. + See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.helpers.html#method_action). ### Event Names @@ -16764,8 +20259,8 @@ class: ## Handlebars `{{view}}` Helper Other `Ember.View` instances can be included as part of a view's template by - using the `{{view}}` Handlebars helper. See `Handlebars.helpers.view` for - additional information. + using the `{{view}}` Handlebars helper. See [Ember.Handlebars.helpers.view](/api/classes/Ember.Handlebars.helpers.html#method_view) + for additional information. @class View @namespace Ember @@ -17694,12 +21189,6 @@ Ember.View = Ember.CoreView.extend( return viewCollection; }, - _elementWillChange: Ember.beforeObserver(function() { - this.forEachChildView(function(view) { - Ember.propertyWillChange(view, 'element'); - }); - }, 'element'), - /** @private @@ -17711,7 +21200,7 @@ Ember.View = Ember.CoreView.extend( */ _elementDidChange: Ember.observer(function() { this.forEachChildView(function(view) { - Ember.propertyDidChange(view, 'element'); + delete meta(view).cache.element; }); }, 'element'), @@ -17806,7 +21295,7 @@ Ember.View = Ember.CoreView.extend( visually challenged users navigate rich web applications. The full list of valid WAI-ARIA roles is available at: - http://www.w3.org/TR/wai-aria/roles#roles_categorization + [http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization) @property ariaRole @type String @@ -18038,6 +21527,10 @@ Ember.View = Ember.CoreView.extend( @return {Ember.View} new instance */ createChildView: function(view, attrs) { + if (!view) { + throw new TypeError("createChildViews first argument must exist"); + } + if (view.isView && view._parentView === this && view.container === this.container) { return view; } @@ -18155,6 +21648,7 @@ Ember.View = Ember.CoreView.extend( if (priorState && priorState.exit) { priorState.exit(this); } if (currentState.enter) { currentState.enter(this); } + if (state === 'inDOM') { delete Ember.meta(this).cache.element; } if (children !== false) { this.forEachChildView(function(view) { @@ -18186,6 +21680,10 @@ Ember.View = Ember.CoreView.extend( target = null; } + if (!root || typeof root !== 'object') { + return; + } + var view = this, stateCheckedObserver = function() { view.currentState.invokeObserver(this, observer); @@ -18281,7 +21779,7 @@ Ember.View.reopenClass({ Parse a path and return an object which holds the parsed properties. - For example a path like "content.isEnabled:enabled:disabled" wil return the + For example a path like "content.isEnabled:enabled:disabled" will return the following object: ```javascript @@ -18502,10 +22000,18 @@ Ember.merge(preRender, { var viewCollection = view.viewHierarchyCollection(); viewCollection.trigger('willInsertElement'); - // after createElement, the view will be in the hasElement state. + fn.call(view); - viewCollection.transitionTo('inDOM', false); - viewCollection.trigger('didInsertElement'); + + // We transition to `inDOM` if the element exists in the DOM + var element = view.get('element'); + while (element = element.parentNode) { + if (element === document) { + viewCollection.transitionTo('inDOM', false); + viewCollection.trigger('didInsertElement'); + } + } + }, renderToBufferIfNeeded: function(view, buffer) { @@ -18714,7 +22220,7 @@ Ember.merge(inDOM, { } view.addBeforeObserver('elementId', function() { - throw new Error("Changing a view's elementId after creation is not allowed"); + throw new Ember.Error("Changing a view's elementId after creation is not allowed"); }); }, @@ -18954,30 +22460,6 @@ var ViewCollection = Ember._ViewCollection; or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM representation will only be the rendered HTML of its child views. - ## Binding a View to Display - - If you would like to display a single view in your ContainerView, you can set - its `currentView` property. When the `currentView` property is set to a view - instance, it will be added to the ContainerView. If the `currentView` property - is later changed to a different view, the new view will replace the old view. - If `currentView` is set to `null`, the last `currentView` will be removed. - - This functionality is useful for cases where you want to bind the display of - a ContainerView to a controller or state manager. For example, you can bind - the `currentView` of a container to a controller like this: - - ```javascript - App.appController = Ember.Object.create({ - view: Ember.View.create({ - templateName: 'person_template' - }) - }); - ``` - - ```handlebars - {{view Ember.ContainerView currentViewBinding="App.appController.view"}} - ``` - @class ContainerView @namespace Ember @extends Ember.View @@ -19044,7 +22526,7 @@ Ember.ContainerView = Ember.View.extend(Ember.MutableArray, { length: Ember.computed(function () { return this._childViews.length; - }), + }).volatile(), /** @private @@ -19162,7 +22644,7 @@ Ember.merge(states._default, { Ember.merge(states.inBuffer, { childViewsDidChange: function(parentView, views, start, added) { - throw new Error('You cannot modify child views while in the inBuffer state'); + throw new Ember.Error('You cannot modify child views while in the inBuffer state'); } }); @@ -19374,11 +22856,6 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt; manipulated. Instead, add, remove, replace items from its `content` property. This will trigger appropriate changes to its rendered HTML. - ## Use in templates via the `{{collection}}` `Ember.Handlebars` helper - - `Ember.Handlebars` provides a helper specifically for adding - `CollectionView`s to templates. See `Ember.Handlebars.collection` for more - details @class CollectionView @namespace Ember @@ -19423,12 +22900,25 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie */ itemViewClass: Ember.View, + /** + Setup a CollectionView + + @method init + */ init: function() { var ret = this._super(); this._contentDidChange(); return ret; }, + /** + @private + + Invoked when the content property is about to change. Notifies observers that the + entire array content will change. + + @method _contentWillChange + */ _contentWillChange: Ember.beforeObserver(function() { var content = this.get('content'); @@ -19459,10 +22949,22 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie this.arrayDidChange(content, 0, null, len); }, 'content'), + /** + @private + + Ensure that the content implements Ember.Array + + @method _assertArrayLike + */ _assertArrayLike: function(content) { Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content)); }, + /** + Removes the content and content observers. + + @method destroy + */ destroy: function() { if (!this._super()) { return; } @@ -19476,6 +22978,19 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie return this; }, + /** + Called when a mutation to the underlying content array will occur. + + This method will remove any views that are no longer in the underlying + content array. + + Invokes whenever the content array itself will change. + + @method arrayWillChange + @param {Array} content the managed collection of objects + @param {Number} start the index at which the changes will occurr + @param {Number} removed number of object to be removed from content + */ arrayWillChange: function(content, start, removedCount) { // If the contents were empty before and this template collection has an // empty view remove it now. @@ -19566,6 +23081,21 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie this.replace(start, 0, addedViews); }, + /** + Instantiates a view to be added to the childViews array during view + initialization. You generally will not call this method directly unless + you are overriding `createChildViews()`. Note that this method will + automatically configure the correct settings on the new view instance to + act as a child of the parent. + + The tag name for the view will be set to the tagName of the viewClass + passed in. + + @method createChildView + @param {Class} viewClass + @param {Hash} [attrs] Attributes to add + @return {Ember.View} new instance + */ createChildView: function(view, attrs) { view = this._super(view, attrs); @@ -19606,7 +23136,9 @@ Ember.CollectionView.CONTAINER_MAP = { (function() { -var get = Ember.get, set = Ember.set, isNone = Ember.isNone; +var get = Ember.get, set = Ember.set, isNone = Ember.isNone, + a_slice = Array.prototype.slice; + /** @module ember @@ -19634,7 +23166,7 @@ var get = Ember.get, set = Ember.set, isNone = Ember.isNone; ```html

    {{person.title}}

    - +

    {{person.signature}}

    ``` @@ -19656,15 +23188,18 @@ var get = Ember.get, set = Ember.set, isNone = Ember.isNone; If you want to customize the component, in order to handle events or actions, you implement a subclass of `Ember.Component` named after the name of the - component. + component. Note that `Component` needs to be appended to the name of + your subclass like `AppProfileComponent`. For example, you could implement the action `hello` for the `app-profile` component: - ```js + ```javascript App.AppProfileComponent = Ember.Component.extend({ - hello: function(name) { - console.log("Hello", name) + actions: { + hello: function(name) { + console.log("Hello", name); + } } }); ``` @@ -19717,7 +23252,8 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { view.appendChild(Ember.View, { isVirtual: true, tagName: '', - template: get(this, 'template'), + _contextView: parentView, + template: template, context: get(parentView, 'context'), controller: get(parentView, 'controller'), templateData: { keywords: parentView.cloneKeywords() } @@ -19725,6 +23261,14 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { } }, + /** + If the component is currently inserted into the DOM of a parent view, this + property will point to the controller of the parent view. + + @property targetObject + @type Ember.Controller + @default null + */ targetObject: Ember.computed(function(key) { var parentView = get(this, '_parentView'); return parentView ? get(parentView, 'controller') : null; @@ -19761,15 +23305,17 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { target's action method. Example: ```javascript - App.MyTree = Ember.Component.extend({ + App.MyTreeComponent = Ember.Component.extend({ click: function() { this.sendAction('didClickTreeNode', this.get('node')); } }); App.CategoriesController = Ember.Controller.extend({ - didClickCategory: function(category) { - //Do something with the node/category that was clicked + actions: { + didClickCategory: function(category) { + //Do something with the node/category that was clicked + } } }); ``` @@ -19783,8 +23329,9 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { @param [action] {String} the action to trigger @param [context] {*} a context to send with the action */ - sendAction: function(action, context) { - var actionName; + sendAction: function(action) { + var actionName, + contexts = a_slice.call(arguments, 1); // Send the default action if (action === undefined) { @@ -19800,7 +23347,7 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { this.triggerAction({ action: actionName, - actionContext: context + actionContext: contexts }); } }); @@ -20013,6 +23560,14 @@ define("metamorph", range.insertNode(fragment); }; + /** + * @public + * + * Remove this object (including starting and ending + * placeholders). + * + * @method remove + */ removeFunc = function() { // get a range for the current metamorph object including // the starting and ending placeholders. @@ -20053,7 +23608,7 @@ define("metamorph", }; } else { - /** + /* * This code is mostly taken from jQuery, with one exception. In jQuery's case, we * have some HTML and we need to figure out how to convert it into some nodes. * @@ -20107,12 +23662,12 @@ define("metamorph", } }; - /** + /* * Given a parent node and some HTML, generate a set of nodes. Return the first * node, which will allow us to traverse the rest using nextSibling. * * We need to do this because innerHTML in IE does not really parse the nodes. - **/ + */ var firstNodeFor = function(parentNode, html) { var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default; var depth = arr[0], start = arr[1], end = arr[2]; @@ -20145,7 +23700,7 @@ define("metamorph", return element; }; - /** + /* * In some cases, Internet Explorer can create an anonymous node in * the hierarchy with no tagName. You can create this scenario via: * @@ -20155,7 +23710,7 @@ define("metamorph", * * If our script markers are inside such a node, we need to find that * node and use *it* as the marker. - **/ + */ var realNode = function(start) { while (start.parentNode.tagName === "") { start = start.parentNode; @@ -20164,7 +23719,7 @@ define("metamorph", return start; }; - /** + /* * When automatically adding a tbody, Internet Explorer inserts the * tbody immediately before the first . Other browsers create it * before the first node, no matter what. @@ -20191,7 +23746,8 @@ define("metamorph", * * This code reparents the first script tag by making it the tbody's * first child. - **/ + * + */ var fixParentage = function(start, end) { if (start.parentNode !== end.parentNode) { end.parentNode.insertBefore(start, end.parentNode.firstChild); @@ -20390,20 +23946,6 @@ Ember.assert("Ember Handlebars requires Handlebars version 1.0.0, COMPILER_REVIS */ Ember.Handlebars = objectCreate(Handlebars); -function makeBindings(options) { - var hash = options.hash, - hashType = options.hashTypes; - - for (var prop in hash) { - if (hashType[prop] === 'ID') { - hash[prop + 'Binding'] = hash[prop]; - hashType[prop + 'Binding'] = 'STRING'; - delete hash[prop]; - delete hashType[prop]; - } - } -} - /** Register a bound helper or custom view helper. @@ -20458,21 +24000,11 @@ function makeBindings(options) { @param {String} dependentKeys* */ Ember.Handlebars.helper = function(name, value) { - if (Ember.Component.detect(value)) { - Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", name.match(/-/)); - - var proto = value.proto(); - if (!proto.layoutName && !proto.templateName) { - value.reopen({ - layoutName: 'components/' + name - }); - } - } + Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", !Ember.Component.detect(value) || name.match(/-/)); if (Ember.View.detect(value)) { Ember.Handlebars.registerHelper(name, function(options) { Ember.assert("You can only pass attributes (such as name=value) not bare values to a helper for a View", arguments.length < 2); - makeBindings(options); return Ember.Handlebars.helpers.view.call(this, value, options); }); } else { @@ -20520,7 +24052,6 @@ if (Handlebars.JavaScriptCompiler) { Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars"; - Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() { return "''"; }; @@ -20888,61 +24419,102 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { numProperties = properties.length, options = arguments[arguments.length - 1], normalizedProperties = [], + types = options.types, data = options.data, hash = options.hash, view = data.view, - currentContext = (options.contexts && options.contexts[0]) || this, - normalized, - pathRoot, path, prefixPathForDependentKeys = '', - loc, hashOption; + contexts = options.contexts, + currentContext = (contexts && contexts.length) ? contexts[0] : this, + prefixPathForDependentKeys = '', + loc, len, hashOption, + boundOption, property, + normalizedValue = Ember._SimpleHandlebarsView.prototype.normalizedValue; Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.fn); // Detect bound options (e.g. countBinding="otherCount") - hash.boundOptions = {}; + var boundOptions = hash.boundOptions = {}; for (hashOption in hash) { - if (!hash.hasOwnProperty(hashOption)) { continue; } - - if (Ember.IS_BINDING.test(hashOption) && typeof hash[hashOption] === 'string') { + if (Ember.IS_BINDING.test(hashOption)) { // Lop off 'Binding' suffix. - hash.boundOptions[hashOption.slice(0, -7)] = hash[hashOption]; + boundOptions[hashOption.slice(0, -7)] = hash[hashOption]; } } // Expose property names on data.properties object. + var watchedProperties = []; data.properties = []; for (loc = 0; loc < numProperties; ++loc) { data.properties.push(properties[loc]); - normalizedProperties.push(normalizePath(currentContext, properties[loc], data)); + if (types[loc] === 'ID') { + var normalizedProp = normalizePath(currentContext, properties[loc], data); + normalizedProperties.push(normalizedProp); + watchedProperties.push(normalizedProp); + } else { + normalizedProperties.push(null); + } } + // Handle case when helper invocation is preceded by `unbound`, e.g. + // {{unbound myHelper foo}} if (data.isUnbound) { return evaluateUnboundHelper(this, fn, normalizedProperties, options); } - if (dependentKeys.length === 0) { - return evaluateMultiPropertyBoundHelper(currentContext, fn, normalizedProperties, options); - } - - Ember.assert("Dependent keys can only be used with single-property helpers.", properties.length === 1); - - normalized = normalizedProperties[0]; - - pathRoot = normalized.root; - path = normalized.path; - - var bindView = new Ember._SimpleHandlebarsView( - path, pathRoot, !options.hash.unescaped, options.data - ); + var bindView = new Ember._SimpleHandlebarsView(null, null, !options.hash.unescaped, options.data); + // Override SimpleHandlebarsView's method for generating the view's content. bindView.normalizedValue = function() { - var value = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView); - return fn.call(view, value, options); + var args = [], boundOption; + + // Copy over bound hash options. + for (boundOption in boundOptions) { + if (!boundOptions.hasOwnProperty(boundOption)) { continue; } + property = normalizePath(currentContext, boundOptions[boundOption], data); + bindView.path = property.path; + bindView.pathRoot = property.root; + hash[boundOption] = normalizedValue.call(bindView); + } + + for (loc = 0; loc < numProperties; ++loc) { + property = normalizedProperties[loc]; + if (property) { + bindView.path = property.path; + bindView.pathRoot = property.root; + args.push(normalizedValue.call(bindView)); + } else { + args.push(properties[loc]); + } + } + args.push(options); + + // Run the supplied helper function. + return fn.apply(currentContext, args); }; view.appendChild(bindView); - view.registerObserver(pathRoot, path, bindView, bindView.rerender); + // Assemble list of watched properties that'll re-render this helper. + for (boundOption in boundOptions) { + if (boundOptions.hasOwnProperty(boundOption)) { + watchedProperties.push(normalizePath(currentContext, boundOptions[boundOption], data)); + } + } + + // Observe each property. + for (loc = 0, len = watchedProperties.length; loc < len; ++loc) { + property = watchedProperties[loc]; + view.registerObserver(property.root, property.path, bindView, bindView.rerender); + } + + if (types[0] !== 'ID' || normalizedProperties.length === 0) { + return; + } + + // Add dependent key observers to the first param + var normalized = normalizedProperties[0], + pathRoot = normalized.root, + path = normalized.path; if(!Ember.isEmpty(path)) { prefixPathForDependentKeys = path + '.'; @@ -20956,68 +24528,6 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { Ember.Handlebars.registerHelper(name, helper); }; -/** - @private - - Renders the unbound form of an otherwise bound helper function. - - @method evaluateMultiPropertyBoundHelper - @param {Function} fn - @param {Object} context - @param {Array} normalizedProperties - @param {String} options -*/ -function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, options) { - var numProperties = normalizedProperties.length, - data = options.data, - view = data.view, - hash = options.hash, - boundOptions = hash.boundOptions, - watchedProperties, - boundOption, bindView, loc, property, len; - - bindView = new Ember._SimpleHandlebarsView(null, null, !hash.unescaped, data); - bindView.normalizedValue = function() { - var args = [], boundOption; - - // Copy over bound options. - for (boundOption in boundOptions) { - if (!boundOptions.hasOwnProperty(boundOption)) { continue; } - property = normalizePath(context, boundOptions[boundOption], data); - bindView.path = property.path; - bindView.pathRoot = property.root; - hash[boundOption] = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView); - } - - for (loc = 0; loc < numProperties; ++loc) { - property = normalizedProperties[loc]; - bindView.path = property.path; - bindView.pathRoot = property.root; - args.push(Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView)); - } - args.push(options); - return fn.apply(context, args); - }; - - view.appendChild(bindView); - - // Assemble list of watched properties that'll re-render this helper. - watchedProperties = []; - for (boundOption in boundOptions) { - if (boundOptions.hasOwnProperty(boundOption)) { - watchedProperties.push(normalizePath(context, boundOptions[boundOption], data)); - } - } - watchedProperties = watchedProperties.concat(normalizedProperties); - - // Observe each property. - for (loc = 0, len = watchedProperties.length; loc < len; ++loc) { - property = watchedProperties[loc]; - view.registerObserver(property.root, property.path, bindView, bindView.rerender); - } - -} - /** @private @@ -21039,7 +24549,7 @@ function evaluateUnboundHelper(context, fn, normalizedProperties, options) { for(loc = 0, len = normalizedProperties.length; loc < len; ++loc) { property = normalizedProperties[loc]; - args.push(Ember.Handlebars.get(context, property.path, options)); + args.push(Ember.Handlebars.get(property.root, property.path, options)); } args.push(options); return fn.apply(context, args); @@ -21067,19 +24577,19 @@ Ember.Handlebars.template = function(spec) { (function() { /** - * Mark a string as safe for unescaped output with Handlebars. If you - * return HTML from a Handlebars helper, use this function to - * ensure Handlebars does not escape the HTML. - * - * ```javascript - * Ember.String.htmlSafe('
    someString
    ') - * ``` - * - * @method htmlSafe - * @for Ember.String - * @static - * @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars - */ + Mark a string as safe for unescaped output with Handlebars. If you + return HTML from a Handlebars helper, use this function to + ensure Handlebars does not escape the HTML. + + ```javascript + Ember.String.htmlSafe('
    someString
    ') + ``` + + @method htmlSafe + @for Ember.String + @static + @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars +*/ Ember.String.htmlSafe = function(str) { return new Handlebars.SafeString(str); }; @@ -21089,18 +24599,18 @@ var htmlSafe = Ember.String.htmlSafe; if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { /** - * Mark a string as being safe for unescaped output with Handlebars. - * - * ```javascript - * '
    someString
    '.htmlSafe() - * ``` - * - * See `Ember.String.htmlSafe`. - * - * @method htmlSafe - * @for String - * @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars - */ + Mark a string as being safe for unescaped output with Handlebars. + + ```javascript + '
    someString
    '.htmlSafe() + ``` + + See [Ember.String.htmlSafe](/api/classes/Ember.String.html#method_htmlSafe). + + @method htmlSafe + @for String + @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars + */ String.prototype.htmlSafe = function() { return htmlSafe(this); }; @@ -21280,6 +24790,8 @@ function SimpleHandlebarsView(path, pathRoot, isEscaped, templateData) { this.morph = Metamorph(); this.state = 'preRender'; this.updateId = null; + this._parentView = null; + this.buffer = null; } Ember._SimpleHandlebarsView = SimpleHandlebarsView; @@ -21293,7 +24805,11 @@ SimpleHandlebarsView.prototype = { Ember.run.cancel(this.updateId); this.updateId = null; } + if (this._parentView) { + this._parentView.removeChild(this); + } this.morph = null; + this.state = 'destroyed'; }, propertyWillChange: Ember.K, @@ -21348,7 +24864,7 @@ SimpleHandlebarsView.prototype = { rerender: function() { switch(this.state) { case 'preRender': - case 'destroying': + case 'destroyed': break; case 'inBuffer': throw new Ember.Error("Something you did tried to replace an {{expression}} before it was inserted into the DOM."); @@ -21677,16 +25193,16 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer } } -function simpleBind(property, options) { +function simpleBind(currentContext, property, options) { var data = options.data, view = data.view, - currentContext = this, - normalized, observer; + normalized, observer, pathRoot, output; normalized = normalizePath(currentContext, property, data); + pathRoot = normalized.root; // Set up observers for observable objects - if ('object' === typeof this) { + if (pathRoot && ('object' === typeof pathRoot)) { if (data.insideGroup) { observer = function() { Ember.run.once(view, 'rerender'); @@ -21718,7 +25234,8 @@ function simpleBind(property, options) { } else { // The object is not observable, so just render it out and // be done with it. - data.buffer.push(handlebarsGet(currentContext, property, options)); + output = handlebarsGet(currentContext, property, options); + data.buffer.push((output === null || typeof output === 'undefined') ? '' : output); } } @@ -21775,10 +25292,10 @@ EmberHandlebars.registerHelper('_triageMustache', function(property, fn) { EmberHandlebars.registerHelper('bind', function(property, options) { Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2); - var context = (options.contexts && options.contexts[0]) || this; + var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; if (!options.fn) { - return simpleBind.call(context, property, options); + return simpleBind(context, property, options); } return bind.call(context, property, options, false, exists); @@ -21803,7 +25320,7 @@ EmberHandlebars.registerHelper('bind', function(property, options) { @return {String} HTML string */ EmberHandlebars.registerHelper('boundIf', function(property, fn) { - var context = (fn.contexts && fn.contexts[0]) || this; + var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this; var func = function(result) { var truthy = result && get(result, 'isTruthy'); if (typeof truthy === 'boolean') { return truthy; } @@ -21863,7 +25380,7 @@ EmberHandlebars.registerHelper('with', function(context, options) { /** - See `boundIf` + See [boundIf](/api/classes/Ember.Handlebars.helpers.html#method_boundIf) @method if @for Ember.Handlebars.helpers @@ -21898,11 +25415,11 @@ EmberHandlebars.registerHelper('unless', function(context, options) { }); /** - `bindAttr` allows you to create a binding between DOM element attributes and + `bind-attr` allows you to create a binding between DOM element attributes and Ember objects. For example: ```handlebars - imageTitle + imageTitle ``` The above handlebars template will fill the ``'s `src` attribute will @@ -21924,17 +25441,17 @@ EmberHandlebars.registerHelper('unless', function(context, options) { A humorous image of a cat ``` - `bindAttr` cannot redeclare existing DOM element attributes. The use of `src` - in the following `bindAttr` example will be ignored and the hard coded value + `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 + imageTitle ``` - ### `bindAttr` and the `class` attribute + ### `bind-attr` and the `class` attribute - `bindAttr` supports a special syntax for handling a number of cases unique + `bind-attr` supports a special syntax for handling a number of cases unique to the `class` DOM element attribute. The `class` attribute combines multiple discreet values into a single attribute as a space-delimited list of strings. Each string can be: @@ -21943,7 +25460,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { * a boolean return value of an object's property * a hard-coded value - A string return value works identically to other uses of `bindAttr`. The + 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: @@ -21956,7 +25473,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { ``` ```handlebars - ``` Result in the following rendered output: @@ -21978,7 +25495,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { ``` ```handlebars - + ``` Result in the following rendered output: @@ -21992,14 +25509,14 @@ EmberHandlebars.registerHelper('unless', function(context, options) { 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: @@ -22012,19 +25529,19 @@ EmberHandlebars.registerHelper('unless', function(context, options) { hard-coded value – can be combined in a single declaration: ```handlebars - + ``` - @method bindAttr + @method bind-attr @for Ember.Handlebars.helpers @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('bindAttr', function(options) { +EmberHandlebars.registerHelper('bind-attr', function(options) { var attrs = options.hash; - Ember.assert("You must specify at least one hash argument to bindAttr", !!Ember.keys(attrs).length); + Ember.assert("You must specify at least one hash argument to bind-attr", !!Ember.keys(attrs).length); var view = options.data.view; var ret = []; @@ -22086,7 +25603,7 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { // 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}} ... {{bindAttr attr="x"}} + // 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); } @@ -22106,6 +25623,18 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { return new EmberHandlebars.SafeString(ret.join(' ')); }); +/** + See `bind-attr` + + @method bindAttr + @for Ember.Handlebars.helpers + @deprecated + @param {Function} context + @param {Hash} options + @return {String} HTML string +*/ +EmberHandlebars.registerHelper('bindAttr', EmberHandlebars.helpers['bind-attr']); + /** @private @@ -22241,6 +25770,35 @@ var EmberHandlebars = Ember.Handlebars; var LOWERCASE_A_Z = /^[a-z]/; var VIEW_PREFIX = /^view\./; +function makeBindings(thisContext, options) { + var hash = options.hash, + hashType = options.hashTypes; + + for (var prop in hash) { + if (hashType[prop] === 'ID') { + + var value = hash[prop]; + + if (Ember.IS_BINDING.test(prop)) { + Ember.warn("You're attempting to render a view by passing " + prop + "=" + value + " to a view helper, but this syntax is ambiguous. You should either surround " + value + " in quotes or remove `Binding` from " + prop + "."); + } else { + hash[prop + 'Binding'] = value; + hashType[prop + 'Binding'] = 'STRING'; + delete hash[prop]; + delete hashType[prop]; + } + } + } + + if (hash.hasOwnProperty('idBinding')) { + // id can't be bound, so just perform one-time lookup. + hash.id = EmberHandlebars.get(thisContext, hash.idBinding, options); + hashType.id = 'STRING'; + delete hash.idBinding; + delete hashType.idBinding; + } +} + EmberHandlebars.ViewHelper = Ember.Object.create({ propertiesFromHTMLOptions: function(options, thisContext) { @@ -22350,6 +25908,8 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ fn = options.fn, newView; + makeBindings(thisContext, options); + if ('string' === typeof path) { // TODO: this is a lame conditional, this should likely change @@ -22585,8 +26145,8 @@ var get = Ember.get, handlebarsGet = Ember.Handlebars.get, fmt = Ember.String.fm /** `{{collection}}` is a `Ember.Handlebars` helper for adding instances of - `Ember.CollectionView` to a template. See `Ember.CollectionView` for - additional information on how a `CollectionView` functions. + `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` @@ -22840,7 +26400,7 @@ Ember.Handlebars.registerHelper('unbound', function(property, fn) { return out; } - context = (fn.contexts && fn.contexts[0]) || this; + context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this; return handlebarsGet(context, property, fn); }); @@ -22870,7 +26430,7 @@ var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.norma @param {String} property */ Ember.Handlebars.registerHelper('log', function(property, options) { - var context = (options.contexts && options.contexts[0]) || this, + var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this, normalized = normalizePath(context, property, options.data), pathRoot = normalized.root, path = normalized.path, @@ -23039,6 +26599,8 @@ GroupedEach.prototype = { }, addArrayObservers: function() { + if (!this.content) { return; } + this.content.addArrayObserver(this, { willChange: 'contentArrayWillChange', didChange: 'contentArrayDidChange' @@ -23046,6 +26608,8 @@ GroupedEach.prototype = { }, removeArrayObservers: function() { + if (!this.content) { return; } + this.content.removeArrayObserver(this, { willChange: 'contentArrayWillChange', didChange: 'contentArrayDidChange' @@ -23063,6 +26627,8 @@ GroupedEach.prototype = { }, render: function() { + if (!this.content) { return; } + var content = this.content, contentLength = get(content, 'length'), data = this.options.data, @@ -23075,12 +26641,21 @@ GroupedEach.prototype = { }, rerenderContainingView: function() { - Ember.run.scheduleOnce('render', this.containingView, 'rerender'); + var self = this; + Ember.run.scheduleOnce('render', this, function() { + // It's possible it's been destroyed after we enqueued a re-render call. + if (!self.destroyed) { + self.containingView.rerender(); + } + }); }, destroy: function() { this.removeContentObservers(); - this.removeArrayObservers(); + if (this.content) { + this.removeArrayObservers(); + } + this.destroyed = true; } }; @@ -23204,6 +26779,49 @@ GroupedEach.prototype = { 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`) @@ -23211,6 +26829,7 @@ GroupedEach.prototype = { @param [options] {Object} Handlebars key/value pairs of options @param [options.itemViewClass] {String} a path to a view class used for each item @param [options.itemController] {String} name of a controller to be created for each item + @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper */ Ember.Handlebars.registerHelper('each', function(path, options) { if (arguments.length === 4) { @@ -23367,6 +26986,10 @@ Ember.Handlebars.registerHelper('partial', function(name, options) { var get = Ember.get, set = Ember.set; /** + `{{yield}}` denotes an area of a template that will be rendered inside + of another template. It has two main uses: + + ### Use with `layout` When used in a Handlebars template that is assigned to an `Ember.View` instance's `layout` property Ember will render the layout template first, inserting the view's own rendered output at the `{{yield}}` location. @@ -23409,7 +27032,34 @@ var get = Ember.get, set = Ember.set; bView.appendTo('body'); // throws - // Uncaught Error: assertion failed: You called yield in a template that was not a layout + // Uncaught Error: assertion failed: + // You called yield in a template that was not a layout + ``` + + ### Use with Ember.Component + When designing components `{{yield}}` is used to denote where, inside the component's + template, an optional block passed to the component should render: + + ```handlebars + + {{#labeled-textfield value=someProperty}} + First name: + {{/my-component}} + ``` + + ```handlebars + + + ``` + + Result: + + ```html +
  • {{post.title}}
  • + {{/each}} + {{/with}} + ``` + + Without the `as` operator, it would be impossible to reference `user.name` in the example above. + @method with @for Ember.Handlebars.helpers @param {Function} context @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('with', function(context, options) { +EmberHandlebars.registerHelper('with', function withHelper(context, options) { if (arguments.length === 4) { - var keywordName, path, rootPath, normalized; + 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]; @@ -25784,8 +27775,12 @@ EmberHandlebars.registerHelper('with', function(context, options) { 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 (Ember.isGlobalPath(path)) { - Ember.bind(options.data.keywords, keywordName, path); + contextPath = path; } else { normalized = normalizePath(this, path, options.data); path = normalized.path; @@ -25794,14 +27789,14 @@ EmberHandlebars.registerHelper('with', function(context, options) { // 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 = Ember.$.expando + Ember.guidFor(rootPath); - options.data.keywords[contextKey] = rootPath; - + localizedOptions.data.keywords[contextKey] = rootPath; // if the path is '' ("this"), just bind directly to the current context - var contextPath = path ? contextKey + '.' + path : contextKey; - Ember.bind(options.data.keywords, keywordName, contextPath); + contextPath = path ? contextKey + '.' + path : contextKey; } - return bind.call(this, path, options, true, exists); + Ember.bind(localizedOptions.data.keywords, keywordName, contextPath); + + return bind.call(this, path, localizedOptions, true, exists); } else { Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2); Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); @@ -25819,7 +27814,7 @@ EmberHandlebars.registerHelper('with', function(context, options) { @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('if', function(context, options) { +EmberHandlebars.registerHelper('if', 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); @@ -25833,7 +27828,7 @@ EmberHandlebars.registerHelper('if', function(context, options) { @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('unless', function(context, options) { +EmberHandlebars.registerHelper('unless', 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); @@ -25968,7 +27963,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('bind-attr', function(options) { +EmberHandlebars.registerHelper('bind-attr', function bindAttrHelper(options) { var attrs = options.hash; @@ -26014,7 +28009,9 @@ EmberHandlebars.registerHelper('bind-attr', function(options) { 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'); + 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 + "']"); @@ -26064,11 +28061,12 @@ EmberHandlebars.registerHelper('bind-attr', function(options) { @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('bindAttr', EmberHandlebars.helpers['bind-attr']); +EmberHandlebars.registerHelper('bindAttr', function bindAttrHelper() { + Ember.warn("The 'bindAttr' view helper is deprecated in favor of 'bind-attr'"); + return EmberHandlebars.helpers['bind-attr'].apply(this, arguments); +}); /** - @private - Helper that, given a space-separated string of property paths and a context, returns an array of class names. Calling this method also has the side effect of setting up observers at those property paths, such that if they @@ -26080,6 +28078,7 @@ EmberHandlebars.registerHelper('bindAttr', EmberHandlebars.helpers['bind-attr']) "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 @@ -26327,7 +28326,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ return 'templateData.keywords.' + path; } else if (Ember.isGlobalPath(path)) { return null; - } else if (path === 'this') { + } else if (path === 'this' || path === '') { return '_parentView.context'; } else { return '_parentView.context.' + path; @@ -26546,7 +28545,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ @param {Hash} options @return {String} HTML string */ -EmberHandlebars.registerHelper('view', function(path, options) { +EmberHandlebars.registerHelper('view', function viewHelper(path, options) { Ember.assert("The view helper only takes a single argument", arguments.length <= 2); // If no path is provided, treat path param as options. @@ -26564,8 +28563,6 @@ EmberHandlebars.registerHelper('view', function(path, options) { (function() { -/*globals Handlebars */ - // TODO: Don't require all of this module /** @module ember @@ -26696,7 +28693,7 @@ var get = Ember.get, handlebarsGet = Ember.Handlebars.get, fmt = Ember.String.fm @return {String} HTML string @deprecated Use `{{each}}` helper instead. */ -Ember.Handlebars.registerHelper('collection', function(path, options) { +Ember.Handlebars.registerHelper('collection', 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. @@ -26727,10 +28724,17 @@ Ember.Handlebars.registerHelper('collection', function(path, options) { if (hash.itemView) { var controller = data.keywords.controller; - Ember.assert('You specified an itemView, but the current context has no container to look the itemView up in. This probably means that you created a view manually, instead of through the container. Instead, use container.lookup("view:viewName"), which will properly instantiate your view.', controller && controller.container); + Ember.assert('You specified an itemView, but the current context has no ' + + 'container to look the itemView up in. This probably means ' + + 'that you created a view manually, instead of through the ' + + 'container. Instead, use container.lookup("view:viewName"), ' + + 'which will properly instantiate your view.', + controller && controller.container); var container = controller.container; itemViewClass = container.resolve('view:' + hash.itemView); - Ember.assert('You specified the itemView ' + hash.itemView + ", but it was not found at " + container.describe("view:" + hash.itemView) + " (and it was not registered in the container)", !!itemViewClass); + 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 { @@ -26764,7 +28768,7 @@ Ember.Handlebars.registerHelper('collection', function(path, options) { } var emptyViewClass; - if (inverse && inverse !== Handlebars.VM.noop) { + if (inverse && inverse !== Ember.Handlebars.VM.noop) { emptyViewClass = get(collectionPrototype, 'emptyViewClass'); emptyViewClass = emptyViewClass.extend({ template: inverse, @@ -26819,13 +28823,13 @@ var handlebarsGet = Ember.Handlebars.get; @param {String} property @return {String} HTML string */ -Ember.Handlebars.registerHelper('unbound', function(property, fn) { +Ember.Handlebars.registerHelper('unbound', function unboundHelper(property, fn) { var options = arguments[arguments.length - 1], helper, context, out; if (arguments.length > 2) { // Unbound helper call. options.data.isUnbound = true; - helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helperMissing; + helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helpers.helperMissing; out = helper.apply(this, Array.prototype.slice.call(arguments, 1)); delete options.data.isUnbound; return out; @@ -26846,7 +28850,7 @@ Ember.Handlebars.registerHelper('unbound', function(property, fn) { @submodule ember-handlebars */ -var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath; +var get = Ember.get, handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath; /** `log` allows you to output the value of a variable in the current rendering @@ -26860,7 +28864,7 @@ var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.norma @for Ember.Handlebars.helpers @param {String} property */ -Ember.Handlebars.registerHelper('log', function(property, options) { +Ember.Handlebars.registerHelper('log', function logHelper(property, options) { var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this, normalized = normalizePath(context, property, options.data), pathRoot = normalized.root, @@ -26876,14 +28880,44 @@ Ember.Handlebars.registerHelper('log', function(property, options) { {{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 */ -Ember.Handlebars.registerHelper('debugger', function(options) { +Ember.Handlebars.registerHelper('debugger', function debuggerHelper(options) { + + // These are helpful values you can inspect while debugging. + var templateContext = this; + var typeOfTemplateContext = Ember.inspect(templateContext); + debugger; }); + + })(); @@ -27262,7 +29296,7 @@ GroupedEach.prototype = { @param [options.itemController] {String} name of a controller to be created for each item @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper */ -Ember.Handlebars.registerHelper('each', function(path, options) { +Ember.Handlebars.registerHelper('each', function eachHelper(path, options) { if (arguments.length === 4) { Ember.assert("If you pass more than one argument to the each helper, it must be in the form #each foo in bar", arguments[1] === "in"); @@ -27410,7 +29444,7 @@ Ember.Handlebars.registerHelper('template', function(name, options) { @param {String} partialName the name of the template to render minus the leading underscore */ -Ember.Handlebars.registerHelper('partial', function(name, options) { +Ember.Handlebars.registerHelper('partial', function partialHelper(name, options) { var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; @@ -27546,7 +29580,7 @@ var get = Ember.get, set = Ember.set; @param {Hash} options @return {String} HTML string */ -Ember.Handlebars.registerHelper('yield', function(options) { +Ember.Handlebars.registerHelper('yield', function yieldHelper(options) { var view = options.data.view; while (view && !get(view, 'layout')) { @@ -27590,7 +29624,7 @@ Ember.Handlebars.registerHelper('yield', function(options) { @param {String} str The string to format */ -Ember.Handlebars.registerHelper('loc', function(str) { +Ember.Handlebars.registerHelper('loc', function locHelper(str) { return Ember.String.loc(str); }); @@ -27622,7 +29656,7 @@ var set = Ember.set, get = Ember.get; The internal class used to create text inputs when the `{{input}}` helper is used with `type` of `checkbox`. - See Handlebars.helpers.input for usage details. + See [handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details. ## Direct manipulation of `checked` @@ -27870,7 +29904,7 @@ var get = Ember.get, set = Ember.set; The internal class used to create text inputs when the `{{input}}` helper is used with `type` of `text`. - See [handlebars.helpers.input](Ember.Handlebars.helpers.html#method_input) for usage details. + See [Handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details. ## Layout and LayoutName properties @@ -27883,8 +29917,7 @@ var get = Ember.get, set = Ember.set; @extends Ember.Component @uses Ember.TextSupport */ -Ember.TextField = Ember.Component.extend(Ember.TextSupport, - /** @scope Ember.TextField.prototype */ { +Ember.TextField = Ember.Component.extend(Ember.TextSupport, { classNames: ['ember-text-field'], tagName: "input", @@ -28439,9 +30472,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ @namespace Ember @extends Ember.View */ -Ember.Select = Ember.View.extend( - /** @scope Ember.Select.prototype */ { - +Ember.Select = Ember.View.extend({ tagName: 'select', classNames: ['ember-select'], defaultTemplate: Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { @@ -28451,11 +30482,12 @@ helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; function program1(depth0,data) { - var buffer = '', hashTypes, hashContexts; + var buffer = '', stack1, hashTypes, hashContexts; data.buffer.push(""); return buffer; } @@ -28855,6 +30887,7 @@ function program7(depth0,data) { if you are deploying to browsers where the `required` attribute is used, you can add this to the `TextField`'s `attributeBindings` property: + ```javascript Ember.TextField.reopen({ attributeBindings: ['required'] @@ -28919,6 +30952,7 @@ function program7(depth0,data) { 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'] @@ -29077,7 +31111,7 @@ Ember.Handlebars.registerHelper('input', function(options) { extend the capabilities of text areas in your application by reopening this class. For example, if you are deploying to browsers where the `required` attribute is used, you can globally add support for the `required` attribute - on all {{textarea}}'s' in your app by reopening `Ember.TextArea` or + on all `{{textarea}}`s' in your app by reopening `Ember.TextArea` or `Ember.TextSupport` and adding it to the `attributeBindings` concatenated property: @@ -29150,8 +31184,6 @@ Ember.ComponentLookup = Ember.Object.extend({ */ /** - @private - Find templates stored in the head tag as script tags and make them available to `Ember.CoreView` in the global `Ember.TEMPLATES` object. This will be run as as jQuery DOM-ready callback. @@ -29161,6 +31193,7 @@ Ember.ComponentLookup = Ember.Object.extend({ Those with type `text/x-raw-handlebars` will be compiled with regular Handlebars and are suitable for use in views' computed properties. + @private @method bootstrap @for Ember.Handlebars @static @@ -29200,35 +31233,6 @@ function bootstrap() { Ember.Handlebars.bootstrap( Ember.$(document) ); } -function registerComponents(container) { - var templates = Ember.TEMPLATES, match; - if (!templates) { return; } - - for (var prop in templates) { - if (match = prop.match(/^components\/(.*)$/)) { - registerComponent(container, match[1]); - } - } -} - - -function registerComponent(container, name) { - Ember.assert("You provided a template named 'components/" + name + "', but custom components must include a '-'", name.match(/-/)); - - var fullName = 'component:' + name; - - container.injection(fullName, 'layout', 'template:components/' + name); - - var Component = container.lookupFactory(fullName); - - if (!Component) { - container.register(fullName, Ember.Component); - Component = container.lookupFactory(fullName); - } - - Ember.Handlebars.helper(name, Component); -} - function registerComponentLookup(container) { container.register('component-lookup:main', Ember.ComponentLookup); } @@ -29250,13 +31254,12 @@ Ember.onLoad('Ember.Application', function(Application) { initialize: bootstrap }); - - Application.initializer({ - name: 'registerComponentLookup', - after: 'domTemplates', - initialize: registerComponentLookup - }); + Application.initializer({ + name: 'registerComponentLookup', + after: 'domTemplates', + initialize: registerComponentLookup }); +}); })(); @@ -30053,7 +32056,7 @@ define("router", // TODO: separate into module? Router.Transition = Transition; - __exports__['default'] = Router; + __exports__["default"] = Router; /** @@ -30283,7 +32286,7 @@ define("router", if (isParam(object)) { var name = recogHandler.names[0]; - if ("" + object !== this.currentParams[name]) { return false; } + if (!this.currentParams || "" + object !== this.currentParams[name]) { return false; } } else if (handlerInfo.context !== object) { return false; } @@ -31367,7 +33370,8 @@ DSL.prototype = { this.push(options.path, name, null, options.queryParams); } - }, + + }, push: function(url, name, callback, queryParams) { var parts = name.split('.'); @@ -31378,7 +33382,7 @@ DSL.prototype = { route: function(name, options) { route(this, name, options); - }, + }, generate: function() { var dslMatches = this.matches; @@ -31433,7 +33437,7 @@ var get = Ember.get; */ /** - + Finds a controller instance. @for Ember @@ -31445,17 +33449,22 @@ Ember.controllerFor = function(container, controllerName, lookupOptions) { }; /** - Generates a controller automatically if none was provided. - The type of generated controller depends on the context. + Generates a controller factory + + The type of the generated controller factory is derived + from the context. If the context is an array an array controller + is generated, if an object, an object controller otherwise, a basic + controller is generated. + You can customize your generated controllers by defining - `App.ObjectController` and `App.ArrayController` - + `App.ObjectController` or `App.ArrayController`. + @for Ember - @method generateController + @method generateControllerFactory @private */ -Ember.generateController = function(container, controllerName, context) { - var ControllerFactory, fullName, instance, name, factoryName, controllerType; +Ember.generateControllerFactory = function(container, controllerName, context) { + var Factory, fullName, instance, name, factoryName, controllerType; if (context && Ember.isArray(context)) { controllerType = 'array'; @@ -31467,7 +33476,7 @@ Ember.generateController = function(container, controllerName, context) { factoryName = 'controller:' + controllerType; - ControllerFactory = container.lookupFactory(factoryName).extend({ + Factory = container.lookupFactory(factoryName).extend({ isGenerated: true, toString: function() { return "(generated " + controllerName + " controller)"; @@ -31476,9 +33485,27 @@ Ember.generateController = function(container, controllerName, context) { fullName = 'controller:' + controllerName; - container.register(fullName, ControllerFactory); + container.register(fullName, Factory); - instance = container.lookup(fullName); + return Factory; +}; + +/** + Generates and instantiates a controller. + + The type of the generated controller factory is derived + from the context. If the context is an array an array controller + is generated, if an object, an object controller otherwise, a basic + controller is generated. + + @for Ember + @method generateController + @private +*/ +Ember.generateController = function(container, controllerName, context) { + Ember.generateControllerFactory(container, controllerName, context); + var fullName = 'controller:' + controllerName; + var instance = container.lookup(fullName); if (get(instance, 'namespace.LOG_ACTIVE_GENERATION')) { Ember.Logger.info("generated -> " + fullName, { fullName: fullName }); @@ -31500,6 +33527,7 @@ Ember.generateController = function(container, controllerName, context) { var Router = requireModule("router")['default']; var get = Ember.get, set = Ember.set; var defineProperty = Ember.defineProperty; +var slice = Array.prototype.slice; var DefaultView = Ember._MetamorphView; /** @@ -31511,18 +33539,49 @@ var DefaultView = Ember._MetamorphView; @extends Ember.Object */ Ember.Router = Ember.Object.extend(Ember.Evented, { + /** + The `location` property determines the type of URL's that your + application will use. + + The following location types are currently available: + + * `hash` + * `history` + * `none` + + @property location + @default 'hash' + @see {Ember.Location} + */ location: 'hash', init: function() { this.router = this.constructor.router || this.constructor.map(Ember.K); this._activeViews = {}; this._setupLocation(); + + if (get(this, 'namespace.LOG_TRANSITIONS_INTERNAL')) { + this.router.log = Ember.Logger.debug; + } }, + /** + Represents the current URL. + + @method url + @returns {String} The current URL. + */ url: Ember.computed(function() { return get(this, 'location').getURL(); }), + /** + Initializes the current router instance and sets up the change handling + event listeners used by the instances `location` implementation. + + @method startRouting + @private + */ startRouting: function() { this.router = this.router || this.constructor.map(Ember.K); @@ -31543,19 +33602,25 @@ Ember.Router = Ember.Object.extend(Ember.Evented, { this.handleURL(location.getURL()); }, + /** + Handles updating the paths and notifying any listeners of the URL + change. + + Triggers the router level `didTransition` hook. + + @method didTransition + @private + */ didTransition: function(infos) { updatePaths(this); - - this._cancelLoadingEvent(); - + this._cancelLoadingEvent(); + this.notifyPropertyChange('url'); - - // Put this in the runloop so url will be accurate. Seems - // less surprising than didTransition being out of sync. - Ember.run.once(this, this.trigger, 'didTransition'); - + // Put this in the runloop so url will be accurate. Seems + // less surprising than didTransition being out of sync. + Ember.run.once(this, this.trigger, 'didTransition'); if (get(this, 'namespace').LOG_TRANSITIONS) { Ember.Logger.log("Transitioned into '" + Ember.Router._routePath(infos) + "'"); @@ -31590,6 +33655,14 @@ Ember.Router = Ember.Object.extend(Ember.Evented, { return this.location.formatURL(url); }, + /** + Determines if the supplied route is currently active. + + @method isActive + @param routeName + @returns {Boolean} + @private + */ isActive: function(routeName) { var router = this.router; return router.isActive.apply(router, arguments); @@ -31599,16 +33672,22 @@ Ember.Router = Ember.Object.extend(Ember.Evented, { this.router.trigger.apply(this.router, arguments); }, + /** + Does this router instance have the given route. + + @method hasRoute + @returns {Boolean} + @private + */ hasRoute: function(route) { return this.router.hasRoute(route); }, /** - @private - Resets the state of the router by clearing the current route handlers and deactivating them. + @private @method reset */ reset: function() { @@ -31655,6 +33734,10 @@ Ember.Router = Ember.Object.extend(Ember.Evented, { options.implementation = location; location = set(this, 'location', Ember.Location.create(options)); } + + // ensure that initState is called AFTER the rootURL is set on + // the location instance + if (typeof location.initState === 'function') { location.initState(); } }, _getHandlerFunction: function() { @@ -31671,8 +33754,6 @@ Ember.Router = Ember.Object.extend(Ember.Evented, { seen[name] = true; if (!handler) { - - container.register(routeName, DefaultRoute.extend()); handler = container.lookup(routeName); @@ -31718,7 +33799,7 @@ Ember.Router = Ember.Object.extend(Ember.Evented, { _doTransition: function(method, args) { // Normalize blank route to root URL. - args = [].slice.call(args); + args = slice.call(args); args[0] = args[0] || '/'; var passedName = args[0], name, self = this, @@ -31739,14 +33820,11 @@ Ember.Router = Ember.Object.extend(Ember.Evented, { var transitionPromise = this.router[method].apply(this.router, args); - // Don't schedule loading state entry if user has already aborted the transition. - - transitionPromise.then(null, function(error) { if (error.name === "UnrecognizedURLError") { Ember.assert("The URL '" + error.message + "' did not match any routes in your application"); } - }); + }, 'Ember: Check for Router unrecognized URL error'); // We want to return the configurable promise object // so that callers of this function can use `.method()` on it, @@ -31756,21 +33834,18 @@ Ember.Router = Ember.Object.extend(Ember.Evented, { _scheduleLoadingEvent: function(transition, originRoute) { this._cancelLoadingEvent(); - - this._loadingStateTimer = Ember.run.scheduleOnce('routerTransitions', this, '_fireLoadingEvent', transition, originRoute); - + this._loadingStateTimer = Ember.run.scheduleOnce('routerTransitions', this, '_fireLoadingEvent', transition, originRoute); }, _fireLoadingEvent: function(transition, originRoute) { - - if (!this.router.activeTransition) { - // Don't fire an event if we've since moved on from - // the transition that put us in a loading state. - return; - } + if (!this.router.activeTransition) { + // Don't fire an event if we've since moved on from + // the transition that put us in a loading state. + return; + } - transition.trigger(true, 'loading', transition, originRoute); - }, + transition.trigger(true, 'loading', transition, originRoute); + }, _cancelLoadingEvent: function () { if (this._loadingStateTimer) { @@ -31781,13 +33856,13 @@ Ember.Router = Ember.Object.extend(Ember.Evented, { }); /** - @private - Helper function for iterating root-ward, starting from (but not including) the provided `originRoute`. Returns true if the last callback fired requested to bubble upward. + + @private */ function forEachRouteAbove(originRoute, transition, callback) { var handlerInfos = transition.handlerInfos, @@ -31820,63 +33895,57 @@ var defaultActionHandlers = { }, error: function(error, transition, originRoute) { - + // Attempt to find an appropriate error substate to enter. + var router = originRoute.router; - // Attempt to find an appropriate error substate to enter. - var router = originRoute.router; - - var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) { - var childErrorRouteName = findChildRouteName(route, childRoute, 'error'); - if (childErrorRouteName) { - router.intermediateTransitionTo(childErrorRouteName, error); - return; - } - return true; - }); - - if (tryTopLevel) { - // Check for top-level error state to enter. - if (routeHasBeenDefined(originRoute.router, 'application_error')) { - router.intermediateTransitionTo('application_error', error); - return; - } - } else { - // Don't fire an assertion if we found an error substate. + var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) { + var childErrorRouteName = findChildRouteName(route, childRoute, 'error'); + if (childErrorRouteName) { + router.intermediateTransitionTo(childErrorRouteName, error); return; } - + return true; + }); - Ember.Logger.assert(false, 'Error while loading route: ' + Ember.inspect(error)); + if (tryTopLevel) { + // Check for top-level error state to enter. + if (routeHasBeenDefined(originRoute.router, 'application_error')) { + router.intermediateTransitionTo('application_error', error); + return; + } + } else { + // Don't fire an assertion if we found an error substate. + return; + } + + Ember.Logger.error('Error while loading route: ' + error.stack); }, loading: function(transition, originRoute) { - + // Attempt to find an appropriate loading substate to enter. + var router = originRoute.router; - // Attempt to find an appropriate loading substate to enter. - var router = originRoute.router; + var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) { + var childLoadingRouteName = findChildRouteName(route, childRoute, 'loading'); - var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) { - var childLoadingRouteName = findChildRouteName(route, childRoute, 'loading'); - - if (childLoadingRouteName) { - router.intermediateTransitionTo(childLoadingRouteName); - return; - } - - // Don't bubble above pivot route. - if (transition.pivotHandler !== route) { - return true; - } - }); - - if (tryTopLevel) { - // Check for top-level loading state to enter. - if (routeHasBeenDefined(originRoute.router, 'application_loading')) { - router.intermediateTransitionTo('application_loading'); - return; - } + if (childLoadingRouteName) { + router.intermediateTransitionTo(childLoadingRouteName); + return; } - + + // Don't bubble above pivot route. + if (transition.pivotHandler !== route) { + return true; + } + }); + + if (tryTopLevel) { + // Check for top-level loading state to enter. + if (routeHasBeenDefined(originRoute.router, 'application_loading')) { + router.intermediateTransitionTo('application_loading'); + return; + } + } } }; @@ -31886,6 +33955,8 @@ function findChildRouteName(parentRoute, originatingChildRoute, name) { targetChildRouteName = originatingChildRoute.routeName.split('.').pop(), namespace = parentRoute.routeName === 'application' ? '' : parentRoute.routeName + '.'; + + // Second, try general loading state, e.g. 'loading' childName = namespace + name; if (routeHasBeenDefined(router, childName)) { return childName; @@ -31927,7 +33998,7 @@ function triggerEvent(handlerInfos, ignoreFailure, args) { } if (!eventWasHandled && !ignoreFailure) { - throw new Ember.Error("Nothing handled the action '" + name + "'."); + throw new Ember.Error("Nothing handled the action '" + name + "'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble."); } } @@ -31957,41 +34028,6 @@ function updatePaths(router) { set(appController, 'currentRouteName', infos[infos.length - 1].name); } -function scheduleLegacyLoadingRouteEntry(router) { - cancelLegacyLoadingRouteEntry(router); - if (router.router.activeTransition) { - router._legacyLoadingStateTimer = Ember.run.scheduleOnce('routerTransitions', null, enterLegacyLoadingRoute, router); - } -} - -function enterLegacyLoadingRoute(router) { - var loadingRoute = router.router.getHandler('loading'); - if (loadingRoute && !loadingRoute._loadingStateActive) { - if (loadingRoute.enter) { loadingRoute.enter(); } - if (loadingRoute.setup) { loadingRoute.setup(); } - loadingRoute._loadingStateActive = true; - } -} - -function cancelLegacyLoadingRouteEntry(router) { - if (router._legacyLoadingStateTimer) { - Ember.run.cancel(router._legacyLoadingStateTimer); - } - router._legacyLoadingStateTimer = null; -} - -function exitLegacyLoadingRoute(router) { - - cancelLegacyLoadingRouteEntry(router); - - var loadingRoute = router.router.getHandler('loading'); - - if (loadingRoute && loadingRoute._loadingStateActive) { - if (loadingRoute.exit) { loadingRoute.exit(); } - loadingRoute._loadingStateActive = false; - } -} - Ember.Router.reopenClass({ router: null, map: function(callback) { @@ -32003,10 +34039,6 @@ Ember.Router.reopenClass({ this.reopenClass({ router: router }); } - if (get(this, 'namespace.LOG_TRANSITIONS_INTERNAL')) { - router.log = Ember.Logger.debug; - } - var dsl = Ember.RouterDSL.map(function() { this.resource('application', { path: "/" }, function() { for (var i=0; i < router.callbacks.length; i++) { @@ -32024,11 +34056,32 @@ Ember.Router.reopenClass({ _routePath: function(handlerInfos) { var path = []; + // We have to handle coalescing resource names that + // are prefixed with their parent's names, e.g. + // ['foo', 'foo.bar.baz'] => 'foo.bar.baz', not 'foo.foo.bar.baz' + + function intersectionMatches(a1, a2) { + for (var i = 0, len = a1.length; i < len; ++i) { + if (a1[i] !== a2[i]) { + return false; + } + } + return true; + } + for (var i=1, l=handlerInfos.length; i= 2); - var contextProvided = arguments.length === 3, + Ember.Handlebars.registerHelper('render', function renderHelper(name, contextString, options) { + var length = arguments.length; + Ember.assert("You must pass a template to render", length >= 2); + var contextProvided = length === 3, container, router, controller, view, context, lookupOptions; - if (arguments.length === 2) { - options = contextString; - contextString = undefined; - } - - if (typeof contextString === 'string') { - context = Ember.Handlebars.get(options.contexts[1], contextString, options); - lookupOptions = { singleton: false }; - } - - name = name.replace(/\//g, '.'); - container = options.data.keywords.controller.container; + container = (options || contextString).data.keywords.controller.container; router = container.lookup('router:main'); - Ember.assert("You can only use the {{render}} helper once without a model object as its second argument, as in {{render \"post\" post}}.", contextProvided || !router || !router._lookupActiveView(name)); + if (length === 2) { + // use the singleton controller + options = contextString; + contextString = undefined; + Ember.assert("You can only use the {{render}} helper once without a model object as its second argument, as in {{render \"post\" post}}.", !router || !router._lookupActiveView(name)); + } else if (length === 3) { + // create a new controller + context = Ember.Handlebars.get(options.contexts[1], contextString, options); + } else { + throw Ember.Error("You must pass a templateName to render"); + } + + // # legacy namespace + name = name.replace(/\//g, '.'); + // \ legacy slash as namespace support + view = container.lookup('view:' + name) || container.lookup('view:default'); - var controllerName = options.hash.controller; + // provide controller override + var controllerName = options.hash.controller || name; + var controllerFullName = 'controller:' + controllerName; - // Look up the controller by name, if provided. - if (controllerName) { - controller = container.lookup('controller:' + controllerName, lookupOptions); - Ember.assert("The controller name you supplied '" + controllerName + "' did not resolve to a controller.", !!controller); - } else { - controller = container.lookup('controller:' + name, lookupOptions) || - Ember.generateController(container, name, context); + if (options.hash.controller) { + Ember.assert("The controller name you supplied '" + controllerName + "' did not resolve to a controller.", container.has(controllerFullName)); } - if (controller && contextProvided) { - controller.set('model', context); + var parentController = options.data.keywords.controller; + + // choose name + if (length > 2) { + var factory = container.lookupFactory(controllerFullName) || + Ember.generateControllerFactory(container, controllerName, context); + + controller = factory.create({ + model: context, + parentController: parentController, + target: parentController + }); + + } else { + controller = container.lookup(controllerFullName) || + Ember.generateController(container, controllerName); + + controller.setProperties({ + target: parentController, + parentController: parentController + }); } var root = options.contexts[1]; @@ -34340,10 +36460,12 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { }); } - controller.set('target', options.data.keywords.controller); - options.hash.viewName = Ember.String.camelize(name); - options.hash.template = container.lookup('template:' + name); + + var templateName = 'template:' + name; + Ember.assert("You used `{{render '" + name + "'}}`, but '" + name + "' can not be found as either a template or a view.", container.has("view:" + name) || container.has(templateName)); + options.hash.template = container.lookup(templateName); + options.hash.controller = controller; if (router && !context) { @@ -34352,7 +36474,6 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { Ember.Handlebars.helpers.view.call(this, view, options); }); - }); })(); @@ -34399,7 +36520,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { if (POINTER_EVENT_TYPE_REGEX.test(event.type)) { return isSimpleClick(event); } else { - allowedKeys = []; + allowedKeys = ''; } } @@ -34423,10 +36544,12 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ActionHelper.registeredActions[actionId] = { eventName: options.eventName, - handler: function(event) { + handler: function handleRegisteredAction(event) { if (!isAllowedEvent(event, allowedKeys)) { return true; } - event.preventDefault(); + if (options.preventDefault !== false) { + event.preventDefault(); + } if (options.bubbles === false) { event.stopPropagation(); @@ -34440,7 +36563,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { target = target.root; } - Ember.run(function() { + Ember.run(function runRegisteredAction() { if (target.send) { target.send.apply(target, args(options.parameters, actionName)); } else { @@ -34483,8 +36606,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { App.ApplicationController = Ember.Controller.extend({ actions: { anActionName: function() { - - } + } } }); ``` @@ -34515,9 +36637,17 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { Events triggered through the action helper will automatically have `.preventDefault()` called on them. You do not need to do so in your event - handlers. + handlers. If you need to allow event propagation (to handle file inputs for + example) you can supply the `preventDefault=false` option to the `{{action}}` helper: - To also disable bubbling, pass `bubbles=false` to the helper: + ```handlebars +
    + + +
    + ``` + + To disable bubbling, pass `bubbles=false` to the helper: ```handlebars @@ -34619,7 +36749,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { @param {Object} [context]* @param {Hash} options */ - EmberHandlebars.registerHelper('action', function(actionName) { + EmberHandlebars.registerHelper('action', function actionHelper(actionName) { var options = arguments[arguments.length - 1], contexts = a_slice.call(arguments, 1, -1); @@ -34650,6 +36780,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { action.target = { root: root, target: target, options: options }; action.bubbles = hash.bubbles; + action.preventDefault = hash.preventDefault; var actionId = ActionHelper.registerAction(actionName, action, hash.allowedKeys); return new SafeString('data-ember-action="' + actionId + '"'); @@ -34661,109 +36792,6 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { -(function() { -/** -@module ember -@submodule ember-routing -*/ - -if (Ember.ENV.EXPERIMENTAL_CONTROL_HELPER) { - var get = Ember.get, set = Ember.set; - - /** - `{{control}}` works like render, except it uses a new controller instance for every call, instead of reusing the singleton controller. - - The control helper is currently under development and is considered experimental. - To enable it, set `ENV.EXPERIMENTAL_CONTROL_HELPER = true` before requiring Ember. - - For example if you had this `author` template. - - ```handlebars -
    - Written by {{firstName}} {{lastName}}. - Total Posts: {{postCount}} -
    - ``` - - You could render it inside the `post` template using the `control` helper. - - ```handlebars -
    -

    {{title}}

    -
    {{body}}
    - {{control "author" author}} -
    - ``` - - @method control - @for Ember.Handlebars.helpers - @param {String} path - @param {String} modelPath - @param {Hash} options - @return {String} HTML string - */ - Ember.Handlebars.registerHelper('control', function(path, modelPath, options) { - if (arguments.length === 2) { - options = modelPath; - modelPath = undefined; - } - - var model; - - if (modelPath) { - model = Ember.Handlebars.get(this, modelPath, options); - } - - var controller = options.data.keywords.controller, - view = options.data.keywords.view, - children = get(controller, '_childContainers'), - controlID = options.hash.controlID, - container, subContainer; - - if (children.hasOwnProperty(controlID)) { - subContainer = children[controlID]; - } else { - container = get(controller, 'container'), - subContainer = container.child(); - children[controlID] = subContainer; - } - - var normalizedPath = path.replace(/\//g, '.'); - - var childView = subContainer.lookup('view:' + normalizedPath) || subContainer.lookup('view:default'), - childController = subContainer.lookup('controller:' + normalizedPath), - childTemplate = subContainer.lookup('template:' + path); - - Ember.assert("Could not find controller for path: " + normalizedPath, childController); - Ember.assert("Could not find view for path: " + normalizedPath, childView); - - set(childController, 'target', controller); - set(childController, 'model', model); - - options.hash.template = childTemplate; - options.hash.controller = childController; - - function observer() { - var model = Ember.Handlebars.get(this, modelPath, options); - set(childController, 'model', model); - childView.rerender(); - } - - if (modelPath) { - Ember.addObserver(this, modelPath, observer); - childView.one('willDestroyElement', this, function() { - Ember.removeObserver(this, modelPath, observer); - }); - } - - Ember.Handlebars.helpers.view.call(this, childView, options); - }); -} - -})(); - - - (function() { })(); @@ -34784,8 +36812,8 @@ Ember.ControllerMixin.reopen({ be either a single route or route path: ```javascript - aController.transitionToRoute('blogPosts'); - aController.transitionToRoute('blogPosts.recentEntries'); + aController.transitionToRoute('blogPosts'); + aController.transitionToRoute('blogPosts.recentEntries'); ``` Optionally supply a model for the route in question. The model @@ -34793,19 +36821,18 @@ Ember.ControllerMixin.reopen({ the route: ```javascript - aController.transitionToRoute('blogPost', aPost); + aController.transitionToRoute('blogPost', aPost); ``` Multiple models will be applied last to first recursively up the resource tree. ```javascript + this.resource('blogPost', {path:':blogPostId'}, function(){ + this.resource('blogComment', {path: ':blogCommentId'}); + }); - this.resource('blogPost', {path:':blogPostId'}, function(){ - this.resource('blogComment', {path: ':blogCommentId'}); - }); - - aController.transitionToRoute('blogComment', aPost, aComment); + aController.transitionToRoute('blogComment', aPost, aComment); ``` See also 'replaceRoute'. @@ -34839,8 +36866,8 @@ Ember.ControllerMixin.reopen({ Beside that, it is identical to `transitionToRoute` in all other respects. ```javascript - aController.replaceRoute('blogPosts'); - aController.replaceRoute('blogPosts.recentEntries'); + aController.replaceRoute('blogPosts'); + aController.replaceRoute('blogPosts.recentEntries'); ``` Optionally supply a model for the route in question. The model @@ -34848,19 +36875,18 @@ Ember.ControllerMixin.reopen({ the route: ```javascript - aController.replaceRoute('blogPost', aPost); + aController.replaceRoute('blogPost', aPost); ``` Multiple models will be applied last to first recursively up the resource tree. ```javascript + this.resource('blogPost', {path:':blogPostId'}, function(){ + this.resource('blogComment', {path: ':blogCommentId'}); + }); - this.resource('blogPost', {path:':blogPostId'}, function(){ - this.resource('blogComment', {path: ':blogCommentId'}); - }); - - aController.replaceRoute('blogComment', aPost, aComment); + aController.replaceRoute('blogComment', aPost, aComment); ``` @param {String} name the name of the route @@ -34926,9 +36952,11 @@ Ember.View.reopen({ // The html for myView now looks like: //
    Child view:
    - myView.connectOutlet('main', Ember.View.extend({ + var FooView = Ember.View.extend({ template: Ember.Handlebars.compile('

    Foo

    ') - })); + }); + var fooView = FooView.create(); + myView.connectOutlet('main', fooView); // The html for myView now looks like: //
    Child view: //

    Foo

    @@ -34961,12 +36989,11 @@ Ember.View.reopen({ }, /** - @private - Determines if the view has already been created by checking if the view has the same constructor, template, and context as the view in the `_outlets` object. + @private @method _hasEquivalentView @param {String} outletName The name of the outlet we are checking @param {Object} view An Ember.View @@ -34994,9 +37021,11 @@ Ember.View.reopen({ // myView's html: //
    Child view:
    - myView.connectOutlet('main', Ember.View.extend({ + var FooView = Ember.View.extend({ template: Ember.Handlebars.compile('

    Foo

    ') - })); + }); + var fooView = FooView.create(); + myView.connectOutlet('main', fooView); // myView's html: //
    Child view: //

    Foo

    @@ -35019,11 +37048,10 @@ Ember.View.reopen({ }, /** - @private - Gets an outlet that is pending disconnection and then nullifys the object on the `_outlet` object. + @private @method _finishDisconnections */ _finishDisconnections: function() { @@ -35068,28 +37096,76 @@ queues.splice(indexOf.call(queues, 'actions') + 1, 0, 'routerTransitions'); var get = Ember.get, set = Ember.set; -/* - This file implements the `location` API used by Ember's router. - - That API is: - - getURL: returns the current URL - setURL(path): sets the current URL - replaceURL(path): replace the current URL (optional) - onUpdateURL(callback): triggers the callback when the URL changes - formatURL(url): formats `url` to be placed into `href` attribute - - Calling setURL or replaceURL will not trigger onUpdateURL callbacks. - - TODO: This should perhaps be moved so that it's visible in the doc output. -*/ - /** Ember.Location returns an instance of the correct implementation of the `location` API. - You can pass it a `implementation` ('hash', 'history', 'none') to force a - particular implementation. + ## Implementations + + You can pass an implementation name (`hash`, `history`, `none`) to force a + particular implementation to be used in your application. + + ### HashLocation + + Using `HashLocation` results in URLs with a `#` (hash sign) separating the + server side URL portion of the URL from the portion that is used by Ember. + This relies upon the `hashchange` event existing in the browser. + + Example: + + ```javascript + App.Router.map(function() { + this.resource('posts', function() { + this.route('new'); + }); + }); + + App.Router.reopen({ + location: 'hash' + }); + ``` + + This will result in a posts.new url of `/#/posts/new`. + + ### HistoryLocation + + Using `HistoryLocation` results in URLs that are indistinguishable from a + standard URL. This relies upon the browser's `history` API. + + Example: + + ```javascript + App.Router.map(function() { + this.resource('posts', function() { + this.route('new'); + }); + }); + + App.Router.reopen({ + location: 'history' + }); + ``` + + This will result in a posts.new url of `/posts/new`. + + ### NoneLocation + + Using `NoneLocation` causes Ember to not store the applications URL state + in the actual URL. This is generally used for testing purposes, and is one + of the changes made when calling `App.setupForTesting()`. + + ## Location API + + Each location implementation must provide the following methods: + + * implementation: returns the string name used to reference the implementation. + * getURL: returns the current URL. + * setURL(path): sets the current URL. + * replaceURL(path): replace the current URL (optional). + * onUpdateURL(callback): triggers the callback when the URL changes. + * formatURL(url): formats `url` to be placed into `href` attribute. + + Calling setURL or replaceURL will not trigger onUpdateURL callbacks. @class Location @namespace Ember @@ -35097,20 +37173,24 @@ var get = Ember.get, set = Ember.set; */ Ember.Location = { /** - Create an instance of a an implementation of the `location` API. Requires - an options object with an `implementation` property. + This is deprecated in favor of using the container to lookup the location + implementation as desired. - Example + For example: ```javascript - var hashLocation = Ember.Location.create({implementation: 'hash'}); - var historyLocation = Ember.Location.create({implementation: 'history'}); - var noneLocation = Ember.Location.create({implementation: 'none'}); + // Given a location registered as follows: + container.register('location:history-test', HistoryTestLocation); + + // You could create a new instance via: + container.lookup('location:history-test'); ``` @method create @param {Object} options @return {Object} an instance of an implementation of the `location` API + @deprecated Use the container to lookup the location implementation that you + need. */ create: function(options) { var implementation = options && options.implementation; @@ -35123,23 +37203,26 @@ Ember.Location = { }, /** - Registers a class that implements the `location` API with an implementation - name. This implementation name can then be specified by the location property on - the application's router class. + This is deprecated in favor of using the container to register the + location implementation as desired. - Example + Example: ```javascript - Ember.Location.registerImplementation('history', Ember.HistoryLocation); + Application.initializer({ + name: "history-test-location", - App.Router.reopen({ - location: 'history' + initialize: function(container, application) { + application.register('location:history-test', HistoryTestLocation); + } }); ``` - @method registerImplementation - @param {String} name - @param {Object} implementation of the `location` API + @method registerImplementation + @param {String} name + @param {Object} implementation of the `location` API + @deprecated Register your custom location implementation with the + container directly. */ registerImplementation: function(name, implementation) { this.implementations[name] = implementation; @@ -35174,10 +37257,9 @@ Ember.NoneLocation = Ember.Object.extend({ path: '', /** - @private - Returns the current path. + @private @method getURL @return {String} path */ @@ -35186,11 +37268,10 @@ Ember.NoneLocation = Ember.Object.extend({ }, /** - @private - Set the path and remembers what was set. Using this method to change the path will not invoke the `updateURL` callback. + @private @method setURL @param path {String} */ @@ -35199,12 +37280,11 @@ Ember.NoneLocation = Ember.Object.extend({ }, /** - @private - Register a callback to be invoked when the path changes. These callbacks will execute when the user presses the back or forward button, but not after `setURL` is invoked. + @private @method onUpdateURL @param callback {Function} */ @@ -35213,10 +37293,9 @@ Ember.NoneLocation = Ember.Object.extend({ }, /** - @private - Sets the path and calls the `updateURL` callback. + @private @method handleURL @param callback {Function} */ @@ -35226,14 +37305,13 @@ Ember.NoneLocation = Ember.Object.extend({ }, /** - @private - Given a URL, formats it to be placed into the page as part of an element's `href` attribute. This is used, for example, when using the {{action}} helper to generate a URL based on an event. + @private @method formatURL @param url {String} @return {String} url @@ -35261,8 +37339,8 @@ Ember.Location.registerImplementation('none', Ember.NoneLocation); var get = Ember.get, set = Ember.set; /** - Ember.HashLocation implements the location API using the browser's - hash. At present, it relies on a hashchange event existing in the + `Ember.HashLocation` implements the location API using the browser's + hash. At present, it relies on a `hashchange` event existing in the browser. @class HashLocation @@ -35276,10 +37354,9 @@ Ember.HashLocation = Ember.Object.extend({ }, /** - @private - Returns the current `location.hash`, minus the '#' at the front. + @private @method getURL */ getURL: function() { @@ -35288,12 +37365,11 @@ Ember.HashLocation = Ember.Object.extend({ }, /** - @private - Set the `location.hash` and remembers what was set. This prevents `onUpdateURL` callbacks from triggering when the hash was set by `HashLocation`. + @private @method setURL @param path {String} */ @@ -35303,11 +37379,10 @@ Ember.HashLocation = Ember.Object.extend({ }, /** - @private - Uses location.replace to update the url without a page reload or history modification. + @private @method replaceURL @param path {String} */ @@ -35316,12 +37391,11 @@ Ember.HashLocation = Ember.Object.extend({ }, /** - @private - Register a callback to be invoked when the hash changes. These callbacks will execute when the user presses the back or forward button, but not after `setURL` is invoked. + @private @method onUpdateURL @param callback {Function} */ @@ -35342,14 +37416,13 @@ Ember.HashLocation = Ember.Object.extend({ }, /** - @private - Given a URL, formats it to be placed into the page as part of an element's `href` attribute. This is used, for example, when using the {{action}} helper to generate a URL based on an event. + @private @method formatURL @param url {String} */ @@ -35358,10 +37431,9 @@ Ember.HashLocation = Ember.Object.extend({ }, /** - @private - Cleans up the HashLocation event listener. + @private @method willDestroy */ willDestroy: function() { @@ -35399,14 +37471,12 @@ Ember.HistoryLocation = Ember.Object.extend({ init: function() { set(this, 'location', get(this, 'location') || window.location); - this.initState(); }, /** - @private - Used to set state on first call to setURL + @private @method initState */ initState: function() { @@ -35423,10 +37493,9 @@ Ember.HistoryLocation = Ember.Object.extend({ rootURL: '/', /** + Returns the current `location.pathname` without `rootURL`. + @private - - Returns the current `location.pathname` without rootURL - @method getURL @return url {String} */ @@ -35443,10 +37512,9 @@ Ember.HistoryLocation = Ember.Object.extend({ }, /** - @private - Uses `history.pushState` to update the url without a page reload. + @private @method setURL @param path {String} */ @@ -35460,11 +37528,10 @@ Ember.HistoryLocation = Ember.Object.extend({ }, /** - @private - Uses `history.replaceState` to update the url without a page reload or history modification. + @private @method replaceURL @param path {String} */ @@ -35478,12 +37545,11 @@ Ember.HistoryLocation = Ember.Object.extend({ }, /** - @private - Get the current `history.state` Polyfill checks for native browser support and falls back to retrieving from a private _historyState variable + @private @method getState @return state {Object} */ @@ -35492,10 +37558,9 @@ Ember.HistoryLocation = Ember.Object.extend({ }, /** + Pushes a new state. + @private - - Pushes a new state - @method pushState @param path {String} */ @@ -35514,10 +37579,9 @@ Ember.HistoryLocation = Ember.Object.extend({ }, /** + Replaces the current state. + @private - - Replaces the current state - @method replaceState @param path {String} */ @@ -35536,11 +37600,10 @@ Ember.HistoryLocation = Ember.Object.extend({ }, /** - @private - Register a callback to be invoked whenever the browser history changes, including using forward and back buttons. + @private @method onUpdateURL @param callback {Function} */ @@ -35559,10 +37622,9 @@ Ember.HistoryLocation = Ember.Object.extend({ }, /** - @private - Used when using `{{action}}` helper. The url is always appended to the rootURL. + @private @method formatURL @param url {String} @return formatted url {String} @@ -35578,10 +37640,9 @@ Ember.HistoryLocation = Ember.Object.extend({ }, /** - @private - Cleans up the HistoryLocation event listener. + @private @method willDestroy */ willDestroy: function() { @@ -35818,7 +37879,10 @@ Ember.DefaultResolver = Ember.Object.extend({ 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); + 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; @@ -35852,7 +37916,7 @@ Ember.DefaultResolver = Ember.Object.extend({ typeSpecificResolveMethod = this[parsedName.resolveMethodName]; if (!parsedName.name || !parsedName.type) { - throw new TypeError("Invalid fullName: `" + fullName + "`, must of of the form `type:name` "); + throw new TypeError("Invalid fullName: `" + fullName + "`, must be of the form `type:name` "); } if (typeSpecificResolveMethod) { @@ -36124,7 +38188,7 @@ DeprecatedContainer.prototype = { App = Ember.Application.create({ customEvents: { // add support for the paste event - 'paste: "paste" + paste: "paste" } }); ``` @@ -36246,7 +38310,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin App = Ember.Application.create({ customEvents: { // add support for the paste event - 'paste: "paste" + paste: "paste" } }); ``` @@ -36288,13 +38352,12 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin }, /** - @private - 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 */ @@ -36305,8 +38368,6 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin }, /** - @private - 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. @@ -36321,6 +38382,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin }); ``` + @private @method defaultRouter @return {Ember.Router} the default router */ @@ -36338,8 +38400,6 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin }, /** - @private - Automatically initialize the application once the DOM has become ready. @@ -36352,6 +38412,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin `advanceReadiness()` once all of your code has finished loading. + @private @method scheduleInitialize */ scheduleInitialize: function() { @@ -36360,7 +38421,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin if (!this.$ || this.$.isReady) { Ember.run.schedule('actions', self, '_initialize'); } else { - this.$().ready(function() { + this.$().ready(function runInitialize() { Ember.run(self, '_initialize'); }); } @@ -36423,12 +38484,12 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin App.Person = Ember.Object.extend({}); App.Orange = Ember.Object.extend({}); App.Email = Ember.Object.extend({}); - App.Session = Ember.Object.create({}); + 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}); + App.register('session', App.session, {instantiate: false}); ``` @method register @@ -36462,28 +38523,26 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin }, /** - @private - @deprecated - 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'); }, /** - @private - 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() { @@ -36637,12 +38696,11 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin }, /** - @private - Setup up the event dispatcher to receive events on the application's `rootElement` with any registered `customEvents`. + @private @method setupEventDispatcher */ setupEventDispatcher: function() { @@ -36655,11 +38713,10 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin }, /** - @private - trigger a new call to `route` whenever the URL changes. If the application has a router, use it to route to the current URL, and + @private @method startRouting @property router {Ember.Router} */ @@ -36732,8 +38789,6 @@ Ember.Application.reopenClass({ }, /** - @private - This creates a container with the default Ember naming conventions. It also configures the container: @@ -36751,6 +38806,7 @@ Ember.Application.reopenClass({ * the application view receives the application template as its `defaultTemplate` property + @private @method buildContainer @static @param {Ember.Application} namespace the application to build the @@ -36771,10 +38827,7 @@ Ember.Application.reopenClass({ container.optionsForType('component', { singleton: false }); container.optionsForType('view', { singleton: false }); container.optionsForType('template', { instantiate: false }); - - - container.optionsForType('helper', { instantiate: false }); - + container.optionsForType('helper', { instantiate: false }); container.register('application:main', namespace, { instantiate: false }); @@ -36797,8 +38850,6 @@ Ember.Application.reopenClass({ }); /** - @private - This function defines the default lookup rules for container lookups: * templates are looked up on `Ember.TEMPLATES` @@ -36809,6 +38860,7 @@ Ember.Application.reopenClass({ 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 @@ -36868,21 +38920,52 @@ Ember.runLoadHooks('Ember.Application', Ember.Application); var get = Ember.get, set = Ember.set; function verifyNeedsDependencies(controller, container, needs) { - var dependency, i, l; + var dependency, i, l, missing = []; for (i=0, l=needs.length; i 1 ? 'they' : 'it') + " could not be found"); + } } +var defaultControllersComputedProperty = Ember.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(' `' + Ember.inspect(this) + ' specifies `needs`, but does not have a container. Please ensure this controller was instantiated with a container.', this.container); + Ember.assert(' `' + Ember.inspect(this) + ' specifies `needs`, but does ' + + "not have a container. Please ensure this controller was " + + "instantiated with a container.", + this.container || Ember.meta(this, false).descs.controllers !== defaultControllersComputedProperty); - verifyNeedsDependencies(this, this.container, needs); + if (this.container) { + verifyNeedsDependencies(this, this.container, needs); + } // if needs then initialize controllers proxy get(this, 'controllers'); @@ -36963,27 +39072,7 @@ Ember.ControllerMixin.reopen({ @property {Object} controllers @default null */ - controllers: Ember.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= 1.0.0' -}; - -Handlebars.helpers = {}; -Handlebars.partials = {}; - -var toString = Object.prototype.toString, - functionType = '[object Function]', - objectType = '[object Object]'; - -Handlebars.registerHelper = function(name, fn, inverse) { - if (toString.call(name) === objectType) { - if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } - Handlebars.Utils.extend(this.helpers, name); - } else { - if (inverse) { fn.not = inverse; } - this.helpers[name] = fn; - } -}; - -Handlebars.registerPartial = function(name, str) { - if (toString.call(name) === objectType) { - Handlebars.Utils.extend(this.partials, name); - } else { - this.partials[name] = str; - } -}; - -Handlebars.registerHelper('helperMissing', function(arg) { - if(arguments.length === 2) { - return undefined; - } else { - throw new Error("Missing helper: '" + arg + "'"); - } -}); - -Handlebars.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse || function() {}, fn = options.fn; - - var type = toString.call(context); - - if(type === functionType) { context = context.call(this); } - - if(context === true) { - return fn(this); - } else if(context === false || context == null) { - return inverse(this); - } else if(type === "[object Array]") { - if(context.length > 0) { - return Handlebars.helpers.each(context, options); - } else { - return inverse(this); - } - } else { - return fn(context); - } -}); - -Handlebars.K = function() {}; - -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; - return obj; -}; - -Handlebars.logger = { - DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, - - methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, - - // can be overridden in the host environment - log: function(level, obj) { - if (Handlebars.logger.level <= level) { - var method = Handlebars.logger.methodMap[level]; - if (typeof console !== 'undefined' && console[method]) { - console[method].call(console, obj); - } - } - } -}; - -Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; - -Handlebars.registerHelper('each', function(context, options) { - var fn = options.fn, inverse = options.inverse; - var i = 0, ret = "", data; - - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } - - if (options.data) { - data = Handlebars.createFrame(options.data); +/* exported Handlebars */ +var Handlebars = (function() { +// handlebars/safe-string.js +var __module4__ = (function() { + "use strict"; + var __exports__; + // Build out our basic SafeString type + function SafeString(string) { + this.string = string; } - if(context && typeof context === 'object') { - if(context instanceof Array){ - for(var j = context.length; i 2) { - expected.push("'" + this.terminals_[p] + "'"); - } - if (this.lexer.showPosition) { - errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; - } else { - errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); - } - this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); - } - } - if (action[0] instanceof Array && action.length > 1) { - throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); - } - switch (action[0]) { - case 1: - stack.push(symbol); - vstack.push(this.lexer.yytext); - lstack.push(this.lexer.yylloc); - stack.push(action[1]); - symbol = null; - if (!preErrorSymbol) { - yyleng = this.lexer.yyleng; - yytext = this.lexer.yytext; - yylineno = this.lexer.yylineno; - yyloc = this.lexer.yylloc; - if (recovering > 0) - recovering--; - } else { - symbol = preErrorSymbol; - preErrorSymbol = null; - } - break; - case 2: - len = this.productions_[action[1]][1]; - yyval.$ = vstack[vstack.length - len]; - yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; - if (ranges) { - yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; - } - r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); - if (typeof r !== "undefined") { - return r; - } - if (len) { - stack = stack.slice(0, -1 * len * 2); - vstack = vstack.slice(0, -1 * len); - lstack = lstack.slice(0, -1 * len); - } - stack.push(this.productions_[action[1]][0]); - vstack.push(yyval.$); - lstack.push(yyval._$); - newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; - stack.push(newState); - break; - case 3: - return true; - } - } - return true; -} -}; -/* Jison generated lexer */ -var lexer = (function(){ -var lexer = ({EOF:1, -parseError:function parseError(str, hash) { - if (this.yy.parser) { - this.yy.parser.parseError(str, hash); - } else { - throw new Error(str); - } - }, -setInput:function (input) { - this._input = input; - this._more = this._less = this.done = false; - this.yylineno = this.yyleng = 0; - this.yytext = this.matched = this.match = ''; - this.conditionStack = ['INITIAL']; - this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; - if (this.options.ranges) this.yylloc.range = [0,0]; - this.offset = 0; - return this; - }, -input:function () { - var ch = this._input[0]; - this.yytext += ch; - this.yyleng++; - this.offset++; - this.match += ch; - this.matched += ch; - var lines = ch.match(/(?:\r\n?|\n).*/g); - if (lines) { - this.yylineno++; - this.yylloc.last_line++; - } else { - this.yylloc.last_column++; - } - if (this.options.ranges) this.yylloc.range[1]++; - - this._input = this._input.slice(1); - return ch; - }, -unput:function (ch) { - var len = ch.length; - var lines = ch.split(/(?:\r\n?|\n)/g); - - this._input = ch + this._input; - this.yytext = this.yytext.substr(0, this.yytext.length-len-1); - //this.yyleng -= len; - this.offset -= len; - var oldLines = this.match.split(/(?:\r\n?|\n)/g); - this.match = this.match.substr(0, this.match.length-1); - this.matched = this.matched.substr(0, this.matched.length-1); - - if (lines.length-1) this.yylineno -= lines.length-1; - var r = this.yylloc.range; - - this.yylloc = {first_line: this.yylloc.first_line, - last_line: this.yylineno+1, - first_column: this.yylloc.first_column, - last_column: lines ? - (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: - this.yylloc.first_column - len - }; - - if (this.options.ranges) { - this.yylloc.range = [r[0], r[0] + this.yyleng - len]; - } - return this; - }, -more:function () { - this._more = true; - return this; - }, -less:function (n) { - this.unput(this.match.slice(n)); - }, -pastInput:function () { - var past = this.matched.substr(0, this.matched.length - this.match.length); - return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); - }, -upcomingInput:function () { - var next = this.match; - if (next.length < 20) { - next += this._input.substr(0, 20-next.length); - } - return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); - }, -showPosition:function () { - var pre = this.pastInput(); - var c = new Array(pre.length + 1).join("-"); - return pre + this.upcomingInput() + "\n" + c+"^"; - }, -next:function () { - if (this.done) { - return this.EOF; - } - if (!this._input) this.done = true; - - var token, - match, - tempMatch, - index, - col, - lines; - if (!this._more) { - this.yytext = ''; - this.match = ''; - } - var rules = this._currentRules(); - for (var i=0;i < rules.length; i++) { - tempMatch = this._input.match(this.rules[rules[i]]); - if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { - match = tempMatch; - index = i; - if (!this.options.flex) break; - } - } - if (match) { - lines = match[0].match(/(?:\r\n?|\n).*/g); - if (lines) this.yylineno += lines.length; - this.yylloc = {first_line: this.yylloc.last_line, - last_line: this.yylineno+1, - first_column: this.yylloc.last_column, - last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; - this.yytext += match[0]; - this.match += match[0]; - this.matches = match; - this.yyleng = this.yytext.length; - if (this.options.ranges) { - this.yylloc.range = [this.offset, this.offset += this.yyleng]; - } - this._more = false; - this._input = this._input.slice(match[0].length); - this.matched += match[0]; - token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); - if (this.done && this._input) this.done = false; - if (token) return token; - else return; - } - if (this._input === "") { - return this.EOF; - } else { - return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), - {text: "", token: null, line: this.yylineno}); - } - }, -lex:function lex() { - var r = this.next(); - if (typeof r !== 'undefined') { - return r; - } else { - return this.lex(); - } - }, -begin:function begin(condition) { - this.conditionStack.push(condition); - }, -popState:function popState() { - return this.conditionStack.pop(); - }, -_currentRules:function _currentRules() { - return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; - }, -topState:function () { - return this.conditionStack[this.conditionStack.length-2]; - }, -pushState:function begin(condition) { - this.begin(condition); - }}); -lexer.options = {}; -lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { - -var YYSTATE=YY_START -switch($avoiding_name_collisions) { -case 0: yy_.yytext = "\\"; return 14; -break; -case 1: - if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); - if(yy_.yytext) return 14; - -break; -case 2: return 14; -break; -case 3: - if(yy_.yytext.slice(-1) !== "\\") this.popState(); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); - return 14; - -break; -case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; -break; -case 5: return 25; -break; -case 6: return 16; -break; -case 7: return 20; -break; -case 8: return 19; -break; -case 9: return 19; -break; -case 10: return 23; -break; -case 11: return 22; -break; -case 12: this.popState(); this.begin('com'); -break; -case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; -break; -case 14: return 22; -break; -case 15: return 37; -break; -case 16: return 36; -break; -case 17: return 36; -break; -case 18: return 40; -break; -case 19: /*ignore whitespace*/ -break; -case 20: this.popState(); return 24; -break; -case 21: this.popState(); return 18; -break; -case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31; -break; -case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31; -break; -case 24: return 38; -break; -case 25: return 33; -break; -case 26: return 33; -break; -case 27: return 32; -break; -case 28: return 36; -break; -case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36; -break; -case 30: return 'INVALID'; -break; -case 31: return 5; -break; -} -}; -lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; -lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}}; -return lexer;})() -parser.lexer = lexer; -function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; -return new Parser; -})();; -// lib/handlebars/compiler/base.js - -Handlebars.Parser = handlebars; - -Handlebars.parse = function(input) { - - // Just return if an already-compile AST was passed in. - if(input.constructor === Handlebars.AST.ProgramNode) { return input; } - - Handlebars.Parser.yy = Handlebars.AST; - return Handlebars.Parser.parse(input); -}; -; -// lib/handlebars/compiler/ast.js -Handlebars.AST = {}; - -Handlebars.AST.ProgramNode = function(statements, inverse) { - this.type = "program"; - this.statements = statements; - if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } -}; - -Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { - this.type = "mustache"; - this.escaped = !unescaped; - this.hash = hash; - - var id = this.id = rawParams[0]; - var params = this.params = rawParams.slice(1); - - // a mustache is an eligible helper if: - // * its id is simple (a single part, not `this` or `..`) - var eligibleHelper = this.eligibleHelper = id.isSimple; - - // a mustache is definitely a helper if: - // * it is an eligible helper, and - // * it has at least one parameter or hash segment - this.isHelper = eligibleHelper && (params.length || hash); - - // if a mustache is an eligible helper but not a definite - // helper, it is ambiguous, and will be resolved in a later - // pass or at runtime. -}; - -Handlebars.AST.PartialNode = function(partialName, context) { - this.type = "partial"; - this.partialName = partialName; - this.context = context; -}; - -Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - var verifyMatch = function(open, close) { - if(open.original !== close.original) { - throw new Handlebars.Exception(open.original + " doesn't match " + close.original); - } + SafeString.prototype.toString = function() { + return "" + this.string; }; - verifyMatch(mustache.id, close); - this.type = "block"; - this.mustache = mustache; - this.program = program; - this.inverse = inverse; + __exports__ = SafeString; + return __exports__; +})(); - if (this.inverse && !this.program) { - this.isInverse = true; - } -}; +// handlebars/utils.js +var __module3__ = (function(__dependency1__) { + "use strict"; + var __exports__ = {}; + /*jshint -W004 */ + var SafeString = __dependency1__; -Handlebars.AST.ContentNode = function(string) { - this.type = "content"; - this.string = string; -}; + var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; -Handlebars.AST.HashNode = function(pairs) { - this.type = "hash"; - this.pairs = pairs; -}; + var badChars = /[&<>"'`]/g; + var possible = /[&<>"'`]/; -Handlebars.AST.IdNode = function(parts) { - this.type = "ID"; - - var original = "", - dig = [], - depth = 0; - - for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + original); } - else if (part === "..") { depth++; } - else { this.isScoped = true; } - } - else { dig.push(part); } + function escapeChar(chr) { + return escape[chr] || "&"; } - this.original = original; - this.parts = dig; - this.string = dig.join('.'); - this.depth = depth; - - // an ID is simple if it only has one part, and that part is not - // `..` or `this`. - this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; - - this.stringModeValue = this.string; -}; - -Handlebars.AST.PartialNameNode = function(name) { - this.type = "PARTIAL_NAME"; - this.name = name.original; -}; - -Handlebars.AST.DataNode = function(id) { - this.type = "DATA"; - this.id = id; -}; - -Handlebars.AST.StringNode = function(string) { - this.type = "STRING"; - this.original = - this.string = - this.stringModeValue = string; -}; - -Handlebars.AST.IntegerNode = function(integer) { - this.type = "INTEGER"; - this.original = - this.integer = integer; - this.stringModeValue = Number(integer); -}; - -Handlebars.AST.BooleanNode = function(bool) { - this.type = "BOOLEAN"; - this.bool = bool; - this.stringModeValue = bool === "true"; -}; - -Handlebars.AST.CommentNode = function(comment) { - this.type = "comment"; - this.comment = comment; -}; -; -// lib/handlebars/utils.js - -var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - -Handlebars.Exception = function(message) { - var tmp = Error.prototype.constructor.apply(this, arguments); - - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (var idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; - } -}; -Handlebars.Exception.prototype = new Error(); - -// Build out our basic SafeString type -Handlebars.SafeString = function(string) { - this.string = string; -}; -Handlebars.SafeString.prototype.toString = function() { - return this.string.toString(); -}; - -var escape = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" -}; - -var badChars = /[&<>"'`]/g; -var possible = /[&<>"'`]/; - -var escapeChar = function(chr) { - return escape[chr] || "&"; -}; - -Handlebars.Utils = { - extend: function(obj, value) { + function extend(obj, value) { for(var key in value) { - if(value.hasOwnProperty(key)) { + if(Object.prototype.hasOwnProperty.call(value, key)) { obj[key] = value[key]; } } - }, + } - escapeExpression: function(string) { + __exports__.extend = extend;var toString = Object.prototype.toString; + __exports__.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + var isFunction = function(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + if (isFunction(/x/)) { + isFunction = function(value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + var isFunction; + __exports__.isFunction = isFunction; + var isArray = Array.isArray || function(value) { + return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; + }; + __exports__.isArray = isArray; + + function escapeExpression(string) { // don't escape SafeStrings, since they're already safe - if (string instanceof Handlebars.SafeString) { + if (string instanceof SafeString) { return string.toString(); - } else if (string == null || string === false) { + } else if (!string && string !== 0) { return ""; } // Force a string conversion as this will be done by the append regardless and // the regex test will do this transparently behind the scenes, causing issues if // an object's to string has escaped characters in it. - string = string.toString(); + string = "" + string; if(!possible.test(string)) { return string; } return string.replace(badChars, escapeChar); - }, + } - isEmpty: function(value) { + __exports__.escapeExpression = escapeExpression;function isEmpty(value) { if (!value && value !== 0) { return true; - } else if(toString.call(value) === "[object Array]" && value.length === 0) { + } else if (isArray(value) && value.length === 0) { return true; } else { return false; } } -}; -; -// lib/handlebars/compiler/compiler.js -/*jshint eqnull:true*/ -var Compiler = Handlebars.Compiler = function() {}; -var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; + __exports__.isEmpty = isEmpty; + return __exports__; +})(__module4__); -// the foundHelper register will disambiguate helper lookup from finding a -// function in a context. This is necessary for mustache compatibility, which -// requires that context functions in blocks are evaluated by blockHelperMissing, -// and then proceed as if the resulting value was provided to blockHelperMissing. +// handlebars/exception.js +var __module5__ = (function() { + "use strict"; + var __exports__; -Compiler.prototype = { - compiler: Compiler, + var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - disassemble: function() { - var opcodes = this.opcodes, opcode, out = [], params, param; + function Exception(/* message */) { + var tmp = Error.prototype.constructor.apply(this, arguments); - for (var i=0, l=opcodes.length; i 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } - - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - if (this.context.aliases.hasOwnProperty(alias)) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; } - - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } - - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } - - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } - - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; - - for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } - } - }, - isInline: function() { - return this.inlineStack.length; - }, - - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; - } - return item; - } - }, - - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; - } - }, - - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 - .replace(/\u2029/g, '\\u2029') + '"'; - }, - - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); - - return { - params: params, - name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") - }; - }, - - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; - - options.push("hash:" + this.popStack()); - - inverse = this.popStack(); - program = this.popStack(); - - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } - - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } - - options.push("inverse:" + inverse); - options.push("fn:" + program); - } - - for(var i=0; i= 1.0.0' }; -}; + __exports__.REVISION_CHANGES = REVISION_CHANGES; + var isArray = Utils.isArray, + isFunction = Utils.isFunction, + toString = Utils.toString, + objectType = '[object Object]'; -; -// lib/handlebars/runtime.js + function HandlebarsEnvironment(helpers, partials) { + this.helpers = helpers || {}; + this.partials = partials || {}; + + registerDefaultHelpers(this); + } + + __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: logger, + log: log, + + registerHelper: function(name, fn, inverse) { + if (toString.call(name) === objectType) { + if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } + Utils.extend(this.helpers, name); + } else { + if (inverse) { fn.not = inverse; } + this.helpers[name] = fn; + } + }, + + registerPartial: function(name, str) { + if (toString.call(name) === objectType) { + Utils.extend(this.partials, name); + } else { + this.partials[name] = str; + } + } + }; + + function registerDefaultHelpers(instance) { + instance.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Error("Missing helper: '" + arg + "'"); + } + }); + + instance.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse || function() {}, fn = options.fn; + + if (isFunction(context)) { context = context.call(this); } + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if (isArray(context)) { + if(context.length > 0) { + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + return fn(context); + } + }); + + instance.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var i = 0, ret = "", data; + + if (isFunction(context)) { context = context.call(this); } + + if (options.data) { + data = createFrame(options.data); + } + + if(context && typeof context === 'object') { + if (isArray(context)) { + for(var j = context.length; i 0) { throw new Exception("Invalid path: " + original); } + else if (part === "..") { depth++; } + else { this.isScoped = true; } + } + else { dig.push(part); } + } + + this.original = original; + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + + this.stringModeValue = this.string; + }, + + PartialNameNode: function(name) { + this.type = "PARTIAL_NAME"; + this.name = name.original; + }, + + DataNode: function(id) { + this.type = "DATA"; + this.id = id; + }, + + StringNode: function(string) { + this.type = "STRING"; + this.original = + this.string = + this.stringModeValue = string; + }, + + IntegerNode: function(integer) { + this.type = "INTEGER"; + this.original = + this.integer = integer; + this.stringModeValue = Number(integer); + }, + + BooleanNode: function(bool) { + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; + }, + + CommentNode: function(comment) { + this.type = "comment"; + this.comment = comment; + } + }; + + // Must be exported as an object rather than the root of the module as the jison lexer + // most modify the object to operate properly. + __exports__ = AST; + return __exports__; +})(__module5__); + +// handlebars/compiler/parser.js +var __module9__ = (function() { + "use strict"; + var __exports__; + /* jshint ignore:start */ + /* Jison generated parser */ + var handlebars = (function(){ + var parser = {trace: function trace() { }, + yy: {}, + symbols_: {"error":2,"root":3,"statements":4,"EOF":5,"program":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"inMustache_repetition0":28,"inMustache_option0":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"hash":35,"hash_repetition_plus0":36,"hashSegment":37,"ID":38,"EQUALS":39,"DATA":40,"pathSegments":41,"SEP":42,"$accept":0,"$end":1}, + terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",32:"STRING",33:"INTEGER",34:"BOOLEAN",38:"ID",39:"EQUALS",40:"DATA",42:"SEP"}, + productions_: [0,[3,2],[3,1],[6,2],[6,3],[6,2],[6,1],[6,1],[6,0],[4,1],[4,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[7,2],[17,3],[17,1],[31,1],[31,1],[31,1],[31,1],[31,1],[35,1],[37,3],[26,1],[26,1],[26,1],[30,2],[21,1],[41,3],[41,1],[27,0],[27,1],[28,0],[28,2],[29,0],[29,1],[36,1],[36,2]], + performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + + var $0 = $$.length - 1; + switch (yystate) { + case 1: return new yy.ProgramNode($$[$0-1]); + break; + case 2: return new yy.ProgramNode([]); + break; + case 3:this.$ = new yy.ProgramNode([], $$[$0-1], $$[$0]); + break; + case 4:this.$ = new yy.ProgramNode($$[$0-2], $$[$0-1], $$[$0]); + break; + case 5:this.$ = new yy.ProgramNode($$[$0-1], $$[$0], []); + break; + case 6:this.$ = new yy.ProgramNode($$[$0]); + break; + case 7:this.$ = new yy.ProgramNode([]); + break; + case 8:this.$ = new yy.ProgramNode([]); + break; + case 9:this.$ = [$$[$0]]; + break; + case 10: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; + break; + case 11:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0]); + break; + case 12:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]); + break; + case 13:this.$ = $$[$0]; + break; + case 14:this.$ = $$[$0]; + break; + case 15:this.$ = new yy.ContentNode($$[$0]); + break; + case 16:this.$ = new yy.CommentNode($$[$0]); + break; + case 17:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0])); + break; + case 18:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0])); + break; + case 19:this.$ = {path: $$[$0-1], strip: stripFlags($$[$0-2], $$[$0])}; + break; + case 20:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0])); + break; + case 21:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0])); + break; + case 22:this.$ = new yy.PartialNode($$[$0-2], $$[$0-1], stripFlags($$[$0-3], $$[$0])); + break; + case 23:this.$ = stripFlags($$[$0-1], $$[$0]); + break; + case 24:this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]; + break; + case 25:this.$ = [[$$[$0]], null]; + break; + case 26:this.$ = $$[$0]; + break; + case 27:this.$ = new yy.StringNode($$[$0]); + break; + case 28:this.$ = new yy.IntegerNode($$[$0]); + break; + case 29:this.$ = new yy.BooleanNode($$[$0]); + break; + case 30:this.$ = $$[$0]; + break; + case 31:this.$ = new yy.HashNode($$[$0]); + break; + case 32:this.$ = [$$[$0-2], $$[$0]]; + break; + case 33:this.$ = new yy.PartialNameNode($$[$0]); + break; + case 34:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); + break; + case 35:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); + break; + case 36:this.$ = new yy.DataNode($$[$0]); + break; + case 37:this.$ = new yy.IdNode($$[$0]); + break; + case 38: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; + break; + case 39:this.$ = [{part: $$[$0]}]; + break; + case 42:this.$ = []; + break; + case 43:$$[$0-1].push($$[$0]); + break; + case 46:this.$ = [$$[$0]]; + break; + case 47:$$[$0-1].push($$[$0]); + break; + } + }, + table: [{3:1,4:2,5:[1,3],8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[3]},{5:[1,16],8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[2,2]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{4:20,6:18,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{4:20,6:22,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{17:23,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:29,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:30,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:31,21:24,30:25,38:[1,28],40:[1,27],41:26},{21:33,26:32,32:[1,34],33:[1,35],38:[1,28],41:26},{1:[2,1]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{10:36,20:[1,37]},{4:38,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,7],22:[1,13],23:[1,14],25:[1,15]},{7:39,8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,6],22:[1,13],23:[1,14],25:[1,15]},{17:23,18:[1,40],21:24,30:25,38:[1,28],40:[1,27],41:26},{10:41,20:[1,37]},{18:[1,42]},{18:[2,42],24:[2,42],28:43,32:[2,42],33:[2,42],34:[2,42],38:[2,42],40:[2,42]},{18:[2,25],24:[2,25]},{18:[2,37],24:[2,37],32:[2,37],33:[2,37],34:[2,37],38:[2,37],40:[2,37],42:[1,44]},{21:45,38:[1,28],41:26},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],38:[2,39],40:[2,39],42:[2,39]},{18:[1,46]},{18:[1,47]},{24:[1,48]},{18:[2,40],21:50,27:49,38:[1,28],41:26},{18:[2,33],38:[2,33]},{18:[2,34],38:[2,34]},{18:[2,35],38:[2,35]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{21:51,38:[1,28],41:26},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,3],22:[1,13],23:[1,14],25:[1,15]},{4:52,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,5],22:[1,13],23:[1,14],25:[1,15]},{14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]},{18:[2,44],21:56,24:[2,44],29:53,30:60,31:54,32:[1,57],33:[1,58],34:[1,59],35:55,36:61,37:62,38:[1,63],40:[1,27],41:26},{38:[1,64]},{18:[2,36],24:[2,36],32:[2,36],33:[2,36],34:[2,36],38:[2,36],40:[2,36]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,65]},{18:[2,41]},{18:[1,66]},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],25:[1,15]},{18:[2,24],24:[2,24]},{18:[2,43],24:[2,43],32:[2,43],33:[2,43],34:[2,43],38:[2,43],40:[2,43]},{18:[2,45],24:[2,45]},{18:[2,26],24:[2,26],32:[2,26],33:[2,26],34:[2,26],38:[2,26],40:[2,26]},{18:[2,27],24:[2,27],32:[2,27],33:[2,27],34:[2,27],38:[2,27],40:[2,27]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],38:[2,28],40:[2,28]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],38:[2,29],40:[2,29]},{18:[2,30],24:[2,30],32:[2,30],33:[2,30],34:[2,30],38:[2,30],40:[2,30]},{18:[2,31],24:[2,31],37:67,38:[1,68]},{18:[2,46],24:[2,46],38:[2,46]},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],38:[2,39],39:[1,69],40:[2,39],42:[2,39]},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],38:[2,38],40:[2,38],42:[2,38]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{18:[2,47],24:[2,47],38:[2,47]},{39:[1,69]},{21:56,30:60,31:70,32:[1,57],33:[1,58],34:[1,59],38:[1,28],40:[1,27],41:26},{18:[2,32],24:[2,32],38:[2,32]}], + defaultActions: {3:[2,2],16:[2,1],50:[2,41]}, + parseError: function parseError(str, hash) { + throw new Error(str); + }, + parse: function parse(input) { + var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + this.yy.parser = this; + if (typeof this.lexer.yylloc == "undefined") + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + var ranges = this.lexer.options && this.lexer.options.ranges; + if (typeof this.yy.parseError === "function") + this.parseError = this.yy.parseError; + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + function lex() { + var token; + token = self.lexer.lex() || 1; + if (typeof token !== "number") { + token = self.symbols_[token] || token; + } + return token; + } + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == "undefined") { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === "undefined" || !action.length || !action[0]) { + var errStr = ""; + if (!recovering) { + expected = []; + for (p in table[state]) + if (this.terminals_[p] && p > 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; + } + }; + + + function stripFlags(open, close) { + return { + left: open.charAt(2) === '~', + right: close.charAt(0) === '~' || close.charAt(1) === '~' + }; + } + + /* Jison generated lexer */ + var lexer = (function(){ + var lexer = ({EOF:1, + parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + if (this.options.ranges) this.yylloc.range = [0,0]; + this.offset = 0; + return this; + }, + input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, + unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length-len-1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length-1); + this.matched = this.matched.substr(0, this.matched.length-1); + + if (lines.length-1) this.yylineno -= lines.length-1; + var r = this.yylloc.range; + + this.yylloc = {first_line: this.yylloc.first_line, + last_line: this.yylineno+1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, + more:function () { + this._more = true; + return this; + }, + less:function (n) { + this.unput(this.match.slice(n)); + }, + pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, + upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, + showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, + next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + tempMatch, + index, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); + if (this.done && this._input) this.done = false; + if (token) return token; + else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, + lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, + begin:function begin(condition) { + this.conditionStack.push(condition); + }, + popState:function popState() { + return this.conditionStack.pop(); + }, + _currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, + topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, + pushState:function begin(condition) { + this.begin(condition); + }}); + lexer.options = {}; + lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + + + function strip(start, end) { + return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end); + } + + + var YYSTATE=YY_START + switch($avoiding_name_collisions) { + case 0: + if(yy_.yytext.slice(-2) === "\\\\") { + strip(0,1); + this.begin("mu"); + } else if(yy_.yytext.slice(-1) === "\\") { + strip(0,1); + this.begin("emu"); + } else { + this.begin("mu"); + } + if(yy_.yytext) return 14; + + break; + case 1:return 14; + break; + case 2: + this.popState(); + return 14; + + break; + case 3:strip(0,4); this.popState(); return 15; + break; + case 4:return 25; + break; + case 5:return 16; + break; + case 6:return 20; + break; + case 7:return 19; + break; + case 8:return 19; + break; + case 9:return 23; + break; + case 10:return 22; + break; + case 11:this.popState(); this.begin('com'); + break; + case 12:strip(3,5); this.popState(); return 15; + break; + case 13:return 22; + break; + case 14:return 39; + break; + case 15:return 38; + break; + case 16:return 38; + break; + case 17:return 42; + break; + case 18:// ignore whitespace + break; + case 19:this.popState(); return 24; + break; + case 20:this.popState(); return 18; + break; + case 21:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 32; + break; + case 22:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 32; + break; + case 23:return 40; + break; + case 24:return 34; + break; + case 25:return 34; + break; + case 26:return 33; + break; + case 27:return 38; + break; + case 28:yy_.yytext = strip(1,2); return 38; + break; + case 29:return 'INVALID'; + break; + case 30:return 5; + break; + } + }; + lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s])))/,/^(?:false(?=([~}\s])))/,/^(?:-?[0-9]+(?=([~}\s])))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; + lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"INITIAL":{"rules":[0,1,30],"inclusive":true}}; + return lexer;})() + parser.lexer = lexer; + function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; + return new Parser; + })();__exports__ = handlebars; + /* jshint ignore:end */ + return __exports__; +})(); + +// handlebars/compiler/base.js +var __module8__ = (function(__dependency1__, __dependency2__) { + "use strict"; + var __exports__ = {}; + var parser = __dependency1__; + var AST = __dependency2__; + + __exports__.parser = parser; + + function parse(input) { + // Just return if an already-compile AST was passed in. + if(input.constructor === AST.ProgramNode) { return input; } + + parser.yy = AST; + return parser.parse(input); + } + + __exports__.parse = parse; + return __exports__; +})(__module9__, __module7__); + +// handlebars/compiler/javascript-compiler.js +var __module11__ = (function(__dependency1__) { + "use strict"; + var __exports__; + var COMPILER_REVISION = __dependency1__.COMPILER_REVISION; + var REVISION_CHANGES = __dependency1__.REVISION_CHANGES; + var log = __dependency1__.log; + + function Literal(value) { + this.value = value; + } + + function JavaScriptCompiler() {} + + JavaScriptCompiler.prototype = { + // PUBLIC API: You can override these methods in a subclass to provide + // alternative compiled forms for name lookup and buffering semantics + nameLookup: function(parent, name /* , type*/) { + var wrap, + ret; + if (parent.indexOf('depth') === 0) { + wrap = true; + } + + if (/^[0-9]+$/.test(name)) { + ret = parent + "[" + name + "]"; + } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { + ret = parent + "." + name; + } + else { + ret = parent + "['" + name + "']"; + } + + if (wrap) { + return '(' + parent + ' && ' + ret + ')'; + } else { + return ret; + } + }, + + compilerInfo: function() { + var revision = COMPILER_REVISION, + versions = REVISION_CHANGES[revision]; + return "this.compilerInfo = ["+revision+",'"+versions+"'];\n"; + }, + + appendToBuffer: function(string) { + if (this.environment.isSimple) { + return "return " + string + ";"; + } else { + return { + appendToBuffer: true, + content: string, + toString: function() { return "buffer += " + string + ";"; } + }; + } + }, + + initializeBuffer: function() { + return this.quotedString(""); + }, + + namespace: "Handlebars", + // END PUBLIC API + + compile: function(environment, options, context, asObject) { + this.environment = environment; + this.options = options || {}; + + log('debug', this.environment.disassemble() + "\n\n"); + + this.name = this.environment.name; + this.isChild = !!context; + this.context = context || { + programs: [], + environments: [], + aliases: { } + }; + + this.preamble(); + + this.stackSlot = 0; + this.stackVars = []; + this.registers = { list: [] }; + this.compileStack = []; + this.inlineStack = []; + + this.compileChildren(environment, options); + + var opcodes = environment.opcodes, opcode; + + this.i = 0; + + for(var l=opcodes.length; this.i 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } + + // Generate minimizer alias mappings + if (!this.isChild) { + for (var alias in this.context.aliases) { + if (this.context.aliases.hasOwnProperty(alias)) { + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; + } + } + } + + if (this.source[1]) { + this.source[1] = "var " + this.source[1].substring(2) + ";"; + } + + // Merge children + if (!this.isChild) { + this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + } + + if (!this.environment.isSimple) { + this.pushSource("return buffer;"); + } + + var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + + for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); + } + } + } + }, + isInline: function() { + return this.inlineStack.length; + }, + + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + this.stackSlot--; + } + return item; + } + }, + + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + setupHelper: function(paramSize, name, missingParams) { + var params = []; + this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, + + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(paramSize, params, useRegister) { + var options = [], contexts = [], types = [], param, inverse, program; + + options.push("hash:" + this.popStack()); + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; + } + + if (!inverse) { + this.context.aliases.self = "this"; + inverse = "self.noop"; + } + + options.push("inverse:" + inverse); + options.push("fn:" + program); + } + + for(var i=0; i Date: Tue, 7 Jan 2014 20:59:27 -0500 Subject: [PATCH 69/71] Use bind-attr instead of deprecated bindAttr. --- NOTES.txt | 2 +- assets/scripts/app/templates/builds/list.hbs | 8 ++++---- assets/scripts/app/templates/builds/show.hbs | 10 +++++----- assets/scripts/app/templates/jobs/list.hbs | 4 ++-- assets/scripts/app/templates/jobs/pre.hbs | 4 ++-- assets/scripts/app/templates/jobs/show.hbs | 12 ++++++------ assets/scripts/app/templates/layouts/top.hbs | 4 ++-- assets/scripts/app/templates/profile/accounts.hbs | 2 +- assets/scripts/app/templates/profile/tabs.hbs | 4 ++-- assets/scripts/app/templates/profile/tabs/hooks.hbs | 10 +++++----- assets/scripts/app/templates/profile/tabs/user.hbs | 4 ++-- assets/scripts/app/templates/queues/list.hbs | 2 +- assets/scripts/app/templates/repos/list.hbs | 4 ++-- assets/scripts/app/templates/repos/list/tabs.hbs | 6 +++--- assets/scripts/app/templates/repos/show.hbs | 4 ++-- assets/scripts/app/templates/repos/show/actions.hbs | 12 ++++++------ assets/scripts/app/templates/repos/show/tabs.hbs | 12 ++++++------ assets/scripts/app/templates/repos/show/tools.hbs | 6 +++--- assets/scripts/app/templates/status_images.hbs | 12 ++++++------ assets/scripts/app/views/repo/show.coffee | 2 +- assets/scripts/app/views/top.coffee | 2 +- 21 files changed, 63 insertions(+), 63 deletions(-) diff --git a/NOTES.txt b/NOTES.txt index 577dc5a8..1b2fc3b7 100644 --- a/NOTES.txt +++ b/NOTES.txt @@ -10,7 +10,7 @@ # Handlebars -* Can't {{bindAttr}} be just {{attr}}? Who cares it's "bound" in that context? +* Can't {{bind-attr}} be just {{attr}}? Who cares it's "bound" in that context? {{#each}} isn't {{#bindEach}} either. * Why is {{#collection contentBinding="foo"}} not just {{#collection foo}}? diff --git a/assets/scripts/app/templates/builds/list.hbs b/assets/scripts/app/templates/builds/list.hbs index 9e1baff4..51547030 100644 --- a/assets/scripts/app/templates/builds/list.hbs +++ b/assets/scripts/app/templates/builds/list.hbs @@ -35,7 +35,7 @@ {{{formatMessage commit.message short="true" repoBinding=build.repo}}} - + {{formatCommit commit}} @@ -44,15 +44,15 @@ {{#if view.isPullRequestsList}} - + #{{pullRequestNumber}} {{/if}} - + {{formatDuration duration}} - + {{formatTime finishedAt}} {{/view}} diff --git a/assets/scripts/app/templates/builds/show.hbs b/assets/scripts/app/templates/builds/show.hbs index 5096dd60..37f2f1f0 100644 --- a/assets/scripts/app/templates/builds/show.hbs +++ b/assets/scripts/app/templates/builds/show.hbs @@ -15,22 +15,22 @@
    {{t builds.state}}
    {{capitalize build.state}}
    {{t builds.finished_at}}
    -
    {{formatTime build.finishedAt}}
    +
    {{formatTime build.finishedAt}}
    {{t builds.duration}}
    -
    {{formatDuration build.duration}}
    +
    {{formatDuration build.duration}}
    {{#with build}}
    {{t builds.commit}}
    -
    {{formatCommit commit}}
    +
    {{formatCommit commit}}
    {{#if pullRequest}}
    {{t builds.pull_request}}
    -
    #{{pullRequestNumber}} {{pullRequestTitle}}
    +
    #{{pullRequestNumber}} {{pullRequestTitle}}
    {{else}} {{#if commit.compareUrl}}
    {{t builds.compare}}
    -
    {{pathFrom commit.compareUrl}}
    +
    {{pathFrom commit.compareUrl}}
    {{/if}} {{/if}} {{#if commit.authorName}} diff --git a/assets/scripts/app/templates/jobs/list.hbs b/assets/scripts/app/templates/jobs/list.hbs index 4429a4d9..591d55ea 100644 --- a/assets/scripts/app/templates/jobs/list.hbs +++ b/assets/scripts/app/templates/jobs/list.hbs @@ -29,10 +29,10 @@ {{/if}} {{/if}} - + {{formatDuration duration}} - + {{formatTime finishedAt}} {{#each value in configValues}} diff --git a/assets/scripts/app/templates/jobs/pre.hbs b/assets/scripts/app/templates/jobs/pre.hbs index 457d9699..c3aee48e 100644 --- a/assets/scripts/app/templates/jobs/pre.hbs +++ b/assets/scripts/app/templates/jobs/pre.hbs @@ -17,14 +17,14 @@ {{#if view.job.sponsor.name}} {{/if}} {{#if view.limited}}

    This log is too long to be displayed. Please reduce the verbosity of your - build or download the the raw log. + build or download the the raw log.

    {{/if}}
    diff --git a/assets/scripts/app/templates/jobs/show.hbs b/assets/scripts/app/templates/jobs/show.hbs index 0d8fa914..06051c7b 100644 --- a/assets/scripts/app/templates/jobs/show.hbs +++ b/assets/scripts/app/templates/jobs/show.hbs @@ -1,5 +1,5 @@ {{#if job.isLoaded}} -
    +
    Job
    @@ -14,22 +14,22 @@
    {{t jobs.state}}
    {{capitalize job.state}}
    {{t jobs.finished_at}}
    -
    {{formatTime job.finishedAt}}
    +
    {{formatTime job.finishedAt}}
    {{t jobs.duration}}
    -
    {{formatDuration job.duration}}
    +
    {{formatDuration job.duration}}
    {{#with job}}
    {{t jobs.commit}}
    -
    {{formatCommit commit}}
    +
    {{formatCommit commit}}
    {{#if build.pullRequest}}
    {{t builds.pull_request}}
    -
    #{{build.pullRequestNumber}} {{build.pullRequestTitle}}
    +
    #{{build.pullRequestNumber}} {{build.pullRequestTitle}}
    {{else}} {{#if commit.compareUrl}}
    {{t jobs.compare}}
    -
    {{pathFrom commit.compareUrl}}
    +
    {{pathFrom commit.compareUrl}}
    {{/if}} {{/if}} {{#if commit.authorName}} diff --git a/assets/scripts/app/templates/layouts/top.hbs b/assets/scripts/app/templates/layouts/top.hbs index e883d6aa..d7085cce 100644 --- a/assets/scripts/app/templates/layouts/top.hbs +++ b/assets/scripts/app/templates/layouts/top.hbs @@ -29,13 +29,13 @@
  • Travis CI for Private Repositories
  • -
  • +
  • {{#if signedOut}} {{t layouts.top.github_login}} {{/if}} {{#if signedIn}} - {{#linkTo "profile" class="signed-in"}}{{userName}}{{/linkTo}} + {{#linkTo "profile" class="signed-in"}}{{userName}}{{/linkTo}} {{/if}} {{#if signingIn}} {{t layouts.top.signing_in}} diff --git a/assets/scripts/app/templates/profile/accounts.hbs b/assets/scripts/app/templates/profile/accounts.hbs index 56f5610b..c58f4ae0 100644 --- a/assets/scripts/app/templates/profile/accounts.hbs +++ b/assets/scripts/app/templates/profile/accounts.hbs @@ -2,7 +2,7 @@

  • diff --git a/assets/scripts/app/templates/profile/tabs.hbs b/assets/scripts/app/templates/profile/tabs.hbs index 8f770575..9ded1ba0 100644 --- a/assets/scripts/app/templates/profile/tabs.hbs +++ b/assets/scripts/app/templates/profile/tabs.hbs @@ -1,5 +1,5 @@
      -
    • +
    • {{#with view.account}} {{#if login}} @@ -9,7 +9,7 @@
    • {{#if view.displayUser}} -
    • +
    • {{#linkTo "account.profile" view.account}}Profile{{/linkTo}}
      diff --git a/assets/scripts/app/templates/profile/tabs/hooks.hbs b/assets/scripts/app/templates/profile/tabs/hooks.hbs index dc920183..8aec75ac 100644 --- a/assets/scripts/app/templates/profile/tabs/hooks.hbs +++ b/assets/scripts/app/templates/profile/tabs/hooks.hbs @@ -17,13 +17,13 @@
        {{#each hook in hooks}} -
      • - {{hook.slug}} +
      • + {{hook.slug}} {{#if hook.isSaving}}{{/if}}

        {{hook.description}}

        - + {{#if hook.active}} ON @@ -54,8 +54,8 @@
          {{#each hook in unAdminisetableHooks}} -
        • - {{hook.slug}} +
        • + {{hook.slug}}

          {{hook.description}}

        • {{/each}} diff --git a/assets/scripts/app/templates/profile/tabs/user.hbs b/assets/scripts/app/templates/profile/tabs/user.hbs index b42239c8..d926d3f2 100644 --- a/assets/scripts/app/templates/profile/tabs/user.hbs +++ b/assets/scripts/app/templates/profile/tabs/user.hbs @@ -1,4 +1,4 @@ - +
          @@ -6,7 +6,7 @@ {{t profiles.show.github}}:
          - {{user.login}} + {{user.login}}
          diff --git a/assets/scripts/app/templates/queues/list.hbs b/assets/scripts/app/templates/queues/list.hbs index fe08ffd2..d781c586 100644 --- a/assets/scripts/app/templates/queues/list.hbs +++ b/assets/scripts/app/templates/queues/list.hbs @@ -2,7 +2,7 @@ {{#each queue in controller}}
        • {{t queue}}: {{queue.name}}

          -
            +
              {{#each job in queue}} {{#view Travis.QueueItemView jobBinding="job"}} {{#if job.repo.slug}} diff --git a/assets/scripts/app/templates/repos/list.hbs b/assets/scripts/app/templates/repos/list.hbs index 378891fc..ddbfe251 100644 --- a/assets/scripts/app/templates/repos/list.hbs +++ b/assets/scripts/app/templates/repos/list.hbs @@ -22,11 +22,11 @@

              {{t repositories.duration}}: - {{formatDuration lastBuildDuration}} + {{formatDuration lastBuildDuration}}

              {{t repositories.finished_at}}: - {{formatTime lastBuildFinishedAt}} + {{formatTime lastBuildFinishedAt}}

              diff --git a/assets/scripts/app/templates/repos/list/tabs.hbs b/assets/scripts/app/templates/repos/list/tabs.hbs index 3511bef4..04d03fa3 100644 --- a/assets/scripts/app/templates/repos/list/tabs.hbs +++ b/assets/scripts/app/templates/repos/list/tabs.hbs @@ -1,13 +1,13 @@ diff --git a/assets/scripts/app/templates/repos/show.hbs b/assets/scripts/app/templates/repos/show.hbs index 7d7d3cf7..5addbe79 100644 --- a/assets/scripts/app/templates/repos/show.hbs +++ b/assets/scripts/app/templates/repos/show.hbs @@ -1,4 +1,4 @@ -
              +
              {{#if view.isEmpty}} {{view Travis.ReposEmptyView}} {{else}} @@ -6,7 +6,7 @@ {{#with repo}}

              {{#linkTo "repo" this}}{{slug}}{{/linkTo}}

              -
              +
              {{view Travis.RepoShowToolsView}}
              diff --git a/assets/scripts/app/templates/repos/show/actions.hbs b/assets/scripts/app/templates/repos/show/actions.hbs index 181d23e6..362c9536 100644 --- a/assets/scripts/app/templates/repos/show/actions.hbs +++ b/assets/scripts/app/templates/repos/show/actions.hbs @@ -3,39 +3,39 @@ {{#if view.displayCancelBuild}}
            • + {{bind-attr class="view.canCancelBuild::disabled"}}>
            • {{/if}} {{#if view.displayCancelJob}}
            • + {{bind-attr class="view.canCancelJob::disabled"}}>
            • {{/if}} {{#if view.displayRequeueBuild}}
            • + {{bind-attr class="view.canRequeueBuild::disabled"}}>
            • {{/if}} {{#if view.displayRequeueJob}}
            • + {{bind-attr class="view.canRequeueJob::disabled"}}>
            • {{/if}} {{!TODO: for some reason showDownloadLog, which just delegates to jobIdForLog does not refresh 'if' properly, need further investigation}} {{#if view.jobIdForLog}}
            • - +
            • {{/if}} {{#if view.displayCodeClimate}}
            • + {{bind-attr class=":open-popup"}}>
            • diff --git a/assets/scripts/app/templates/repos/show/tabs.hbs b/assets/scripts/app/templates/repos/show/tabs.hbs index b1671c2f..2b3ef740 100644 --- a/assets/scripts/app/templates/repos/show/tabs.hbs +++ b/assets/scripts/app/templates/repos/show/tabs.hbs @@ -1,5 +1,5 @@
                -
              • +
              • {{#if repo.slug}} {{#linkTo "repo" repo currentWhen="repo.index"}} @@ -8,7 +8,7 @@ {{/if}}
              • -
              • +
              • {{#if repo.slug}} {{#linkTo "builds" repo}} @@ -17,7 +17,7 @@ {{/if}}
              • -
              • +
              • {{#if repo.slug}} {{#linkTo "pullRequests" repo}} @@ -26,7 +26,7 @@ {{/if}}
              • -
              • +
              • {{#if repo.slug}} {{#linkTo "branches" repo}} @@ -35,7 +35,7 @@ {{/if}}
              • -
              • +
              • {{#if build.id}} {{#if repo.slug}} @@ -46,7 +46,7 @@ {{/if}}
              • -
              • +
              • {{#if job.id}} {{#if repo.slug}} diff --git a/assets/scripts/app/templates/repos/show/tools.hbs b/assets/scripts/app/templates/repos/show/tools.hbs index f6368253..5cc706df 100644 --- a/assets/scripts/app/templates/repos/show/tools.hbs +++ b/assets/scripts/app/templates/repos/show/tools.hbs @@ -6,7 +6,7 @@
              • + {{bind-attr class=":open-popup view.canRegenerateKey::disabled"}}> Regenerate Key
              • @@ -14,7 +14,7 @@
              {{#if view.displayStatusImages}} - + {{/if}} @@ -58,7 +58,7 @@

              Integrating Code Climate's test coverage reporting with your test suite on Travis CI allows to track changes in coverage over time. If you haven't tried it out already, sign + {{bind-attr href="Travis.config.code_climate_url"}}" target="_blank">sign up today to improve your code's quality. New customers get 20% off for the first three months!

              diff --git a/assets/scripts/app/templates/status_images.hbs b/assets/scripts/app/templates/status_images.hbs index f5dc5c0a..0f832f2b 100644 --- a/assets/scripts/app/templates/status_images.hbs +++ b/assets/scripts/app/templates/status_images.hbs @@ -9,25 +9,25 @@

              - +

              - +

              - +

              - +

              - +

              - +

              diff --git a/assets/scripts/app/views/repo/show.coffee b/assets/scripts/app/views/repo/show.coffee index a27a0b29..5deaf914 100644 --- a/assets/scripts/app/views/repo/show.coffee +++ b/assets/scripts/app/views/repo/show.coffee @@ -37,7 +37,7 @@ Travis.reopen tabBinding: 'controller.tab' contextBinding: 'controller' - # hrm. how to parametrize bindAttr? + # hrm. how to parametrize bind-attr? classCurrent: (-> 'active' if @get('tab') == 'current' ).property('tab') diff --git a/assets/scripts/app/views/top.coffee b/assets/scripts/app/views/top.coffee index ac9e8d24..fe5d7aea 100644 --- a/assets/scripts/app/views/top.coffee +++ b/assets/scripts/app/views/top.coffee @@ -4,7 +4,7 @@ tabBinding: 'controller.tab' - # hrm. how to parametrize bindAttr? + # hrm. how to parametrize bind-attr? classHome: (-> 'active' if @get('tab') == 'home' ).property('tab') From 8d681e85affc5abcb283d95de783e9fe14383fba Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Tue, 7 Jan 2014 21:02:24 -0500 Subject: [PATCH 70/71] Use link-to instead of deprecated linkTo. --- assets/scripts/app/templates/builds/list.hbs | 4 ++-- assets/scripts/app/templates/builds/show.hbs | 2 +- assets/scripts/app/templates/jobs/list.hbs | 2 +- assets/scripts/app/templates/jobs/show.hbs | 2 +- assets/scripts/app/templates/layouts/top.hbs | 10 ++++---- .../scripts/app/templates/no_owned_repos.hbs | 2 +- .../app/templates/profile/accounts.hbs | 2 +- assets/scripts/app/templates/profile/tabs.hbs | 4 ++-- assets/scripts/app/templates/queues/list.hbs | 4 ++-- assets/scripts/app/templates/repos/list.hbs | 4 ++-- assets/scripts/app/templates/repos/show.hbs | 2 +- .../scripts/app/templates/repos/show/tabs.hbs | 24 +++++++++---------- assets/scripts/app/templates/workers/list.hbs | 4 ++-- 13 files changed, 33 insertions(+), 33 deletions(-) diff --git a/assets/scripts/app/templates/builds/list.hbs b/assets/scripts/app/templates/builds/list.hbs index 51547030..5d73ca69 100644 --- a/assets/scripts/app/templates/builds/list.hbs +++ b/assets/scripts/app/templates/builds/list.hbs @@ -26,9 +26,9 @@ {{#if id}} - {{#linkTo "build" repo this}} + {{#link-to "build" repo this}} {{number}} - {{/linkTo}} + {{/link-to}} {{/if}} diff --git a/assets/scripts/app/templates/builds/show.hbs b/assets/scripts/app/templates/builds/show.hbs index 37f2f1f0..7c3d8f81 100644 --- a/assets/scripts/app/templates/builds/show.hbs +++ b/assets/scripts/app/templates/builds/show.hbs @@ -8,7 +8,7 @@ {{#if build.id}} {{#if build.repo.slug}} - {{#linkTo "build" repo build}}{{build.number}}{{/linkTo}} + {{#link-to "build" repo build}}{{build.number}}{{/link-to}} {{/if}} {{/if}} diff --git a/assets/scripts/app/templates/jobs/list.hbs b/assets/scripts/app/templates/jobs/list.hbs index 591d55ea..22314a35 100644 --- a/assets/scripts/app/templates/jobs/list.hbs +++ b/assets/scripts/app/templates/jobs/list.hbs @@ -25,7 +25,7 @@ {{#if job.id}} {{#if job.repo.slug}} - {{#linkTo "job" repo job}}{{number}}{{/linkTo}} + {{#link-to "job" repo job}}{{number}}{{/link-to}} {{/if}} {{/if}} diff --git a/assets/scripts/app/templates/jobs/show.hbs b/assets/scripts/app/templates/jobs/show.hbs index 06051c7b..0d3bd35e 100644 --- a/assets/scripts/app/templates/jobs/show.hbs +++ b/assets/scripts/app/templates/jobs/show.hbs @@ -7,7 +7,7 @@ {{#if job.id}} {{#if job.repo.slug}} - {{#linkTo "job" repo job}}{{job.number}}{{/linkTo}} + {{#link-to "job" repo job}}{{job.number}}{{/link-to}} {{/if}} {{/if}} diff --git a/assets/scripts/app/templates/layouts/top.hbs b/assets/scripts/app/templates/layouts/top.hbs index d7085cce..0f583cf6 100644 --- a/assets/scripts/app/templates/layouts/top.hbs +++ b/assets/scripts/app/templates/layouts/top.hbs @@ -1,10 +1,10 @@ -{{#linkTo "index.current"}} +{{#link-to "index.current"}}

              Travis

              -{{/linkTo}} +{{/link-to}}