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.
-
- 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.
-
->>>>>>> f6867e8a4cddfef041367a301d22d0969c6f886d
diff --git a/assets/scripts/app/templates/repos/show/tools.hbs b/assets/scripts/app/templates/repos/show/tools.hbs
index b2541b3c..f6368253 100644
--- a/assets/scripts/app/templates/repos/show/tools.hbs
+++ b/assets/scripts/app/templates/repos/show/tools.hbs
@@ -59,7 +59,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
- up today for to improve your code's quality. New customers get 20% off for the first three months!
+ up today to improve your code's quality. New customers get 20% off for the first three months!
diff --git a/assets/scripts/app/views.coffee b/assets/scripts/app/views.coffee
index ae5928c1..7ddf2bc8 100644
--- a/assets/scripts/app/views.coffee
+++ b/assets/scripts/app/views.coffee
@@ -43,7 +43,7 @@ Travis.FirstSyncView = Travis.View.extend
).then(null, (e) ->
console.log('There was a problem while redirecting from first sync', e)
)
- , 5000
+ , Travis.config.syncingPageRedirectionTime
require 'views/accounts'
require 'views/application'
diff --git a/assets/scripts/app/views/job.coffee b/assets/scripts/app/views/job.coffee
index 9027ebf7..1edaf375 100644
--- a/assets/scripts/app/views/job.coffee
+++ b/assets/scripts/app/views/job.coffee
@@ -29,11 +29,3 @@ Travis.reopen
urlGithubCommit: (->
Travis.Urls.githubCommit(@get('repo.slug'), @get('commit.sha'))
).property('repo.slug', 'commit.sha')
-
- urlAuthor: (->
- Travis.Urls.email(@get('commit.authorEmail'))
- ).property('commit.authorEmail')
-
- urlCommitter: (->
- Travis.Urls.email(@get('commit.committerEmail'))
- ).property('commit.committerEmail')
diff --git a/assets/scripts/app/views/log.coffee b/assets/scripts/app/views/log.coffee
index 5bd671a7..2bacdfe2 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')
@@ -34,12 +34,12 @@ Travis.reopen
parts.removeArrayObserver(@, didChange: 'partsDidChange', willChange: 'noop')
versionDidChange: (->
- @rerender() if @get('inDOM')
+ @rerender() if @get('state') == 'inDOM'
).observes('log.version')
logDidChange: (->
console.log 'log view: log did change: rerender' if Log.DEBUG
- @rerender() if @get('inDOM')
+ @rerender() if @get('state') == 'inDOM'
).observes('log')
createEngine: ->
@@ -97,8 +97,9 @@ Travis.reopen
window.history.pushState({ path: path }, null, path);
@set('controller.lineNumber', number)
- toTop: () ->
- $(window).scrollTop(0)
+ actions:
+ toTop: () ->
+ $(window).scrollTop(0)
noop: -> # TODO required?
diff --git a/assets/scripts/app/views/repo/show.coffee b/assets/scripts/app/views/repo/show.coffee
index 4dcaafde..a27a0b29 100644
--- a/assets/scripts/app/views/repo/show.coffee
+++ b/assets/scripts/app/views/repo/show.coffee
@@ -107,14 +107,19 @@ Travis.reopen
displayRegenerateKey: true
canRegenerateKey: (->
- @get('displayRegenerateKey') && @get('hasPermission')
- ).property('hasPermission')
+ @get('displayRegenerateKey') && @get('hasAdminPermission')
+ ).property('hasAdminPermission')
hasPermission: (->
if permissions = @get('currentUser.permissions')
permissions.contains parseInt(@get('repo.id'))
).property('currentUser.permissions.length', 'repo.id')
+ hasAdminPermission: (->
+ if permissions = @get('currentUser.adminPermissions')
+ permissions.contains parseInt(@get('repo.id'))
+ ).property('currentUser.adminPermissions.length', 'repo.id')
+
statusImageUrl: (->
Travis.Urls.statusImage(@get('slug'))
).property('slug')
@@ -174,6 +179,11 @@ Travis.reopen
permissions.contains parseInt(@get('repo.id'))
).property('currentUser.permissions.length', 'repo.id')
+ hasPushPermission: (->
+ if permissions = @get('currentUser.pushPermissions')
+ permissions.contains parseInt(@get('repo.id'))
+ ).property('currentUser.pushPermissions.length', 'repo.id')
+
displayRequeueBuild: (->
@get('isBuildTab') && @get('build.isFinished')
).property('isBuildTab', 'build.isFinished')
diff --git a/assets/scripts/lib/travis/adapter.coffee b/assets/scripts/lib/travis/adapter.coffee
index ca92965a..91259fdb 100644
--- a/assets/scripts/lib/travis/adapter.coffee
+++ b/assets/scripts/lib/travis/adapter.coffee
@@ -3,10 +3,10 @@ Travis.Adapter = Ember.RESTAdapter.extend
Travis.ajax.ajax(url, method || 'get', data: params)
findMany: (klass, records, ids) ->
- url = @buildURL(klass) + '?' + ids.map( (id) -> "ids[]=#{id}" ).join('&')
+ url = @buildURL(klass)
self = this
- @ajax(url).then (data) ->
+ @ajax(url, ids: ids).then (data) ->
self.didFindMany(klass, records, data)
didFindMany: (klass, records, data) ->
@@ -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
diff --git a/assets/scripts/lib/travis/ajax.coffee b/assets/scripts/lib/travis/ajax.coffee
index e856d364..00c32f01 100644
--- a/assets/scripts/lib/travis/ajax.coffee
+++ b/assets/scripts/lib/travis/ajax.coffee
@@ -59,7 +59,7 @@ Travis.ajax = Em.Object.create
options = $.extend(options, Travis.ajax.DEFAULT_OPTIONS)
if testMode?
- console.log('RUnning ajax with', options.url)
+ console.log('Running ajax with', options.url)
# we use jquery.mockjax for test, I don't want to hack it or rewrite it,
# so let's just pretend we still use ajax if testing mode is on
diff --git a/assets/scripts/lib/travis/model.coffee b/assets/scripts/lib/travis/model.coffee
index 4c37fe50..9226130b 100644
--- a/assets/scripts/lib/travis/model.coffee
+++ b/assets/scripts/lib/travis/model.coffee
@@ -38,18 +38,19 @@ Array.prototype.diff = (a) ->
@loadedAttributes = []
@loadedRelationships = []
- attributes = this.attributes || []
- relationships = this.relationships || []
+ attributes = this.constructor.getAttributes() || []
+ relationships = this.constructor.getRelationships() || []
- for key in attributes
- dataKey = @dataKey(key)
- if hash.hasOwnProperty(dataKey)
- @loadedAttributes.pushObject(key)
+ if hash
+ for key in attributes
+ dataKey = @dataKey(key)
+ if hash.hasOwnProperty(dataKey)
+ @loadedAttributes.pushObject(key)
- for key in relationships
- dataKey = @dataKey(key)
- if hash.hasOwnProperty(dataKey)
- @loadedRelationships.pushObject(key)
+ for key in relationships
+ dataKey = @dataKey(key)
+ if hash.hasOwnProperty(dataKey)
+ @loadedRelationships.pushObject(key)
incomplete = Ember.EnumerableUtils.intersection(@loadedAttributes, attributes).length != attributes.length ||
Ember.EnumerableUtils.intersection(@loadedRelationships, relationships).length != relationships.length
@@ -86,10 +87,10 @@ Array.prototype.diff = (a) ->
@loadTheRest(key)
isAttribute: (name) ->
- this.attributes.contains(name)
+ this.constructor.getAttributes().contains(name)
isRelationship: (name) ->
- this.relationships.contains(name)
+ this.constructor.getRelationships().contains(name)
loadTheRest: (key) ->
# for some weird reason key comes changed to a string and for some weird reason it even is called with
@@ -145,19 +146,19 @@ Array.prototype.diff = (a) ->
).property()
isRecordLoaded: (id) ->
- !!@_referenceForId(id).record
+ reference = @_getReferenceById(id)
+ reference && reference.record
camelizeKeys: true
# TODO: the functions below will be added to Ember Model, remove them when that
# happens
resetData: ->
- @_idToReference = null
- @sideloadedData = null
- @recordCache = null
- @recordArrays = null
- @_currentBatchIds = null
- @_hasManyArrays = null
+ @_referenceCache = {}
+ @sideloadedData = {}
+ @recordArrays = []
+ @_currentBatchIds = []
+ @_hasManyArrays = []
@_findAllRecordArray = null
unload: (record) ->
@@ -172,10 +173,8 @@ Array.prototype.diff = (a) ->
delete this.recordCache[key]
loadRecordForReference: (reference) ->
- record = @create({ _reference: reference })
- @recordCache = {} unless @recordCache
+ record = @create({ _reference: reference, id: reference.id })
@sideloadedData = {} unless @sideloadedData
- @recordCache[reference.id] = record
reference.record = record
record.load(reference.id, @sideloadedData[reference.id])
# TODO: find a nicer way to not add record to record arrays twice
diff --git a/assets/scripts/spec/integration/build_spec.coffee b/assets/scripts/spec/integration/build_spec.coffee
index b479ed7d..a8fd4d11 100644
--- a/assets/scripts/spec/integration/build_spec.coffee
+++ b/assets/scripts/spec/integration/build_spec.coffee
@@ -34,7 +34,7 @@ test "displaying information on build page", ->
listsJobs
table: '#jobs'
- headers: ['Job', 'Duration', 'Finished', 'Rvm']
+ headers: ['Job', 'Duration', 'Finished', 'Ruby']
jobs: [
{ color: 'green', id: 1, number: '1.1', repo: 'travis-ci/travis-core', finishedAt: '3 minutes ago', duration: '30 sec', rvm: 'rbx' }
{ color: 'red', id: 2, number: '1.2', repo: 'travis-ci/travis-core', finishedAt: '2 minutes ago', duration: '40 sec', rvm: '1.9.3' }
@@ -42,7 +42,7 @@ test "displaying information on build page", ->
listsJobs
table: '#allowed_failure_jobs'
- headers: ['Job', 'Duration', 'Finished', 'Rvm']
+ headers: ['Job', 'Duration', 'Finished', 'Ruby']
jobs: [
{ color: '', id: 3, number: '1.3', repo: 'travis-ci/travis-core', finishedAt: '-', duration: '-', rvm: 'jruby' }
]
diff --git a/assets/scripts/spec/integration/current_spec.coffee b/assets/scripts/spec/integration/current_spec.coffee
index 082f6189..e602df76 100644
--- a/assets/scripts/spec/integration/current_spec.coffee
+++ b/assets/scripts/spec/integration/current_spec.coffee
@@ -34,7 +34,7 @@ test "displaying information on repo page", ->
listsJobs
table: '#jobs'
- headers: ['Job', 'Duration', 'Finished', 'Rvm']
+ headers: ['Job', 'Duration', 'Finished', 'Ruby']
jobs: [
{ id: 1, color: 'green', number: '1.1', repo: 'travis-ci/travis-core', finishedAt: '3 minutes ago', duration: '30 sec', rvm: 'rbx' }
{ id: 2, color: 'red', number: '1.2', repo: 'travis-ci/travis-core', finishedAt: '2 minutes ago', duration: '40 sec', rvm: '1.9.3' }
@@ -42,7 +42,7 @@ test "displaying information on repo page", ->
listsJobs
table: '#allowed_failure_jobs'
- headers: ['Job', 'Duration', 'Finished', 'Rvm']
+ headers: ['Job', 'Duration', 'Finished', 'Ruby']
jobs: [
{ id: 3, color: '', number: '1.3', repo: 'travis-ci/travis-core', finishedAt: '-', duration: '-', rvm: 'jruby' }
]
diff --git a/assets/scripts/spec/integration/event_spec.coffee b/assets/scripts/spec/integration/event_spec.coffee
index 9c2ba882..3cd2f835 100644
--- a/assets/scripts/spec/integration/event_spec.coffee
+++ b/assets/scripts/spec/integration/event_spec.coffee
@@ -29,12 +29,48 @@ test "event containing a repository, adds repository to repos list", ->
last_build_number: 10
last_build_started_at: '2012-07-02T00:01:00Z'
last_build_finished_at: '2012-07-02T00:02:30Z'
+ last_build_state: 'passed'
+ last_build_duration: 90
wait().then ->
listsRepo
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 +98,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 =
diff --git a/assets/scripts/spec/integration/index_spec.coffee b/assets/scripts/spec/integration/index_spec.coffee
index ea19cdb6..8eb426f9 100644
--- a/assets/scripts/spec/integration/index_spec.coffee
+++ b/assets/scripts/spec/integration/index_spec.coffee
@@ -34,7 +34,7 @@ test "displaying information on index page", ->
listsJobs
table: '#jobs'
- headers: ['Job', 'Duration', 'Finished', 'Rvm']
+ headers: ['Job', 'Duration', 'Finished', 'Ruby']
jobs: [
{ color: 'green', id: 1, number: '1.1', repo: 'travis-ci/travis-core', finishedAt: '3 minutes ago', duration: '30 sec', rvm: 'rbx' }
{ color: 'red', id: 2, number: '1.2', repo: 'travis-ci/travis-core', finishedAt: '2 minutes ago', duration: '40 sec', rvm: '1.9.3' }
@@ -42,7 +42,7 @@ test "displaying information on index page", ->
listsJobs
table: '#allowed_failure_jobs'
- headers: ['Job', 'Duration', 'Finished', 'Rvm']
+ headers: ['Job', 'Duration', 'Finished', 'Ruby']
jobs: [
{ color: '', id: 3, number: '1.3', repo: 'travis-ci/travis-core', finishedAt: '-', duration: '-', rvm: 'jruby' }
]
diff --git a/assets/scripts/spec/integration/my_repos_spec.coffee b/assets/scripts/spec/integration/my_repos_spec.coffee
index 1c086406..6011772b 100644
--- a/assets/scripts/spec/integration/my_repos_spec.coffee
+++ b/assets/scripts/spec/integration/my_repos_spec.coffee
@@ -5,7 +5,6 @@ module "My repos",
Ember.run -> Travis.reset()
test "my repos is active by default when user is signed in", ->
-
Ember.run -> signInUser()
visit('/').then ->
wait().then ->
diff --git a/assets/scripts/spec/integration/sync.coffee b/assets/scripts/spec/integration/sync.coffee
new file mode 100644
index 00000000..a28b95b1
--- /dev/null
+++ b/assets/scripts/spec/integration/sync.coffee
@@ -0,0 +1,30 @@
+#module "Sync",
+# setup: ->
+# Ember.run -> Travis.advanceReadiness()
+# teardown: ->
+# Ember.run -> Travis.reset()
+#
+#test "first sync page is show when user just signed up and is syncing", ->
+# Ember.run ->
+# signInUser
+# is_syncing: true
+# synced_at: null
+#
+# $.mockjax
+# url: '/hooks'
+# responseTime: 10
+# responseText:
+# hooks: []
+#
+# Travis.config.syncingPageRedirectionTime = 100
+#
+# wait().then ->
+# ok $('#first_sync').text().match(/Just a few more seconds as we talk to GitHub to find out which repositories belong to you./)
+#
+# stop()
+# Travis.__container__.lookup('controller:currentUser').get('content').set('isSyncing', false)
+# setTimeout ->
+# start()
+# visit('/').then ->
+# ok $('#getting-started').text().match(/Welcome to Travis CI!/)
+# , 120
diff --git a/assets/scripts/spec/spec_helper.coffee b/assets/scripts/spec/spec_helper.coffee
index e869902c..c3edead9 100644
--- a/assets/scripts/spec/spec_helper.coffee
+++ b/assets/scripts/spec/spec_helper.coffee
@@ -16,15 +16,54 @@ Ember.Container.prototype.stub = (fullName, instance) ->
instance.destroy = instance.destroy || (->)
this.cache.dict[fullName] = instance
-window.signInUser = ->
+window.signInUser = (data) ->
+ data ||= {}
+ userData = {
+ id: 1
+ email: 'tyrion@example.org'
+ login: 'tyrion'
+ token: 'abcdef'
+ created_at: "2011-05-10T15:43:59Z"
+ gravatar_id: "582034b63279abeaa8e76acf12f5ee30"
+ is_syncing: false
+ locale: "en"
+ name: "Tyrion"
+ synced_at: "2013-12-09T09:41:47Z"
+ }
+ userData = Ember.merge(userData, data)
+ $.mockjax
+ url: '/users/1'
+ responseTime: 10
+ responseText:
+ user: userData
+ $.mockjax
+ url: '/users'
+ responseTime: 10
+ responseText:
+ user: userData
+ $.mockjax
+ url: '/users/permissions'
+ responseTime: 10
+ responseText:
+ permissions: []
+ admin: []
+ push: []
+ pull: []
+ $.mockjax
+ url: '/broadcasts'
+ responseTime: 10
+ responseText:
+ broadcasts: []
+ $.mockjax
+ url: '/accounts'
+ responseTime: 10
+ responseText:
+ accounts: []
+
# for now let's just use harcoded data to log in the user,
# we may extend it in the future to pass specific user data
Travis.auth.signIn
- user:
- id: 1
- email: 'tyrion@example.org'
- login: 'tyrion'
- token: 'abcdef'
+ user: userData
token: 'abcdef'
#@app = (url, options = {}) ->
diff --git a/assets/scripts/spec/support/expectations.coffee b/assets/scripts/spec/support/expectations.coffee
index 03050d29..f16de344 100644
--- a/assets/scripts/spec/support/expectations.coffee
+++ b/assets/scripts/spec/support/expectations.coffee
@@ -5,8 +5,8 @@
@displaysTabs = (tabs) ->
for name, tab of tabs
equal($("#tab_#{name} a").attr('href'), tab.href, "#{name} tab should link to #{tab.href}") unless tab.hidden
- equal($("#tab_#{name}").hasClass('active'), !!tab.active, "#{name} tab should be active")
- equal($("#tab_#{name}").hasClass('display-inline'), !tab.hidden, "#{name} tab should has class display-inline") if name in ['build', 'job']
+ equal($("#tab_#{name}").hasClass('active'), !!tab.active, "#{name} tab should #{'not' unless tab.active} be active")
+ equal($("#tab_#{name}").hasClass('display-inline'), !tab.hidden, "#{name} tab should have class display-inline") if name in ['build', 'job']
@displaysSummaryBuildLink = (link, number) ->
element = $('#summary .number a')
diff --git a/assets/scripts/spec/support/mocks.coffee b/assets/scripts/spec/support/mocks.coffee
index 7a5213c1..76f3b655 100644
--- a/assets/scripts/spec/support/mocks.coffee
+++ b/assets/scripts/spec/support/mocks.coffee
@@ -1,11 +1,11 @@
minispade.require 'ext/jquery'
-responseTime = 0
+responseTime = 10
repos = [
- { id: '1', owner: 'travis-ci', name: 'travis-core', slug: 'travis-ci/travis-core', build_ids: [1, 2], last_build_id: 1, last_build_number: 1, last_build_state: 'passed', last_build_duration: 30, last_build_started_at: '2012-07-02T00:00:00Z', last_build_finished_at: '2012-07-02T00:00:30Z', description: 'Description of travis-core' },
- { id: '2', owner: 'travis-ci', name: 'travis-assets', slug: 'travis-ci/travis-assets', build_ids: [3], last_build_id: 3, last_build_number: 3, last_build_state: 'failed', last_build_duration: 30, last_build_started_at: '2012-07-02T00:01:00Z', last_build_finished_at: '2012-07-01T00:01:30Z', description: 'Description of travis-assets'},
- { id: '3', owner: 'travis-ci', name: 'travis-hub', slug: 'travis-ci/travis-hub', build_ids: [4], last_build_id: 4, last_build_number: 4, last_build_state: null, last_build_duration: null, last_build_started_at: '2012-07-02T00:02:00Z', last_build_finished_at: null, description: 'Description of travis-hub'},
+ { id: '1', owner: 'travis-ci', name: 'travis-core', slug: 'travis-ci/travis-core', build_ids: [1, 2], last_build_id: 1, last_build_number: 1, last_build_state: 'passed', last_build_duration: 30, last_build_started_at: '2012-07-02T00:00:00Z', last_build_finished_at: '2012-07-02T00:00:30Z', description: 'Description of travis-core', github_language: 'ruby' },
+ { id: '2', owner: 'travis-ci', name: 'travis-assets', slug: 'travis-ci/travis-assets', build_ids: [3], last_build_id: 3, last_build_number: 3, last_build_state: 'failed', last_build_duration: 30, last_build_started_at: '2012-07-02T00:01:00Z', last_build_finished_at: '2012-07-01T00:01:30Z', description: 'Description of travis-assets', github_language: 'ruby'},
+ { id: '3', owner: 'travis-ci', name: 'travis-hub', slug: 'travis-ci/travis-hub', build_ids: [4], last_build_id: 4, last_build_number: 4, last_build_state: null, last_build_duration: null, last_build_started_at: '2012-07-02T00:02:00Z', last_build_finished_at: null, description: 'Description of travis-hub', github_language: 'ruby' },
]
reposByName = (name) ->
@@ -18,9 +18,9 @@ reposByName = (name) ->
builds = [
{ id: '1', repository_id: '1', commit_id: 1, job_ids: [1, 2, 3], number: 1, pull_request: false, config: { rvm: ['rbx', '1.9.3', 'jruby'] }, duration: 30, started_at: '2012-07-02T00:00:00Z', finished_at: '2012-07-02T00:00:30Z', state: 'passed' },
- { id: '2', repository_id: '1', commit_id: 2, job_ids: [4], number: 2, pull_request: false, config: { rvm: ['rbx'] }, duration: null },
+ { id: '2', repository_id: '1', commit_id: 2, job_ids: [4], number: 2, pull_request: false, config: { rvm: ['rbx'] }, duration: null, state: 'created', finished_at: null },
{ id: '3', repository_id: '2', commit_id: 3, job_ids: [5], number: 3, pull_request: false, config: { rvm: ['rbx'] }, duration: 30, started_at: '2012-07-02T00:01:00Z', finished_at: '2012-07-01T00:01:30Z', state: 'failed' },
- { id: '4', repository_id: '3', commit_id: 4, job_ids: [6], number: 4, pull_request: false, config: { rvm: ['rbx'] }, duration: null, started_at: '2012-07-02T00:02:00Z' },
+ { id: '4', repository_id: '3', commit_id: 4, job_ids: [6], number: 4, pull_request: false, config: { rvm: ['rbx'] }, duration: null, started_at: '2012-07-02T00:02:00Z', state: 'queued', finished_at: null },
]
commits = [
@@ -174,4 +174,3 @@ $.mockjax
url: '/profile/hooks'
responseTime: responseTime
responseText: { hooks: hooks }
-
diff --git a/assets/scripts/spec/unit/build_spec.coffee b/assets/scripts/spec/unit/build_spec.coffee
index 5c3c34ee..1cae1aed 100644
--- a/assets/scripts/spec/unit/build_spec.coffee
+++ b/assets/scripts/spec/unit/build_spec.coffee
@@ -54,4 +54,4 @@ test 'it takes into account all the jobs when getting config keys', ->
configKeys = build.get('configKeys')
deepEqual(rawConfigKeys, ['rvm', 'env', 'gemfile', 'jdk' ])
- deepEqual(configKeys, [ 'Job', 'Duration', 'Finished', 'Rvm', 'Env', 'Gemfile', 'Jdk' ])
+ deepEqual(configKeys, [ 'Job', 'Duration', 'Finished', 'Ruby', 'ENV', 'Gemfile', 'JDK' ])
diff --git a/assets/scripts/spec/unit/helpers_spec.coffee b/assets/scripts/spec/unit/helpers_spec.coffee
index 829c8a13..fe049984 100644
--- a/assets/scripts/spec/unit/helpers_spec.coffee
+++ b/assets/scripts/spec/unit/helpers_spec.coffee
@@ -52,3 +52,9 @@ test 'replaces @user with github user link', ->
expected = 'It is for you @tender_love1'
equal(result, expected, "@user should be converted to a link")
+
+test 'does not replace @user if it is a sign-off', ->
+ message = 'Signed-off-by: GitHub User '
+ result = Travis.Helpers.githubify(message, 'travis-ci', 'travis-web')
+
+ equal(result, message, "@user should not be converted to a link if it matches an email")
diff --git a/assets/scripts/spec/unit/tailing_spec.coffee b/assets/scripts/spec/unit/tailing_spec.coffee
new file mode 100644
index 00000000..18a836f0
--- /dev/null
+++ b/assets/scripts/spec/unit/tailing_spec.coffee
@@ -0,0 +1,89 @@
+fakeWindow =
+ scroll: sinon.spy()
+ scrollTop: sinon.stub().returns(0)
+ height: sinon.stub().returns(40)
+element = jQuery('')
+log = jQuery('')
+tail = new Travis.Tailing(fakeWindow, '#specTail', '#specLog')
+tail.tail = -> element
+tail.log = -> log
+
+module "Travis.Tailing",
+ setup: ->
+ jQuery('body').append(element)
+ jQuery('body').append(log)
+
+ teardown: ->
+ element.remove()
+ log.remove()
+ tail.stop()
+
+test "toggle", ->
+ equal(element.hasClass('active'), false)
+ tail.toggle()
+ equal(element.hasClass('active'), true)
+ tail.toggle()
+ stop()
+
+ Ember.run.later ->
+ start()
+ equal(element.hasClass('active'), false)
+ , 300
+
+test "active", ->
+ equal(tail.active(), false)
+ element.addClass('active')
+ equal(tail.active(), true)
+
+test "autoscroll when inactive", ->
+ tail.scrollTo = sinon.spy()
+
+ equal(tail.active(), false)
+ equal(tail.autoScroll(), false)
+ equal(tail.scrollTo.called, false)
+
+test "autoscroll", ->
+ element.addClass('active')
+ log.offset = -> {top: 1}
+ log.outerHeight = -> 1
+
+ equal(tail.active(), true)
+ equal(tail.autoScroll(), true)
+ equal(fakeWindow.scrollTop.calledWith(2), true)
+
+test "autoscroll when we're at the bottom", ->
+ element.addClass('active')
+ log.offset = -> {top: 0}
+ log.outerHeight = -> 0
+
+ equal(tail.active(), true)
+ equal(tail.autoScroll(), false)
+ equal(fakeWindow.scrollTop.calledWith(0), false)
+
+test 'should stop scrolling if the position changed', ->
+ element.addClass('active')
+ tail.position = 100
+ tail.onScroll()
+ equal(element.hasClass('active'), false)
+
+test 'positionButton adds the scrolling class', ->
+ log.offset = -> {top: -1}
+
+ tail.positionButton()
+ equal(element.hasClass('scrolling'), true)
+ equal(element.hasClass('bottom'), false)
+
+test 'positionButton removes the scrolling class', ->
+ log.offset = -> {top: 1}
+ tail.positionButton()
+ equal(element.hasClass('scrolling'), false)
+ equal(element.hasClass('bottom'), false)
+
+test 'positionButton sets the button as bottom', ->
+ log.offset = -> {top: -100}
+ log.height = -> 50
+ tail.height = -> 1
+
+ tail.positionButton()
+ equal(element.hasClass('scrolling'), false)
+ equal(element.hasClass('bottom'), true)
diff --git a/assets/scripts/spec/unit/user_spec.coffee b/assets/scripts/spec/unit/user_spec.coffee
new file mode 100644
index 00000000..8df3fcdb
--- /dev/null
+++ b/assets/scripts/spec/unit/user_spec.coffee
@@ -0,0 +1,47 @@
+record = null
+
+module "Travis.User",
+ setup: ->
+ teardown: ->
+ Travis.User.resetData()
+
+test '', ->
+ # TODO: we should not need to mock entire user response
+ # just for user creation. It happens, because whenever
+ # a user is created we try to get fresh data
+ userData = {
+ id: 1
+ email: 'tyrion@example.org'
+ login: 'tyrion'
+ token: 'abcdef'
+ created_at: "2011-05-10T15:43:59Z"
+ gravatar_id: "582034b63279abeaa8e76acf12f5ee30"
+ is_syncing: false
+ locale: "en"
+ name: "Tyrion"
+ synced_at: "2013-12-09T09:41:47Z"
+ }
+ $.mockjax
+ url: '/users/1'
+ responseTime: 10
+ responseText:
+ user: userData
+
+
+ Travis.User.load [{ id: '1', login: 'test@travis-ci.org' }]
+ user = null
+ pushPermissions = null
+ adminPermissions = null
+
+ Ember.run ->
+ user = Travis.User.find(1)
+ user.set '_rawPermissions',
+ then: (func) ->
+ func(permissions: [1], admin: [1], pull: [2], push: [3])
+
+ pushPermissions = user.get('pushPermissions')
+ adminPermissions = user.get('adminPermissions')
+
+ wait().then ->
+ deepEqual(adminPermissions.toArray(), [1])
+ deepEqual(pushPermissions.toArray(), [3])
diff --git a/assets/scripts/travis.coffee b/assets/scripts/travis.coffee
index 81f611e3..2491ca60 100644
--- a/assets/scripts/travis.coffee
+++ b/assets/scripts/travis.coffee
@@ -61,7 +61,11 @@ Ember.RecordArray.reopen
).observes('content')
window.Travis = TravisApplication.create(
- LOG_TRANSITIONS: true
+ LOG_ACTIVE_GENERATION: true,
+ LOG_MODULE_RESOLVER: true,
+ LOG_TRANSITIONS: true,
+ LOG_TRANSITIONS_INTERNAL: true,
+ LOG_VIEW_LOOKUPS: true
)
Travis.deferReadiness()
@@ -71,13 +75,29 @@ $.extend Travis,
Travis.advanceReadiness() # bc, remove once merged to master
config:
+ syncingPageRedirectionTime: 5000
api_endpoint: $('meta[rel="travis.api_endpoint"]').attr('href')
pusher_key: $('meta[name="travis.pusher_key"]').attr('value')
ga_code: $('meta[name="travis.ga_code"]').attr('value')
code_climate: $('meta[name="travis.code_climate"]').attr('value')
code_climate_url: $('meta[name="travis.code_climate_url"]').attr('value')
- CONFIG_KEYS: ['go', 'rvm', 'gemfile', 'env', 'jdk', 'otp_release', 'php', 'node_js', 'perl', 'python', 'scala', 'compiler']
+ CONFIG_KEYS_MAP: {
+ go: 'Go'
+ rvm: 'Ruby'
+ gemfile: 'Gemfile'
+ env: 'ENV'
+ jdk: 'JDK'
+ otp_release: 'OTP Release'
+ php: 'PHP'
+ node_js: 'Node.js'
+ perl: 'Perl'
+ python: 'Python'
+ scala: 'Scala'
+ compiler: 'Compiler'
+ ghc: 'GHC'
+ os: 'OS'
+ }
QUEUES: [
{ name: 'linux', display: 'Linux' }
@@ -131,6 +151,9 @@ Travis.Router.reopen
if Travis.config.ga_code
_gaq.push ['_trackPageview', location.pathname]
+Ember.LinkView.reopen
+ loadingClass: 'loading_link'
+
require 'ext/i18n'
require 'travis/ajax'
require 'travis/adapter'
diff --git a/assets/scripts/vendor/ember-model.js b/assets/scripts/vendor/ember-model.js
index 4665d5d1..b04a12ab 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) {
+ return modelClass.adapter.findAll(modelClass, this);
+ } else if (this._query) {
+ return modelClass.adapter.findQuery(modelClass, this, this._query);
+ } else {
+ promises = this.map(function(record) {
+ return record.reload();
+ });
+ return 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: [],
+
+ 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;
@@ -566,19 +641,13 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
},
reload: function() {
+ this.getWithDefault('_dirtyAttributes', []).clear();
return this.constructor.reload(this.get(get(this.constructor, 'primaryKey')));
},
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 +656,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 +693,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 +708,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 +729,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 +750,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 +833,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 +933,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);
}
@@ -766,7 +951,8 @@ Ember.Model.reopenClass({
_executeBatch: function() {
var batchIds = this._currentBatchIds,
- batchRecordArrays = this._currentBatchRecordArrays,
+ batchRecordArrays = this._currentBatchRecordArrays || [],
+ batchDeferreds = this._currentBatchDeferreds,
self = this,
requestIds = [],
promise,
@@ -774,41 +960,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 +1023,7 @@ Ember.Model.reopenClass({
return record;
},
+
addToRecordArrays: function(record) {
if (this._findAllRecordArray) {
this._findAllRecordArray.pushObject(record);
@@ -834,6 +1040,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 +1095,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 +1136,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 +1191,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 +1206,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 +1222,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 +1266,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 +1287,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,25 +1322,54 @@ function deserialize(value, type) {
} else if (type && Ember.Model.dataTypes[type]) {
return Ember.Model.dataTypes[type].deserialize(value);
} else {
- return wrapObject(value);
+ return value;
}
}
+function serialize(value, type) {
+ if (type && type.serialize) {
+ return type.serialize(value);
+ } else if (type && Ember.Model.dataTypes[type]) {
+ return Ember.Model.dataTypes[type].serialize(value);
+ } else {
+ return value;
+ }
+}
Ember.attr = function(type, options) {
return Ember.computed(function(key, value) {
var data = get(this, '_data'),
dataKey = this.dataKey(key),
dataValue = data && get(data, dataKey),
- beingCreated = meta(this).proto === this;
+ 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 !== serialize(value, type)) {
+ dirtyAttributes.pushObject(key);
+ } else {
+ dirtyAttributes.removeObject(key);
+ }
+
+ if (createdDirtyAttributes) {
+ set(this, '_dirtyAttributes', dirtyAttributes);
+ }
+
+ return value;
}
return this.getAttr(key, deserialize(dataValue, type));
@@ -1132,6 +1390,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({
return this.ajax(url).then(function(data) {
self.didFind(record, id, data);
+ return record;
});
},
@@ -1148,6 +1407,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({
return this.ajax(url).then(function(data) {
self.didFindAll(klass, records, data);
+ return records;
});
},
@@ -1164,6 +1424,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({
return this.ajax(url, params).then(function(data) {
self.didFindQuery(klass, records, params, data);
+ return records;
});
},
@@ -1180,6 +1441,7 @@ Ember.RESTAdapter = Ember.Adapter.extend({
return this.ajax(url, record.toJSON(), "POST").then(function(data) {
self.didCreateRecord(record, data);
+ return record;
});
},
@@ -1187,7 +1449,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 +1460,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;
});
},
@@ -1220,15 +1482,15 @@ Ember.RESTAdapter = Ember.Adapter.extend({
record.didDeleteRecord();
},
- ajax: function(url, params, method) {
- return this._ajax(url, params, method || "GET");
+ ajax: function(url, params, method, settings) {
+ return this._ajax(url, params, (method || "GET"), settings);
},
buildURL: function(klass, id) {
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 +1505,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 +1525,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 +1573,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..735ee4b1 100644
--- a/assets/scripts/vendor/ember.js
+++ b/assets/scripts/vendor/ember.js
@@ -1,7 +1,15 @@
-// Version: v1.0.0-rc.7-59-g4048275
-// Last commit: 4048275 (2013-08-25 00:57:38 -0400)
+// ==========================================================================
+// Project: Ember - JavaScript Application Framework
+// Copyright: Copyright 2011-2013 Tilde Inc. and contributors
+// Portions Copyright 2006-2011 Strobe Inc.
+// Portions Copyright 2008-2011 Apple Inc. All rights reserved.
+// License: Licensed under MIT license
+// See https://raw.github.com/emberjs/ember.js/master/LICENSE
+// ==========================================================================
+ // Version: 1.2.0
+
(function() {
/*global __fail__*/
@@ -55,7 +63,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 +115,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 +146,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,12 +170,32 @@ 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)
+// ==========================================================================
+// Project: Ember - JavaScript Application Framework
+// Copyright: Copyright 2011-2013 Tilde Inc. and contributors
+// Portions Copyright 2006-2011 Strobe Inc.
+// Portions Copyright 2008-2011 Apple Inc. All rights reserved.
+// License: Licensed under MIT license
+// See https://raw.github.com/emberjs/ember.js/master/LICENSE
+// ==========================================================================
+ // Version: 1.2.0
+
(function() {
var define, requireModule;
@@ -225,7 +259,7 @@ var define, requireModule;
@class Ember
@static
- @version 1.0.0-rc.7
+ @version 1.2.0
*/
if ('undefined' === typeof Ember) {
@@ -252,10 +286,10 @@ Ember.toString = function() { return "Ember"; };
/**
@property VERSION
@type String
- @default '1.0.0-rc.7'
+ @default '1.2.0'
@final
*/
-Ember.VERSION = '1.0.0-rc.7';
+Ember.VERSION = '1.2.0';
/**
Standard environmental variables. You can define these in a global `ENV`
@@ -280,6 +314,45 @@ Ember.ENV = Ember.ENV || ENV;
Ember.config = Ember.config || {};
+/**
+ Hash of enabled Canary features. Add to before creating your application.
+
+ You can also define `ENV.FEATURES` if you need to enable features flagged at runtime.
+
+ @property FEATURES
+ @type Hash
+*/
+
+Ember.FEATURES = Ember.ENV.FEATURES || {};
+
+/**
+ Test that a feature is enabled. Parsed by Ember's build tools to leave
+ experimental features out of beta/stable builds.
+
+ You can define the following configuration options:
+
+ * `ENV.ENABLE_ALL_FEATURES` - force all features to be enabled.
+ * `ENV.ENABLE_OPTIONAL_FEATURES` - enable any features that have not been explicitly
+ enabled/disabled.
+
+ @method isEnabled
+ @param {string} feature
+*/
+
+Ember.FEATURES.isEnabled = function(feature) {
+ var featureValue = Ember.FEATURES[feature];
+
+ if (Ember.ENV.ENABLE_ALL_FEATURES) {
+ return true;
+ } else if (featureValue === true || featureValue === false || featureValue === undefined) {
+ return featureValue;
+ } else if (Ember.ENV.ENABLE_OPTIONAL_FEATURES) {
+ return true;
+ } else {
+ return false;
+ }
+};
+
// ..........................................................
// BOOTSTRAP
//
@@ -332,7 +405,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
@@ -361,104 +434,21 @@ if ('undefined' === typeof Ember.deprecateFunc) {
*/
Ember.uuid = 0;
-// ..........................................................
-// LOGGER
-//
-
-function consoleMethod(name) {
- var consoleObj;
- if (imports.console) {
- consoleObj = imports.console;
- } else if (typeof console !== 'undefined') {
- consoleObj = console;
- }
-
- var method = typeof consoleObj === 'object' ? consoleObj[name] : null;
-
- if (method) {
- // Older IE doesn't support apply, but Chrome needs it
- if (method.apply) {
- return function() {
- method.apply(consoleObj, arguments);
- };
- } else {
- return function() {
- var message = Array.prototype.join.call(arguments, ', ');
- method(message);
- };
- }
- }
-}
-
-function assertPolyfill(test, message) {
- if (!test) {
- try {
- // attempt to preserve the stack
- throw new Error("assertion failed: " + message);
- } catch(error) {
- setTimeout(function() {
- throw error;
- }, 0);
- }
- }
-}
-
/**
- Inside Ember-Metal, simply uses the methods from `imports.console`.
- Override this to provide more robust logging functionality.
+ Merge the contents of two objects together into the first object.
- @class Logger
- @namespace Ember
-*/
-Ember.Logger = {
- log: consoleMethod('log') || Ember.K,
- warn: consoleMethod('warn') || Ember.K,
- error: consoleMethod('error') || Ember.K,
- info: consoleMethod('info') || Ember.K,
- debug: consoleMethod('debug') || consoleMethod('info') || Ember.K,
- assert: consoleMethod('assert') || assertPolyfill
-};
+ ```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'}
+ ```
-
-// ..........................................................
-// ERROR HANDLING
-//
-
-/**
- A function may be assigned to `Ember.onerror` to be called when Ember
- internals encounter an error. This is useful for specialized error handling
- and reporting code.
-
- @event onerror
+ @method merge
@for Ember
- @param {Exception} error the error object
+ @param {Object} original The object to merge into
+ @param {Object} updates The object to copy properties from
+ @return {Object}
*/
-Ember.onerror = null;
-
-/**
- @private
-
- Wrap code block in a try/catch if `Ember.onerror` is set.
-
- @method handleErrors
- @for Ember
- @param {Function} func
- @param [context]
-*/
-Ember.handleErrors = function(func, context) {
- // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error,
- // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch
- if ('function' === typeof Ember.onerror) {
- try {
- return func.call(context || this);
- } catch (error) {
- Ember.onerror(error);
- }
- } else {
- return func.call(context || this);
- }
-};
-
Ember.merge = function(original, updates) {
for (var prop in updates) {
if (!updates.hasOwnProperty(prop)) { continue; }
@@ -547,7 +537,7 @@ var platform = Ember.platform = {};
*/
Ember.create = Object.create;
-// IE8 has Object.create but it couldn't treat property descripters.
+// IE8 has Object.create but it couldn't treat property descriptors.
if (Ember.create) {
if (Ember.create({a: 1}, {a: {value: 2}}).a !== 2) {
Ember.create = null;
@@ -761,6 +751,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,
@@ -785,11 +781,93 @@ if (Ember.SHIM_ES5) {
+(function() {
+var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
+
+/**
+ A subclass of the JavaScript Error object for use in Ember.
+
+ @class Error
+ @namespace Ember
+ @extends Error
+ @constructor
+*/
+Ember.Error = function() {
+ var tmp = Error.apply(this, arguments);
+
+ // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
+ for (var idx = 0; idx < errorProps.length; idx++) {
+ this[errorProps[idx]] = tmp[errorProps[idx]];
+ }
+};
+
+Ember.Error.prototype = Ember.create(Error.prototype);
+
+// ..........................................................
+// ERROR HANDLING
+//
+
+/**
+ A function may be assigned to `Ember.onerror` to be called when Ember
+ internals encounter an error. This is useful for specialized error handling
+ and reporting code.
+
+ ```javascript
+ Ember.onerror = function(error) {
+ Em.$.ajax('/report-error', 'POST', {
+ stack: error.stack,
+ otherInformation: 'whatever app state you want to provide'
+ });
+ };
+ ```
+
+ @event onerror
+ @for Ember
+ @param {Exception} error the error object
+*/
+Ember.onerror = null;
+
+/**
+ @private
+
+ Wrap code block in a try/catch if `Ember.onerror` is set.
+
+ @method handleErrors
+ @for Ember
+ @param {Function} func
+ @param [context]
+*/
+Ember.handleErrors = function(func, context) {
+ // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error,
+ // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch
+ if ('function' === typeof Ember.onerror) {
+ try {
+ return func.call(context || this);
+ } catch (error) {
+ Ember.onerror(error);
+ }
+ } else {
+ return func.call(context || this);
+ }
+};
+
+})();
+
+
+
(function() {
/**
@module ember-metal
*/
+/**
+ @private
+
+ Prefix used for guids through out Ember.
+
+*/
+Ember.GUID_PREFIX = 'ember';
+
var o_defineProperty = Ember.platform.defineProperty,
o_create = Ember.create,
@@ -844,13 +922,13 @@ var GUID_DESC = {
@return {String} the guid
*/
Ember.generateGuid = function generateGuid(obj, prefix) {
- if (!prefix) prefix = 'ember';
+ if (!prefix) prefix = Ember.GUID_PREFIX;
var ret = (prefix + (uuid++));
if (obj) {
GUID_DESC.value = ret;
o_defineProperty(obj, GUID_KEY, GUID_DESC);
}
- return ret ;
+ return ret;
};
/**
@@ -1174,10 +1252,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 +1271,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 +1309,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 +1370,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);
}
},
@@ -1747,7 +1931,7 @@ var normalizeTuple = Ember.normalizeTuple = function(target, path) {
}
// must return some kind of path to be valid else other things will break.
- if (!path || path.length===0) throw new Error('Invalid Path');
+ if (!path || path.length===0) throw new Ember.Error('Invalid Path');
return [ target, path ];
};
@@ -1788,7 +1972,6 @@ Ember.getWithDefault = function(root, key, defaultValue) {
Ember.get = get;
-Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get);
})();
@@ -2016,7 +2199,7 @@ function suspendListener(obj, eventName, target, method, callback) {
Suspends multiple listeners during a callback.
-
+
@method suspendListeners
@for Ember
@param obj
@@ -2032,6 +2215,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 +2493,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 +2502,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 +2511,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 +2524,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 +2568,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);
@@ -2418,21 +2617,27 @@ Ember.overrideChains = function(obj, keyName, m) {
/**
@method beginPropertyChanges
@chainable
+ @private
*/
-var beginPropertyChanges = Ember.beginPropertyChanges = function() {
+function beginPropertyChanges() {
deferred++;
-};
+}
+
+Ember.beginPropertyChanges = beginPropertyChanges;
/**
@method endPropertyChanges
+ @private
*/
-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 +2659,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 +2670,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 +2682,7 @@ var notifyObservers = function(obj, keyName) {
} else {
sendEvent(obj, eventName, [obj, keyName]);
}
-};
+}
})();
@@ -2496,7 +2701,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 +2711,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
@@ -2595,19 +2800,18 @@ function setPath(root, path, value, tolerant) {
}
if (!keyName || keyName.length === 0) {
- throw new Error('You passed an empty path');
+ throw new Ember.Error('You passed an empty path');
}
if (!root) {
if (tolerant) { return; }
- else { throw new Error('Object in path '+path+' could not be found or was destroyed.'); }
+ else { throw new Ember.Error('Object in path '+path+' could not be found or was destroyed.'); }
}
return set(root, keyName, value);
}
Ember.set = set;
-Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now supports paths', Ember.set);
/**
Error-tolerant form of `Ember.set`. Will not blow up if any part of the
@@ -2625,7 +2829,6 @@ Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now support
Ember.trySet = function(root, path, value) {
return set(root, path, value, true);
};
-Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet);
})();
@@ -2837,14 +3040,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.
@@ -3010,6 +3213,142 @@ MapWithDefault.prototype.copy = function() {
+(function() {
+function consoleMethod(name) {
+ var consoleObj;
+ if (Ember.imports.console) {
+ consoleObj = Ember.imports.console;
+ } else if (typeof console !== 'undefined') {
+ consoleObj = console;
+ }
+
+ var method = typeof consoleObj === 'object' ? consoleObj[name] : null;
+
+ if (method) {
+ // Older IE doesn't support apply, but Chrome needs it
+ if (method.apply) {
+ return function() {
+ method.apply(consoleObj, arguments);
+ };
+ } else {
+ return function() {
+ var message = Array.prototype.join.call(arguments, ', ');
+ method(message);
+ };
+ }
+ }
+}
+
+function assertPolyfill(test, message) {
+ if (!test) {
+ try {
+ // attempt to preserve the stack
+ throw new Ember.Error("assertion failed: " + message);
+ } catch(error) {
+ setTimeout(function() {
+ throw error;
+ }, 0);
+ }
+ }
+}
+
+/**
+ Inside Ember-Metal, simply uses the methods from `imports.console`.
+ Override this to provide more robust logging functionality.
+
+ @class Logger
+ @namespace Ember
+*/
+Ember.Logger = {
+ /**
+ Logs the arguments to the console.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ var foo = 1;
+ Ember.Logger.log('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
+ ```
+
+ @method log
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ log: consoleMethod('log') || Ember.K,
+ /**
+ Prints the arguments to the console with a warning icon.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ Ember.Logger.warn('Something happened!'); // "Something happened!" will be printed to the console with a warning icon.
+ ```
+
+ @method warn
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ warn: consoleMethod('warn') || Ember.K,
+ /**
+ Prints the arguments to the console with an error icon, red text and a stack race.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ Ember.Logger.error('Danger! Danger!'); // "Danger! Danger!" will be printed to the console in red text.
+ ```
+
+ @method error
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ error: consoleMethod('error') || Ember.K,
+ /**
+ Logs the arguments to the console.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ var foo = 1;
+ Ember.Logger.info('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
+ ```
+
+ @method info
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ info: consoleMethod('info') || Ember.K,
+ /**
+ Logs the arguments to the console in blue text.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ var foo = 1;
+ Ember.Logger.debug('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
+ ```
+
+ @method debug
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ debug: consoleMethod('debug') || consoleMethod('info') || Ember.K,
+ /**
+
+ If the value passed into Ember.Logger.assert is not truthy it will throw an error with a stack trace.
+
+ ```javascript
+ Ember.Logger.assert(true); // undefined
+ Ember.Logger.assert(true === false); // Throws an Assertion failed error.
+ ```
+
+ @method assert
+ @for Ember.Logger
+ @param {Boolean} bool Value to test
+ */
+ assert: consoleMethod('assert') || assertPolyfill
+};
+
+
+})();
+
+
+
(function() {
/**
@module ember-metal
@@ -3165,6 +3504,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 +3554,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 +3651,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 +3884,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 +3949,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 +3966,20 @@ Ember.finishChains = function(obj) {
if (chains.value() !== obj) {
m.chains = chains = chains.copy(obj);
}
- chains.didChange(true);
+ chains.didChange(null);
}
};
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-metal
+*/
+
+
})();
@@ -3641,6 +4047,7 @@ var metaFor = Ember.meta, // utils.js
generateGuid = Ember.generateGuid,
IS_PATH = /[\.\*]/;
+
// returns true if the passed path is just a keyName
function isKeyName(path) {
return path==='*' || !IS_PATH.test(path);
@@ -3660,15 +4067,17 @@ function isKeyName(path) {
@param obj
@param {String} keyName
*/
-Ember.watch = function(obj, keyPath) {
+Ember.watch = function(obj, _keyPath) {
// can't watch length on Array - it is special...
- if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
+ if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
- if (isKeyName(keyPath)) {
- watchKey(obj, keyPath);
- } else {
- watchPath(obj, keyPath);
- }
+
+ if (isKeyName(_keyPath)) {
+ watchKey(obj, _keyPath);
+ } else {
+ watchPath(obj, _keyPath);
+ }
+
};
Ember.isWatching = function isWatching(obj, key) {
@@ -3678,15 +4087,17 @@ Ember.isWatching = function isWatching(obj, key) {
Ember.watch.flushPending = Ember.flushPendingChains;
-Ember.unwatch = function(obj, keyPath) {
+Ember.unwatch = function(obj, _keyPath) {
// can't watch length on Array - it is special...
- if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
+ if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
- if (isKeyName(keyPath)) {
- unwatchKey(obj, keyPath);
- } else {
- unwatchPath(obj, keyPath);
- }
+
+ if (isKeyName(_keyPath)) {
+ unwatchKey(obj, _keyPath);
+ } else {
+ unwatchPath(obj, _keyPath);
+ }
+
};
/**
@@ -3705,7 +4116,7 @@ Ember.rewatch = function(obj) {
// make sure the object has its own guid.
if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
- generateGuid(obj, 'ember');
+ generateGuid(obj);
}
// make sure any chained watchers update.
@@ -3778,6 +4189,7 @@ var get = Ember.get,
watch = Ember.watch,
unwatch = Ember.unwatch;
+
// ..........................................................
// DEPENDENT KEYS
//
@@ -3854,6 +4266,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 +4359,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.
@@ -3956,9 +4443,14 @@ ComputedPropertyPrototype.readOnly = function(readOnly) {
@chainable
*/
ComputedPropertyPrototype.property = function() {
+ var addArg;
+
+
var args = [];
for (var i = 0, l = arguments.length; i < l; i++) {
- args.push(arguments[i]);
+
+ args.push(arguments[i]);
+
}
this._dependentKeys = args;
return this;
@@ -4014,11 +4506,37 @@ ComputedPropertyPrototype.didChange = function(obj, keyName) {
function finishChains(chainNodes)
{
for (var i=0, l=chainNodes.length; i 2) {
- args = slice.call(arguments, 2);
-
- fn = function() {
- method.apply(target, args);
- };
- } else {
- fn = function() {
- method.call(target);
- };
+ function fn() {
+ method.apply(target, args);
}
// find position to insert - TODO: binary search
@@ -4994,7 +5840,7 @@ define("backburner",
throttle: function(target, method /* , args, wait */) {
var self = this,
args = arguments,
- wait = pop.call(args),
+ wait = parseInt(pop.call(args), 10),
throttler;
for (var i = 0, l = throttlers.length; i < l; i++) {
@@ -5029,13 +5875,14 @@ define("backburner",
index,
debouncee;
- if (typeof immediate === "number") {
+ if (typeof immediate === "number" || typeof immediate === "string") {
wait = immediate;
immediate = false;
} else {
wait = pop.call(args);
}
+ wait = parseInt(wait, 10);
// Remove debouncee
index = findDebouncee(target, method);
@@ -5045,7 +5892,7 @@ define("backburner",
clearTimeout(debouncee[2]);
}
- var timer = window.setTimeout(function() {
+ var timer = global.setTimeout(function() {
if (!immediate) {
self.run.apply(self, args);
}
@@ -5114,8 +5961,8 @@ define("backburner",
function createAutorun(backburner) {
backburner.begin();
autorun = global.setTimeout(function() {
- backburner.end();
autorun = null;
+ backburner.end();
});
}
@@ -5165,6 +6012,7 @@ define("backburner",
__exports__.Backburner = Backburner;
});
+
})();
@@ -5762,7 +6610,7 @@ Binding.prototype = {
This copies the Binding so it can be connected to another object.
@method copy
- @return {Ember.Binding}
+ @return {Ember.Binding} `this`
*/
copy: function () {
var copy = new Binding(this._to, this._from);
@@ -5970,7 +6818,7 @@ function mixinProperties(to, from) {
mixinProperties(Binding, {
- /**
+ /*
See `Ember.Binding.from`.
@method from
@@ -5981,7 +6829,7 @@ mixinProperties(Binding, {
return binding.from.apply(binding, arguments);
},
- /**
+ /*
See `Ember.Binding.to`.
@method to
@@ -6006,6 +6854,7 @@ mixinProperties(Binding, {
@param {Boolean} [flag] (Optional) passing nothing here will make the
binding `oneWay`. You can instead pass `false` to disable `oneWay`, making the
binding two way again.
+ @return {Ember.Binding} `this`
*/
oneWay: function(from, flag) {
var C = this, binding = new C(null, from);
@@ -6337,15 +7186,14 @@ function addNormalizedProperty(base, key, value, meta, descs, values, concats, m
descs[key] = value;
values[key] = undefined;
} else {
- // impl super if needed...
- if (isMethod(value)) {
- value = giveMethodSuper(base, key, value, values, descs);
- } else if ((concats && a_indexOf.call(concats, key) >= 0) ||
+ 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 +7218,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 +7392,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 +7402,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 +7629,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,25 +7661,68 @@ Ember.aliasMethod = function(methodName) {
//
/**
+ Specify a method that observes property changes.
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: Ember.observer('value', function() {
+ // Executes whenever the "value" property changes
+ })
+ });
+ ```
+
+ In the future this method may become asynchronous. If you want to ensure
+ synchronous behavior, use `immediateObserver`.
+
+ Also available as `Function.prototype.observes` if prototype extensions are
+ enabled.
+
@method observer
@for Ember
- @param {Function} func
@param {String} propertyNames*
+ @param {Function} func
@return func
*/
-Ember.observer = function(func) {
- var paths = a_slice.call(arguments, 1);
+Ember.observer = function() {
+ var func = a_slice.call(arguments, -1)[0];
+ var paths = a_slice.call(arguments, 0, -1);
+
+ if (typeof func !== "function") {
+ // revert to old, soft-deprecated argument ordering
+
+ func = arguments[0];
+ paths = a_slice.call(arguments, 1);
+ }
+
+ if (typeof func !== "function") {
+ throw new Ember.Error("Ember.observer called without a function");
+ }
+
func.__ember_observes__ = paths;
return func;
};
-// If observers ever become asynchronous, Ember.immediateObserver
-// must remain synchronous.
/**
+ Specify a method that observes property changes.
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: Ember.immediateObserver('value', function() {
+ // Executes whenever the "value" property changes
+ })
+ });
+ ```
+
+ In the future, `Ember.observer` may become asynchronous. In this event,
+ `Ember.immediateObserver` will maintain the synchronous behavior.
+
+ Also available as `Function.prototype.observesImmediately` if prototype extensions are
+ enabled.
+
@method immediateObserver
@for Ember
- @param {Function} func
@param {String} propertyNames*
+ @param {Function} func
@return func
*/
Ember.immediateObserver = function() {
@@ -6830,32 +7746,52 @@ Ember.immediateObserver = function() {
```javascript
App.PersonView = Ember.View.extend({
+
friends: [{ name: 'Tom' }, { name: 'Stefan' }, { name: 'Kris' }],
- valueWillChange: function (obj, keyName) {
+
+ valueWillChange: Ember.beforeObserver('content.value', function(obj, keyName) {
this.changingFrom = obj.get(keyName);
- }.observesBefore('content.value'),
- valueDidChange: function(obj, keyName) {
+ }),
+
+ valueDidChange: Ember.observer('content.value', 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) {
+ }),
+
+ friendsDidChange: Ember.observer('friends.@each.name', function(obj, keyName) {
// some logic
// obj.get(keyName) returns friends array
- }.observes('friends.@each.name')
+ })
});
```
+ Also available as `Function.prototype.observesBefore` if prototype extensions are
+ enabled.
+
@method beforeObserver
@for Ember
- @param {Function} func
@param {String} propertyNames*
+ @param {Function} func
@return func
*/
-Ember.beforeObserver = function(func) {
- var paths = a_slice.call(arguments, 1);
+Ember.beforeObserver = function() {
+ var func = a_slice.call(arguments, -1)[0];
+ var paths = a_slice.call(arguments, 0, -1);
+
+ if (typeof func !== "function") {
+ // revert to old, soft-deprecated argument ordering
+
+ func = arguments[0];
+ paths = a_slice.call(arguments, 1);
+ }
+
+ if (typeof func !== "function") {
+ throw new Ember.Error("Ember.beforeObserver called without a function");
+ }
+
func.__ember_observesBefore__ = paths;
return func;
};
@@ -6864,6 +7800,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 +7916,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 +7967,7 @@ define("rsvp/async",
function useSetTimeout() {
return function(callback, arg) {
- setTimeout(function() {
+ local.setTimeout(function() {
callback(arg);
}, 1);
};
@@ -7364,6 +8350,10 @@ define("rsvp/promise",
});
return thenPromise;
+ },
+
+ fail: function(fail) {
+ return this.then(null, fail);
}
};
@@ -7466,19 +8456,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 +8496,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 +8597,7 @@ define("container",
@method has
@param {String} key
- @returns {Boolean}
+ @return {Boolean}
*/
has: function(key) {
var dict = this.dict;
@@ -7614,11 +8631,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 +8723,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 +8759,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 +8793,7 @@ define("container",
container.unregister('model:user')
container.lookup('model:user') === undefined //=> true
+ ```
@method unregister
@param {String} fullName
@@ -7786,6 +8803,7 @@ define("container",
this.registry.remove(normalizedName);
this.cache.remove(normalizedName);
+ this.factoryCache.remove(normalizedName);
this._options.remove(normalizedName);
},
@@ -7819,7 +8837,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 +8868,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 +8929,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 +9007,7 @@ define("container",
this.optionsForType(type, options);
},
- /*
+ /**
@private
Used only via `injection`.
@@ -8020,7 +9049,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 +9057,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 +9104,7 @@ define("container",
},
- /*
+ /**
@private
Used only via `factoryInjection`.
@@ -8112,7 +9141,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 +9152,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 +9255,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 +9285,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,61 +9669,43 @@ 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;
};
}
-// ..........................................................
-// ERROR
-//
-
-var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
-
-/**
- A subclass of the JavaScript Error object for use in Ember.
-
- @class Error
- @namespace Ember
- @extends Error
- @constructor
-*/
-Ember.Error = function() {
- var tmp = Error.apply(this, arguments);
-
- // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
- for (var idx = 0; idx < errorProps.length; idx++) {
- this[errorProps[idx]] = tmp[errorProps[idx]];
- }
-};
-
-Ember.Error.prototype = Ember.create(Error.prototype);
-
-})();
-
-
-
-(function() {
-/**
- Expose RSVP implementation
-
- Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md
-
- @class RSVP
- @namespace Ember
- @constructor
-*/
-Ember.RSVP = requireModule('rsvp');
-
})();
@@ -8700,7 +9718,7 @@ Ember.RSVP = requireModule('rsvp');
var STRING_DASHERIZE_REGEXP = (/[ _]/g);
var STRING_DASHERIZE_CACHE = {};
-var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g);
+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);
@@ -8826,7 +9844,7 @@ Ember.String = {
},
/**
- Replaces underscores or spaces with dashes.
+ Replaces underscores, spaces, or camelCase with dashes.
```javascript
'innerHTML'.dasherize(); // 'inner-html'
@@ -8940,9 +9958,10 @@ Ember.String = {
capitalize: function(str) {
return str.charAt(0).toUpperCase() + str.substr(1);
}
-
};
+
+
})();
@@ -8965,10 +9984,11 @@ var fmt = Ember.String.fmt,
capitalize = Ember.String.capitalize,
classify = Ember.String.classify;
+
if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
/**
- See `Ember.String.fmt`.
+ See [Ember.String.fmt](/api/classes/Ember.String.html#method_fmt).
@method fmt
@for String
@@ -8978,7 +9998,7 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
};
/**
- See `Ember.String.w`.
+ See [Ember.String.w](/api/classes/Ember.String.html#method_w).
@method w
@for String
@@ -8988,7 +10008,7 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
};
/**
- See `Ember.String.loc`.
+ See [Ember.String.loc](/api/classes/Ember.String.html#method_loc).
@method loc
@for String
@@ -8998,7 +10018,7 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
};
/**
- See `Ember.String.camelize`.
+ See [Ember.String.camelize](/api/classes/Ember.String.html#method_camelize).
@method camelize
@for String
@@ -9008,7 +10028,7 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
};
/**
- See `Ember.String.decamelize`.
+ See [Ember.String.decamelize](/api/classes/Ember.String.html#method_decamelize).
@method decamelize
@for String
@@ -9018,7 +10038,7 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
};
/**
- See `Ember.String.dasherize`.
+ See [Ember.String.dasherize](/api/classes/Ember.String.html#method_dasherize).
@method dasherize
@for String
@@ -9028,7 +10048,7 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
};
/**
- See `Ember.String.underscore`.
+ See [Ember.String.underscore](/api/classes/Ember.String.html#method_underscore).
@method underscore
@for String
@@ -9038,7 +10058,7 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
};
/**
- See `Ember.String.classify`.
+ See [Ember.String.classify](/api/classes/Ember.String.html#method_classify).
@method classify
@for String
@@ -9048,7 +10068,7 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
};
/**
- See `Ember.String.capitalize`.
+ See [Ember.String.capitalize](/api/classes/Ember.String.html#method_capitalize).
@method capitalize
@for String
@@ -9057,6 +10077,7 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
return capitalize(this);
};
+
}
@@ -9070,158 +10091,1619 @@ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
@submodule ember-runtime
*/
-var a_slice = Array.prototype.slice;
+var get = Ember.get,
+ set = Ember.set,
+ slice = Array.prototype.slice,
+ getProperties = Ember.getProperties;
-if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) {
+/**
+ ## Overview
+
+ This mixin provides properties and property observing functionality, core
+ features of the Ember object model.
+
+ Properties and observers allow one object to observe changes to a
+ property on another object. This is one of the fundamental ways that
+ models, controllers and views communicate with each other in an Ember
+ application.
+
+ Any object that has this mixin applied can be used in observer
+ operations. That includes `Ember.Object` and most objects you will
+ interact with as you write your Ember application.
+
+ Note that you will not generally apply this mixin to classes yourself,
+ but you will use the features provided by this module frequently, so it
+ is important to understand how to use it.
+
+ ## Using `get()` and `set()`
+
+ Because of Ember's support for bindings and observers, you will always
+ access properties using the get method, and set properties using the
+ set method. This allows the observing objects to be notified and
+ computed properties to be handled properly.
+
+ More documentation about `get` and `set` are below.
+
+ ## Observing Property Changes
+
+ You typically observe property changes simply by adding the `observes`
+ call to the end of your method declarations in classes that you write.
+ For example:
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: function() {
+ // Executes whenever the "value" property changes
+ }.observes('value')
+ });
+ ```
+
+ Although this is the most common way to add an observer, this capability
+ is actually built into the `Ember.Object` class on top of two methods
+ defined in this mixin: `addObserver` and `removeObserver`. You can use
+ these two methods to add and remove observers yourself if you need to
+ do so at runtime.
+
+ To add an observer for a property, call:
+
+ ```javascript
+ object.addObserver('propertyKey', targetObject, targetAction)
+ ```
+
+ This will call the `targetAction` method on the `targetObject` whenever
+ the value of the `propertyKey` changes.
+
+ Note that if `propertyKey` is a computed property, the observer will be
+ called when any of the property dependencies are changed, even if the
+ resulting value of the computed property is unchanged. This is necessary
+ because computed properties are not computed until `get` is called.
+
+ @class Observable
+ @namespace Ember
+*/
+Ember.Observable = Ember.Mixin.create({
/**
- The `property` extension of Javascript's Function prototype is available
- when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
- `true`, which is the default.
+ Retrieves the value of a property from the object.
- Computed properties allow you to treat a function like a property:
+ This method is usually similar to using `object[keyName]` or `object.keyName`,
+ however it supports both computed properties and the unknownProperty
+ handler.
+
+ Because `get` unifies the syntax for accessing all these kinds
+ of properties, it can make many refactorings easier, such as replacing a
+ simple property with a computed property, or vice versa.
+
+ ### Computed Properties
+
+ Computed properties are methods defined with the `property` modifier
+ declared at the end, such as:
```javascript
- MyApp.president = Ember.Object.create({
- firstName: "Barack",
- lastName: "Obama",
-
- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
-
- // Call this flag to mark the function as a property
- }.property()
- });
-
- MyApp.president.get('fullName'); // "Barack Obama"
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+ }.property('firstName', 'lastName')
```
- Treating a function like a property is useful because they can work with
- bindings, just like any other property.
+ When you call `get` on a computed property, the function will be
+ called and the return value will be returned instead of the function
+ itself.
- Many computed properties have dependencies on other properties. For
- example, in the above example, the `fullName` property depends on
- `firstName` and `lastName` to determine its value. You can tell Ember
- about these dependencies like this:
+ ### Unknown Properties
- ```javascript
- MyApp.president = Ember.Object.create({
- firstName: "Barack",
- lastName: "Obama",
+ Likewise, if you try to call `get` on a property whose value is
+ `undefined`, the `unknownProperty()` method will be called on the object.
+ If this method returns any value other than `undefined`, it will be returned
+ instead. This allows you to implement "virtual" properties that are
+ not defined upfront.
- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
-
- // Tell Ember.js that this computed property depends on firstName
- // and lastName
- }.property('firstName', 'lastName')
- });
- ```
-
- Make sure you list these dependencies so Ember knows when to update
- bindings that connect to a computed property. Changing a dependency
- will not immediately trigger an update of the computed property, but
- will instead clear the cache so that it is updated when the next `get`
- is called on the property.
-
- See `Ember.ComputedProperty`, `Ember.computed`.
-
- @method property
- @for Function
+ @method get
+ @param {String} keyName The property to retrieve
+ @return {Object} The property value or undefined.
*/
- Function.prototype.property = function() {
- var ret = Ember.computed(this);
- return ret.property.apply(ret, arguments);
- };
+ get: function(keyName) {
+ return get(this, keyName);
+ },
/**
- The `observes` extension of Javascript's Function prototype is available
- when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
- true, which is the default.
-
- You can observe property changes simply by adding the `observes`
- call to the end of your method declarations in classes that you write.
- For example:
+ To get multiple properties at once, call `getProperties`
+ with a list of strings or an array:
```javascript
- Ember.Object.create({
- valueObserver: function() {
- // Executes whenever the "value" property changes
- }.observes('value')
- });
+ record.getProperties('firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
```
- See `Ember.observes`.
+ is equivalent to:
- @method observes
- @for Function
+ ```javascript
+ record.getProperties(['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
+
+ @method getProperties
+ @param {String...|Array} list of keys to get
+ @return {Hash}
*/
- Function.prototype.observes = function() {
- this.__ember_observes__ = a_slice.call(arguments);
+ getProperties: function() {
+ return getProperties.apply(null, [this].concat(slice.call(arguments)));
+ },
+
+ /**
+ Sets the provided key or path to the value.
+
+ This method is generally very similar to calling `object[key] = value` or
+ `object.key = value`, except that it provides support for computed
+ properties, the `setUnknownProperty()` method and property observers.
+
+ ### Computed Properties
+
+ If you try to set a value on a key that has a computed property handler
+ defined (see the `get()` method for an example), then `set()` will call
+ that method, passing both the value and key instead of simply changing
+ the value itself. This is useful for those times when you need to
+ implement a property that is composed of one or more member
+ properties.
+
+ ### Unknown Properties
+
+ If you try to set a value on a key that is undefined in the target
+ object, then the `setUnknownProperty()` handler will be called instead. This
+ gives you an opportunity to implement complex "virtual" properties that
+ are not predefined on the object. If `setUnknownProperty()` returns
+ undefined, then `set()` will simply set the value on the object.
+
+ ### Property Observers
+
+ In addition to changing the property, `set()` will also register a property
+ change with the object. Unless you have placed this call inside of a
+ `beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers
+ (i.e. observer methods declared on the same object), will be called
+ immediately. Any "remote" observers (i.e. observer methods declared on
+ another object) will be placed in a queue and called at a later time in a
+ coalesced manner.
+
+ ### Chaining
+
+ In addition to property changes, `set()` returns the value of the object
+ itself so you can do chaining like this:
+
+ ```javascript
+ record.set('firstName', 'Charles').set('lastName', 'Jolley');
+ ```
+
+ @method set
+ @param {String} keyName The property to set
+ @param {Object} value The value to set or `null`.
+ @return {Ember.Observable}
+ */
+ set: function(keyName, value) {
+ set(this, keyName, value);
return this;
- };
+ },
/**
- The `observesBefore` extension of Javascript's Function prototype is
- available when `Ember.EXTEND_PROTOTYPES` or
- `Ember.EXTEND_PROTOTYPES.Function` is true, which is the default.
-
- You can get notified when a property changes is about to happen by
- by adding the `observesBefore` call to the end of your method
- declarations in classes that you write. For example:
+ To set multiple properties at once, call `setProperties`
+ with a Hash:
```javascript
- Ember.Object.create({
- valueObserver: function() {
- // Executes whenever the "value" property is about to change
- }.observesBefore('value')
- });
+ record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
```
- See `Ember.observesBefore`.
-
- @method observesBefore
- @for Function
+ @method setProperties
+ @param {Hash} hash the hash of keys and values to set
+ @return {Ember.Observable}
*/
- Function.prototype.observesBefore = function() {
- this.__ember_observesBefore__ = a_slice.call(arguments);
- return this;
- };
+ setProperties: function(hash) {
+ return Ember.setProperties(this, hash);
+ },
/**
- The `on` extension of Javascript's Function prototype is available
- when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
- true, which is the default.
+ Begins a grouping of property changes.
- You can listen for events simply by adding the `on` call to the end of
- your method declarations in classes or mixins that you write. For example:
+ You can use this method to group property changes so that notifications
+ will not be sent until the changes are finished. If you plan to make a
+ large number of changes to an object at one time, you should call this
+ method at the beginning of the changes to begin deferring change
+ notifications. When you are done making changes, call
+ `endPropertyChanges()` to deliver the deferred change notifications and end
+ deferring.
+
+ @method beginPropertyChanges
+ @return {Ember.Observable}
+ */
+ beginPropertyChanges: function() {
+ Ember.beginPropertyChanges();
+ return this;
+ },
+
+ /**
+ Ends a grouping of property changes.
+
+ You can use this method to group property changes so that notifications
+ will not be sent until the changes are finished. If you plan to make a
+ large number of changes to an object at one time, you should call
+ `beginPropertyChanges()` at the beginning of the changes to defer change
+ notifications. When you are done making changes, call this method to
+ deliver the deferred change notifications and end deferring.
+
+ @method endPropertyChanges
+ @return {Ember.Observable}
+ */
+ endPropertyChanges: function() {
+ Ember.endPropertyChanges();
+ return this;
+ },
+
+ /**
+ Notify the observer system that a property is about to change.
+
+ Sometimes you need to change a value directly or indirectly without
+ actually calling `get()` or `set()` on it. In this case, you can use this
+ method and `propertyDidChange()` instead. Calling these two methods
+ together will notify all observers that the property has potentially
+ changed value.
+
+ Note that you must always call `propertyWillChange` and `propertyDidChange`
+ as a pair. If you do not, it may get the property change groups out of
+ order and cause notifications to be delivered more often than you would
+ like.
+
+ @method propertyWillChange
+ @param {String} keyName The property key that is about to change.
+ @return {Ember.Observable}
+ */
+ propertyWillChange: function(keyName) {
+ Ember.propertyWillChange(this, keyName);
+ return this;
+ },
+
+ /**
+ Notify the observer system that a property has just changed.
+
+ Sometimes you need to change a value directly or indirectly without
+ actually calling `get()` or `set()` on it. In this case, you can use this
+ method and `propertyWillChange()` instead. Calling these two methods
+ together will notify all observers that the property has potentially
+ changed value.
+
+ Note that you must always call `propertyWillChange` and `propertyDidChange`
+ as a pair. If you do not, it may get the property change groups out of
+ order and cause notifications to be delivered more often than you would
+ like.
+
+ @method propertyDidChange
+ @param {String} keyName The property key that has just changed.
+ @return {Ember.Observable}
+ */
+ propertyDidChange: function(keyName) {
+ Ember.propertyDidChange(this, keyName);
+ return this;
+ },
+
+ /**
+ Convenience method to call `propertyWillChange` and `propertyDidChange` in
+ succession.
+
+ @method notifyPropertyChange
+ @param {String} keyName The property key to be notified about.
+ @return {Ember.Observable}
+ */
+ notifyPropertyChange: function(keyName) {
+ this.propertyWillChange(keyName);
+ this.propertyDidChange(keyName);
+ return this;
+ },
+
+ addBeforeObserver: function(key, target, method) {
+ Ember.addBeforeObserver(this, key, target, method);
+ },
+
+ /**
+ Adds an observer on a property.
+
+ This is the core method used to register an observer for a property.
+
+ Once you call this method, any time the key's value is set, your observer
+ will be notified. Note that the observers are triggered any time the
+ value is set, regardless of whether it has actually changed. Your
+ observer should be prepared to handle that.
+
+ You can also pass an optional context parameter to this method. The
+ context will be passed to your observer method whenever it is triggered.
+ Note that if you add the same target/method pair on a key multiple times
+ with different context parameters, your observer will only be called once
+ with the last context you passed.
+
+ ### Observer Methods
+
+ Observer methods you pass should generally have the following signature if
+ you do not pass a `context` parameter:
```javascript
- Ember.Mixin.create({
- doSomethingWithElement: function() {
- // Executes whenever the "didInsertElement" event fires
- }.on('didInsertElement')
- });
+ fooDidChange: function(sender, key, value, rev) { };
```
- See `Ember.on`.
+ The sender is the object that changed. The key is the property that
+ changes. The value property is currently reserved and unused. The rev
+ is the last property revision of the object when it changed, which you can
+ use to detect if the key value has really changed or not.
- @method on
- @for Function
+ If you pass a `context` parameter, the context will be passed before the
+ revision like so:
+
+ ```javascript
+ fooDidChange: function(sender, key, value, context, rev) { };
+ ```
+
+ Usually you will not need the value, context or revision parameters at
+ the end. In this case, it is common to write observer methods that take
+ only a sender and key value as parameters or, if you aren't interested in
+ any of these values, to write an observer that has no parameters at all.
+
+ @method addObserver
+ @param {String} key The key to observer
+ @param {Object} target The target object to invoke
+ @param {String|Function} method The method to invoke.
+ @return {Ember.Object} self
*/
- Function.prototype.on = function() {
- var events = a_slice.call(arguments);
- this.__ember_listens__ = events;
- return this;
- };
-}
+ addObserver: function(key, target, method) {
+ Ember.addObserver(this, key, target, method);
+ },
+ /**
+ Remove an observer you have previously registered on this object. Pass
+ the same key, target, and method you passed to `addObserver()` and your
+ target will no longer receive notifications.
+
+ @method removeObserver
+ @param {String} key The key to observer
+ @param {Object} target The target object to invoke
+ @param {String|Function} method The method to invoke.
+ @return {Ember.Observable} receiver
+ */
+ removeObserver: function(key, target, method) {
+ Ember.removeObserver(this, key, target, method);
+ },
+
+ /**
+ Returns `true` if the object currently has observers registered for a
+ particular key. You can use this method to potentially defer performing
+ an expensive action until someone begins observing a particular property
+ on the object.
+
+ @method hasObserverFor
+ @param {String} key Key to check
+ @return {Boolean}
+ */
+ hasObserverFor: function(key) {
+ return Ember.hasListeners(this, key+':change');
+ },
+
+ /**
+ Retrieves the value of a property, or a default value in the case that the
+ property returns `undefined`.
+
+ ```javascript
+ person.getWithDefault('lastName', 'Doe');
+ ```
+
+ @method getWithDefault
+ @param {String} keyName The name of the property to retrieve
+ @param {Object} defaultValue The value to return if the property value is undefined
+ @return {Object} The property value or the defaultValue.
+ */
+ getWithDefault: function(keyName, defaultValue) {
+ return Ember.getWithDefault(this, keyName, defaultValue);
+ },
+
+ /**
+ Set the value of a property to the current value plus some amount.
+
+ ```javascript
+ person.incrementProperty('age');
+ team.incrementProperty('score', 2);
+ ```
+
+ @method incrementProperty
+ @param {String} keyName The name of the property to increment
+ @param {Number} increment The amount to increment by. Defaults to 1
+ @return {Number} The new property value
+ */
+ incrementProperty: function(keyName, increment) {
+ if (Ember.isNone(increment)) { increment = 1; }
+ Ember.assert("Must pass a numeric value to incrementProperty", (!isNaN(parseFloat(increment)) && isFinite(increment)));
+ set(this, keyName, (get(this, keyName) || 0) + increment);
+ return get(this, keyName);
+ },
+
+ /**
+ Set the value of a property to the current value minus some amount.
+
+ ```javascript
+ player.decrementProperty('lives');
+ orc.decrementProperty('health', 5);
+ ```
+
+ @method decrementProperty
+ @param {String} keyName The name of the property to decrement
+ @param {Number} decrement The amount to decrement by. Defaults to 1
+ @return {Number} The new property value
+ */
+ decrementProperty: function(keyName, decrement) {
+ if (Ember.isNone(decrement)) { decrement = 1; }
+ Ember.assert("Must pass a numeric value to decrementProperty", (!isNaN(parseFloat(decrement)) && isFinite(decrement)));
+ set(this, keyName, (get(this, keyName) || 0) - decrement);
+ return get(this, keyName);
+ },
+
+ /**
+ Set the value of a boolean property to the opposite of it's
+ current value.
+
+ ```javascript
+ starship.toggleProperty('warpDriveEngaged');
+ ```
+
+ @method toggleProperty
+ @param {String} keyName The name of the property to toggle
+ @return {Object} The new property value
+ */
+ toggleProperty: function(keyName) {
+ set(this, keyName, !get(this, keyName));
+ return get(this, keyName);
+ },
+
+ /**
+ Returns the cached value of a computed property, if it exists.
+ This allows you to inspect the value of a computed property
+ without accidentally invoking it if it is intended to be
+ generated lazily.
+
+ @method cacheFor
+ @param {String} keyName
+ @return {Object} The cached value of the computed property, if any
+ */
+ cacheFor: function(keyName) {
+ return Ember.cacheFor(this, keyName);
+ },
+
+ // intended for debugging purposes
+ observersForKey: function(keyName) {
+ return Ember.observersFor(this, keyName);
+ }
+});
})();
(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+// NOTE: this object should never be included directly. Instead use `Ember.Object`.
+// We only define this separately so that `Ember.Set` can depend on it.
+
+
+var set = Ember.set, get = Ember.get,
+ o_create = Ember.create,
+ o_defineProperty = Ember.platform.defineProperty,
+ GUID_KEY = Ember.GUID_KEY,
+ guidFor = Ember.guidFor,
+ generateGuid = Ember.generateGuid,
+ meta = Ember.meta,
+ rewatch = Ember.rewatch,
+ finishChains = Ember.finishChains,
+ sendEvent = Ember.sendEvent,
+ destroy = Ember.destroy,
+ schedule = Ember.run.schedule,
+ Mixin = Ember.Mixin,
+ applyMixin = Mixin._apply,
+ finishPartial = Mixin.finishPartial,
+ reopen = Mixin.prototype.reopen,
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ indexOf = Ember.EnumerableUtils.indexOf;
+
+var undefinedDescriptor = {
+ configurable: true,
+ writable: true,
+ enumerable: false,
+ value: undefined
+};
+
+function makeCtor() {
+
+ // Note: avoid accessing any properties on the object since it makes the
+ // method a lot faster. This is glue code so we want it to be as fast as
+ // possible.
+
+ var wasApplied = false, initMixins, initProperties;
+
+ var Class = function() {
+ if (!wasApplied) {
+ Class.proto(); // prepare prototype...
+ }
+ o_defineProperty(this, GUID_KEY, undefinedDescriptor);
+ o_defineProperty(this, '_super', undefinedDescriptor);
+ var m = meta(this), proto = m.proto;
+ m.proto = this;
+ if (initMixins) {
+ // capture locally so we can clear the closed over variable
+ var mixins = initMixins;
+ initMixins = null;
+ this.reopen.apply(this, mixins);
+ }
+ if (initProperties) {
+ // capture locally so we can clear the closed over variable
+ var props = initProperties;
+ initProperties = null;
+
+ var concatenatedProperties = this.concatenatedProperties;
+
+ for (var i = 0, l = props.length; i < l; i++) {
+ var properties = props[i];
+
+ Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
+
+ if (properties === null || typeof properties !== 'object') {
+ Ember.assert("Ember.Object.create only accepts objects.");
+ continue;
+ }
+
+ 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],
+ IS_BINDING = Ember.IS_BINDING;
+
+ if (IS_BINDING.test(keyName)) {
+ var bindings = m.bindings;
+ if (!bindings) {
+ bindings = m.bindings = {};
+ } else if (!m.hasOwnProperty('bindings')) {
+ bindings = m.bindings = o_create(m.bindings);
+ }
+ bindings[keyName] = value;
+ }
+
+ var desc = m.descs[keyName];
+
+ Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
+ Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
+ 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];
+
+ if (baseValue) {
+ if ('function' === typeof baseValue.concat) {
+ value = baseValue.concat(value);
+ } else {
+ value = Ember.makeArray(baseValue).concat(value);
+ }
+ } else {
+ value = Ember.makeArray(value);
+ }
+ }
+
+ if (desc) {
+ desc.set(this, keyName, value);
+ } else {
+ if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
+ this.setUnknownProperty(keyName, value);
+ } else if (MANDATORY_SETTER) {
+ Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
+ } else {
+ this[keyName] = value;
+ }
+ }
+ }
+ }
+ }
+ finishPartial(this, m);
+ this.init.apply(this, arguments);
+ m.proto = proto;
+ finishChains(this);
+ sendEvent(this, "init");
+ };
+
+ Class.toString = Mixin.prototype.toString;
+ Class.willReopen = function() {
+ if (wasApplied) {
+ Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
+ }
+
+ wasApplied = false;
+ };
+ Class._initMixins = function(args) { initMixins = args; };
+ Class._initProperties = function(args) { initProperties = args; };
+
+ Class.proto = function() {
+ var superclass = Class.superclass;
+ if (superclass) { superclass.proto(); }
+
+ if (!wasApplied) {
+ wasApplied = true;
+ Class.PrototypeMixin.applyPartial(Class.prototype);
+ rewatch(Class.prototype);
+ }
+
+ return this.prototype;
+ };
+
+ return Class;
+
+}
+
+/**
+ @class CoreObject
+ @namespace Ember
+*/
+var CoreObject = makeCtor();
+CoreObject.toString = function() { return "Ember.CoreObject"; };
+
+CoreObject.PrototypeMixin = Mixin.create({
+ reopen: function() {
+ applyMixin(this, arguments, true);
+ return this;
+ },
+
+ /**
+ An overridable method called when objects are instantiated. By default,
+ does nothing unless it is overridden during class definition.
+
+ Example:
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ init: function() {
+ alert('Name is ' + this.get('name'));
+ }
+ });
+
+ var steve = App.Person.create({
+ name: "Steve"
+ });
+
+ // alerts 'Name is Steve'.
+ ```
+
+ NOTE: If you do override `init` for a framework class like `Ember.View` or
+ `Ember.ArrayController`, be sure to call `this._super()` in your
+ `init` declaration! If you don't, Ember may not have an opportunity to
+ do important setup work, and you'll see strange behavior in your
+ application.
+
+ @method init
+ */
+ init: function() {},
+
+ /**
+ Defines the properties that will be concatenated from the superclass
+ (instead of overridden).
+
+ By default, when you extend an Ember class a property defined in
+ the subclass overrides a property with the same name that is defined
+ in the superclass. However, there are some cases where it is preferable
+ to build up a property's value by combining the superclass' property
+ value with the subclass' value. An example of this in use within Ember
+ is the `classNames` property of `Ember.View`.
+
+ Here is some sample code showing the difference between a concatenated
+ property and a normal one:
+
+ ```javascript
+ App.BarView = Ember.View.extend({
+ someNonConcatenatedProperty: ['bar'],
+ classNames: ['bar']
+ });
+
+ App.FooBarView = App.BarView.extend({
+ someNonConcatenatedProperty: ['foo'],
+ classNames: ['foo'],
+ });
+
+ var fooBarView = App.FooBarView.create();
+ fooBarView.get('someNonConcatenatedProperty'); // ['foo']
+ fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo']
+ ```
+
+ This behavior extends to object creation as well. Continuing the
+ above example:
+
+ ```javascript
+ var view = App.FooBarView.create({
+ someNonConcatenatedProperty: ['baz'],
+ classNames: ['baz']
+ })
+ view.get('someNonConcatenatedProperty'); // ['baz']
+ view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
+ ```
+ Adding a single property that is not an array will just add it in the array:
+
+ ```javascript
+ var view = App.FooBarView.create({
+ classNames: 'baz'
+ })
+ view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
+ ```
+
+ Using the `concatenatedProperties` property, we can tell to Ember that mix
+ the content of the properties.
+
+ In `Ember.View` the `classNameBindings` and `attributeBindings` properties
+ are also concatenated, in addition to `classNames`.
+
+ This feature is available for you to use throughout the Ember object model,
+ although typical app developers are likely to use it infrequently. 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
+ @default null
+ */
+ concatenatedProperties: null,
+
+ /**
+ Destroyed object property flag.
+
+ if this property is `true` the observers and bindings were already
+ removed by the effect of calling the `destroy()` method.
+
+ @property isDestroyed
+ @default false
+ */
+ isDestroyed: false,
+
+ /**
+ Destruction scheduled flag. The `destroy()` method has been called.
+
+ The object stays intact until the end of the run loop at which point
+ the `isDestroyed` flag is set.
+
+ @property isDestroying
+ @default false
+ */
+ isDestroying: false,
+
+ /**
+ Destroys an object by setting the `isDestroyed` flag and removing its
+ metadata, which effectively destroys observers and bindings.
+
+ If you try to set a property on a destroyed object, an exception will be
+ raised.
+
+ Note that destruction is scheduled for the end of the run loop and does not
+ happen immediately. It will set an isDestroying flag immediately.
+
+ @method destroy
+ @return {Ember.Object} receiver
+ */
+ destroy: function() {
+ if (this.isDestroying) { return; }
+ this.isDestroying = true;
+
+ schedule('actions', this, this.willDestroy);
+ schedule('destroy', this, this._scheduledDestroy);
+ return this;
+ },
+
+ /**
+ Override to implement teardown.
+
+ @method willDestroy
+ */
+ willDestroy: Ember.K,
+
+ /**
+ @private
+
+ Invoked by the run loop to actually destroy the object. This is
+ scheduled for execution by the `destroy` method.
+
+ @method _scheduledDestroy
+ */
+ _scheduledDestroy: function() {
+ if (this.isDestroyed) { return; }
+ destroy(this);
+ this.isDestroyed = true;
+ },
+
+ bind: function(to, from) {
+ if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
+ from.to(to).connect(this);
+ return from;
+ },
+
+ /**
+ Returns a string representation which attempts to provide more information
+ than Javascript's `toString` typically does, in a generic way for all Ember
+ objects.
+
+ App.Person = Em.Object.extend()
+ person = App.Person.create()
+ person.toString() //=> ""
+
+ If the object's class is not defined on an Ember namespace, it will
+ indicate it is a subclass of the registered superclass:
+
+ Student = App.Person.extend()
+ student = Student.create()
+ student.toString() //=> "<(subclass of App.Person):ember1025>"
+
+ If the method `toStringExtension` is defined, its return value will be
+ included in the output.
+
+ App.Teacher = App.Person.extend({
+ toStringExtension: function() {
+ return this.get('fullName');
+ }
+ });
+ teacher = App.Teacher.create()
+ teacher.toString(); //=> ""
+
+ @method toString
+ @return {String} string representation
+ */
+ toString: function toString() {
+ var hasToStringExtension = typeof this.toStringExtension === 'function',
+ extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
+ var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
+ this.toString = makeToString(ret);
+ return ret;
+ }
+});
+
+CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
+
+function makeToString(ret) {
+ return function() { return ret; };
+}
+
+if (Ember.config.overridePrototypeMixin) {
+ Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
+}
+
+CoreObject.__super__ = null;
+
+var ClassMixin = Mixin.create({
+
+ ClassMixin: Ember.required(),
+
+ PrototypeMixin: Ember.required(),
+
+ isClass: true,
+
+ isMethod: false,
+
+ /**
+ 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);
+ Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
+
+ Class.ClassMixin.ownerConstructor = Class;
+ Class.PrototypeMixin.ownerConstructor = Class;
+
+ reopen.apply(Class.PrototypeMixin, arguments);
+
+ Class.superclass = this;
+ Class.__super__ = this.prototype;
+
+ proto = Class.prototype = o_create(this.prototype);
+ proto.constructor = Class;
+ generateGuid(proto);
+ meta(proto).proto = proto; // this will disable observers on prototype
+
+ Class.ClassMixin.apply(Class);
+ return Class;
+ },
+
+ /**
+ Equivalent to doing `extend(arguments).create()`.
+ If possible use the normal `create` method instead.
+
+ @method createWithMixins
+ @static
+ @param [arguments]*
+ */
+ createWithMixins: function() {
+ var C = this;
+ if (arguments.length>0) { this._initMixins(arguments); }
+ return new C();
+ },
+
+ /**
+ Creates an instance of a class. Accepts either no arguments, or an object
+ containing values to initialize the newly instantiated object with.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ helloWorld: function() {
+ alert("Hi, my name is " + this.get('name'));
+ }
+ });
+
+ var tom = App.Person.create({
+ name: 'Tom Dale'
+ });
+
+ tom.helloWorld(); // alerts "Hi, my name is Tom Dale".
+ ```
+
+ `create` will call the `init` function if defined during
+ `Ember.AnyObject.extend`
+
+ If no arguments are passed to `create`, it will not set values to the new
+ instance during initialization:
+
+ ```javascript
+ var noName = App.Person.create();
+ noName.helloWorld(); // alerts undefined
+ ```
+
+ NOTE: For performance reasons, you cannot declare methods or computed
+ properties during `create`. You should instead declare methods and computed
+ properties when using `extend` or use the `createWithMixins` shorthand.
+
+ @method create
+ @static
+ @param [arguments]*
+ */
+ create: function() {
+ var C = this;
+ if (arguments.length>0) { this._initProperties(arguments); }
+ return new C();
+ },
+
+ /**
+
+ Augments a constructor's prototype with additional
+ properties and functions:
+
+ ```javascript
+ MyObject = Ember.Object.extend({
+ name: 'an object'
+ });
+
+ o = MyObject.create();
+ o.get('name'); // 'an object'
+
+ MyObject.reopen({
+ say: function(msg){
+ console.log(msg);
+ }
+ })
+
+ o2 = MyObject.create();
+ o2.say("hello"); // logs "hello"
+
+ o.say("goodbye"); // logs "goodbye"
+ ```
+
+ To add functions and properties to the constructor itself,
+ see `reopenClass`
+
+ @method reopen
+ */
+ reopen: function() {
+ this.willReopen();
+ reopen.apply(this.PrototypeMixin, arguments);
+ return this;
+ },
+
+ /**
+ Augments a constructor's own properties and functions:
+
+ ```javascript
+ MyObject = Ember.Object.extend({
+ name: 'an object'
+ });
+
+
+ MyObject.reopenClass({
+ canBuild: false
+ });
+
+ MyObject.canBuild; // false
+ o = MyObject.create();
+ ```
+
+ In other words, this creates static properties and functions for the class. These are only available on the class
+ and not on any instance of that class.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ name : "",
+ sayHello : function(){
+ alert("Hello. My name is " + this.get('name'));
+ }
+ });
+
+ App.Person.reopenClass({
+ species : "Homo sapiens",
+ createPerson: function(newPersonsName){
+ return App.Person.create({
+ name:newPersonsName
+ });
+ }
+ });
+
+ var tom = App.Person.create({
+ name : "Tom Dale"
+ });
+ var yehuda = App.Person.createPerson("Yehuda Katz");
+
+ tom.sayHello(); // "Hello. My name is Tom Dale"
+ yehuda.sayHello(); // "Hello. My name is Yehuda Katz"
+ alert(App.Person.species); // "Homo sapiens"
+ ```
+
+ Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda`
+ variables. They are only valid on `App.Person`.
+
+ To add functions and properties to instances of
+ a constructor by extending the constructor's prototype
+ see `reopen`
+
+ @method reopenClass
+ */
+ reopenClass: function() {
+ reopen.apply(this.ClassMixin, arguments);
+ applyMixin(this, arguments, false);
+ return this;
+ },
+
+ detect: function(obj) {
+ if ('function' !== typeof obj) { return false; }
+ while(obj) {
+ if (obj===this) { return true; }
+ obj = obj.superclass;
+ }
+ return false;
+ },
+
+ detectInstance: function(obj) {
+ return obj instanceof this;
+ },
+
+ /**
+ In some cases, you may want to annotate computed properties with additional
+ metadata about how they function or what values they operate on. For
+ example, computed property functions may close over variables that are then
+ no longer available for introspection.
+
+ You can pass a hash of these values to a computed property like this:
+
+ ```javascript
+ person: function() {
+ var personId = this.get('personId');
+ return App.Person.create({ id: personId });
+ }.property().meta({ type: App.Person })
+ ```
+
+ Once you've done this, you can retrieve the values saved to the computed
+ property from your class like this:
+
+ ```javascript
+ MyClass.metaForProperty('person');
+ ```
+
+ This will return the original hash that was passed to `meta()`.
+
+ @method metaForProperty
+ @param key {String} property name
+ */
+ metaForProperty: function(key) {
+ var desc = meta(this.proto(), false).descs[key];
+
+ Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
+ return desc._meta || {};
+ },
+
+ /**
+ Iterate over each computed property for the class, passing its name
+ and any associated metadata (see `metaForProperty`) to the callback.
+
+ @method eachComputedProperty
+ @param {Function} callback
+ @param {Object} binding
+ */
+ eachComputedProperty: function(callback, binding) {
+ var proto = this.proto(),
+ descs = meta(proto).descs,
+ empty = {},
+ property;
+
+ for (var name in descs) {
+ property = descs[name];
+
+ if (property instanceof Ember.ComputedProperty) {
+ callback.call(binding || this, name, property._meta || empty);
+ }
+ }
+ }
+
+});
+
+ClassMixin.ownerConstructor = CoreObject;
+
+if (Ember.config.overrideClassMixin) {
+ Ember.config.overrideClassMixin(ClassMixin);
+}
+
+CoreObject.ClassMixin = ClassMixin;
+ClassMixin.apply(CoreObject);
+
+Ember.CoreObject = CoreObject;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ `Ember.Object` is the main base class for all Ember objects. It is a subclass
+ of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
+ see the documentation for each of these.
+
+ @class Object
+ @namespace Ember
+ @extends Ember.CoreObject
+ @uses Ember.Observable
+*/
+Ember.Object = Ember.CoreObject.extend(Ember.Observable);
+Ember.Object.toString = function() { return "Ember.Object"; };
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf;
+
+/**
+ A Namespace is an object usually used to contain other objects or methods
+ such as an application or framework. Create a namespace anytime you want
+ to define one of these new containers.
+
+ # Example Usage
+
+ ```javascript
+ MyFramework = Ember.Namespace.create({
+ VERSION: '1.0.0'
+ });
+ ```
+
+ @class Namespace
+ @namespace Ember
+ @extends Ember.Object
+*/
+var Namespace = Ember.Namespace = Ember.Object.extend({
+ isNamespace: true,
+
+ init: function() {
+ Ember.Namespace.NAMESPACES.push(this);
+ Ember.Namespace.PROCESSED = false;
+ },
+
+ toString: function() {
+ var name = get(this, 'name');
+ if (name) { return name; }
+
+ findNamespaces();
+ return this[Ember.GUID_KEY+'_name'];
+ },
+
+ nameClasses: function() {
+ processNamespace([this.toString()], this, {});
+ },
+
+ destroy: function() {
+ var namespaces = Ember.Namespace.NAMESPACES;
+ Ember.lookup[this.toString()] = undefined;
+ namespaces.splice(indexOf.call(namespaces, this), 1);
+ this._super();
+ }
+});
+
+Namespace.reopenClass({
+ NAMESPACES: [Ember],
+ NAMESPACES_BY_ID: {},
+ PROCESSED: false,
+ processAll: processAllNamespaces,
+ byName: function(name) {
+ if (!Ember.BOOTED) {
+ processAllNamespaces();
+ }
+
+ return NAMESPACES_BY_ID[name];
+ }
+});
+
+var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID;
+
+var hasOwnProp = ({}).hasOwnProperty,
+ guidFor = Ember.guidFor;
+
+function processNamespace(paths, root, seen) {
+ var idx = paths.length;
+
+ NAMESPACES_BY_ID[paths.join('.')] = root;
+
+ // Loop over all of the keys in the namespace, looking for classes
+ for(var key in root) {
+ if (!hasOwnProp.call(root, key)) { continue; }
+ var obj = root[key];
+
+ // If we are processing the `Ember` namespace, for example, the
+ // `paths` will start with `["Ember"]`. Every iteration through
+ // the loop will update the **second** element of this list with
+ // the key, so processing `Ember.View` will make the Array
+ // `['Ember', 'View']`.
+ paths[idx] = key;
+
+ // If we have found an unprocessed class
+ if (obj && obj.toString === classToString) {
+ // Replace the class' `toString` with the dot-separated path
+ // and set its `NAME_KEY`
+ obj.toString = makeToString(paths.join('.'));
+ obj[NAME_KEY] = paths.join('.');
+
+ // Support nested namespaces
+ } else if (obj && obj.isNamespace) {
+ // Skip aliased namespaces
+ if (seen[guidFor(obj)]) { continue; }
+ seen[guidFor(obj)] = true;
+
+ // Process the child namespace
+ processNamespace(paths, obj, seen);
+ }
+ }
+
+ paths.length = idx; // cut out last item
+}
+
+function findNamespaces() {
+ var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace;
+
+ if (Namespace.PROCESSED) { return; }
+
+ for (var prop in lookup) {
+ // These don't raise exceptions but can cause warnings
+ if (prop === "parent" || prop === "top" || prop === "frameElement" || prop === "webkitStorageInfo") { continue; }
+
+ // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox.
+ // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage
+ if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; }
+ // Unfortunately, some versions of IE don't support window.hasOwnProperty
+ if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; }
+
+ // At times we are not allowed to access certain properties for security reasons.
+ // There are also times where even if we can access them, we are not allowed to access their properties.
+ try {
+ obj = Ember.lookup[prop];
+ isNamespace = obj && obj.isNamespace;
+ } catch (e) {
+ continue;
+ }
+
+ if (isNamespace) {
+ Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop));
+ obj[NAME_KEY] = prop;
+ }
+ }
+}
+
+var NAME_KEY = Ember.NAME_KEY = Ember.GUID_KEY + '_name';
+
+function superClassString(mixin) {
+ var superclass = mixin.superclass;
+ if (superclass) {
+ if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
+ else { return superClassString(superclass); }
+ } else {
+ return;
+ }
+}
+
+function classToString() {
+ if (!Ember.BOOTED && !this[NAME_KEY]) {
+ processAllNamespaces();
+ }
+
+ var ret;
+
+ if (this[NAME_KEY]) {
+ ret = this[NAME_KEY];
+ } else if (this._toString) {
+ ret = this._toString;
+ } else {
+ var str = superClassString(this);
+ if (str) {
+ ret = "(subclass of " + str + ")";
+ } else {
+ ret = "(unknown mixin)";
+ }
+ this.toString = makeToString(ret);
+ }
+
+ return ret;
+}
+
+function processAllNamespaces() {
+ var unprocessedNamespaces = !Namespace.PROCESSED,
+ unprocessedMixins = Ember.anyUnprocessedMixins;
+
+ if (unprocessedNamespaces) {
+ findNamespaces();
+ Namespace.PROCESSED = true;
+ }
+
+ if (unprocessedNamespaces || unprocessedMixins) {
+ var namespaces = Namespace.NAMESPACES, namespace;
+ for (var i=0, l=namespaces.length; i Ember.TrackedArray instances. We use
+ // this to lazily recompute indexes for item property observers.
+ this.trackedArraysByGuid = {};
+
+ // We suspend observers to ignore replacements from `reset` when totally
+ // recomputing. Unfortunately we cannot properly suspend the observers
+ // because we only have the key; instead we make the observers no-ops
+ this.suspended = false;
+
+ // This is used to coalesce item changes from property observers.
+ this.changedItems = {};
+}
+
+function ItemPropertyObserverContext (dependentArray, index, trackedArray) {
+ Ember.assert("Internal error: trackedArray is null or undefined", trackedArray);
+
+ this.dependentArray = dependentArray;
+ this.index = index;
+ this.item = dependentArray.objectAt(index);
+ this.trackedArray = trackedArray;
+ this.beforeObserver = null;
+ this.observer = null;
+
+ this.destroyed = false;
+}
+
+DependentArraysObserver.prototype = {
+ setValue: function (newValue) {
+ this.instanceMeta.setValue(newValue, true);
+ },
+ getValue: function () {
+ return this.instanceMeta.getValue();
+ },
+
+ setupObservers: function (dependentArray, dependentKey) {
+ Ember.assert("dependent array must be an `Ember.Array`", Ember.Array.detect(dependentArray));
+
+ this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey;
+
+ dependentArray.addArrayObserver(this, {
+ willChange: 'dependentArrayWillChange',
+ didChange: 'dependentArrayDidChange'
+ });
+
+ if (this.cp._itemPropertyKeys[dependentKey]) {
+ this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]);
+ }
+ },
+
+ teardownObservers: function (dependentArray, dependentKey) {
+ var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [];
+
+ delete this.dependentKeysByGuid[guidFor(dependentArray)];
+
+ this.teardownPropertyObservers(dependentKey, itemPropertyKeys);
+
+ dependentArray.removeArrayObserver(this, {
+ willChange: 'dependentArrayWillChange',
+ didChange: 'dependentArrayDidChange'
+ });
+ },
+
+ suspendArrayObservers: function (callback, binding) {
+ var oldSuspended = this.suspended;
+ this.suspended = true;
+ callback.call(binding);
+ this.suspended = oldSuspended;
+ },
+
+ setupPropertyObservers: function (dependentKey, itemPropertyKeys) {
+ var dependentArray = get(this.instanceMeta.context, dependentKey),
+ length = get(dependentArray, 'length'),
+ observerContexts = new Array(length);
+
+ this.resetTransformations(dependentKey, observerContexts);
+
+ forEach(dependentArray, function (item, index) {
+ var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]);
+ observerContexts[index] = observerContext;
+
+ forEach(itemPropertyKeys, function (propertyKey) {
+ addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
+ addObserver(item, propertyKey, this, observerContext.observer);
+ }, this);
+ }, this);
+ },
+
+ teardownPropertyObservers: function (dependentKey, itemPropertyKeys) {
+ var dependentArrayObserver = this,
+ trackedArray = this.trackedArraysByGuid[dependentKey],
+ beforeObserver,
+ observer,
+ item;
+
+ if (!trackedArray) { return; }
+
+ trackedArray.apply(function (observerContexts, offset, operation) {
+ if (operation === Ember.TrackedArray.DELETE) { return; }
+
+ forEach(observerContexts, function (observerContext) {
+ observerContext.destroyed = true;
+ beforeObserver = observerContext.beforeObserver;
+ observer = observerContext.observer;
+ item = observerContext.item;
+
+ forEach(itemPropertyKeys, function (propertyKey) {
+ removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver);
+ removeObserver(item, propertyKey, dependentArrayObserver, observer);
+ });
+ });
+ });
+ },
+
+ 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) {
+ if (this.suspended) { return; }
+
+ var removedItem = this.callbacks.removedItem,
+ changeMeta,
+ guid = guidFor(dependentArray),
+ dependentKey = this.dependentKeysByGuid[guid],
+ itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [],
+ length = get(dependentArray, 'length'),
+ normalizedIndex = normalizeIndex(index, length, 0),
+ normalizedRemoveCount = normalizeRemoveCount(normalizedIndex, length, removedCount),
+ item,
+ itemIndex,
+ sliceIndex,
+ observerContexts;
+
+ observerContexts = this.trackRemove(dependentKey, normalizedIndex, normalizedRemoveCount);
+
+ function removeObservers(propertyKey) {
+ observerContexts[sliceIndex].destroyed = true;
+ removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver);
+ removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer);
+ }
+
+ for (sliceIndex = normalizedRemoveCount - 1; sliceIndex >= 0; --sliceIndex) {
+ itemIndex = normalizedIndex + sliceIndex;
+ if (itemIndex >= length) { break; }
+
+ item = dependentArray.objectAt(itemIndex);
+
+ forEach(itemPropertyKeys, removeObservers, this);
+
+ changeMeta = createChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp);
+ this.setValue( removedItem.call(
+ this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
+ }
+ },
+
+ dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) {
+ if (this.suspended) { return; }
+
+ var addedItem = this.callbacks.addedItem,
+ guid = guidFor(dependentArray),
+ dependentKey = this.dependentKeysByGuid[guid],
+ observerContexts = new Array(addedCount),
+ itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey],
+ length = get(dependentArray, 'length'),
+ normalizedIndex = normalizeIndex(index, length, addedCount),
+ changeMeta,
+ observerContext;
+
+ forEach(dependentArray.slice(normalizedIndex, normalizedIndex + addedCount), function (item, sliceIndex) {
+ if (itemPropertyKeys) {
+ observerContext =
+ observerContexts[sliceIndex] =
+ this.createPropertyObserverContext(dependentArray, normalizedIndex + sliceIndex, this.trackedArraysByGuid[dependentKey]);
+ forEach(itemPropertyKeys, function (propertyKey) {
+ addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
+ addObserver(item, propertyKey, this, observerContext.observer);
+ }, this);
+ }
+
+ changeMeta = createChangeMeta(dependentArray, item, normalizedIndex + sliceIndex, this.instanceMeta.propertyName, this.cp);
+ this.setValue( addedItem.call(
+ this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
+ }, this);
+
+ this.trackAdd(dependentKey, normalizedIndex, observerContexts);
+ },
+
+ itemPropertyWillChange: function (obj, keyName, array, observerContext) {
+ var guid = guidFor(obj);
+
+ if (!this.changedItems[guid]) {
+ this.changedItems[guid] = {
+ array: array,
+ observerContext: observerContext,
+ obj: obj,
+ previousValues: {}
+ };
+ }
+
+ this.changedItems[guid].previousValues[keyName] = get(obj, keyName);
+ },
+
+ itemPropertyDidChange: function(obj, keyName, array, observerContext) {
+ this.flushChanges();
+ },
+
+ flushChanges: function() {
+ var changedItems = this.changedItems, key, c, changeMeta;
+
+ for (key in changedItems) {
+ c = changedItems[key];
+ if (c.observerContext.destroyed) { continue; }
+
+ this.updateIndexes(c.observerContext.trackedArray, c.observerContext.dependentArray);
+
+ changeMeta = createChangeMeta(c.array, c.obj, c.observerContext.index, this.instanceMeta.propertyName, this.cp, c.previousValues);
+ this.setValue(
+ this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
+ this.setValue(
+ this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
+ }
+ this.changedItems = {};
+ }
+};
+
+function normalizeIndex(index, length, newItemsOffset) {
+ if (index < 0) {
+ return Math.max(0, length + index);
+ } else if (index < length) {
+ return index;
+ } else /* index > length */ {
+ return Math.min(length - newItemsOffset, index);
+ }
+}
+
+function normalizeRemoveCount(index, length, removedCount) {
+ return Math.min(removedCount, length - index);
+}
+
+function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) {
+ var meta = {
+ arrayChanged: dependentArray,
+ index: index,
+ item: item,
+ propertyName: propertyName,
+ property: property
+ };
+
+ if (previousValues) {
+ // previous values only available for item property changes
+ meta.previousValues = previousValues;
+ }
+
+ return meta;
+}
+
+function addItems (dependentArray, callbacks, cp, propertyName, meta) {
+ forEach(dependentArray, function (item, index) {
+ meta.setValue( callbacks.addedItem.call(
+ this, meta.getValue(), item, createChangeMeta(dependentArray, item, index, propertyName, cp), meta.sugarMeta));
+ }, this);
+}
+
+function reset(cp, propertyName) {
+ var callbacks = cp._callbacks(),
+ meta;
+
+ if (cp._hasInstanceMeta(this, propertyName)) {
+ meta = cp._instanceMeta(this, propertyName);
+ meta.setValue(cp.resetValue(meta.getValue()));
+ } else {
+ meta = cp._instanceMeta(this, propertyName);
+ }
+
+ if (cp.options.initialize) {
+ cp.options.initialize.call(this, meta.getValue(), { property: cp, propertyName: propertyName }, meta.sugarMeta);
+ }
+}
+
+function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) {
+ this.context = context;
+ this.propertyName = propertyName;
+ this.cache = metaFor(context).cache;
+
+ this.dependentArrays = {};
+ this.sugarMeta = {};
+
+ this.initialValue = initialValue;
+}
+
+ReduceComputedPropertyInstanceMeta.prototype = {
+ getValue: function () {
+ if (this.propertyName in this.cache) {
+ return this.cache[this.propertyName];
+ } else {
+ return this.initialValue;
+ }
+ },
+
+ setValue: function(newValue, triggerObservers) {
+ // This lets sugars force a recomputation, handy for very simple
+ // implementations of eg max.
+ if (newValue !== undefined) {
+ var fireObservers = triggerObservers && (newValue !== this.cache[this.propertyName]);
+
+ if (fireObservers) {
+ propertyWillChange(this.context, this.propertyName);
+ }
+
+ this.cache[this.propertyName] = newValue;
+
+ if (fireObservers) {
+ propertyDidChange(this.context, this.propertyName);
+ }
+ } else {
+ delete this.cache[this.propertyName];
+ }
+ }
+};
+
+/**
+ A computed property whose dependent keys are arrays and which is updated with
+ "one at a time" semantics.
+
+ @class ReduceComputedProperty
+ @namespace Ember
+ @extends Ember.ComputedProperty
+ @constructor
+*/
+function ReduceComputedProperty(options) {
+ var cp = this;
+
+ this.options = options;
+ this._instanceMetas = {};
+
+ this._dependentKeys = null;
+ // A map of dependentKey -> [itemProperty, ...] that tracks what properties of
+ // items in the array we must track to update this property.
+ this._itemPropertyKeys = {};
+ this._previousItemPropertyKeys = {};
+
+ this.readOnly();
+ this.cacheable();
+
+ this.recomputeOnce = function(propertyName) {
+ // What we really want to do is coalesce by .
+ // We need a form of `scheduleOnce` that accepts an arbitrary token to
+ // coalesce by, in addition to the target and method.
+ Ember.run.once(this, recompute, propertyName);
+ };
+ var recompute = function(propertyName) {
+ var dependentKeys = cp._dependentKeys,
+ meta = cp._instanceMeta(this, propertyName),
+ callbacks = cp._callbacks();
+
+ reset.call(this, cp, propertyName);
+
+ meta.dependentArraysObserver.suspendArrayObservers(function () {
+ forEach(cp._dependentKeys, function (dependentKey) {
+ var dependentArray = get(this, dependentKey),
+ previousDependentArray = meta.dependentArrays[dependentKey];
+
+ if (dependentArray === previousDependentArray) {
+ // The array may be the same, but our item property keys may have
+ // changed, so we set them up again. We can't easily tell if they've
+ // changed: the array may be the same object, but with different
+ // contents.
+ if (cp._previousItemPropertyKeys[dependentKey]) {
+ delete cp._previousItemPropertyKeys[dependentKey];
+ meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]);
+ }
+ } else {
+ meta.dependentArrays[dependentKey] = dependentArray;
+
+ if (previousDependentArray) {
+ meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey);
+ }
+
+ if (dependentArray) {
+ meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey);
+ }
+ }
+ }, this);
+ }, this);
+
+ forEach(cp._dependentKeys, function(dependentKey) {
+ var dependentArray = get(this, dependentKey);
+ if (dependentArray) {
+ addItems.call(this, dependentArray, callbacks, cp, propertyName, meta);
+ }
+ }, this);
+ };
+
+ this.func = function (propertyName) {
+ Ember.assert("Computed reduce values require at least one dependent key", cp._dependentKeys);
+
+ recompute.call(this, propertyName);
+
+ return cp._instanceMeta(this, propertyName).getValue();
+ };
+}
+
+Ember.ReduceComputedProperty = ReduceComputedProperty;
+ReduceComputedProperty.prototype = o_create(ComputedProperty.prototype);
+
+function defaultCallback(computedValue) {
+ return computedValue;
+}
+
+ReduceComputedProperty.prototype._callbacks = function () {
+ if (!this.callbacks) {
+ var options = this.options;
+ this.callbacks = {
+ removedItem: options.removedItem || defaultCallback,
+ addedItem: options.addedItem || defaultCallback
+ };
+ }
+ return this.callbacks;
+};
+
+ReduceComputedProperty.prototype._hasInstanceMeta = function (context, propertyName) {
+ var guid = guidFor(context),
+ key = guid + ':' + propertyName;
+
+ return !!this._instanceMetas[key];
+};
+
+ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) {
+ var guid = guidFor(context),
+ key = guid + ':' + propertyName,
+ meta = this._instanceMetas[key];
+
+ if (!meta) {
+ meta = this._instanceMetas[key] = new ReduceComputedPropertyInstanceMeta(context, propertyName, this.initialValue());
+ meta.dependentArraysObserver = new DependentArraysObserver(this._callbacks(), this, meta, context, propertyName, meta.sugarMeta);
+ }
+
+ return meta;
+};
+
+ReduceComputedProperty.prototype.initialValue = function () {
+ if (typeof this.options.initialValue === 'function') {
+ return this.options.initialValue();
+ }
+ else {
+ return this.options.initialValue;
+ }
+};
+
+ReduceComputedProperty.prototype.resetValue = function (value) {
+ return this.initialValue();
+};
+
+ReduceComputedProperty.prototype.itemPropertyKey = function (dependentArrayKey, itemPropertyKey) {
+ this._itemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey] || [];
+ this._itemPropertyKeys[dependentArrayKey].push(itemPropertyKey);
+};
+
+ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArrayKey) {
+ if (this._itemPropertyKeys[dependentArrayKey]) {
+ this._previousItemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey];
+ this._itemPropertyKeys[dependentArrayKey] = [];
+ }
+};
+
+ReduceComputedProperty.prototype.property = function () {
+ var cp = this,
+ args = a_slice.call(arguments),
+ propertyArgs = new Ember.Set(),
+ match,
+ dependentArrayKey,
+ itemPropertyKey;
+
+ forEach(a_slice.call(arguments), function (dependentKey) {
+ if (doubleEachPropertyPattern.test(dependentKey)) {
+ throw new Ember.Error("Nested @each properties not supported: " + dependentKey);
+ } else if (match = eachPropertyPattern.exec(dependentKey)) {
+ dependentArrayKey = match[1];
+ itemPropertyKey = match[2];
+ cp.itemPropertyKey(dependentArrayKey, itemPropertyKey);
+ propertyArgs.add(dependentArrayKey);
+ } else {
+ propertyArgs.add(dependentKey);
+ }
+ });
+
+ return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray());
+};
+
+/**
+ Creates a computed property which operates on dependent arrays and
+ is updated with "one at a time" semantics. When items are added or
+ removed from the dependent array(s) a reduce computed only operates
+ on the change instead of re-evaluating the entire array.
+
+ If there are more than one arguments the first arguments are
+ considered to be dependent property keys. The last argument is
+ required to be an options object. The options object can have the
+ following four properties:
+
+ `initialValue` - A value or function that will be used as the initial
+ value for the computed. If this property is a function the result of calling
+ the function will be used as the initial value. This property is required.
+
+ `initialize` - An optional initialize function. Typically this will be used
+ to set up state on the instanceMeta object.
+
+ `removedItem` - A function that is called each time an element is removed
+ from the array.
+
+ `addedItem` - A function that is called each time an element is added to
+ the array.
+
+
+ The `initialize` function has the following signature:
+
+ ```javascript
+ function (initialValue, changeMeta, instanceMeta)
+ ```
+
+ `initialValue` - The value of the `initialValue` property from the
+ options object.
+
+ `changeMeta` - An object which contains meta information about the
+ computed. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+
+ The `removedItem` and `addedItem` functions both have the following signature:
+
+ ```javascript
+ function (accumulatedValue, item, changeMeta, instanceMeta)
+ ```
+
+ `accumulatedValue` - The value returned from the last time
+ `removedItem` or `addedItem` was called or `initialValue`.
+
+ `item` - the element added or removed from the array
+
+ `changeMeta` - An object which contains meta information about the
+ change. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+ - `index` the index of the added or removed item
+ - `item` the added or removed item: this is exactly the same as
+ the second arg
+ - `arrayChanged` the array that triggered the change. Can be
+ useful when depending on multiple arrays.
+
+ For property changes triggered on an item property change (when
+ depKey is something like `someArray.@each.someProperty`),
+ `changeMeta` will also contain the following property:
+
+ - `previousValues` an object whose keys are the properties that changed on
+ the item, and whose values are the item's previous values.
+
+ `previousValues` is important Ember coalesces item property changes via
+ Ember.run.once. This means that by the time removedItem gets called, item has
+ the new values, but you may need the previous value (eg for sorting &
+ filtering).
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+ The `removedItem` and `addedItem` functions should return the accumulated
+ value. It is acceptable to not return anything (ie return undefined)
+ to invalidate the computation. This is generally not a good idea for
+ arrayComputed but it's used in eg max and min.
+
+ Note that observers will be fired if either of these functions return a value
+ that differs from the accumulated value. When returning an object that
+ mutates in response to array changes, for example an array that maps
+ everything from some other array (see `Ember.computed.map`), it is usually
+ important that the *same* array be returned to avoid accidentally triggering observers.
+
+ Example
+
+ ```javascript
+ Ember.computed.max = function (dependentKey) {
+ return Ember.reduceComputed.call(null, dependentKey, {
+ initialValue: -Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.max(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item < accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+ };
+ ```
+
+ Dependent keys may refer to `@this` to observe changes to the object itself,
+ which must be array-like, rather than a property of the object. This is
+ mostly useful for array proxies, to ensure objects are retrieved via
+ `objectAtContent`. This is how you could sort items by properties defined on an item controller.
+
+ Example
+
+ ```javascript
+ App.PeopleController = Ember.ArrayController.extend({
+ itemController: 'person',
+
+ sortedPeople: Ember.computed.sort('@this.@each.reversedName', function(personA, personB) {
+ // `reversedName` isn't defined on Person, but we have access to it via
+ // the item controller App.PersonController. If we'd used
+ // `content.@each.reversedName` above, we would be getting the objects
+ // directly and not have access to `reversedName`.
+ //
+ var reversedNameA = get(personA, 'reversedName'),
+ reversedNameB = get(personB, 'reversedName');
+
+ return Ember.compare(reversedNameA, reversedNameB);
+ })
+ });
+
+ App.PersonController = Ember.ObjectController.extend({
+ reversedName: function () {
+ return reverse(get(this, 'name'));
+ }.property('name')
+ })
+ ```
+
+ @method reduceComputed
+ @for Ember
+ @param {String} [dependentKeys*]
+ @param {Object} options
+ @return {Ember.ComputedProperty}
+*/
+Ember.reduceComputed = function (options) {
+ var args;
+
+ if (arguments.length > 1) {
+ args = a_slice.call(arguments, 0, -1);
+ options = a_slice.call(arguments, -1)[0];
+ }
+
+ if (typeof options !== "object") {
+ throw new Ember.Error("Reduce Computed Property declared without an options hash");
+ }
+
+ if (!('initialValue' in options)) {
+ throw new Ember.Error("Reduce Computed Property declared without an initial value");
+ }
+
+ var cp = new ReduceComputedProperty(options);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+})();
+
+
+
+(function() {
+var ReduceComputedProperty = Ember.ReduceComputedProperty,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ forEach = Ember.EnumerableUtils.forEach;
+
+function ArrayComputedProperty() {
+ var cp = this;
+
+ ReduceComputedProperty.apply(this, arguments);
+
+ this.func = (function(reduceFunc) {
+ return function (propertyName) {
+ if (!cp._hasInstanceMeta(this, propertyName)) {
+ // When we recompute an array computed property, we need already
+ // retrieved arrays to be updated; we can't simply empty the cache and
+ // hope the array is re-retrieved.
+ forEach(cp._dependentKeys, function(dependentKey) {
+ Ember.addObserver(this, dependentKey, function() {
+ cp.recomputeOnce.call(this, propertyName);
+ });
+ }, this);
+ }
+
+ return reduceFunc.apply(this, arguments);
+ };
+ })(this.func);
+
+ return this;
+}
+Ember.ArrayComputedProperty = ArrayComputedProperty;
+ArrayComputedProperty.prototype = o_create(ReduceComputedProperty.prototype);
+ArrayComputedProperty.prototype.initialValue = function () {
+ return Ember.A();
+};
+ArrayComputedProperty.prototype.resetValue = function (array) {
+ array.clear();
+ return array;
+};
+
+/**
+ Creates a computed property which operates on dependent arrays and
+ is updated with "one at a time" semantics. When items are added or
+ removed from the dependent array(s) an array computed only operates
+ on the change instead of re-evaluating the entire array. This should
+ return an array, if you'd like to use "one at a time" semantics and
+ compute some value other then an array look at
+ `Ember.reduceComputed`.
+
+ If there are more than one arguments the first arguments are
+ considered to be dependent property keys. The last argument is
+ required to be an options object. The options object can have the
+ following three properties.
+
+ `initialize` - An optional initialize function. Typically this will be used
+ to set up state on the instanceMeta object.
+
+ `removedItem` - A function that is called each time an element is
+ removed from the array.
+
+ `addedItem` - A function that is called each time an element is
+ added to the array.
+
+
+ The `initialize` function has the following signature:
+
+ ```javascript
+ function (array, changeMeta, instanceMeta)
+ ```
+
+ `array` - The initial value of the arrayComputed, an empty array.
+
+ `changeMeta` - An object which contains meta information about the
+ computed. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+
+ The `removedItem` and `addedItem` functions both have the following signature:
+
+ ```javascript
+ function (accumulatedValue, item, changeMeta, instanceMeta)
+ ```
+
+ `accumulatedValue` - The value returned from the last time
+ `removedItem` or `addedItem` was called or an empty array.
+
+ `item` - the element added or removed from the array
+
+ `changeMeta` - An object which contains meta information about the
+ change. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+ - `index` the index of the added or removed item
+ - `item` the added or removed item: this is exactly the same as
+ the second arg
+ - `arrayChanged` the array that triggered the change. Can be
+ useful when depending on multiple arrays.
+
+ For property changes triggered on an item property change (when
+ depKey is something like `someArray.@each.someProperty`),
+ `changeMeta` will also contain the following property:
+
+ - `previousValues` an object whose keys are the properties that changed on
+ the item, and whose values are the item's previous values.
+
+ `previousValues` is important Ember coalesces item property changes via
+ Ember.run.once. This means that by the time removedItem gets called, item has
+ the new values, but you may need the previous value (eg for sorting &
+ filtering).
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+ The `removedItem` and `addedItem` functions should return the accumulated
+ value. It is acceptable to not return anything (ie return undefined)
+ to invalidate the computation. This is generally not a good idea for
+ arrayComputed but it's used in eg max and min.
+
+ Example
+
+ ```javascript
+ Ember.computed.map = function(dependentKey, callback) {
+ var options = {
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var mapped = callback(item);
+ array.insertAt(changeMeta.index, mapped);
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ array.removeAt(changeMeta.index, 1);
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+ };
+ ```
+
+ @method arrayComputed
+ @for Ember
+ @param {String} [dependentKeys*]
+ @param {Object} options
+ @return {Ember.ComputedProperty}
+*/
+Ember.arrayComputed = function (options) {
+ var args;
+
+ if (arguments.length > 1) {
+ args = a_slice.call(arguments, 0, -1);
+ options = a_slice.call(arguments, -1)[0];
+ }
+
+ if (typeof options !== "object") {
+ throw new Ember.Error("Array Computed Property declared without an options hash");
+ }
+
+ var cp = new ArrayComputedProperty(options);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get,
+ set = Ember.set,
+ guidFor = Ember.guidFor,
+ merge = Ember.merge,
+ a_slice = [].slice,
+ forEach = Ember.EnumerableUtils.forEach,
+ map = Ember.EnumerableUtils.map,
+ SearchProxy;
+
+/**
+ A computed property that calculates the maximum value in the
+ dependent array. This will return `-Infinity` when the dependent
+ array is empty.
+
+ Example
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age'),
+ maxChildAge: Ember.computed.max('childAges')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('maxChildAge'); // -Infinity
+ lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7});
+ lordByron.get('maxChildAge'); // 7
+ lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]);
+ lordByron.get('maxChildAge'); // 8
+ ```
+
+ @method computed.max
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array
+*/
+Ember.computed.max = function (dependentKey) {
+ return Ember.reduceComputed.call(null, dependentKey, {
+ initialValue: -Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.max(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item < accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+};
+
+/**
+ A computed property that calculates the minimum value in the
+ dependent array. This will return `Infinity` when the dependent
+ array is empty.
+
+ Example
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age'),
+ minChildAge: Ember.computed.min('childAges')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('minChildAge'); // Infinity
+ lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7});
+ lordByron.get('minChildAge'); // 7
+ lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]);
+ lordByron.get('minChildAge'); // 5
+ ```
+
+ @method computed.min
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array
+*/
+Ember.computed.min = function (dependentKey) {
+ return Ember.reduceComputed.call(null, dependentKey, {
+ initialValue: Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.min(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item > accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+};
+
+/**
+ Returns an array mapped via the callback
+
+ The callback method you provide should have the following signature:
+
+ ```javascript
+ function(item);
+ ```
+
+ - `item` is the current item in the iteration.
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ excitingChores: Ember.computed.map('chores', function(chore) {
+ return chore.toUpperCase() + '!';
+ })
+ });
+
+ var hamster = App.Hamster.create({chores: ['cook', 'clean', 'write more unit tests']});
+ hamster.get('excitingChores'); // ['COOK!', 'CLEAN!', 'WRITE MORE UNIT TESTS!']
+ ```
+
+ @method computed.map
+ @for Ember
+ @param {String} dependentKey
+ @param {Function} callback
+ @return {Ember.ComputedProperty} an array mapped via the callback
+*/
+Ember.computed.map = function(dependentKey, callback) {
+ var options = {
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var mapped = callback.call(this, item);
+ array.insertAt(changeMeta.index, mapped);
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ array.removeAt(changeMeta.index, 1);
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+};
+
+/**
+ Returns an array mapped to the specified key.
+
+ Example
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('childAges'); // []
+ lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7});
+ lordByron.get('childAges'); // [7]
+ lordByron.get('children').pushObjects([{name: 'Allegra Byron', age: 5}, {name: 'Elizabeth Medora Leigh', age: 8}]);
+ lordByron.get('childAges'); // [7, 5, 8]
+ ```
+
+ @method computed.mapBy
+ @for Ember
+ @param {String} dependentKey
+ @param {String} propertyKey
+ @return {Ember.ComputedProperty} an array mapped to the specified key
+*/
+Ember.computed.mapBy = function(dependentKey, propertyKey) {
+ var callback = function(item) { return get(item, propertyKey); };
+ return Ember.computed.map(dependentKey + '.@each.' + propertyKey, callback);
+};
+
+/**
+ @method computed.mapProperty
+ @for Ember
+ @deprecated Use `Ember.computed.mapBy` instead
+ @param dependentKey
+ @param propertyKey
+*/
+Ember.computed.mapProperty = Ember.computed.mapBy;
+
+/**
+ Filters the array by the callback.
+
+ The callback method you provide should have the following signature:
+
+ ```javascript
+ function(item);
+ ```
+
+ - `item` is the current item in the iteration.
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ remainingChores: Ember.computed.filter('chores', function(chore) {
+ return !chore.done;
+ })
+ });
+
+ var hamster = App.Hamster.create({chores: [
+ {name: 'cook', done: true},
+ {name: 'clean', done: true},
+ {name: 'write more unit tests', done: false}
+ ]});
+ hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
+ ```
+
+ @method computed.filter
+ @for Ember
+ @param {String} dependentKey
+ @param {Function} callback
+ @return {Ember.ComputedProperty} the filtered array
+*/
+Ember.computed.filter = function(dependentKey, callback) {
+ var options = {
+ initialize: function (array, changeMeta, instanceMeta) {
+ instanceMeta.filteredArrayIndexes = new Ember.SubArray();
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var match = !!callback.call(this, item),
+ filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match);
+
+ if (match) {
+ array.insertAt(filterIndex, item);
+ }
+
+ return array;
+ },
+
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index);
+
+ if (filterIndex > -1) {
+ array.removeAt(filterIndex);
+ }
+
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+};
+
+/**
+ Filters the array by the property and value
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ remainingChores: Ember.computed.filterBy('chores', 'done', false)
+ });
+
+ var hamster = App.Hamster.create({chores: [
+ {name: 'cook', done: true},
+ {name: 'clean', done: true},
+ {name: 'write more unit tests', done: false}
+ ]});
+ hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
+ ```
+
+ @method computed.filterBy
+ @for Ember
+ @param {String} dependentKey
+ @param {String} propertyKey
+ @param {String} value
+ @return {Ember.ComputedProperty} the filtered array
+*/
+Ember.computed.filterBy = function(dependentKey, propertyKey, value) {
+ var callback;
+
+ if (arguments.length === 2) {
+ callback = function(item) {
+ return get(item, propertyKey);
+ };
+ } else {
+ callback = function(item) {
+ return get(item, propertyKey) === value;
+ };
+ }
+
+ return Ember.computed.filter(dependentKey + '.@each.' + propertyKey, callback);
+};
+
+/**
+ @method computed.filterProperty
+ @for Ember
+ @param dependentKey
+ @param propertyKey
+ @param value
+ @deprecated Use `Ember.computed.filterBy` instead
+*/
+Ember.computed.filterProperty = Ember.computed.filterBy;
+
+/**
+ A computed property which returns a new array with all the unique
+ elements from one or more dependent arrays.
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ uniqueFruits: Ember.computed.uniq('fruits')
+ });
+
+ var hamster = App.Hamster.create({fruits: [
+ 'banana',
+ 'grape',
+ 'kale',
+ 'banana'
+ ]});
+ hamster.get('uniqueFruits'); // ['banana', 'grape', 'kale']
+ ```
+
+ @method computed.uniq
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ unique elements from the dependent array
+*/
+Ember.computed.uniq = function() {
+ var args = a_slice.call(arguments);
+ args.push({
+ initialize: function(array, changeMeta, instanceMeta) {
+ instanceMeta.itemCounts = {};
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var guid = guidFor(item);
+
+ if (!instanceMeta.itemCounts[guid]) {
+ instanceMeta.itemCounts[guid] = 1;
+ } else {
+ ++instanceMeta.itemCounts[guid];
+ }
+ array.addObject(item);
+ return array;
+ },
+ removedItem: function(array, item, _, instanceMeta) {
+ var guid = guidFor(item),
+ itemCounts = instanceMeta.itemCounts;
+
+ if (--itemCounts[guid] === 0) {
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+ return Ember.arrayComputed.apply(null, args);
+};
+
+/**
+ Alias for [Ember.computed.uniq](/api/#method_computed_uniq).
+
+ @method computed.union
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ unique elements from the dependent array
+*/
+Ember.computed.union = Ember.computed.uniq;
+
+/**
+ A computed property which returns a new array with all the duplicated
+ elements from two or more dependeny arrays.
+
+ Example
+
+ ```javascript
+ var obj = Ember.Object.createWithMixins({
+ adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'],
+ charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'],
+ friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends')
+ });
+
+ obj.get('friendsInCommon'); // ['William King', 'Mary Somerville']
+ ```
+
+ @method computed.intersect
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ duplicated elements from the dependent arrays
+*/
+Ember.computed.intersect = function () {
+ var getDependentKeyGuids = function (changeMeta) {
+ return map(changeMeta.property._dependentKeys, function (dependentKey) {
+ return guidFor(dependentKey);
+ });
+ };
+
+ var args = a_slice.call(arguments);
+ args.push({
+ initialize: function (array, changeMeta, instanceMeta) {
+ instanceMeta.itemCounts = {};
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var itemGuid = guidFor(item),
+ dependentGuids = getDependentKeyGuids(changeMeta),
+ dependentGuid = guidFor(changeMeta.arrayChanged),
+ numberOfDependentArrays = changeMeta.property._dependentKeys.length,
+ itemCounts = instanceMeta.itemCounts;
+
+ if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; }
+ if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; }
+
+ if (++itemCounts[itemGuid][dependentGuid] === 1 &&
+ numberOfDependentArrays === Ember.keys(itemCounts[itemGuid]).length) {
+
+ array.addObject(item);
+ }
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ var itemGuid = guidFor(item),
+ dependentGuids = getDependentKeyGuids(changeMeta),
+ dependentGuid = guidFor(changeMeta.arrayChanged),
+ numberOfDependentArrays = changeMeta.property._dependentKeys.length,
+ numberOfArraysItemAppearsIn,
+ itemCounts = instanceMeta.itemCounts;
+
+ if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; }
+ if (--itemCounts[itemGuid][dependentGuid] === 0) {
+ delete itemCounts[itemGuid][dependentGuid];
+ numberOfArraysItemAppearsIn = Ember.keys(itemCounts[itemGuid]).length;
+
+ if (numberOfArraysItemAppearsIn === 0) {
+ delete itemCounts[itemGuid];
+ }
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+ return Ember.arrayComputed.apply(null, args);
+};
+
+/**
+ A computed property which returns a new array with all the
+ properties from the first dependent array that are not in the second
+ dependent array.
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ likes: ['banana', 'grape', 'kale'],
+ wants: Ember.computed.setDiff('likes', 'fruits')
+ });
+
+ var hamster = App.Hamster.create({fruits: [
+ 'grape',
+ 'kale',
+ ]});
+ hamster.get('wants'); // ['banana']
+ ```
+
+ @method computed.setDiff
+ @for Ember
+ @param {String} setAProperty
+ @param {String} setBProperty
+ @return {Ember.ComputedProperty} computes a new array with all the
+ items from the first dependent array that are not in the second
+ dependent array
+*/
+Ember.computed.setDiff = function (setAProperty, setBProperty) {
+ if (arguments.length !== 2) {
+ throw new Ember.Error("setDiff requires exactly two dependent arrays.");
+ }
+ return Ember.arrayComputed.call(null, setAProperty, setBProperty, {
+ addedItem: function (array, item, changeMeta, instanceMeta) {
+ var setA = get(this, setAProperty),
+ setB = get(this, setBProperty);
+
+ if (changeMeta.arrayChanged === setA) {
+ if (!setB.contains(item)) {
+ array.addObject(item);
+ }
+ } else {
+ array.removeObject(item);
+ }
+ return array;
+ },
+
+ removedItem: function (array, item, changeMeta, instanceMeta) {
+ var setA = get(this, setAProperty),
+ setB = get(this, setBProperty);
+
+ if (changeMeta.arrayChanged === setB) {
+ if (setA.contains(item)) {
+ array.addObject(item);
+ }
+ } else {
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+};
+
+function binarySearch(array, item, low, high) {
+ var mid, midItem, res, guidMid, guidItem;
+
+ if (arguments.length < 4) { high = get(array, 'length'); }
+ if (arguments.length < 3) { low = 0; }
+
+ if (low === high) {
+ return low;
+ }
+
+ mid = low + Math.floor((high - low) / 2);
+ midItem = array.objectAt(mid);
+
+ guidMid = _guidFor(midItem);
+ guidItem = _guidFor(item);
+
+ if (guidMid === guidItem) {
+ return mid;
+ }
+
+ res = this.order(midItem, item);
+ if (res === 0) {
+ res = guidMid < guidItem ? -1 : 1;
+ }
+
+
+ if (res < 0) {
+ return this.binarySearch(array, item, mid+1, high);
+ } else if (res > 0) {
+ return this.binarySearch(array, item, low, mid);
+ }
+
+ return mid;
+
+ function _guidFor(item) {
+ if (SearchProxy.detectInstance(item)) {
+ return guidFor(get(item, 'content'));
+ }
+ return guidFor(item);
+ }
+}
+
+
+SearchProxy = Ember.ObjectProxy.extend();
+
+/**
+ A computed property which returns a new array with all the
+ properties from the first dependent array sorted based on a property
+ or sort function.
+
+ The callback method you provide should have the following signature:
+
+ ```javascript
+ function(itemA, itemB);
+ ```
+
+ - `itemA` the first item to compare.
+ - `itemB` the second item to compare.
+
+ This function should return `-1` when `itemA` should come before
+ `itemB`. It should return `1` when `itemA` should come after
+ `itemB`. If the `itemA` and `itemB` are equal this function should return `0`.
+
+ Example
+
+ ```javascript
+ var ToDoList = Ember.Object.extend({
+ todosSorting: ['name'],
+ sortedTodos: Ember.computed.sort('todos', 'todosSorting'),
+ priorityTodos: Ember.computed.sort('todos', function(a, b){
+ if (a.priority > b.priority) {
+ return 1;
+ } else if (a.priority < b.priority) {
+ return -1;
+ }
+ return 0;
+ }),
+ });
+ var todoList = ToDoList.create({todos: [
+ {name: 'Unit Test', priority: 2},
+ {name: 'Documentation', priority: 3},
+ {name: 'Release', priority: 1}
+ ]});
+
+ todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}]
+ todoList.get('priorityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}]
+ ```
+
+ @method computed.sort
+ @for Ember
+ @param {String} dependentKey
+ @param {String or Function} sortDefinition a dependent key to an
+ array of sort properties or a function to use when sorting
+ @return {Ember.ComputedProperty} computes a new sorted array based
+ on the sort property array or callback function
+*/
+Ember.computed.sort = function (itemsKey, sortDefinition) {
+ Ember.assert("Ember.computed.sort requires two arguments: an array key to sort and either a sort properties key or sort function", arguments.length === 2);
+
+ var initFn, sortPropertiesKey;
+
+ if (typeof sortDefinition === 'function') {
+ initFn = function (array, changeMeta, instanceMeta) {
+ instanceMeta.order = sortDefinition;
+ instanceMeta.binarySearch = binarySearch;
+ };
+ } else {
+ sortPropertiesKey = sortDefinition;
+ initFn = function (array, changeMeta, instanceMeta) {
+ function setupSortProperties() {
+ var sortPropertyDefinitions = get(this, sortPropertiesKey),
+ sortProperty,
+ sortProperties = instanceMeta.sortProperties = [],
+ sortPropertyAscending = instanceMeta.sortPropertyAscending = {},
+ idx,
+ asc;
+
+ Ember.assert("Cannot sort: '" + sortPropertiesKey + "' is not an array.", Ember.isArray(sortPropertyDefinitions));
+
+ changeMeta.property.clearItemPropertyKeys(itemsKey);
+
+ forEach(sortPropertyDefinitions, function (sortPropertyDefinition) {
+ if ((idx = sortPropertyDefinition.indexOf(':')) !== -1) {
+ sortProperty = sortPropertyDefinition.substring(0, idx);
+ asc = sortPropertyDefinition.substring(idx+1).toLowerCase() !== 'desc';
+ } else {
+ sortProperty = sortPropertyDefinition;
+ asc = true;
+ }
+
+ sortProperties.push(sortProperty);
+ sortPropertyAscending[sortProperty] = asc;
+ changeMeta.property.itemPropertyKey(itemsKey, sortProperty);
+ });
+
+ sortPropertyDefinitions.addObserver('@each', this, updateSortPropertiesOnce);
+ }
+
+ function updateSortPropertiesOnce() {
+ Ember.run.once(this, updateSortProperties, changeMeta.propertyName);
+ }
+
+ function updateSortProperties(propertyName) {
+ setupSortProperties.call(this);
+ changeMeta.property.recomputeOnce.call(this, propertyName);
+ }
+
+ Ember.addObserver(this, sortPropertiesKey, updateSortPropertiesOnce);
+
+ setupSortProperties.call(this);
+
+
+ instanceMeta.order = function (itemA, itemB) {
+ var sortProperty, result, asc;
+ for (var i = 0; i < this.sortProperties.length; ++i) {
+ sortProperty = this.sortProperties[i];
+ result = Ember.compare(get(itemA, sortProperty), get(itemB, sortProperty));
+
+ if (result !== 0) {
+ asc = this.sortPropertyAscending[sortProperty];
+ return asc ? result : (-1 * result);
+ }
+ }
+
+ return 0;
+ };
+
+ instanceMeta.binarySearch = binarySearch;
+ };
+ }
+
+ return Ember.arrayComputed.call(null, itemsKey, {
+ initialize: initFn,
+
+ addedItem: function (array, item, changeMeta, instanceMeta) {
+ var index = instanceMeta.binarySearch(array, item);
+ array.insertAt(index, item);
+ return array;
+ },
+
+ removedItem: function (array, item, changeMeta, instanceMeta) {
+ var proxyProperties, index, searchItem;
+
+ if (changeMeta.previousValues) {
+ proxyProperties = merge({ content: item }, changeMeta.previousValues);
+
+ searchItem = SearchProxy.create(proxyProperties);
+ } else {
+ searchItem = item;
+ }
+
+ index = instanceMeta.binarySearch(array, searchItem);
+ array.removeAt(index);
+ return array;
+ }
+ });
+};
+
+})();
+
+
+
+(function() {
+/**
+ Expose RSVP implementation
+
+ Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md
+
+ @class RSVP
+ @namespace Ember
+ @constructor
+*/
+Ember.RSVP = requireModule('rsvp');
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var a_slice = Array.prototype.slice;
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) {
+
+ /**
+ The `property` extension of Javascript's Function prototype is available
+ when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
+ `true`, which is the default.
+
+ Computed properties allow you to treat a function like a property:
+
+ ```javascript
+ MyApp.President = Ember.Object.extend({
+ firstName: '',
+ lastName: '',
+
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Call this flag to mark the function as a property
+ }.property()
+ });
+
+ var president = MyApp.President.create({
+ firstName: "Barack",
+ lastName: "Obama"
+ });
+
+ president.get('fullName'); // "Barack Obama"
+ ```
+
+ Treating a function like a property is useful because they can work with
+ bindings, just like any other property.
+
+ Many computed properties have dependencies on other properties. For
+ example, in the above example, the `fullName` property depends on
+ `firstName` and `lastName` to determine its value. You can tell Ember
+ about these dependencies like this:
+
+ ```javascript
+ MyApp.President = Ember.Object.extend({
+ firstName: '',
+ lastName: '',
+
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Tell Ember.js that this computed property depends on firstName
+ // and lastName
+ }.property('firstName', 'lastName')
+ });
+ ```
+
+ Make sure you list these dependencies so Ember knows when to update
+ bindings that connect to a computed property. Changing a dependency
+ will not immediately trigger an update of the computed property, but
+ will instead clear the cache so that it is updated when the next `get`
+ is called on the property.
+
+ See [Ember.ComputedProperty](/api/classes/Ember.ComputedProperty.html), [Ember.computed](/api/#method_computed).
+
+ @method property
+ @for Function
+ */
+ Function.prototype.property = function() {
+ var ret = Ember.computed(this);
+ return ret.property.apply(ret, arguments);
+ };
+
+ /**
+ The `observes` extension of Javascript's Function prototype is available
+ when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
+ true, which is the default.
+
+ You can observe property changes simply by adding the `observes`
+ call to the end of your method declarations in classes that you write.
+ For example:
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: function() {
+ // Executes whenever the "value" property changes
+ }.observes('value')
+ });
+ ```
+
+ In the future this method may become asynchronous. If you want to ensure
+ synchronous behavior, use `observesImmediately`.
+
+ See `Ember.observer`.
+
+ @method observes
+ @for Function
+ */
+ Function.prototype.observes = function() {
+ this.__ember_observes__ = a_slice.call(arguments);
+ return this;
+ };
+
+ /**
+ The `observesImmediately` extension of Javascript's Function prototype is
+ available when `Ember.EXTEND_PROTOTYPES` or
+ `Ember.EXTEND_PROTOTYPES.Function` is true, which is the default.
+
+ You can observe property changes simply by adding the `observesImmediately`
+ call to the end of your method declarations in classes that you write.
+ For example:
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: function() {
+ // Executes immediately after the "value" property changes
+ }.observesImmediately('value')
+ });
+ ```
+
+ In the future, `observes` may become asynchronous. In this event,
+ `observesImmediately` will maintain the synchronous behavior.
+
+ See `Ember.immediateObserver`.
+
+ @method observesImmediately
+ @for Function
+ */
+ Function.prototype.observesImmediately = function() {
+ for (var i=0, l=arguments.length; i 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 +15465,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
@@ -11254,525 +15659,6 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,/**
-(function() {
-/**
-@module ember
-@submodule ember-runtime
-*/
-
-var get = Ember.get, set = Ember.set;
-
-/**
- ## Overview
-
- This mixin provides properties and property observing functionality, core
- features of the Ember object model.
-
- Properties and observers allow one object to observe changes to a
- property on another object. This is one of the fundamental ways that
- models, controllers and views communicate with each other in an Ember
- application.
-
- Any object that has this mixin applied can be used in observer
- operations. That includes `Ember.Object` and most objects you will
- interact with as you write your Ember application.
-
- Note that you will not generally apply this mixin to classes yourself,
- but you will use the features provided by this module frequently, so it
- is important to understand how to use it.
-
- ## Using `get()` and `set()`
-
- Because of Ember's support for bindings and observers, you will always
- access properties using the get method, and set properties using the
- set method. This allows the observing objects to be notified and
- computed properties to be handled properly.
-
- More documentation about `get` and `set` are below.
-
- ## Observing Property Changes
-
- You typically observe property changes simply by adding the `observes`
- call to the end of your method declarations in classes that you write.
- For example:
-
- ```javascript
- Ember.Object.extend({
- valueObserver: function() {
- // Executes whenever the "value" property changes
- }.observes('value')
- });
- ```
-
- Although this is the most common way to add an observer, this capability
- is actually built into the `Ember.Object` class on top of two methods
- defined in this mixin: `addObserver` and `removeObserver`. You can use
- these two methods to add and remove observers yourself if you need to
- do so at runtime.
-
- To add an observer for a property, call:
-
- ```javascript
- object.addObserver('propertyKey', targetObject, targetAction)
- ```
-
- This will call the `targetAction` method on the `targetObject` whenever
- the value of the `propertyKey` changes.
-
- Note that if `propertyKey` is a computed property, the observer will be
- called when any of the property dependencies are changed, even if the
- resulting value of the computed property is unchanged. This is necessary
- because computed properties are not computed until `get` is called.
-
- @class Observable
- @namespace Ember
-*/
-Ember.Observable = Ember.Mixin.create({
-
- /**
- Retrieves the value of a property from the object.
-
- This method is usually similar to using `object[keyName]` or `object.keyName`,
- however it supports both computed properties and the unknownProperty
- handler.
-
- Because `get` unifies the syntax for accessing all these kinds
- of properties, it can make many refactorings easier, such as replacing a
- simple property with a computed property, or vice versa.
-
- ### Computed Properties
-
- Computed properties are methods defined with the `property` modifier
- declared at the end, such as:
-
- ```javascript
- fullName: function() {
- return this.getEach('firstName', 'lastName').compact().join(' ');
- }.property('firstName', 'lastName')
- ```
-
- When you call `get` on a computed property, the function will be
- called and the return value will be returned instead of the function
- itself.
-
- ### Unknown Properties
-
- Likewise, if you try to call `get` on a property whose value is
- `undefined`, the `unknownProperty()` method will be called on the object.
- If this method returns any value other than `undefined`, it will be returned
- instead. This allows you to implement "virtual" properties that are
- not defined upfront.
-
- @method get
- @param {String} keyName The property to retrieve
- @return {Object} The property value or undefined.
- */
- get: function(keyName) {
- return get(this, keyName);
- },
-
- /**
- To get multiple properties at once, call `getProperties`
- with a list of strings or an array:
-
- ```javascript
- record.getProperties('firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
- ```
-
- is equivalent to:
-
- ```javascript
- record.getProperties(['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
- ```
-
- @method getProperties
- @param {String...|Array} list of keys to get
- @return {Hash}
- */
- getProperties: function() {
- var ret = {};
- var propertyNames = arguments;
- if (arguments.length === 1 && Ember.typeOf(arguments[0]) === 'array') {
- propertyNames = arguments[0];
- }
- for(var i = 0; i < propertyNames.length; i++) {
- ret[propertyNames[i]] = get(this, propertyNames[i]);
- }
- return ret;
- },
-
- /**
- Sets the provided key or path to the value.
-
- This method is generally very similar to calling `object[key] = value` or
- `object.key = value`, except that it provides support for computed
- properties, the `unknownProperty()` method and property observers.
-
- ### Computed Properties
-
- If you try to set a value on a key that has a computed property handler
- defined (see the `get()` method for an example), then `set()` will call
- that method, passing both the value and key instead of simply changing
- the value itself. This is useful for those times when you need to
- implement a property that is composed of one or more member
- properties.
-
- ### Unknown Properties
-
- If you try to set a value on a key that is undefined in the target
- object, then the `unknownProperty()` handler will be called instead. This
- gives you an opportunity to implement complex "virtual" properties that
- are not predefined on the object. If `unknownProperty()` returns
- undefined, then `set()` will simply set the value on the object.
-
- ### Property Observers
-
- In addition to changing the property, `set()` will also register a property
- change with the object. Unless you have placed this call inside of a
- `beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers
- (i.e. observer methods declared on the same object), will be called
- immediately. Any "remote" observers (i.e. observer methods declared on
- another object) will be placed in a queue and called at a later time in a
- coalesced manner.
-
- ### Chaining
-
- In addition to property changes, `set()` returns the value of the object
- itself so you can do chaining like this:
-
- ```javascript
- record.set('firstName', 'Charles').set('lastName', 'Jolley');
- ```
-
- @method set
- @param {String} keyName The property to set
- @param {Object} value The value to set or `null`.
- @return {Ember.Observable}
- */
- set: function(keyName, value) {
- set(this, keyName, value);
- return this;
- },
-
- /**
- To set multiple properties at once, call `setProperties`
- with a Hash:
-
- ```javascript
- record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
- ```
-
- @method setProperties
- @param {Hash} hash the hash of keys and values to set
- @return {Ember.Observable}
- */
- setProperties: function(hash) {
- return Ember.setProperties(this, hash);
- },
-
- /**
- Begins a grouping of property changes.
-
- You can use this method to group property changes so that notifications
- will not be sent until the changes are finished. If you plan to make a
- large number of changes to an object at one time, you should call this
- method at the beginning of the changes to begin deferring change
- notifications. When you are done making changes, call
- `endPropertyChanges()` to deliver the deferred change notifications and end
- deferring.
-
- @method beginPropertyChanges
- @return {Ember.Observable}
- */
- beginPropertyChanges: function() {
- Ember.beginPropertyChanges();
- return this;
- },
-
- /**
- Ends a grouping of property changes.
-
- You can use this method to group property changes so that notifications
- will not be sent until the changes are finished. If you plan to make a
- large number of changes to an object at one time, you should call
- `beginPropertyChanges()` at the beginning of the changes to defer change
- notifications. When you are done making changes, call this method to
- deliver the deferred change notifications and end deferring.
-
- @method endPropertyChanges
- @return {Ember.Observable}
- */
- endPropertyChanges: function() {
- Ember.endPropertyChanges();
- return this;
- },
-
- /**
- Notify the observer system that a property is about to change.
-
- Sometimes you need to change a value directly or indirectly without
- actually calling `get()` or `set()` on it. In this case, you can use this
- method and `propertyDidChange()` instead. Calling these two methods
- together will notify all observers that the property has potentially
- changed value.
-
- Note that you must always call `propertyWillChange` and `propertyDidChange`
- as a pair. If you do not, it may get the property change groups out of
- order and cause notifications to be delivered more often than you would
- like.
-
- @method propertyWillChange
- @param {String} keyName The property key that is about to change.
- @return {Ember.Observable}
- */
- propertyWillChange: function(keyName) {
- Ember.propertyWillChange(this, keyName);
- return this;
- },
-
- /**
- Notify the observer system that a property has just changed.
-
- Sometimes you need to change a value directly or indirectly without
- actually calling `get()` or `set()` on it. In this case, you can use this
- method and `propertyWillChange()` instead. Calling these two methods
- together will notify all observers that the property has potentially
- changed value.
-
- Note that you must always call `propertyWillChange` and `propertyDidChange`
- as a pair. If you do not, it may get the property change groups out of
- order and cause notifications to be delivered more often than you would
- like.
-
- @method propertyDidChange
- @param {String} keyName The property key that has just changed.
- @return {Ember.Observable}
- */
- propertyDidChange: function(keyName) {
- Ember.propertyDidChange(this, keyName);
- return this;
- },
-
- /**
- Convenience method to call `propertyWillChange` and `propertyDidChange` in
- succession.
-
- @method notifyPropertyChange
- @param {String} keyName The property key to be notified about.
- @return {Ember.Observable}
- */
- notifyPropertyChange: function(keyName) {
- this.propertyWillChange(keyName);
- this.propertyDidChange(keyName);
- return this;
- },
-
- addBeforeObserver: function(key, target, method) {
- Ember.addBeforeObserver(this, key, target, method);
- },
-
- /**
- Adds an observer on a property.
-
- This is the core method used to register an observer for a property.
-
- Once you call this method, any time the key's value is set, your observer
- will be notified. Note that the observers are triggered any time the
- value is set, regardless of whether it has actually changed. Your
- observer should be prepared to handle that.
-
- You can also pass an optional context parameter to this method. The
- context will be passed to your observer method whenever it is triggered.
- Note that if you add the same target/method pair on a key multiple times
- with different context parameters, your observer will only be called once
- with the last context you passed.
-
- ### Observer Methods
-
- Observer methods you pass should generally have the following signature if
- you do not pass a `context` parameter:
-
- ```javascript
- fooDidChange: function(sender, key, value, rev) { };
- ```
-
- The sender is the object that changed. The key is the property that
- changes. The value property is currently reserved and unused. The rev
- is the last property revision of the object when it changed, which you can
- use to detect if the key value has really changed or not.
-
- If you pass a `context` parameter, the context will be passed before the
- revision like so:
-
- ```javascript
- fooDidChange: function(sender, key, value, context, rev) { };
- ```
-
- Usually you will not need the value, context or revision parameters at
- the end. In this case, it is common to write observer methods that take
- only a sender and key value as parameters or, if you aren't interested in
- any of these values, to write an observer that has no parameters at all.
-
- @method addObserver
- @param {String} key The key to observer
- @param {Object} target The target object to invoke
- @param {String|Function} method The method to invoke.
- @return {Ember.Object} self
- */
- addObserver: function(key, target, method) {
- Ember.addObserver(this, key, target, method);
- },
-
- /**
- Remove an observer you have previously registered on this object. Pass
- the same key, target, and method you passed to `addObserver()` and your
- target will no longer receive notifications.
-
- @method removeObserver
- @param {String} key The key to observer
- @param {Object} target The target object to invoke
- @param {String|Function} method The method to invoke.
- @return {Ember.Observable} receiver
- */
- removeObserver: function(key, target, method) {
- Ember.removeObserver(this, key, target, method);
- },
-
- /**
- Returns `true` if the object currently has observers registered for a
- particular key. You can use this method to potentially defer performing
- an expensive action until someone begins observing a particular property
- on the object.
-
- @method hasObserverFor
- @param {String} key Key to check
- @return {Boolean}
- */
- hasObserverFor: function(key) {
- return Ember.hasListeners(this, key+':change');
- },
-
- /**
- @deprecated
- @method getPath
- @param {String} path The property path to retrieve
- @return {Object} The property value or undefined.
- */
- getPath: function(path) {
- Ember.deprecate("getPath is deprecated since get now supports paths");
- return this.get(path);
- },
-
- /**
- @deprecated
- @method setPath
- @param {String} path The path to the property that will be set
- @param {Object} value The value to set or `null`.
- @return {Ember.Observable}
- */
- setPath: function(path, value) {
- Ember.deprecate("setPath is deprecated since set now supports paths");
- return this.set(path, value);
- },
-
- /**
- Retrieves the value of a property, or a default value in the case that the
- property returns `undefined`.
-
- ```javascript
- person.getWithDefault('lastName', 'Doe');
- ```
-
- @method getWithDefault
- @param {String} keyName The name of the property to retrieve
- @param {Object} defaultValue The value to return if the property value is undefined
- @return {Object} The property value or the defaultValue.
- */
- getWithDefault: function(keyName, defaultValue) {
- return Ember.getWithDefault(this, keyName, defaultValue);
- },
-
- /**
- Set the value of a property to the current value plus some amount.
-
- ```javascript
- person.incrementProperty('age');
- team.incrementProperty('score', 2);
- ```
-
- @method incrementProperty
- @param {String} keyName The name of the property to increment
- @param {Number} increment The amount to increment by. Defaults to 1
- @return {Number} The new property value
- */
- incrementProperty: function(keyName, increment) {
- if (Ember.isNone(increment)) { increment = 1; }
- Ember.assert("Must pass a numeric value to incrementProperty", (!isNaN(parseFloat(increment)) && isFinite(increment)));
- set(this, keyName, (get(this, keyName) || 0) + increment);
- return get(this, keyName);
- },
-
- /**
- Set the value of a property to the current value minus some amount.
-
- ```javascript
- player.decrementProperty('lives');
- orc.decrementProperty('health', 5);
- ```
-
- @method decrementProperty
- @param {String} keyName The name of the property to decrement
- @param {Number} decrement The amount to decrement by. Defaults to 1
- @return {Number} The new property value
- */
- decrementProperty: function(keyName, decrement) {
- if (Ember.isNone(decrement)) { decrement = 1; }
- Ember.assert("Must pass a numeric value to decrementProperty", (!isNaN(parseFloat(decrement)) && isFinite(decrement)));
- set(this, keyName, (get(this, keyName) || 0) - decrement);
- return get(this, keyName);
- },
-
- /**
- Set the value of a boolean property to the opposite of it's
- current value.
-
- ```javascript
- starship.toggleProperty('warpDriveEngaged');
- ```
-
- @method toggleProperty
- @param {String} keyName The name of the property to toggle
- @return {Object} The new property value
- */
- toggleProperty: function(keyName) {
- set(this, keyName, !get(this, keyName));
- return get(this, keyName);
- },
-
- /**
- Returns the cached value of a computed property, if it exists.
- This allows you to inspect the value of a computed property
- without accidentally invoking it if it is intended to be
- generated lazily.
-
- @method cacheFor
- @param {String} keyName
- @return {Object} The cached value of the computed property, if any
- */
- cacheFor: function(keyName) {
- return Ember.cacheFor(this, keyName);
- },
-
- // intended for debugging purposes
- observersForKey: function(keyName) {
- return Ember.observersFor(this, keyName);
- }
-});
-
-})();
-
-
-
(function() {
/**
@module ember
@@ -11878,18 +15764,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 +15921,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 +15974,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 +16033,719 @@ Ember.DeferredMixin = Ember.Mixin.create({
(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, typeOf = Ember.typeOf;
+
+/**
+ The `Ember.ActionHandler` mixin implements support for moving an `actions`
+ property to an `_actions` property at extend time, and adding `_actions`
+ to the object's mergedProperties list.
+
+ `Ember.ActionHandler` is used internally by Ember in `Ember.View`,
+ `Ember.Controller`, and `Ember.Route`.
+
+ @class ActionHandler
+ @namespace Ember
+*/
+Ember.ActionHandler = Ember.Mixin.create({
+ mergedProperties: ['_actions'],
+
+ /**
+ @private
+
+ Moves `actions` to `_actions` at extend time. Note that this currently
+ modifies the mixin themselves, which is technically dubious but
+ is practically of little consequence. This may change in the future.
+
+ @method willMergeMixin
+ */
+ willMergeMixin: function(props) {
+ var hashName;
+
+ if (!props._actions) {
+ if (typeOf(props.actions) === 'object') {
+ hashName = 'actions';
+ } else if (typeOf(props.events) === 'object') {
+ Ember.deprecate('Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object', false);
+ hashName = 'events';
+ }
+
+ if (hashName) {
+ props._actions = Ember.merge(props._actions || {}, props[hashName]);
+ }
+
+ delete props[hashName];
+ }
+ },
+
+ send: function(actionName) {
+ var args = [].slice.call(arguments, 1), target;
+
+ if (this._actions && this._actions[actionName]) {
+ if (this._actions[actionName].apply(this, args) === true) {
+ // handler returned true, so this action will bubble
+ } else {
+ return;
+ }
+ } else if (this.deprecatedSend && this.deprecatedSendHandles && this.deprecatedSendHandles(actionName)) {
+ if (this.deprecatedSend.apply(this, [].slice.call(arguments)) === true) {
+ // handler return true, so this action will bubble
+ } else {
+ return;
+ }
+ }
+
+ if (target = get(this, 'target')) {
+ Ember.assert("The `target` for " + this + " (" + target + ") does not have a `send` method", typeof target.send === 'function');
+ target.send.apply(target, arguments);
+ }
+ }
+
+});
+
+})();
+
+
+
+(function() {
+var set = Ember.set, get = Ember.get,
+ resolve = Ember.RSVP.resolve,
+ rethrow = Ember.RSVP.rethrow,
+ not = Ember.computed.not,
+ or = Ember.computed.or;
+
+/**
+ @module ember
+ @submodule ember-runtime
+ */
+
+function observePromise(proxy, promise) {
+ promise.then(function(value) {
+ set(proxy, 'isFulfilled', true);
+ set(proxy, 'content', value);
+ }, function(reason) {
+ set(proxy, 'isRejected', true);
+ set(proxy, 'reason', reason);
+ // don't re-throw, as we are merely observing
+ });
+}
+
+/**
+ A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware.
+
+ ```javascript
+ var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin);
+
+ var controller = ObjectPromiseController.create({
+ promise: $.getJSON('/some/remote/data.json')
+ });
+
+ controller.then(function(json){
+ // the json
+ }, function(reason) {
+ // the reason why you have no json
+ });
+ ```
+
+ the controller has bindable attributes which
+ track the promises life cycle
+
+ ```javascript
+ controller.get('isPending') //=> true
+ controller.get('isSettled') //=> false
+ controller.get('isRejected') //=> false
+ controller.get('isFulfilled') //=> false
+ ```
+
+ When the the $.getJSON completes, and the promise is fulfilled
+ with json, the life cycle attributes will update accordingly.
+
+ ```javascript
+ controller.get('isPending') //=> false
+ controller.get('isSettled') //=> true
+ controller.get('isRejected') //=> false
+ controller.get('isFulfilled') //=> true
+ ```
+
+ As the controller is an ObjectController, and the json now its content,
+ all the json properties will be available directly from the controller.
+
+ ```javascript
+ // Assuming the following json:
+ {
+ firstName: 'Stefan',
+ lastName: 'Penner'
+ }
+
+ // both properties will accessible on the controller
+ controller.get('firstName') //=> 'Stefan'
+ controller.get('lastName') //=> 'Penner'
+ ```
+
+ If the controller is backing a template, the attributes are
+ bindable from within that template
+
+ ```handlebars
+ {{#if isPending}}
+ loading...
+ {{else}}
+ firstName: {{firstName}}
+ lastName: {{lastName}}
+ {{/if}}
+ ```
+ @class Ember.PromiseProxyMixin
+*/
+Ember.PromiseProxyMixin = Ember.Mixin.create({
+ reason: null,
+ isPending: not('isSettled').readOnly(),
+ isSettled: or('isRejected', 'isFulfilled').readOnly(),
+ isRejected: false,
+ isFulfilled: false,
+
+ promise: Ember.computed(function(key, promise) {
+ if (arguments.length === 2) {
+ promise = resolve(promise);
+ observePromise(this, promise);
+ return promise.then(); // fork the promise.
+ } else {
+ throw new Ember.Error("PromiseProxy's promise must be set");
+ }
+ }),
+
+ then: function(fulfill, reject) {
+ return get(this, 'promise').then(fulfill, reject);
+ }
+});
+
+
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var get = Ember.get,
+ forEach = Ember.EnumerableUtils.forEach,
+ RETAIN = 'r',
+ INSERT = 'i',
+ DELETE = 'd';
+
+/**
+ An `Ember.TrackedArray` tracks array operations. It's useful when you want to
+ lazily compute the indexes of items in an array after they've been shifted by
+ subsequent operations.
+
+ @class TrackedArray
+ @namespace Ember
+ @param {array} [items=[]] The array to be tracked. This is used just to get
+ the initial items for the starting state of retain:n.
+*/
+Ember.TrackedArray = function (items) {
+ if (arguments.length < 1) { items = []; }
+
+ var length = get(items, 'length');
+
+ if (length) {
+ this._operations = [new ArrayOperation(RETAIN, length, items)];
+ } else {
+ this._operations = [];
+ }
+};
+
+Ember.TrackedArray.RETAIN = RETAIN;
+Ember.TrackedArray.INSERT = INSERT;
+Ember.TrackedArray.DELETE = DELETE;
+
+Ember.TrackedArray.prototype = {
+
+ /**
+ Track that `newItems` were added to the tracked array at `index`.
+
+ @method addItems
+ @param index
+ @param newItems
+ */
+ addItems: function (index, newItems) {
+ var count = get(newItems, 'length');
+ if (count < 1) { return; }
+
+ var match = this._findArrayOperation(index),
+ arrayOperation = match.operation,
+ arrayOperationIndex = match.index,
+ arrayOperationRangeStart = match.rangeStart,
+ composeIndex,
+ splitIndex,
+ splitItems,
+ splitArrayOperation,
+ newArrayOperation;
+
+ newArrayOperation = new ArrayOperation(INSERT, count, newItems);
+
+ if (arrayOperation) {
+ if (!match.split) {
+ // insert left of arrayOperation
+ this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ } else {
+ this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
+ composeIndex = arrayOperationIndex + 1;
+ }
+ } else {
+ // insert at end
+ this._operations.push(newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ }
+
+ this._composeInsert(composeIndex);
+ },
+
+ /**
+ Track that `count` items were removed at `index`.
+
+ @method removeItems
+ @param index
+ @param count
+ */
+ removeItems: function (index, count) {
+ if (count < 1) { return; }
+
+ var match = this._findArrayOperation(index),
+ arrayOperation = match.operation,
+ arrayOperationIndex = match.index,
+ arrayOperationRangeStart = match.rangeStart,
+ newArrayOperation,
+ composeIndex;
+
+ newArrayOperation = new ArrayOperation(DELETE, count);
+ if (!match.split) {
+ // insert left of arrayOperation
+ this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ } else {
+ this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
+ composeIndex = arrayOperationIndex + 1;
+ }
+
+ return this._composeDelete(composeIndex);
+ },
+
+ /**
+ Apply all operations, reducing them to retain:n, for `n`, the number of
+ items in the array.
+
+ `callback` will be called for each operation and will be passed the following arguments:
+ * {array} items The items for the given operation
+ * {number} offset The computed offset of the items, ie the index in the
+ array of the first item for this operation.
+ * {string} operation The type of the operation. One of
+ `Ember.TrackedArray.{RETAIN, DELETE, INSERT}`
+
+ @method apply
+ @param {function} callback
+ */
+ apply: function (callback) {
+ var items = [],
+ offset = 0;
+
+ forEach(this._operations, function (arrayOperation) {
+ callback(arrayOperation.items, offset, arrayOperation.type);
+
+ if (arrayOperation.type !== DELETE) {
+ offset += arrayOperation.count;
+ items = items.concat(arrayOperation.items);
+ }
+ });
+
+ this._operations = [new ArrayOperation(RETAIN, items.length, items)];
+ },
+
+ /**
+ Return an ArrayOperationMatch for the operation that contains the item at `index`.
+
+ @method _findArrayOperation
+
+ @param {number} index the index of the item whose operation information
+ should be returned.
+ @private
+ */
+ _findArrayOperation: function (index) {
+ var arrayOperationIndex,
+ len,
+ split = false,
+ arrayOperation,
+ arrayOperationRangeStart,
+ arrayOperationRangeEnd;
+
+ // OPTIMIZE: we could search these faster if we kept a balanced tree.
+ // find leftmost arrayOperation to the right of `index`
+ for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) {
+ arrayOperation = this._operations[arrayOperationIndex];
+
+ if (arrayOperation.type === DELETE) { continue; }
+
+ arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1;
+
+ if (index === arrayOperationRangeStart) {
+ break;
+ } else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) {
+ split = true;
+ break;
+ } else {
+ arrayOperationRangeStart = arrayOperationRangeEnd + 1;
+ }
+ }
+
+ return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart);
+ },
+
+ _split: function (arrayOperationIndex, splitIndex, newArrayOperation) {
+ var arrayOperation = this._operations[arrayOperationIndex],
+ splitItems = arrayOperation.items.slice(splitIndex),
+ splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems);
+
+ // truncate LHS
+ arrayOperation.count = splitIndex;
+ arrayOperation.items = arrayOperation.items.slice(0, splitIndex);
+
+ this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation);
+ },
+
+ // see SubArray for a better implementation.
+ _composeInsert: function (index) {
+ var newArrayOperation = this._operations[index],
+ leftArrayOperation = this._operations[index-1], // may be undefined
+ rightArrayOperation = this._operations[index+1], // may be undefined
+ leftOp = leftArrayOperation && leftArrayOperation.type,
+ rightOp = rightArrayOperation && rightArrayOperation.type;
+
+ if (leftOp === INSERT) {
+ // merge left
+ leftArrayOperation.count += newArrayOperation.count;
+ leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items);
+
+ if (rightOp === INSERT) {
+ // also merge right (we have split an insert with an insert)
+ leftArrayOperation.count += rightArrayOperation.count;
+ leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items);
+ this._operations.splice(index, 2);
+ } else {
+ // only merge left
+ this._operations.splice(index, 1);
+ }
+ } else if (rightOp === INSERT) {
+ // merge right
+ newArrayOperation.count += rightArrayOperation.count;
+ newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items);
+ this._operations.splice(index + 1, 1);
+ }
+ },
+
+ _composeDelete: function (index) {
+ var arrayOperation = this._operations[index],
+ deletesToGo = arrayOperation.count,
+ leftArrayOperation = this._operations[index-1], // may be undefined
+ leftOp = leftArrayOperation && leftArrayOperation.type,
+ nextArrayOperation,
+ nextOp,
+ nextCount,
+ removeNewAndNextOp = false,
+ removedItems = [];
+
+ if (leftOp === DELETE) {
+ arrayOperation = leftArrayOperation;
+ index -= 1;
+ }
+
+ for (var i = index + 1; deletesToGo > 0; ++i) {
+ nextArrayOperation = this._operations[i];
+ nextOp = nextArrayOperation.type;
+ nextCount = nextArrayOperation.count;
+
+ if (nextOp === DELETE) {
+ arrayOperation.count += nextCount;
+ continue;
+ }
+
+ if (nextCount > deletesToGo) {
+ // d:2 {r,i}:5 we reduce the retain or insert, but it stays
+ removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo));
+ nextArrayOperation.count -= deletesToGo;
+
+ // In the case where we truncate the last arrayOperation, we don't need to
+ // remove it; also the deletesToGo reduction is not the entirety of
+ // nextCount
+ i -= 1;
+ nextCount = deletesToGo;
+
+ deletesToGo = 0;
+ } else {
+ if (nextCount === deletesToGo) {
+ // Handle edge case of d:2 i:2 in which case both operations go away
+ // during composition.
+ removeNewAndNextOp = true;
+ }
+ 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);
+ }
+ }
+ },
+
+ toString: function () {
+ var str = "";
+ forEach(this._operations, function (operation) {
+ str += " " + operation.type + ":" + operation.count;
+ });
+ return str.substring(1);
+ }
+};
})();
@@ -12154,808 +16759,6 @@ Ember.Container.set = Ember.set;
-(function() {
-/**
-@module ember
-@submodule ember-runtime
-*/
-
-
-// NOTE: this object should never be included directly. Instead use `Ember.Object`.
-// We only define this separately so that `Ember.Set` can depend on it.
-
-
-var set = Ember.set, get = Ember.get,
- o_create = Ember.create,
- o_defineProperty = Ember.platform.defineProperty,
- GUID_KEY = Ember.GUID_KEY,
- guidFor = Ember.guidFor,
- generateGuid = Ember.generateGuid,
- meta = Ember.meta,
- rewatch = Ember.rewatch,
- finishChains = Ember.finishChains,
- sendEvent = Ember.sendEvent,
- destroy = Ember.destroy,
- schedule = Ember.run.schedule,
- Mixin = Ember.Mixin,
- applyMixin = Mixin._apply,
- finishPartial = Mixin.finishPartial,
- reopen = Mixin.prototype.reopen,
- MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
- indexOf = Ember.EnumerableUtils.indexOf;
-
-var undefinedDescriptor = {
- configurable: true,
- writable: true,
- enumerable: false,
- value: undefined
-};
-
-function makeCtor() {
-
- // Note: avoid accessing any properties on the object since it makes the
- // method a lot faster. This is glue code so we want it to be as fast as
- // possible.
-
- var wasApplied = false, initMixins, initProperties;
-
- var Class = function() {
- if (!wasApplied) {
- Class.proto(); // prepare prototype...
- }
- o_defineProperty(this, GUID_KEY, undefinedDescriptor);
- o_defineProperty(this, '_super', undefinedDescriptor);
- var m = meta(this), proto = m.proto;
- m.proto = this;
- if (initMixins) {
- // capture locally so we can clear the closed over variable
- var mixins = initMixins;
- initMixins = null;
- this.reopen.apply(this, mixins);
- }
- if (initProperties) {
- // capture locally so we can clear the closed over variable
- var props = initProperties;
- initProperties = null;
-
- var concatenatedProperties = this.concatenatedProperties;
-
- for (var i = 0, l = props.length; i < l; i++) {
- var properties = props[i];
-
- Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
-
- for (var keyName in properties) {
- if (!properties.hasOwnProperty(keyName)) { continue; }
-
- var value = properties[keyName],
- IS_BINDING = Ember.IS_BINDING;
-
- if (IS_BINDING.test(keyName)) {
- var bindings = m.bindings;
- if (!bindings) {
- bindings = m.bindings = {};
- } else if (!m.hasOwnProperty('bindings')) {
- bindings = m.bindings = o_create(m.bindings);
- }
- bindings[keyName] = value;
- }
-
- var desc = m.descs[keyName];
-
- Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
- Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
-
- if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
- var baseValue = this[keyName];
-
- if (baseValue) {
- if ('function' === typeof baseValue.concat) {
- value = baseValue.concat(value);
- } else {
- value = Ember.makeArray(baseValue).concat(value);
- }
- } else {
- value = Ember.makeArray(value);
- }
- }
-
- if (desc) {
- desc.set(this, keyName, value);
- } else {
- if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
- this.setUnknownProperty(keyName, value);
- } else if (MANDATORY_SETTER) {
- Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
- } else {
- this[keyName] = value;
- }
- }
- }
- }
- }
- finishPartial(this, m);
- m.proto = proto;
- finishChains(this);
- this.init.apply(this, arguments);
- sendEvent(this, "init");
- };
-
- Class.toString = Mixin.prototype.toString;
- Class.willReopen = function() {
- if (wasApplied) {
- Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
- }
-
- wasApplied = false;
- };
- Class._initMixins = function(args) { initMixins = args; };
- Class._initProperties = function(args) { initProperties = args; };
-
- Class.proto = function() {
- var superclass = Class.superclass;
- if (superclass) { superclass.proto(); }
-
- if (!wasApplied) {
- wasApplied = true;
- Class.PrototypeMixin.applyPartial(Class.prototype);
- rewatch(Class.prototype);
- }
-
- return this.prototype;
- };
-
- return Class;
-
-}
-
-/**
- @class CoreObject
- @namespace Ember
-*/
-var CoreObject = makeCtor();
-CoreObject.toString = function() { return "Ember.CoreObject"; };
-
-CoreObject.PrototypeMixin = Mixin.create({
- reopen: function() {
- applyMixin(this, arguments, true);
- return this;
- },
-
- /**
- An overridable method called when objects are instantiated. By default,
- does nothing unless it is overridden during class definition.
-
- Example:
-
- ```javascript
- App.Person = Ember.Object.extend({
- init: function() {
- this._super();
- alert('Name is ' + this.get('name'));
- }
- });
-
- var steve = App.Person.create({
- name: "Steve"
- });
-
- // alerts 'Name is Steve'.
- ```
-
- NOTE: If you do override `init` for a framework class like `Ember.View` or
- `Ember.ArrayController`, be sure to call `this._super()` in your
- `init` declaration! If you don't, Ember may not have an opportunity to
- do important setup work, and you'll see strange behavior in your
- application.
-
- @method init
- */
- init: function() {},
-
- /**
- Defines the properties that will be concatenated from the superclass
- (instead of overridden).
-
- By default, when you extend an Ember class a property defined in
- the subclass overrides a property with the same name that is defined
- in the superclass. However, there are some cases where it is preferable
- to build up a property's value by combining the superclass' property
- value with the subclass' value. An example of this in use within Ember
- is the `classNames` property of `Ember.View`.
-
- Here is some sample code showing the difference between a concatenated
- property and a normal one:
-
- ```javascript
- App.BarView = Ember.View.extend({
- someNonConcatenatedProperty: ['bar'],
- classNames: ['bar']
- });
-
- App.FooBarView = App.BarView.extend({
- someNonConcatenatedProperty: ['foo'],
- classNames: ['foo'],
- });
-
- var fooBarView = App.FooBarView.create();
- fooBarView.get('someNonConcatenatedProperty'); // ['foo']
- fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo']
- ```
-
- This behavior extends to object creation as well. Continuing the
- above example:
-
- ```javascript
- var view = App.FooBarView.create({
- someNonConcatenatedProperty: ['baz'],
- classNames: ['baz']
- })
- view.get('someNonConcatenatedProperty'); // ['baz']
- view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
- ```
- Adding a single property that is not an array will just add it in the array:
-
- ```javascript
- var view = App.FooBarView.create({
- classNames: 'baz'
- })
- view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
- ```
-
- Using the `concatenatedProperties` property, we can tell to Ember that mix
- the content of the properties.
-
- In `Ember.View` the `classNameBindings` and `attributeBindings` properties
- are also concatenated, in addition to `classNames`.
-
- This feature is available for you to use throughout the Ember object model,
- although typical app developers are likely to use it infrequently.
-
- @property concatenatedProperties
- @type Array
- @default null
- */
- concatenatedProperties: null,
-
- /**
- Destroyed object property flag.
-
- if this property is `true` the observers and bindings were already
- removed by the effect of calling the `destroy()` method.
-
- @property isDestroyed
- @default false
- */
- isDestroyed: false,
-
- /**
- Destruction scheduled flag. The `destroy()` method has been called.
-
- The object stays intact until the end of the run loop at which point
- the `isDestroyed` flag is set.
-
- @property isDestroying
- @default false
- */
- isDestroying: false,
-
- /**
- Destroys an object by setting the `isDestroyed` flag and removing its
- metadata, which effectively destroys observers and bindings.
-
- If you try to set a property on a destroyed object, an exception will be
- raised.
-
- Note that destruction is scheduled for the end of the run loop and does not
- happen immediately. It will set an isDestroying flag immediately.
-
- @method destroy
- @return {Ember.Object} receiver
- */
- destroy: function() {
- if (this.isDestroying) { return; }
- this.isDestroying = true;
-
- schedule('actions', this, this.willDestroy);
- schedule('destroy', this, this._scheduledDestroy);
- return this;
- },
-
- /**
- Override to implement teardown.
-
- @method willDestroy
- */
- willDestroy: Ember.K,
-
- /**
- @private
-
- Invoked by the run loop to actually destroy the object. This is
- scheduled for execution by the `destroy` method.
-
- @method _scheduledDestroy
- */
- _scheduledDestroy: function() {
- if (this.isDestroyed) { return; }
- destroy(this);
- this.isDestroyed = true;
- },
-
- bind: function(to, from) {
- if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
- from.to(to).connect(this);
- return from;
- },
-
- /**
- Returns a string representation which attempts to provide more information
- than Javascript's `toString` typically does, in a generic way for all Ember
- objects.
-
- App.Person = Em.Object.extend()
- person = App.Person.create()
- person.toString() //=> ""
-
- If the object's class is not defined on an Ember namespace, it will
- indicate it is a subclass of the registered superclass:
-
- Student = App.Person.extend()
- student = Student.create()
- student.toString() //=> "<(subclass of App.Person):ember1025>"
-
- If the method `toStringExtension` is defined, its return value will be
- included in the output.
-
- App.Teacher = App.Person.extend({
- toStringExtension: function() {
- return this.get('fullName');
- }
- });
- teacher = App.Teacher.create()
- teacher.toString(); //=> ""
-
- @method toString
- @return {String} string representation
- */
- toString: function toString() {
- var hasToStringExtension = typeof this.toStringExtension === 'function',
- extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
- var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
- this.toString = makeToString(ret);
- return ret;
- }
-});
-
-CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
-
-function makeToString(ret) {
- return function() { return ret; };
-}
-
-if (Ember.config.overridePrototypeMixin) {
- Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
-}
-
-CoreObject.__super__ = null;
-
-var ClassMixin = Mixin.create({
-
- ClassMixin: Ember.required(),
-
- PrototypeMixin: Ember.required(),
-
- isClass: true,
-
- isMethod: false,
-
- extend: function() {
- var Class = makeCtor(), proto;
- Class.ClassMixin = Mixin.create(this.ClassMixin);
- Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
-
- Class.ClassMixin.ownerConstructor = Class;
- Class.PrototypeMixin.ownerConstructor = Class;
-
- reopen.apply(Class.PrototypeMixin, arguments);
-
- Class.superclass = this;
- Class.__super__ = this.prototype;
-
- proto = Class.prototype = o_create(this.prototype);
- proto.constructor = Class;
- generateGuid(proto, 'ember');
- meta(proto).proto = proto; // this will disable observers on prototype
-
- Class.ClassMixin.apply(Class);
- return Class;
- },
-
- /**
- Equivalent to doing `extend(arguments).create()`.
- If possible use the normal `create` method instead.
-
- @method createWithMixins
- @static
- @param [arguments]*
- */
- createWithMixins: function() {
- var C = this;
- if (arguments.length>0) { this._initMixins(arguments); }
- return new C();
- },
-
- /**
- Creates an instance of a class. Accepts either no arguments, or an object
- containing values to initialize the newly instantiated object with.
-
- ```javascript
- App.Person = Ember.Object.extend({
- helloWorld: function() {
- alert("Hi, my name is " + this.get('name'));
- }
- });
-
- var tom = App.Person.create({
- name: 'Tom Dale'
- });
-
- tom.helloWorld(); // alerts "Hi, my name is Tom Dale".
- ```
-
- `create` will call the `init` function if defined during
- `Ember.AnyObject.extend`
-
- If no arguments are passed to `create`, it will not set values to the new
- instance during initialization:
-
- ```javascript
- var noName = App.Person.create();
- noName.helloWorld(); // alerts undefined
- ```
-
- NOTE: For performance reasons, you cannot declare methods or computed
- properties during `create`. You should instead declare methods and computed
- properties when using `extend` or use the `createWithMixins` shorthand.
-
- @method create
- @static
- @param [arguments]*
- */
- create: function() {
- var C = this;
- if (arguments.length>0) { this._initProperties(arguments); }
- return new C();
- },
-
- reopen: function() {
- this.willReopen();
- reopen.apply(this.PrototypeMixin, arguments);
- return this;
- },
-
- reopenClass: function() {
- reopen.apply(this.ClassMixin, arguments);
- applyMixin(this, arguments, false);
- return this;
- },
-
- detect: function(obj) {
- if ('function' !== typeof obj) { return false; }
- while(obj) {
- if (obj===this) { return true; }
- obj = obj.superclass;
- }
- return false;
- },
-
- detectInstance: function(obj) {
- return obj instanceof this;
- },
-
- /**
- In some cases, you may want to annotate computed properties with additional
- metadata about how they function or what values they operate on. For
- example, computed property functions may close over variables that are then
- no longer available for introspection.
-
- You can pass a hash of these values to a computed property like this:
-
- ```javascript
- person: function() {
- var personId = this.get('personId');
- return App.Person.create({ id: personId });
- }.property().meta({ type: App.Person })
- ```
-
- Once you've done this, you can retrieve the values saved to the computed
- property from your class like this:
-
- ```javascript
- MyClass.metaForProperty('person');
- ```
-
- This will return the original hash that was passed to `meta()`.
-
- @method metaForProperty
- @param key {String} property name
- */
- metaForProperty: function(key) {
- var desc = meta(this.proto(), false).descs[key];
-
- Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
- return desc._meta || {};
- },
-
- /**
- Iterate over each computed property for the class, passing its name
- and any associated metadata (see `metaForProperty`) to the callback.
-
- @method eachComputedProperty
- @param {Function} callback
- @param {Object} binding
- */
- eachComputedProperty: function(callback, binding) {
- var proto = this.proto(),
- descs = meta(proto).descs,
- empty = {},
- property;
-
- for (var name in descs) {
- property = descs[name];
-
- if (property instanceof Ember.ComputedProperty) {
- callback.call(binding || this, name, property._meta || empty);
- }
- }
- }
-
-});
-
-ClassMixin.ownerConstructor = CoreObject;
-
-if (Ember.config.overrideClassMixin) {
- Ember.config.overrideClassMixin(ClassMixin);
-}
-
-CoreObject.ClassMixin = ClassMixin;
-ClassMixin.apply(CoreObject);
-
-Ember.CoreObject = CoreObject;
-
-})();
-
-
-
-(function() {
-/**
-@module ember
-@submodule ember-runtime
-*/
-
-/**
- `Ember.Object` is the main base class for all Ember objects. It is a subclass
- of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
- see the documentation for each of these.
-
- @class Object
- @namespace Ember
- @extends Ember.CoreObject
- @uses Ember.Observable
-*/
-Ember.Object = Ember.CoreObject.extend(Ember.Observable);
-Ember.Object.toString = function() { return "Ember.Object"; };
-
-})();
-
-
-
-(function() {
-/**
-@module ember
-@submodule ember-runtime
-*/
-
-var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf;
-
-/**
- A Namespace is an object usually used to contain other objects or methods
- such as an application or framework. Create a namespace anytime you want
- to define one of these new containers.
-
- # Example Usage
-
- ```javascript
- MyFramework = Ember.Namespace.create({
- VERSION: '1.0.0'
- });
- ```
-
- @class Namespace
- @namespace Ember
- @extends Ember.Object
-*/
-var Namespace = Ember.Namespace = Ember.Object.extend({
- isNamespace: true,
-
- init: function() {
- Ember.Namespace.NAMESPACES.push(this);
- Ember.Namespace.PROCESSED = false;
- },
-
- toString: function() {
- var name = get(this, 'name');
- if (name) { return name; }
-
- findNamespaces();
- return this[Ember.GUID_KEY+'_name'];
- },
-
- nameClasses: function() {
- processNamespace([this.toString()], this, {});
- },
-
- destroy: function() {
- var namespaces = Ember.Namespace.NAMESPACES;
- Ember.lookup[this.toString()] = undefined;
- namespaces.splice(indexOf.call(namespaces, this), 1);
- this._super();
- }
-});
-
-Namespace.reopenClass({
- NAMESPACES: [Ember],
- NAMESPACES_BY_ID: {},
- PROCESSED: false,
- processAll: processAllNamespaces,
- byName: function(name) {
- if (!Ember.BOOTED) {
- processAllNamespaces();
- }
-
- return NAMESPACES_BY_ID[name];
- }
-});
-
-var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID;
-
-var hasOwnProp = ({}).hasOwnProperty,
- guidFor = Ember.guidFor;
-
-function processNamespace(paths, root, seen) {
- var idx = paths.length;
-
- NAMESPACES_BY_ID[paths.join('.')] = root;
-
- // Loop over all of the keys in the namespace, looking for classes
- for(var key in root) {
- if (!hasOwnProp.call(root, key)) { continue; }
- var obj = root[key];
-
- // If we are processing the `Ember` namespace, for example, the
- // `paths` will start with `["Ember"]`. Every iteration through
- // the loop will update the **second** element of this list with
- // the key, so processing `Ember.View` will make the Array
- // `['Ember', 'View']`.
- paths[idx] = key;
-
- // If we have found an unprocessed class
- if (obj && obj.toString === classToString) {
- // Replace the class' `toString` with the dot-separated path
- // and set its `NAME_KEY`
- obj.toString = makeToString(paths.join('.'));
- obj[NAME_KEY] = paths.join('.');
-
- // Support nested namespaces
- } else if (obj && obj.isNamespace) {
- // Skip aliased namespaces
- if (seen[guidFor(obj)]) { continue; }
- seen[guidFor(obj)] = true;
-
- // Process the child namespace
- processNamespace(paths, obj, seen);
- }
- }
-
- paths.length = idx; // cut out last item
-}
-
-function findNamespaces() {
- var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace;
-
- if (Namespace.PROCESSED) { return; }
-
- for (var prop in lookup) {
- // These don't raise exceptions but can cause warnings
- if (prop === "parent" || prop === "top" || prop === "frameElement" || prop === "webkitStorageInfo") { continue; }
-
- // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox.
- // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage
- if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; }
- // Unfortunately, some versions of IE don't support window.hasOwnProperty
- if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; }
-
- // At times we are not allowed to access certain properties for security reasons.
- // There are also times where even if we can access them, we are not allowed to access their properties.
- try {
- obj = Ember.lookup[prop];
- isNamespace = obj && obj.isNamespace;
- } catch (e) {
- continue;
- }
-
- if (isNamespace) {
- Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop));
- obj[NAME_KEY] = prop;
- }
- }
-}
-
-var NAME_KEY = Ember.NAME_KEY = Ember.GUID_KEY + '_name';
-
-function superClassString(mixin) {
- var superclass = mixin.superclass;
- if (superclass) {
- if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
- else { return superClassString(superclass); }
- } else {
- return;
- }
-}
-
-function classToString() {
- if (!Ember.BOOTED && !this[NAME_KEY]) {
- processAllNamespaces();
- }
-
- var ret;
-
- if (this[NAME_KEY]) {
- ret = this[NAME_KEY];
- } else {
- var str = superClassString(this);
- if (str) {
- ret = "(subclass of " + str + ")";
- } else {
- ret = "(unknown mixin)";
- }
- this.toString = makeToString(ret);
- }
-
- return ret;
-}
-
-function processAllNamespaces() {
- var unprocessedNamespaces = !Namespace.PROCESSED,
- unprocessedMixins = Ember.anyUnprocessedMixins;
-
- if (unprocessedNamespaces) {
- findNamespaces();
- Namespace.PROCESSED = true;
- }
-
- if (unprocessedNamespaces || unprocessedMixins) {
- var namespaces = Namespace.NAMESPACES, namespace;
- for (var i=0, l=namespaces.length; i get(this, 'content.length')) throw new Error(OUT_OF_RANGE_EXCEPTION);
+ if (idx > get(this, 'content.length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
this._replace(idx, 0, [object]);
return this;
},
@@ -13211,7 +17014,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;
@@ -13295,168 +17098,6 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.Array
-(function() {
-/**
-@module ember
-@submodule ember-runtime
-*/
-
-var get = Ember.get,
- set = Ember.set,
- fmt = Ember.String.fmt,
- addBeforeObserver = Ember.addBeforeObserver,
- addObserver = Ember.addObserver,
- removeBeforeObserver = Ember.removeBeforeObserver,
- removeObserver = Ember.removeObserver,
- propertyWillChange = Ember.propertyWillChange,
- propertyDidChange = Ember.propertyDidChange;
-
-function contentPropertyWillChange(content, contentKey) {
- var key = contentKey.slice(8); // remove "content."
- if (key in this) { return; } // if shadowed in proxy
- propertyWillChange(this, key);
-}
-
-function contentPropertyDidChange(content, contentKey) {
- var key = contentKey.slice(8); // remove "content."
- if (key in this) { return; } // if shadowed in proxy
- propertyDidChange(this, key);
-}
-
-/**
- `Ember.ObjectProxy` forwards all properties not defined by the proxy itself
- to a proxied `content` object.
-
- ```javascript
- object = Ember.Object.create({
- name: 'Foo'
- });
-
- proxy = Ember.ObjectProxy.create({
- content: object
- });
-
- // Access and change existing properties
- proxy.get('name') // 'Foo'
- proxy.set('name', 'Bar');
- object.get('name') // 'Bar'
-
- // Create new 'description' property on `object`
- proxy.set('description', 'Foo is a whizboo baz');
- object.get('description') // 'Foo is a whizboo baz'
- ```
-
- While `content` is unset, setting a property to be delegated will throw an
- Error.
-
- ```javascript
- proxy = Ember.ObjectProxy.create({
- content: null,
- flag: null
- });
- proxy.set('flag', true);
- proxy.get('flag'); // true
- proxy.get('foo'); // undefined
- proxy.set('foo', 'data'); // throws Error
- ```
-
- Delegated properties can be bound to and will change when content is updated.
-
- Computed properties on the proxy itself can depend on delegated properties.
-
- ```javascript
- ProxyWithComputedProperty = Ember.ObjectProxy.extend({
- fullName: function () {
- var firstName = this.get('firstName'),
- lastName = this.get('lastName');
- if (firstName && lastName) {
- return firstName + ' ' + lastName;
- }
- return firstName || lastName;
- }.property('firstName', 'lastName')
- });
-
- proxy = ProxyWithComputedProperty.create();
-
- proxy.get('fullName'); // undefined
- proxy.set('content', {
- firstName: 'Tom', lastName: 'Dale'
- }); // triggers property change for fullName on proxy
-
- proxy.get('fullName'); // 'Tom Dale'
- ```
-
- @class ObjectProxy
- @namespace Ember
- @extends Ember.Object
-*/
-Ember.ObjectProxy = Ember.Object.extend(/** @scope Ember.ObjectProxy.prototype */ {
- /**
- The object whose properties will be forwarded.
-
- @property content
- @type Ember.Object
- @default null
- */
- content: null,
- _contentDidChange: Ember.observer(function() {
- Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this);
- }, 'content'),
-
- isTruthy: Ember.computed.bool('content'),
-
- _debugContainerKey: null,
-
- willWatchProperty: function (key) {
- var contentKey = 'content.' + key;
- addBeforeObserver(this, contentKey, null, contentPropertyWillChange);
- addObserver(this, contentKey, null, contentPropertyDidChange);
- },
-
- didUnwatchProperty: function (key) {
- var contentKey = 'content.' + key;
- removeBeforeObserver(this, contentKey, null, contentPropertyWillChange);
- removeObserver(this, contentKey, null, contentPropertyDidChange);
- },
-
- unknownProperty: function (key) {
- var content = get(this, 'content');
- if (content) {
- return get(content, key);
- }
- },
-
- setUnknownProperty: function (key, value) {
- var 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);
- }
-
-});
-
-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);
- }
-});
-
-})();
-
-
-
(function() {
/**
@module ember
@@ -13465,7 +17106,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 +17165,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 +17314,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 +17336,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
@@ -13702,15 +17344,14 @@ var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember
var len = objects ? get(objects, 'length') : 0;
this.arrayContentWillChange(idx, amt, len);
- if (!objects || objects.length === 0) {
- this.splice(idx, amt) ;
+ if (len === 0) {
+ 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 +17428,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 guarantee that `Ember.EXTEND_PROTOTYPES`
+ will be `true`.
+
+ Example
+
+ ```js
+ var Pagination = Ember.CollectionView.extend({
+ tagName: 'ul',
+ classNames: ['pagination'],
+ init: function() {
+ this._super();
+ if (!this.get('content')) {
+ this.set('content', Ember.A([]));
+ }
+ }
+ });
+ ```
@method A
@for Ember
@@ -13800,7 +17460,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 +17565,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 +17628,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 +17738,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 +17855,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 +17883,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 +17981,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 +18012,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 +18054,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 +18083,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 +18141,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 +18173,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 +18189,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 +18246,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'),
@@ -14575,7 +18275,7 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
return content;
}),
- _contentWillChange: Ember.beforeObserver(function() {
+ _contentWillChange: Ember.beforeObserver('content', function() {
var content = get(this, 'content'),
sortProperties = get(this, 'sortProperties');
@@ -14588,18 +18288,18 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
}
this._super();
- }, 'content'),
+ }),
- sortAscendingWillChange: Ember.beforeObserver(function() {
+ sortAscendingWillChange: Ember.beforeObserver('sortAscending', function() {
this._lastSortAscending = get(this, 'sortAscending');
- }, 'sortAscending'),
+ }),
- sortAscendingDidChange: Ember.observer(function() {
+ sortAscendingDidChange: Ember.observer('sortAscending', function() {
if (get(this, 'sortAscending') !== this._lastSortAscending) {
var arrangedContent = get(this, 'arrangedContent');
arrangedContent.reverseObjects();
}
- }, 'sortAscending'),
+ }),
contentArrayWillChange: function(array, idx, removedCount, addedCount) {
var isSorted = get(this, 'isSorted');
@@ -14870,11 +18570,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 +18590,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 +18629,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
@@ -14964,7 +18670,11 @@ Ember Runtime
@submodule ember-views
*/
-var jQuery = Ember.imports.jQuery;
+var jQuery = this.jQuery || (Ember.imports && Ember.imports.jQuery);
+if (!jQuery && typeof require === 'function') {
+ jQuery = require('jquery');
+}
+
Ember.assert("Ember Views require jQuery 1.7, 1.8, 1.9, 1.10, or 2.0", jQuery && (jQuery().jquery.match(/^((1\.(7|8|9|10))|2.0)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
/**
@@ -15206,14 +18916,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 +19288,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);
@@ -15928,7 +19664,7 @@ Ember.ControllerMixin.reopen({
set(this, '_childContainers', {});
},
- _modelDidChange: Ember.observer(function() {
+ _modelDidChange: Ember.observer('model', function() {
var containers = get(this, '_childContainers');
for (var prop in containers) {
@@ -15937,7 +19673,7 @@ Ember.ControllerMixin.reopen({
}
set(this, '_childContainers', {});
- }, 'model')
+ })
});
})();
@@ -15962,6 +19698,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 +19719,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 +19739,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 +19753,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 +19868,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 +19981,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 +20155,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 +20216,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 +20259,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 +20364,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 +20462,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 +20517,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
@@ -16791,9 +20544,8 @@ Ember.View = Ember.CoreView.extend(
/**
The name of the template to lookup if no template is provided.
- `Ember.View` will look for a template with this name in this view's
- `templates` object. By default, this will be a global object
- shared in `Ember.TEMPLATES`.
+ By default `Ember.View` will lookup a template with this name in
+ `Ember.TEMPLATES` (a shared global object).
@property templateName
@type String
@@ -16804,9 +20556,8 @@ Ember.View = Ember.CoreView.extend(
/**
The name of the layout to lookup if no layout is provided.
- `Ember.View` will look for a template with this name in this view's
- `templates` object. By default, this will be a global object
- shared in `Ember.TEMPLATES`.
+ By default `Ember.View` will lookup a template with this name in
+ `Ember.TEMPLATES` (a shared global object).
@property layoutName
@type String
@@ -16814,15 +20565,6 @@ Ember.View = Ember.CoreView.extend(
*/
layoutName: null,
- /**
- The hash in which to look for `templateName`.
-
- @property templates
- @type Ember.Object
- @default Ember.TEMPLATES
- */
- templates: Ember.TEMPLATES,
-
/**
The template used to render the view. This should be a function that
accepts an optional context parameter and returns a string of HTML that
@@ -16957,9 +20699,9 @@ Ember.View = Ember.CoreView.extend(
@method _contextDidChange
*/
- _contextDidChange: Ember.observer(function() {
+ _contextDidChange: Ember.observer('context', function() {
this.rerender();
- }, 'context'),
+ }),
/**
If `false`, the view will appear hidden in DOM.
@@ -16986,21 +20728,21 @@ Ember.View = Ember.CoreView.extend(
// When it's a virtual view, we need to notify the parent that their
// childViews will change.
- _childViewsWillChange: Ember.beforeObserver(function() {
+ _childViewsWillChange: Ember.beforeObserver('childViews', function() {
if (this.isVirtual) {
var parentView = get(this, 'parentView');
if (parentView) { Ember.propertyWillChange(parentView, 'childViews'); }
}
- }, 'childViews'),
+ }),
// When it's a virtual view, we need to notify the parent that their
// childViews did change.
- _childViewsDidChange: Ember.observer(function() {
+ _childViewsDidChange: Ember.observer('childViews', function() {
if (this.isVirtual) {
var parentView = get(this, 'parentView');
if (parentView) { Ember.propertyDidChange(parentView, 'childViews'); }
}
- }, 'childViews'),
+ }),
/**
Return the nearest ancestor that is an instance of the provided
@@ -17082,7 +20824,7 @@ Ember.View = Ember.CoreView.extend(
@method _parentViewDidChange
*/
- _parentViewDidChange: Ember.observer(function() {
+ _parentViewDidChange: Ember.observer('_parentView', function() {
if (this.isDestroying) { return; }
this.trigger('parentViewDidChange');
@@ -17090,9 +20832,9 @@ Ember.View = Ember.CoreView.extend(
if (get(this, 'parentView.controller') && !get(this, 'controller')) {
this.notifyPropertyChange('controller');
}
- }, '_parentView'),
+ }),
- _controllerDidChange: Ember.observer(function() {
+ _controllerDidChange: Ember.observer('controller', function() {
if (this.isDestroying) { return; }
this.rerender();
@@ -17100,7 +20842,7 @@ Ember.View = Ember.CoreView.extend(
this.forEachChildView(function(view) {
view.propertyDidChange('controller');
});
- }, 'controller'),
+ }),
cloneKeywords: function() {
var templateData = get(this, 'templateData');
@@ -17656,8 +21398,8 @@ Ember.View = Ember.CoreView.extend(
If you write a `willDestroyElement()` handler, you can assume that your
`didInsertElement()` handler was called earlier for the same element.
- Normally you will not call or override this method yourself, but you may
- want to implement the above callbacks when it is run.
+ You should not call or override this method yourself, but you may
+ want to implement the above callbacks.
@method destroyElement
@return {Ember.View} receiver
@@ -17694,12 +21436,6 @@ Ember.View = Ember.CoreView.extend(
return viewCollection;
},
- _elementWillChange: Ember.beforeObserver(function() {
- this.forEachChildView(function(view) {
- Ember.propertyWillChange(view, 'element');
- });
- }, 'element'),
-
/**
@private
@@ -17709,11 +21445,11 @@ Ember.View = Ember.CoreView.extend(
@method _elementDidChange
*/
- _elementDidChange: Ember.observer(function() {
+ _elementDidChange: Ember.observer('element', function() {
this.forEachChildView(function(view) {
- Ember.propertyDidChange(view, 'element');
+ delete meta(view).cache.element;
});
- }, 'element'),
+ }),
/**
Called when the parentView property has changed.
@@ -17806,7 +21542,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 +21774,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;
}
@@ -18090,7 +21830,7 @@ Ember.View = Ember.CoreView.extend(
@method _isVisibleDidChange
*/
- _isVisibleDidChange: Ember.observer(function() {
+ _isVisibleDidChange: Ember.observer('isVisible', function() {
var $el = this.$();
if (!$el) { return; }
@@ -18105,7 +21845,7 @@ Ember.View = Ember.CoreView.extend(
} else {
this._notifyBecameHidden();
}
- }, 'isVisible'),
+ }),
_notifyBecameVisible: function() {
this.trigger('becameVisible');
@@ -18155,6 +21895,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 +21927,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 +22026,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 +22247,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 +22467,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 +22707,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 +22773,7 @@ Ember.ContainerView = Ember.View.extend(Ember.MutableArray, {
length: Ember.computed(function () {
return this._childViews.length;
- }),
+ }).volatile(),
/**
@private
@@ -19134,20 +22863,20 @@ Ember.ContainerView = Ember.View.extend(Ember.MutableArray, {
currentView: null,
- _currentViewWillChange: Ember.beforeObserver(function() {
+ _currentViewWillChange: Ember.beforeObserver('currentView', function() {
var currentView = get(this, 'currentView');
if (currentView) {
currentView.destroy();
}
- }, 'currentView'),
+ }),
- _currentViewDidChange: Ember.observer(function() {
+ _currentViewDidChange: Ember.observer('currentView', function() {
var currentView = get(this, 'currentView');
if (currentView) {
Ember.assert("You tried to set a current view that already has a parent. Make sure you don't have multiple outlets in the same view.", !get(currentView, '_parentView'));
this.pushObject(currentView);
}
- }, 'currentView'),
+ }),
_ensureChildrenAreInDOM: function () {
this.currentState.ensureChildrenAreInDOM(this);
@@ -19162,7 +22891,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 +23103,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,19 +23147,32 @@ 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;
},
- _contentWillChange: Ember.beforeObserver(function() {
+ /**
+ @private
+
+ Invoked when the content property is about to change. Notifies observers that the
+ entire array content will change.
+
+ @method _contentWillChange
+ */
+ _contentWillChange: Ember.beforeObserver('content', function() {
var content = this.get('content');
if (content) { content.removeArrayObserver(this); }
var len = content ? get(content, 'length') : 0;
this.arrayWillChange(content, 0, len);
- }, 'content'),
+ }),
/**
@private
@@ -19447,7 +23184,7 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie
@method _contentDidChange
*/
- _contentDidChange: Ember.observer(function() {
+ _contentDidChange: Ember.observer('content', function() {
var content = get(this, 'content');
if (content) {
@@ -19457,12 +23194,24 @@ Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionVie
var len = content ? get(content, 'length') : 0;
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 +23225,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 +23328,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 +23383,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 +23413,7 @@ var get = Ember.get, set = Ember.set, isNone = Ember.isNone;
```html
{{person.title}}
-
+
{{person.signature}}
```
@@ -19656,15 +23435,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 +23499,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,66 +23508,102 @@ 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;
}).property('_parentView'),
/**
- Sends an action to component's controller. A component inherits its
- controller from the context in which it is used.
+ Triggers a named action on the controller context where the component is used if
+ this controller has registered for notifications of the action.
- By default, calling `sendAction()` will send an action with the name
- of the component's `action` property.
+ For example a component for playing or pausing music may translate click events
+ into action notifications of "play" or "stop" depending on some internal state
+ of the component:
- For example, if the component had a property `action` with the value
- `"addItem"`, calling `sendAction()` would send the `addItem` action
- to the component's controller.
-
- If you provide the `action` argument to `sendAction()`, that key will
- be used to look up the action name.
-
- For example, if the component had a property `playing` with the value
- `didStartPlaying`, calling `sendAction('playing')` would send the
- `didStartPlaying` action to the component's controller.
-
- Whether or not you are using the default action or a named action, if
- the action name is not defined on the component, calling `sendAction()`
- does not have any effect.
-
- For example, if you call `sendAction()` on a component that does not have
- an `action` property defined, no action will be sent to the controller,
- nor will an exception be raised.
-
- You can send a context object with the action by supplying the `context`
- argument. The context will be supplied as the first argument in the
- target's action method. Example:
```javascript
- App.MyTree = Ember.Component.extend({
- click: function() {
- this.sendAction('didClickTreeNode', this.get('node'));
+ App.PlayButtonComponent = Ember.Component.extend({
+ click: function(){
+ if (this.get('isPlaying')) {
+ this.triggerAction('play');
+ } else {
+ this.triggerAction('stop');
+ }
}
});
+ ```
- App.CategoriesController = Ember.Controller.extend({
- didClickCategory: function(category) {
- //Do something with the node/category that was clicked
+ When used inside a template these component actions are configured to
+ trigger actions in the outer application context:
+
+ ```handlebars
+ {{! application.hbs }}
+ {{play-button play="musicStarted" stop="musicStopped"}}
+ ```
+
+ When the component receives a browser `click` event it translate this
+ interaction into application-specific semantics ("play" or "stop") and
+ triggers the specified action name on the controller for the template
+ where the component is used:
+
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ actions: {
+ musicStarted: function(){
+ // called when the play button is clicked
+ // and the music started playing
+ },
+ musicStopped: function(){
+ // called when the play button is clicked
+ // and the music stopped playing
+ }
+ }
+ });
+ ```
+
+ If no action name is passed to `sendAction` a default name of "action"
+ is assumed.
+
+ ```javascript
+ App.NextButtonComponent = Ember.Component.extend({
+ click: function(){
+ this.sendAction();
}
});
```
```handlebars
- {{! categories.hbs}}
- {{my-tree didClickTreeNode='didClickCategory'}}
+ {{! application.hbs }}
+ {{next-button action="playNextSongInAlbum"}}
+ ```
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ actions: {
+ playNextSongInAlbum: function(){
+ ...
+ }
+ }
+ });
```
@method sendAction
@param [action] {String} the action to trigger
@param [context] {*} a context to send with the action
*/
- sendAction: function(action, context) {
- var actionName;
+ sendAction: function(action) {
+ var actionName,
+ contexts = a_slice.call(arguments, 1);
// Send the default action
if (action === undefined) {
@@ -19800,7 +23619,7 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, {
this.triggerAction({
action: actionName,
- actionContext: context
+ actionContext: contexts
});
}
});
@@ -20013,6 +23832,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 +23880,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 +23934,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 +23972,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 +23982,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 +23991,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 +24018,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);
@@ -20371,8 +24199,8 @@ if (!Handlebars && typeof require === 'function') {
Handlebars = require('handlebars');
}
-Ember.assert("Ember Handlebars requires Handlebars version 1.0.0. Include a SCRIPT tag in the HTML HEAD linking to the Handlebars file before you link to Ember.", Handlebars);
-Ember.assert("Ember Handlebars requires Handlebars version 1.0.0, COMPILER_REVISION expected: 4, got: " + Handlebars.COMPILER_REVISION + " - Please note: Builds of master may have other COMPILER_REVISION values.", Handlebars.COMPILER_REVISION === 4);
+Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1. Include a SCRIPT tag in the HTML HEAD linking to the Handlebars file before you link to Ember.", Handlebars);
+Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1, COMPILER_REVISION expected: 4, got: " + Handlebars.COMPILER_REVISION + " - Please note: Builds of master may have other COMPILER_REVISION values.", Handlebars.COMPILER_REVISION === 4);
/**
Prepares the Handlebars templating library for use inside Ember's view
@@ -20390,20 +24218,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,28 +24272,34 @@ 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);
- });
+ Ember.Handlebars.registerHelper(name, Ember.Handlebars.makeViewHelper(value));
} else {
Ember.Handlebars.registerBoundHelper.apply(null, arguments);
}
};
+/**
+ @private
+
+ Returns a helper function that renders the provided ViewClass.
+
+ Used internally by Ember.Handlebars.helper and other methods
+ involving helper/component registration.
+
+ @method helper
+ @for Ember.Handlebars
+ @param {Function} ViewClass view class constructor
+*/
+Ember.Handlebars.makeViewHelper = function(ViewClass) {
+ return function(options) {
+ Ember.assert("You can only pass attributes (such as name=value) not bare values to a helper for a View", arguments.length < 2);
+ return Ember.Handlebars.helpers.view.call(this, ViewClass, options);
+ };
+};
+
/**
@class helpers
@namespace Ember.Handlebars
@@ -20520,7 +24340,6 @@ if (Handlebars.JavaScriptCompiler) {
Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";
-
Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
return "''";
};
@@ -20539,6 +24358,43 @@ Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string)
return "data.buffer.push("+string+");";
};
+// Hacks ahead:
+// Handlebars presently has a bug where the `blockHelperMissing` hook
+// doesn't get passed the name of the missing helper name, but rather
+// gets passed the value of that missing helper evaluated on the current
+// context, which is most likely `undefined` and totally useless.
+//
+// So we alter the compiled template function to pass the name of the helper
+// instead, as expected.
+//
+// This can go away once the following is closed:
+// https://github.com/wycats/handlebars.js/issues/617
+
+var DOT_LOOKUP_REGEX = /helpers\.(.*?)\)/,
+ BRACKET_STRING_LOOKUP_REGEX = /helpers\['(.*?)'/,
+ INVOCATION_SPLITTING_REGEX = /(.*blockHelperMissing\.call\(.*)(stack[0-9]+)(,.*)/;
+
+Ember.Handlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation = function(source) {
+ var helperInvocation = source[source.length - 1],
+ helperName = (DOT_LOOKUP_REGEX.exec(helperInvocation) || BRACKET_STRING_LOOKUP_REGEX.exec(helperInvocation))[1],
+ matches = INVOCATION_SPLITTING_REGEX.exec(helperInvocation);
+
+ source[source.length - 1] = matches[1] + "'" + helperName + "'" + matches[3];
+}
+var stringifyBlockHelperMissing = Ember.Handlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation;
+
+var originalBlockValue = Ember.Handlebars.JavaScriptCompiler.prototype.blockValue;
+Ember.Handlebars.JavaScriptCompiler.prototype.blockValue = function() {
+ originalBlockValue.apply(this, arguments);
+ stringifyBlockHelperMissing(this.source);
+};
+
+var originalAmbiguousBlockValue = Ember.Handlebars.JavaScriptCompiler.prototype.ambiguousBlockValue;
+Ember.Handlebars.JavaScriptCompiler.prototype.ambiguousBlockValue = function() {
+ originalAmbiguousBlockValue.apply(this, arguments);
+ stringifyBlockHelperMissing(this.source);
+};
+
var prefix = "ember" + (+new Date()), incr = 1;
/**
@@ -20633,7 +24489,8 @@ if (Handlebars.compile) {
})();
(function() {
-var slice = Array.prototype.slice;
+var slice = Array.prototype.slice,
+ originalTemplate = Ember.Handlebars.template;
/**
@private
@@ -20709,7 +24566,6 @@ var handlebarsGet = Ember.Handlebars.get = function(root, path, options) {
}
return value;
};
-Ember.Handlebars.getPath = Ember.deprecateFunc('`Ember.Handlebars.getPath` has been changed to `Ember.Handlebars.get` for consistency.', Ember.Handlebars.get);
Ember.Handlebars.resolveParams = function(context, params, options) {
var resolvedParams = [], types = options.types, param, type;
@@ -20761,9 +24617,20 @@ Ember.Handlebars.resolveHash = function(context, hash, options) {
@param {String} path
@param {Hash} options
*/
-Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
+Ember.Handlebars.registerHelper('helperMissing', function(path) {
var error, view = "";
+ var options = arguments[arguments.length - 1];
+
+
+
+ var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path);
+
+ if (helper) {
+ return helper.apply(this, slice.call(arguments, 1));
+ }
+
+
error = "%@ Handlebars error: Could not find property '%@' on object %@.";
if (options.data) {
view = options.data.view;
@@ -20771,6 +24638,39 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
});
+/**
+ @private
+
+ Registers a helper in Handlebars that will be called if no property with the
+ given name can be found on the current context object, and no helper with
+ that name is registered.
+
+ This throws an exception with a more helpful error message so the user can
+ track down where the problem is happening.
+
+ @method helperMissing
+ @for Ember.Handlebars.helpers
+ @param {String} path
+ @param {Hash} options
+*/
+Ember.Handlebars.registerHelper('blockHelperMissing', function(path) {
+
+ var options = arguments[arguments.length - 1];
+
+
+
+ Ember.assert("`blockHelperMissing` was invoked without a helper name, which is most likely due to a mismatch between the version of Ember.js you're running now and the one used to precompile your templates. Please make sure the version of `ember-handlebars-compiler` you're using is up to date.", path);
+
+ var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path);
+
+ if (helper) {
+ return helper.apply(this, slice.call(arguments, 1));
+ }
+
+
+ return Handlebars.helpers.blockHelperMissing.apply(this, arguments);
+});
+
/**
Register a bound handlebars helper. Bound helpers behave similarly to regular
handlebars helpers, with the added ability to re-render when the underlying data
@@ -20881,68 +24781,141 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
@param {String} dependentKeys*
*/
Ember.Handlebars.registerBoundHelper = function(name, fn) {
- var dependentKeys = slice.call(arguments, 2);
+ var boundHelperArgs = slice.call(arguments, 1),
+ boundFn = Ember.Handlebars.makeBoundHelper.apply(this, boundHelperArgs);
+ Ember.Handlebars.registerHelper(name, boundFn);
+};
+
+/**
+ @private
+
+ A (mostly) private helper function to `registerBoundHelper`. Takes the
+ provided Handlebars helper function fn and returns it in wrapped
+ bound helper form.
+
+ The main use case for using this outside of `registerBoundHelper`
+ is for registering helpers on the container:
+
+ ```js
+ var boundHelperFn = Ember.Handlebars.makeBoundHelper(function(word) {
+ return word.toUpperCase();
+ });
+
+ container.register('helper:my-bound-helper', boundHelperFn);
+ ```
+
+ In the above example, if the helper function hadn't been wrapped in
+ `makeBoundHelper`, the registered helper would be unbound.
+
+ @method makeBoundHelper
+ @for Ember.Handlebars
+ @param {Function} function
+ @param {String} dependentKeys*
+*/
+Ember.Handlebars.makeBoundHelper = function(fn) {
+ var dependentKeys = slice.call(arguments, 1);
function helper() {
var properties = slice.call(arguments, 0, -1),
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 + '.';
@@ -20953,71 +24926,9 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) {
}
helper._rawFunction = fn;
- Ember.Handlebars.registerHelper(name, helper);
+ return 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 +24950,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);
@@ -21053,10 +24964,10 @@ function evaluateUnboundHelper(context, fn, normalizedProperties, options) {
@method template
@for Ember.Handlebars
- @param {String} template spec
+ @param {String} spec
*/
Ember.Handlebars.template = function(spec) {
- var t = Handlebars.template(spec);
+ var t = originalTemplate(spec);
t.isTop = true;
return t;
};
@@ -21067,19 +24978,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 +25000,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 +25191,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 +25206,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 +25265,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 +25594,18 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer
}
}
-function simpleBind(property, options) {
+EmberHandlebars.bind = bind;
+
+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,14 +25637,15 @@ 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);
}
}
/**
@private
- '_triageMustache' is used internally select between a binding and helper for
+ '_triageMustache' is used internally select between a binding, helper, or component for
the given context. Until this point, it would be hard to determine if the
mustache is a property reference or a regular helper reference. This triage
helper resolves that.
@@ -21735,19 +25655,47 @@ function simpleBind(property, options) {
@method _triageMustache
@for Ember.Handlebars.helpers
@param {String} property Property/helperID to triage
- @param {Function} fn Context to provide for rendering
+ @param {Object} options hash of template/rendering options
@return {String} HTML string
*/
-EmberHandlebars.registerHelper('_triageMustache', function(property, fn) {
+EmberHandlebars.registerHelper('_triageMustache', function(property, options) {
Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2);
+
if (helpers[property]) {
- return helpers[property].call(this, fn);
- }
- else {
- return helpers.bind.apply(this, arguments);
+ return helpers[property].call(this, options);
}
+
+
+
+ var helper = Ember.Handlebars.resolveHelper(options.data.view.container, property);
+ if (helper) {
+ return helper.call(this, options);
+ }
+
+
+ return helpers.bind.call(this, property, options);
});
+Ember.Handlebars.resolveHelper = function(container, name) {
+
+ if (!container || name.indexOf('-') === -1) {
+ return;
+ }
+
+ var helper = container.lookup('helper:' + name);
+ if (!helper) {
+ var componentLookup = container.lookup('component-lookup:main');
+ Ember.assert("Could not find 'component-lookup:main' on the provided container, which is necessary for performing component lookups", componentLookup);
+
+ var Component = componentLookup.lookupFactory(name, container);
+ if (Component) {
+ helper = EmberHandlebars.makeViewHelper(Component);
+ container.register('helper:' + name, helper);
+ }
+ }
+ return helper;
+};
+
/**
@private
@@ -21775,10 +25723,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 +25751,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 +25811,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 +25846,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
-
+
```
The above handlebars template will fill the ``'s `src` attribute will
@@ -21924,26 +25872,26 @@ EmberHandlebars.registerHelper('unless', function(context, options) {
```
- `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
-
+
```
- ### `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
+ multiple discrete values into a single attribute as a space-delimited
list of strings. Each string can be:
* a string return value of an object's property.
* a boolean return value of an object's property
* a hard-coded value
- A string return value works identically to other uses of `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 +25904,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) {
```
```handlebars
-
```
Result in the following rendered output:
@@ -21978,7 +25926,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) {
```
```handlebars
-
+
```
Result in the following rendered output:
@@ -21992,14 +25940,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 +25960,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 +26034,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 +26054,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,9 +26201,38 @@ 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) {
+ propertiesFromHTMLOptions: function(options) {
var hash = options.hash, data = options.data;
var extensions = {},
classes = hash['class'],
@@ -22350,6 +26339,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 +26576,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`
@@ -22738,7 +26729,7 @@ Ember.Handlebars.registerHelper('collection', function(path, options) {
var controller = data.keywords.controller;
Ember.assert('You specified an itemView, but the current context has no container to look the itemView up in. This probably means that you created a view manually, instead of through the container. Instead, use container.lookup("view:viewName"), which will properly instantiate your view.', controller && controller.container);
var container = controller.container;
- itemViewClass = container.resolve('view:' + Ember.String.camelize(hash.itemView));
+ itemViewClass = container.resolve('view:' + hash.itemView);
Ember.assert('You specified the itemView ' + hash.itemView + ", but it was not found at " + container.describe("view:" + hash.itemView) + " (and it was not registered in the container)", !!itemViewClass);
} else if (hash.itemViewClass) {
itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options);
@@ -22840,7 +26831,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 +26861,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 +27030,8 @@ GroupedEach.prototype = {
},
addArrayObservers: function() {
+ if (!this.content) { return; }
+
this.content.addArrayObserver(this, {
willChange: 'contentArrayWillChange',
didChange: 'contentArrayDidChange'
@@ -23046,6 +27039,8 @@ GroupedEach.prototype = {
},
removeArrayObservers: function() {
+ if (!this.content) { return; }
+
this.content.removeArrayObserver(this, {
willChange: 'contentArrayWillChange',
didChange: 'contentArrayDidChange'
@@ -23063,6 +27058,8 @@ GroupedEach.prototype = {
},
render: function() {
+ if (!this.content) { return; }
+
var content = this.content,
contentLength = get(content, 'length'),
data = this.options.data,
@@ -23075,12 +27072,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 +27210,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 +27260,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) {
@@ -23311,24 +27361,48 @@ Ember.Handlebars.registerHelper('template', function(name, options) {
*/
/**
- `partial` renders a template directly using the current context.
- If needed the context can be set using the `{{#with foo}}` helper.
+ The `partial` helper renders another template without
+ changing the template context:
- ```html
-
+ ```handlebars
+ {{foo}}
+ {{partial "nav"}}
```
- The `data-template-name` attribute of a partial template
- is prefixed with an underscore.
+ The above example template will render a template named
+ "_nav", which has the same context as the parent template
+ it's rendered into, so if the "_nav" template also referenced
+ `{{foo}}`, it would print the same thing as the `{{foo}}`
+ in the above example.
- ```html
-
+ If a "_nav" template isn't found, the `partial` helper will
+ fall back to a template named "nav".
+
+ ## Bound template names
+
+ The parameter supplied to `partial` can also be a path
+ to a property containing a template name, e.g.:
+
+ ```handlebars
+ {{partial someTemplateName}}
+ ```
+
+ The above example will look up the value of `someTemplateName`
+ on the template context (e.g. a controller) and use that
+ value as the name of the template to render. If the resolved
+ value is falsy, nothing will be rendered. If `someTemplateName`
+ changes, the partial will be re-rendered using the new template
+ name.
+
+ ## Setting the partial's context with `with`
+
+ The `partial` helper can be used in conjunction with the `with`
+ helper to set a context that will be used by the partial:
+
+ ```handlebars
+ {{#with currentUser}}
+ {{partial "user_info"}}
+ {{/with}}
```
@method partial
@@ -23337,6 +27411,30 @@ Ember.Handlebars.registerHelper('template', function(name, options) {
*/
Ember.Handlebars.registerHelper('partial', function(name, options) {
+
+ var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this;
+
+ if (options.types[0] === "ID") {
+ // Helper was passed a property path; we need to
+ // create a binding that will re-render whenever
+ // this property changes.
+ options.fn = function(context, fnOptions) {
+ var partialName = Ember.Handlebars.get(context, name, fnOptions);
+ renderPartial(context, partialName, fnOptions);
+ };
+
+ return Ember.Handlebars.bind.call(context, name, options, true, exists);
+ } else {
+ // Render the partial right into parent template.
+ renderPartial(context, name, options);
+ }
+});
+
+function exists(value) {
+ return !Ember.isNone(value);
+}
+
+function renderPartial(context, name, options) {
var nameParts = name.split("/"),
lastPart = nameParts[nameParts.length - 1];
@@ -23351,8 +27449,8 @@ Ember.Handlebars.registerHelper('partial', function(name, options) {
template = template || deprecatedTemplate;
- template(this, { data: options.data });
-});
+ template(context, { data: options.data });
+}
})();
@@ -23367,6 +27465,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 +27511,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:
+ {{/labeled-textfield}}
+ ```
+
+ ```handlebars
+
+
+ ```
+
+ Result:
+
+ ```html
+