diff --git a/Gemfile b/Gemfile index e11cac00..69bb11d3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ ruby '1.9.3' rescue nil -source :rubygems +source 'https://rubygems.org' gem 'puma' gem 'rack-ssl', '~> 1.3' diff --git a/Gemfile.lock b/Gemfile.lock index fcdcc460..91af9a57 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,7 +16,7 @@ GIT rake-pipeline (~> 0.6) GEM - remote: http://rubygems.org/ + remote: https://rubygems.org/ specs: POpen4 (0.1.4) Platform (>= 0.4.0) diff --git a/assets/scripts/app/controllers/flash.coffee b/assets/scripts/app/controllers/flash.coffee index 006af592..67ad6ae5 100644 --- a/assets/scripts/app/controllers/flash.coffee +++ b/assets/scripts/app/controllers/flash.coffee @@ -5,8 +5,8 @@ Travis.FlashController = Ember.ArrayController.extend broadcastBinding: 'currentUser.broadcasts' init: -> - @set('flashes', Ember.A()) @_super.apply this, arguments + @set('flashes', Ember.A()) content: (-> @get('unseenBroadcasts').concat(@get('flashes')) diff --git a/assets/scripts/app/controllers/job.coffee b/assets/scripts/app/controllers/job.coffee index 274a7555..9d5734fe 100644 --- a/assets/scripts/app/controllers/job.coffee +++ b/assets/scripts/app/controllers/job.coffee @@ -4,3 +4,15 @@ Travis.JobController = Em.Controller.extend jobBinding: 'controllers.repo.job' repoBinding: 'controllers.repo.repo' commitBinding: 'job.commit' + + 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/controllers/profile.coffee b/assets/scripts/app/controllers/profile.coffee index ae0dc24f..84b7fd4f 100644 --- a/assets/scripts/app/controllers/profile.coffee +++ b/assets/scripts/app/controllers/profile.coffee @@ -6,6 +6,8 @@ Travis.ProfileController = Travis.Controller.extend accountsBinding: 'controllers.accounts' init: -> + @_super.apply this, arguments + self = this Travis.on("user:synced", (-> self.reloadHooks() diff --git a/assets/scripts/app/controllers/repo.coffee b/assets/scripts/app/controllers/repo.coffee index 556dd5dc..256ba110 100644 --- a/assets/scripts/app/controllers/repo.coffee +++ b/assets/scripts/app/controllers/repo.coffee @@ -6,17 +6,6 @@ Travis.RepoController = Travis.Controller.extend init: -> @_super.apply this, arguments Ember.run.later(@updateTimes.bind(this), Travis.INTERVALS.updateTimes) - @set 'builds', Em.ArrayProxy.create(Em.SortableMixin, - isLoadedBinding: 'content.isLoaded' - sortProperties: ['number'] - sortAscending: false - content: [] - isLoadingBinding: 'content.isLoading' - load: (records) -> - content = @get('content') - if content && content.load - content.load(records) - ) updateTimes: -> if builds = @get('builds') @@ -45,15 +34,15 @@ Travis.RepoController = Travis.Controller.extend viewBuilds: -> @connectTab('builds') - @_bind('builds.content', 'repo.builds') + @_bind('builds', 'repo.builds') viewPullRequests: -> @connectTab('pull_requests') - @_bind('builds.content', 'repo.pullRequests') + @_bind('builds', 'repo.pullRequests') viewBranches: -> @connectTab('branches') - @_bind('builds.content', 'repo.branches') + @_bind('builds', 'repo.branches') viewEvents: -> @connectTab('events') diff --git a/assets/scripts/app/controllers/repos.coffee b/assets/scripts/app/controllers/repos.coffee index d3c64ab8..c26ee34e 100644 --- a/assets/scripts/app/controllers/repos.coffee +++ b/assets/scripts/app/controllers/repos.coffee @@ -13,8 +13,8 @@ Travis.ReposController = Ember.ArrayController.extend ).property('controllers.repo.repo', 'controllers.repo.repo.content') init: -> - Ember.run.later(@updateTimes.bind(this), Travis.INTERVALS.updateTimes) @_super.apply this, arguments + Ember.run.later(@updateTimes.bind(this), Travis.INTERVALS.updateTimes) recentRepos: (-> Travis.LimitedArray.create diff --git a/assets/scripts/app/controllers/running_jobs.coffee b/assets/scripts/app/controllers/running_jobs.coffee index 71778532..08eab756 100644 --- a/assets/scripts/app/controllers/running_jobs.coffee +++ b/assets/scripts/app/controllers/running_jobs.coffee @@ -3,6 +3,7 @@ Travis.RunningJobsController = Em.ArrayProxy.extend repo: (-> @get('jobs.firstObject.repo') ).property('jobs.firstObject.repo') init: -> + @_super.apply this, arguments @set 'jobs', [] @set 'sortedJobs', Em.ArrayProxy.extend(Em.SortableMixin, diff --git a/assets/scripts/app/controllers/sidebar.coffee b/assets/scripts/app/controllers/sidebar.coffee index 390ffdad..42bfd638 100644 --- a/assets/scripts/app/controllers/sidebar.coffee +++ b/assets/scripts/app/controllers/sidebar.coffee @@ -1,6 +1,7 @@ Travis.reopen SidebarController: Em.ArrayController.extend init: -> + @_super.apply this, arguments @tickables = [] Travis.Ticker.create(target: this, interval: Travis.INTERVALS.sponsors) diff --git a/assets/scripts/app/controllers/stats.coffee b/assets/scripts/app/controllers/stats.coffee index b0b31704..3d6da596 100644 --- a/assets/scripts/app/controllers/stats.coffee +++ b/assets/scripts/app/controllers/stats.coffee @@ -2,7 +2,7 @@ Travis.StatsController = Travis.Controller.extend name: 'stats' init: -> - @_super('top') + @_super.apply this, arguments #@connectOutlet(outletName: 'main', controller: this, viewClass: Travis.StatsView) activate: (action, params) -> diff --git a/assets/scripts/app/helpers/helpers.coffee b/assets/scripts/app/helpers/helpers.coffee index 2c4ed12e..f3decb63 100644 --- a/assets/scripts/app/helpers/helpers.coffee +++ b/assets/scripts/app/helpers/helpers.coffee @@ -39,16 +39,6 @@ require 'config/emoij' message = message.split(/\n/)[0] if options.short @_emojize(@_escape(message)).replace /\n/g, '
' - formatLog: (log, repo, item) -> - event = if item.constructor == Travis.Build - 'showBuild' - else - 'showJob' - - url = Travis.app.get('router').urlForEvent(event, repo, item) - - Travis.Log.filter(log, url) - pathFrom: (url) -> (url || '').split('/').pop() diff --git a/assets/scripts/app/models/repo.coffee b/assets/scripts/app/models/repo.coffee index 332063b1..a4ee3cbf 100644 --- a/assets/scripts/app/models/repo.coffee +++ b/assets/scripts/app/models/repo.coffee @@ -28,6 +28,7 @@ require 'travis/model' builds: (-> id = @get('id') builds = Travis.Build.byRepoId id, event_type: 'push' + # TODO: move to controller array = Travis.ExpandableRecordArray.create type: Travis.Build diff --git a/assets/scripts/app/store.coffee b/assets/scripts/app/store.coffee index cf8f8d5c..b0a1717d 100644 --- a/assets/scripts/app/store.coffee +++ b/assets/scripts/app/store.coffee @@ -141,6 +141,7 @@ Travis.Store = DS.Store.extend result = @merge(type, hash, true) if result && result.clientId @addLoadedData(type, result.clientId, hash) + #@_updateRelationships(type, hash) result @@ -158,3 +159,18 @@ Travis.Store = DS.Store.extend root = type.pluralName() @adapter.sideload(store, type, json, root) @loadMany(type, json[root]) + + _updateRelationships: (type, data) -> + Em.get(type, 'relationshipsByName').forEach (key, meta) => + if meta.kind == 'belongsTo' + id = data["#{key}_id"] + if clientId = @typeMapFor(meta.type).idToCid[id] + if parent = this.findByClientId(meta.type, clientId, id) + dataProxy = parent.get('data') + if ids = dataProxy['hasMany'][type.pluralName()] + unless data.id in ids + state = parent.get('stateManager.currentState.path') + unless state == "rootState.loaded.materializing" + parent.send('materializingData') + ids.pushObject(data.id) + parent.notifyPropertyChange('data') diff --git a/assets/scripts/app/templates/jobs/show.hbs b/assets/scripts/app/templates/jobs/show.hbs index f24454ba..1d7a9989 100644 --- a/assets/scripts/app/templates/jobs/show.hbs +++ b/assets/scripts/app/templates/jobs/show.hbs @@ -19,22 +19,24 @@
{{formatDuration job.duration}}
-
-
{{t jobs.commit}}
-
{{formatCommit commit}}
- {{#if commit.compareUrl}} -
{{t jobs.compare}}
-
{{pathFrom commit.compareUrl}}
- {{/if}} - {{#if commit.authorName}} -
{{t jobs.author}}
-
{{commit.authorName}}
- {{/if}} - {{#if commit.committerName}} -
{{t jobs.committer}}
-
{{commit.committerName}}
- {{/if}} -
+ {{#if commit}} +
+
{{t jobs.commit}}
+
{{formatCommit commit}}
+ {{#if commit.compareUrl}} +
{{t jobs.compare}}
+
{{pathFrom commit.compareUrl}}
+ {{/if}} + {{#if commit.authorName}} +
{{t jobs.author}}
+
{{commit.authorName}}
+ {{/if}} + {{#if commit.committerName}} +
{{t jobs.committer}}
+
{{commit.committerName}}
+ {{/if}} +
+ {{/if}}
{{t jobs.message}}
{{formatMessage commit.message}}
diff --git a/assets/scripts/app/views/repo/show.coffee b/assets/scripts/app/views/repo/show.coffee index b44d91cb..0664abd8 100644 --- a/assets/scripts/app/views/repo/show.coffee +++ b/assets/scripts/app/views/repo/show.coffee @@ -123,7 +123,7 @@ Travis.reopen success: => @popup('regeneration-success') error: -> - Travis.app.router.flashController.loadFlashes([{ error: 'Travis encountered an error while trying to regenerate the key, please try again.'}]) + Travis.lookup('controller:flash').loadFlashes([{ error: 'Travis encountered an error while trying to regenerate the key, please try again.'}]) displayRequeueBuild: (-> @get('isBuildTab') && @get('build.isFinished') diff --git a/assets/scripts/lib/travis/ajax.coffee b/assets/scripts/lib/travis/ajax.coffee index a9e4175b..40939951 100644 --- a/assets/scripts/lib/travis/ajax.coffee +++ b/assets/scripts/lib/travis/ajax.coffee @@ -33,13 +33,13 @@ Travis.ajax = Em.Object.create success = options.success || (->) options.success = (data) => - Travis.app.router.flashController.loadFlashes(data.flash) if Travis.app?.router && data.flash + Travis.lookup('controller:flash').loadFlashes(data.flash) if data.flash delete data.flash if data? success.apply(this, arguments) error = options.error || (->) options.error = (data) => - Travis.app.router.flashController.pushObject(data.flash) if data.flash + Travis.lookup('controller:flash').pushObject(data.flash) if data.flash delete data.flash if data? error.apply(this, arguments) diff --git a/assets/scripts/lib/travis/model.coffee b/assets/scripts/lib/travis/model.coffee index 9084dd02..4587033e 100644 --- a/assets/scripts/lib/travis/model.coffee +++ b/assets/scripts/lib/travis/model.coffee @@ -42,14 +42,14 @@ # undefined key return if !key || key == 'undefined' - message = "Load missing fields for #{@constructor.toString()} because of missing key '#{key}', cid: #{@get('clientId')}" + message = "Load missing fields for #{@constructor.toString()} because of missing key '#{key}', cid: #{@get('clientId')}, id: #{@get('id')}" if @constructor.isAttribute('state') && key != 'state' message += ", in state: #{@get('state')}" console.log message return if @get('isCompleting') @set 'isCompleting', true - if @get('stateManager.currentState.path') != 'rootState.loaded.materializing' + unless @get('stateManager.currentState.path').match /^rootState.loaded.materializing/ @reload() @set 'incomplete', false diff --git a/assets/scripts/lib/travis/ticker.coffee b/assets/scripts/lib/travis/ticker.coffee index b85506d0..81f14517 100644 --- a/assets/scripts/lib/travis/ticker.coffee +++ b/assets/scripts/lib/travis/ticker.coffee @@ -11,6 +11,4 @@ @schedule() schedule: -> - Ember.run.later((=> @tick()), @get('interval') || Travis.app.TICK_INTERVAL) - - + Ember.run.later((=> @tick()), @get('interval') || Travis.TICK_INTERVAL) diff --git a/assets/scripts/spec/event_spec.coffee b/assets/scripts/spec/event_spec.coffee index 1f2f6f48..8ea55595 100644 --- a/assets/scripts/spec/event_spec.coffee +++ b/assets/scripts/spec/event_spec.coffee @@ -21,7 +21,7 @@ describe 'events', -> responseText: payload Em.run -> - Travis.app.receive 'build:started', + Travis.receive 'build:started', build: id: 10 repository: @@ -54,13 +54,12 @@ describe 'events', -> started_at: '2012-07-02T00:02:00Z' finished_at: '2012-07-02T00:02:55Z' event_type: 'push' - result: 1 message: 'commit message 3' commit: '1234567' - state: 'started' + state: 'failed' Em.run -> - Travis.app.receive 'build:started', payload + Travis.receive 'build:started', payload waits(100) runs -> @@ -75,40 +74,6 @@ describe 'events', -> runs -> waitFor queuesRendered - it 'adds a job to the jobs matrix', -> - payload = - job: - id: 15 - repository_id: 1 - build_id: 1 - commit_id: 1 - log_id: 1 - number: '1.4' - duration: 55 - started_at: '2012-07-02T00:02:00Z' - finished_at: '2012-07-02T00:02:55Z' - config: { rvm: 'jruby' } - - $.mockjax - url: '/jobs/15' - responseTime: 0 - responseText: payload - - Em.run -> - Travis.app.receive 'job:started', - job: - id: 15 - repository_id: 1 - build_id: 1 - commit_id: 1 - - waits(100) - runs -> - listsJob - table: $('#jobs') - row: 3 - item: { id: 15, number: '1.4', repo: 'travis-ci/travis-core', finishedAt: 'less than a minute ago', duration: '55 sec', rvm: 'jruby' } - it 'adds a job to the jobs queue', -> payload = job: @@ -123,7 +88,7 @@ describe 'events', -> responseText: payload Em.run -> - Travis.app.receive 'job:started', + Travis.receive 'job:started', job: id: 12 repository_id: 1 @@ -140,7 +105,7 @@ describe 'events', -> it 'updates only keys that are available', -> Em.run -> - Travis.app.receive 'job:started', + Travis.receive 'job:started', job: id: 1 build_id: 1 @@ -171,7 +136,7 @@ describe 'events', -> responseText: payload Em.run -> - Travis.app.receive 'worker:created', + Travis.receive 'worker:created', worker: id: 10 name: 'ruby-3' @@ -191,7 +156,7 @@ describe 'events', -> app '/travis-ci/travis-core' waitFor workersRendered - it 'does not update repository if it\'s already in store', -> + it 'does not update repository if it\'s already in the store', -> payload = worker: id: 1 @@ -205,7 +170,7 @@ describe 'events', -> last_build_number: '999' Em.run -> - Travis.app.receive 'worker:updated', payload + Travis.receive 'worker:updated', payload waits(100) runs -> diff --git a/assets/scripts/spec/job_spec.coffee b/assets/scripts/spec/job_spec.coffee index 73e63337..4b4c5f88 100644 --- a/assets/scripts/spec/job_spec.coffee +++ b/assets/scripts/spec/job_spec.coffee @@ -1,5 +1,11 @@ describe 'on the "job" state', -> beforeEach -> + $.mockjax + url: '/jobs/1/log?cors_hax=true' + responseTime: 0 + responseText: 'log 1' + + app 'travis-ci/travis-core/jobs/1' waitFor jobRendered runs -> @@ -32,6 +38,8 @@ describe 'on the "job" state', -> build: { href: '/travis-ci/travis-core/builds/1' } job: { href: '/travis-ci/travis-core/jobs/1', active: true } - displaysLog [ - 'log 1' - ] + waits 10 + runs -> + displaysLog [ + 'log 1' + ] diff --git a/assets/scripts/spec/support/expectations.coffee b/assets/scripts/spec/support/expectations.coffee index 6365fdc7..edf80ddc 100644 --- a/assets/scripts/spec/support/expectations.coffee +++ b/assets/scripts/spec/support/expectations.coffee @@ -41,9 +41,8 @@ expect(element.text()).toEqual data.message @displaysLog = (lines) -> - ix = 0 - log = $.map(lines, (line) -> ix += 1; "#{ix}#{line}").join("\n") - expect($('#log p').text().trim()).toEqual log + log = lines.join() + expect($('#log').text().trim()).toEqual log @listsRepos = (items) -> listsItems('repo', items) diff --git a/assets/scripts/spec/support/mocks.coffee b/assets/scripts/spec/support/mocks.coffee index e7b153ab..6d31b625 100644 --- a/assets/scripts/spec/support/mocks.coffee +++ b/assets/scripts/spec/support/mocks.coffee @@ -1,45 +1,45 @@ responseTime = 0 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_result: 0, 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_result: 1, 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_result: undefined, last_build_duration: undefined, last_build_started_at: '2012-07-02T00:02:00Z', last_build_finished_at: undefined, 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_result: 0, 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_result: 1, 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_result: undefined, last_build_duration: undefined, last_build_started_at: '2012-07-02T00:02:00Z', last_build_finished_at: undefined, description: 'Description of travis-hub'}, ] 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'] } }, - { 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'] }, started_at: '2012-07-02T00:02:00Z' }, + { 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'] } }, + { 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'] }, started_at: '2012-07-02T00:02:00Z' }, ] commits = [ - { id: 1, sha: '1234567', branch: 'master', message: 'commit message 1', author_name: 'author name', author_email: 'author@email.com', committer_name: 'committer name', committer_email: 'committer@email.com', compare_url: 'http://github.com/compare/0123456..1234567' }, - { id: 2, sha: '2345678', branch: 'feature', message: 'commit message 2', author_name: 'author name', author_email: 'author@email.com', committer_name: 'committer name', committer_email: 'committer@email.com', compare_url: 'http://github.com/compare/0123456..2345678' }, - { id: 3, sha: '3456789', branch: 'master', message: 'commit message 3', author_name: 'author name', author_email: 'author@email.com', committer_name: 'committer name', committer_email: 'committer@email.com', compare_url: 'http://github.com/compare/0123456..3456789' }, - { id: 4, sha: '4567890', branch: 'master', message: 'commit message 4', author_name: 'author name', author_email: 'author@email.com', committer_name: 'committer name', committer_email: 'committer@email.com', compare_url: 'http://github.com/compare/0123456..4567890' }, + { id: '1', sha: '1234567', branch: 'master', message: 'commit message 1', author_name: 'author name', author_email: 'author@email.com', committer_name: 'committer name', committer_email: 'committer@email.com', compare_url: 'http://github.com/compare/0123456..1234567' }, + { id: '2', sha: '2345678', branch: 'feature', message: 'commit message 2', author_name: 'author name', author_email: 'author@email.com', committer_name: 'committer name', committer_email: 'committer@email.com', compare_url: 'http://github.com/compare/0123456..2345678' }, + { id: '3', sha: '3456789', branch: 'master', message: 'commit message 3', author_name: 'author name', author_email: 'author@email.com', committer_name: 'committer name', committer_email: 'committer@email.com', compare_url: 'http://github.com/compare/0123456..3456789' }, + { id: '4', sha: '4567890', branch: 'master', message: 'commit message 4', author_name: 'author name', author_email: 'author@email.com', committer_name: 'committer name', committer_email: 'committer@email.com', compare_url: 'http://github.com/compare/0123456..4567890' }, ] jobs = [ - { id: 1, repository_id: 1, build_id: 1, commit_id: 1, log_id: 1, number: '1.1', config: { rvm: 'rbx' }, duration: 30, started_at: '2012-07-02T00:00:00Z', finished_at: '2012-07-02T00:00:30Z', state: 'passed' } - { id: 2, repository_id: 1, build_id: 1, commit_id: 1, log_id: 2, number: '1.2', config: { rvm: '1.9.3' }, duration: 40, started_at: '2012-07-02T00:00:00Z', finished_at: '2012-07-02T00:00:40Z', state: 'failed' } - { id: 3, repository_id: 1, build_id: 1, commit_id: 1, log_id: 3, number: '1.3', config: { rvm: 'jruby' }, allow_failure: true } - { id: 4, repository_id: 1, build_id: 2, commit_id: 2, log_id: 4, number: '2.1', config: { rvm: 'rbx' } } - { id: 5, repository_id: 2, build_id: 3, commit_id: 3, log_id: 5, number: '3.1', config: { rvm: 'rbx' }, duration: 30, started_at: '2012-07-02T00:01:00Z', finished_at: '2012-07-02T00:01:30Z', state: 'failed' } - { id: 6, repository_id: 3, build_id: 4, commit_id: 4, log_id: 6, number: '4.1', config: { rvm: 'rbx' }, started_at: '2012-07-02T00:02:00Z' } - { id: 7, repository_id: 1, build_id: 5, commit_id: 5, log_id: 7, number: '5.1', config: { rvm: 'rbx' }, state: 'created', queue: 'builds.common' } - { id: 8, repository_id: 1, build_id: 5, commit_id: 5, log_id: 8, number: '5.2', config: { rvm: 'rbx' }, state: 'created', queue: 'builds.common' } + { id: '1', repository_id: 1, build_id: 1, commit_id: 1, log_id: 1, number: '1.1', config: { rvm: 'rbx' }, duration: 30, started_at: '2012-07-02T00:00:00Z', finished_at: '2012-07-02T00:00:30Z', state: 'passed' } + { id: '2', repository_id: 1, build_id: 1, commit_id: 1, log_id: 2, number: '1.2', config: { rvm: '1.9.3' }, duration: 40, started_at: '2012-07-02T00:00:00Z', finished_at: '2012-07-02T00:00:40Z', state: 'failed' } + { id: '3', repository_id: 1, build_id: 1, commit_id: 1, log_id: 3, number: '1.3', config: { rvm: 'jruby' }, allow_failure: true } + { id: '4', repository_id: 1, build_id: 2, commit_id: 2, log_id: 4, number: '2.1', config: { rvm: 'rbx' } } + { id: '5', repository_id: 2, build_id: 3, commit_id: 3, log_id: 5, number: '3.1', config: { rvm: 'rbx' }, duration: 30, started_at: '2012-07-02T00:01:00Z', finished_at: '2012-07-02T00:01:30Z', state: 'failed' } + { id: '6', repository_id: 3, build_id: 4, commit_id: 4, log_id: 6, number: '4.1', config: { rvm: 'rbx' }, started_at: '2012-07-02T00:02:00Z' } + { id: '7', repository_id: 1, build_id: 5, commit_id: 5, log_id: 7, number: '5.1', config: { rvm: 'rbx' }, state: 'created', queue: 'builds.common' } + { id: '8', repository_id: 1, build_id: 5, commit_id: 5, log_id: 8, number: '5.2', config: { rvm: 'rbx' }, state: 'created', queue: 'builds.common' } ] artifacts = [ - { id: 1, body: 'log 1' } - { id: 2, body: 'log 2' } - { id: 3, body: 'log 3' } - { id: 4, body: 'log 4' } - { id: 5, body: 'log 5' } - { id: 6, body: 'log 6' } - { id: 7, body: 'log 7' } - { id: 8, body: 'log 8' } + { id: '1', body: 'log 1' } + { id: '2', body: 'log 2' } + { id: '3', body: 'log 3' } + { id: '4', body: 'log 4' } + { id: '5', body: 'log 5' } + { id: '6', body: 'log 6' } + { id: '7', body: 'log 7' } + { id: '8', body: 'log 8' } ] branches = [ @@ -49,8 +49,8 @@ branches = [ ] workers = [ - { id: 1, name: 'ruby-1', host: 'worker.travis-ci.org', state: 'ready' } - { id: 2, name: 'ruby-2', host: 'worker.travis-ci.org', state: 'ready' } + { id: '1', name: 'ruby-1', host: 'worker.travis-ci.org', state: 'ready' } + { id: '2', name: 'ruby-2', host: 'worker.travis-ci.org', state: 'ready' } ] hooks = [ diff --git a/assets/scripts/spec/unit/artifact_spec.coffee b/assets/scripts/spec/unit/artifact_spec.coffee deleted file mode 100644 index 4458152e..00000000 --- a/assets/scripts/spec/unit/artifact_spec.coffee +++ /dev/null @@ -1,36 +0,0 @@ -store = null -record = null - -describe 'Travis.Artifact', -> - beforeEach -> - store = Travis.Store.create() - - afterEach -> - store.destroy() - - describe 'with part of the body loaded', -> - beforeEach => - store.load Travis.Artifact, 1, { id: 1, body: 'first\nsecond\n' } - record = store.find(Travis.Artifact, 1) - - it 'packs the existing part of the body to parts', -> - expect( record.get('parts').toArray() ).toEqual( ['first\nsecond\n'] ) - - it 'adds new chunks of log to parts', -> - record.append('third\n') - expect( record.get('parts').toArray() ).toEqual( ['first\nsecond\n', 'third\n'] ) - - it 'properly handles array observers', -> - called = 0 - observer = { - arrayDidChange: -> called += 1 - arrayWillChange: -> called += 1 - } - - record.get('parts').addArrayObserver observer, - willChange: 'arrayWillChange' - didChange: 'arrayDidChange' - - record.append('something') - - expect(called).toEqual 2 diff --git a/assets/scripts/spec/unit/build_spec.coffee b/assets/scripts/spec/unit/build_spec.coffee index 2a81e1d1..f7374224 100644 --- a/assets/scripts/spec/unit/build_spec.coffee +++ b/assets/scripts/spec/unit/build_spec.coffee @@ -10,7 +10,8 @@ describe 'Travis.Build', -> describe 'incomplete attributes', -> beforeEach -> - record = store.loadIncomplete Travis.Build, { id: 1, state: 'started' } + store.loadIncomplete Travis.Build, { id: 1, state: 'started' } + record = store.find Travis.Build, 1 it 'does not load record on duration, finishedAt and result if job is not in finished state', -> record.get('_duration') @@ -19,12 +20,13 @@ describe 'Travis.Build', -> waits 50 runs -> - expect( record.get('complete') ).toBeFalsy() + expect( record.get('incomplete') ).toBeTruthy() it 'loads the rest of the record if it\'s in finished state', -> - record = store.loadIncomplete Travis.Build, { id: 1, state: 'finished' } + store.loadIncomplete Travis.Build, { id: 1, state: 'passed' } + record = store.find Travis.Build, 1 record.get('finishedAt') waits 50 runs -> - expect( record.get('complete') ).toBeTruthy() + expect( record.get('incomplete') ).toBeFalsy() diff --git a/assets/scripts/spec/unit/incomplete_spec.coffee b/assets/scripts/spec/unit/incomplete_spec.coffee index fdbcdfce..adf501c3 100644 --- a/assets/scripts/spec/unit/incomplete_spec.coffee +++ b/assets/scripts/spec/unit/incomplete_spec.coffee @@ -2,13 +2,13 @@ record = null store = null adapterClass = null -$.mockjax - url: '/foos/1' - responseTime: 10 - responseText: { foo: { id: 1, name: 'foo', description: 'bar' } } - describe 'Travis.Model - incomplete', -> beforeEach -> + $.mockjax + url: '/foos/1' + responseTime: 1 + responseText: { foo: { id: 1, name: 'foo', description: 'bar' } } + Travis.Foo = Travis.Model.extend name: DS.attr('string') description: DS.attr('string') @@ -18,10 +18,14 @@ describe 'Travis.Model - incomplete', -> niceBar: DS.belongsTo('Travis.Bar') veryNiceBar: DS.belongsTo('Travis.Bar') + Travis.Foo.toString = -> 'Travis.Foo' + Travis.Bar = Travis.Model.extend name: DS.attr('string') foos: DS.hasMany('Travis.Foo') + Travis.Bar.toString = -> 'Travis.Bar' + adapterClass = Travis.RestAdapter.extend() adapterClass.map 'Travis.Foo', veryNiceBar: { key: 'very_nice_bar_indeed_id' } diff --git a/assets/scripts/spec/unit/job_spec.coffee b/assets/scripts/spec/unit/job_spec.coffee index 2550058b..42efff80 100644 --- a/assets/scripts/spec/unit/job_spec.coffee +++ b/assets/scripts/spec/unit/job_spec.coffee @@ -10,7 +10,8 @@ describe 'Travis.Job', -> describe 'incomplete attributes', -> beforeEach -> - record = store.loadIncomplete Travis.Job, { id: 1, state: 'started' } + store.loadIncomplete Travis.Job, { id: 1, state: 'started' } + record = store.find Travis.Job, 1 it 'does not load record on duration, finishedAt and result if job is not in finished state', -> record.get('_duration') @@ -19,15 +20,16 @@ describe 'Travis.Job', -> waits 50 runs -> - expect( record.get('complete') ).toBeFalsy() + expect( record.get('incomplete') ).toBeTruthy() it 'loads the rest of the record if it\'s in finished state', -> - record = store.loadIncomplete Travis.Job, { id: 1, state: 'finished' } + store.loadIncomplete Travis.Job, { id: 1, state: 'passed' } + record = store.find Travis.Job, 1 record.get('finishedAt') waits 50 runs -> - expect( record.get('complete') ).toBeTruthy() + expect( record.get('incomplete') ).toBeFalsy() describe 'with different number of config keys in sibling jobs', -> diff --git a/assets/scripts/spec/unit/log_spec.coffee b/assets/scripts/spec/unit/log_spec.coffee deleted file mode 100644 index a8ff7d1d..00000000 --- a/assets/scripts/spec/unit/log_spec.coffee +++ /dev/null @@ -1,229 +0,0 @@ -log = null -target = null - -describe 'Travis.Log', -> - beforeEach -> - target = Em.Object.create - calls: [] - appendLog: (payloads) -> - lines = payloads.map (p) -> - line = p.content - delete p.content - line - - @get('calls').pushObject - options: payloads - lines: lines - - log = Travis.Log.create(target: target) - - it 'works with log passed as a string', -> - log.append '1\n2' - - expect( target.get('calls.firstObject.lines') ).toEqual ['1', '2'] - - - it 'splits lines', -> - log.append ['1\r\n2\n\n', '3'] - - expect( target.get('calls.length') ).toEqual 1 - expect( target.get('calls.firstObject.lines') ).toEqual ['1', '2', '', '3'] - - it 'escapes html characters', -> - log.append '<>' - - expect( target.get('calls.firstObject.lines') ).toEqual ['<>'] - - it 'normalizes ansi mess', -> - log.append ['foo\r\r', 'bar'] - - expect( target.get('calls.firstObject.lines') ).toEqual [ 'foo', 'bar' ] - - it 'calls target with folds separation', -> - fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/ - log.addFold fold - - fold = Em.Object.create name: 'qux', startPattern: /^\$ qux/, endPattern: /^\$/ - log.addFold fold - - log.append [ - '1\n', '2\n' - '$ foo --foo\n', '1\n' - '$ bar\n' - '$ baz\n' - '$ qux\n', '1\n', '2\n' - '$ end\n' - ] - - # expect( target.get('calls.length') ).toEqual 5 - lines = target.get('calls').map (call) -> call.lines - options = target.get('calls').map (call) -> call.options - - expect( lines[0] ).toEqual ['1', '2'] - expect( options[0]).toEqual [ { number: 1 }, { number: 2 } ] - - expect( lines[1] ).toEqual ['$ foo --foo', '1'] - expect( options[1]).toEqual [ - { number: 3, fold: 'foo' }, - { number: 4, fold: 'foo', foldContinuation: true, foldEnd: true }] - - expect( lines[2] ).toEqual ['$ bar', '$ baz'] - expect( options[2]).toEqual [{ number: 5 }, { number: 6 }] - - expect( lines[3] ).toEqual ['$ qux', '1', '2'] - expect( options[3]).toEqual [ - { number: 7, fold: 'qux' }, - { number: 8, fold: 'qux', foldContinuation: true }, - { number: 9, fold: 'qux', foldContinuation: true, foldEnd: true }] - - expect( lines[4] ).toEqual ['$ end'] - expect( options[4]).toEqual [{ number: 10 }] - - it 'works properly when log is started with fold', -> - fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/ - log.addFold fold - - log.append [ - '$ foo --foo\n', '1\n' - '$ bar\n' - ] - - expect( target.get('calls.length') ).toEqual 2 - lines = target.get('calls').map (call) -> call.lines - options = target.get('calls').map (call) -> call.options - - expect( lines[0] ).toEqual ['$ foo --foo', '1'] - expect( options[0]).toEqual [ - { number: 1, fold: 'foo' }, - { number: 2, fold: 'foo', foldContinuation: true, foldEnd: true }] - - expect( lines[1] ).toEqual ['$ bar'] - expect( options[1]).toEqual [{ number: 3 }] - - it 'works properly for 2 consecutive folds', -> - fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/ - log.addFold fold - - log.append [ - '$ foo --foo\n', '1\n' - '$ foo --bar\n', '2\n' - '$ bar\n' - ] - - expect( target.get('calls.length') ).toEqual 3 - lines = target.get('calls').map (call) -> call.lines - options = target.get('calls').map (call) -> call.options - - expect( lines[0] ).toEqual ['$ foo --foo', '1'] - expect( options[0]).toEqual [ - { number: 1, fold: 'foo' }, - { number: 2, fold: 'foo', foldContinuation: true, foldEnd: true }] - - expect( lines[1] ).toEqual ['$ foo --bar', '2'] - expect( options[1]).toEqual [ - { number: 3, fold: 'foo' }, - { number: 4, fold: 'foo', foldContinuation: true, foldEnd: true }] - - expect( lines[2] ).toEqual ['$ bar'] - expect( options[2]).toEqual [{ number: 5 }] - - it 'works fine with not finalized fold', -> - fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/ - log.addFold fold - - log.append [ - '$ foo --foo\n', '1\n' - ] - - expect( target.get('calls.length') ).toEqual 1 - lines = target.get('calls').map (call) -> call.lines - options = target.get('calls').map (call) -> call.options - - expect( lines[0] ).toEqual ['$ foo --foo', '1'] - expect( options[0]).toEqual [ - { fold: 'foo', number: 1 }, - { fold: 'foo', number: 2, foldContinuation: true }] - - it 'allows to continue fold', -> - fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/ - log.addFold fold - - log.append [ - '$ foo --foo\n', '1\n' - ] - - log.append '2\n' - - log.append [ - '3\n' - '$ bar\n' - ] - - expect( target.get('calls.length') ).toEqual 4 - lines = target.get('calls').map (call) -> call.lines - options = target.get('calls').map (call) -> call.options - - expect( lines[0] ).toEqual ['$ foo --foo', '1'] - expect( options[0]).toEqual [ - { fold: 'foo', number: 1 }, - { fold: 'foo', number: 2, foldContinuation: true }] - - expect( lines[1] ).toEqual ['2'] - expect( options[1]).toEqual [ - { fold: 'foo', number: 3, foldContinuation: true } - ] - - expect( lines[2] ).toEqual ['3'] - expect( options[2]).toEqual [ - { fold: 'foo', number: 4, foldContinuation: true, foldEnd: true } - ] - - expect( lines[3] ).toEqual ['$ bar'] - expect( options[3]).toEqual [{ number: 5 }] - - it 'notifies that the line should be appended', -> - log.append '$ foo\n.' - - log.append '...' - - log.append '..\n$ bar\n' - - expect( target.get('calls.length') ).toEqual 3 - lines = target.get('calls').map (call) -> call.lines - options = target.get('calls').map (call) -> call.options - - expect( lines[0] ).toEqual ['$ foo', '.'] - expect( options[0]).toEqual [{ number: 1 }, { number: 2 }] - - expect( lines[1] ).toEqual ['...'] - expect( options[1]).toEqual [{ append: true, number: 2 }] - - expect( lines[2] ).toEqual ['..', '$ bar'] - expect( options[2]).toEqual [{ append: true, number: 2 }, { number: 3 }] - - it 'notifies that the line should be replaced', -> - log.append '$ foo\n' - - log.append '\rDownloading 50%' - log.append '\rDownloading 100%\r\n' - - log.append '$ bar\n' - - expect( target.get('calls.length') ).toEqual 4 - lines = target.get('calls').map (call) -> call.lines - options = target.get('calls').map (call) -> call.options - - expect( lines[0] ).toEqual ['$ foo'] - expect( options[0]).toEqual [{ number: 1 }] - - expect( lines[1] ).toEqual ['', 'Downloading 50%'] - expect( options[1]).toEqual [{ number: 2 }, { number: 2, replace: true }] - - expect( lines[2] ).toEqual ['', 'Downloading 100%'] - expect( options[2]).toEqual [{ number: 2, append: true }, { number: 2, replace: true }] - - expect( lines[3] ).toEqual ['$ bar'] - expect( options[3]).toEqual [{ number: 3 }] - - it 'notifies that the line should be replaced even if carriage return is in the middle', -> - diff --git a/assets/scripts/spec/unit/merge_spec.coffee b/assets/scripts/spec/unit/merge_spec.coffee index 7feda345..75007d1e 100644 --- a/assets/scripts/spec/unit/merge_spec.coffee +++ b/assets/scripts/spec/unit/merge_spec.coffee @@ -36,7 +36,7 @@ describe 'Travis.Model - merge', -> record.removeObserver 'firstName', observer - expect(changes).toEqual(1) + expect(changes > 0).toBeTruthy() expect(record.get('firstName')).toEqual('Peter') expect(record.get('login')).toEqual('drogus') expect(record.get('email')).toEqual('drogus@example.org') diff --git a/assets/scripts/spec/unit/pre_view_spec.coffee b/assets/scripts/spec/unit/pre_view_spec.coffee deleted file mode 100644 index c1a95ce5..00000000 --- a/assets/scripts/spec/unit/pre_view_spec.coffee +++ /dev/null @@ -1,161 +0,0 @@ -view = null -store = null -record = null - -describe 'Travis.PreView', -> - beforeEach -> - store = Travis.Store.create() - - afterEach -> - store.destroy() - view.remove() - view.destroy() - - it 'works fine with existing log, which is appended', -> - store.load Travis.Artifact, 1, { id: 1, body: '$ start\n' } - log = Travis.Artifact.find(1) - log.set('version', 1) - - Ember.run -> - view = Travis.PreView.create(log: null) - view.append() - - expect( view.$('#log').length ).toEqual 1 - - Ember.run -> - view.set 'log', log - log.set 'isLoaded', true - - waits 50 - runs -> - expect( view.$('#log p').length ).toEqual 1 - expect( view.$('#log p').text().trim() ).toEqual '1$ start' - - Ember.run -> - log.append('$ end') - - waits 50 - runs -> - expect( view.$('#log p').length ).toEqual 2 - expect( view.$('#log p').text().trim() ).toEqual '1$ start2$ end' - - it 'works fine with log already attahed to view', -> - store.load Travis.Artifact, 1, { id: 1, body: '$ start\n' } - log = Travis.Artifact.find(1) - - Ember.run -> - view = Travis.PreView.create() - view.set('log', log) - view.append() - - Ember.run -> - log.append('end') - - waits 50 - runs -> - expect( view.$('#log p').length ).toEqual 2 - expect( view.$('#log p').text().trim() ).toEqual '1$ start2end' - - it 'folds items', -> - store.load Travis.Artifact, 1, { id: 1, body: '$ start\n' } - log = Travis.Artifact.find(1) - - Ember.run -> - view = Travis.PreView.create() - view.set('log', log) - view.append() - - Ember.run -> - log.append '$ bundle install\n1\n2\n' - - Ember.run -> - log.append '3\n4\n$ something' - - waits 50 - runs -> - expect( view.$('#log > p').length ).toEqual 2 - expect( view.$('#log .fold.bundle').length ).toEqual 1 - expect( view.$('#log .fold.bundle > p').length ).toEqual 5 - - - it 'works properly with fragment document', -> - store.load Travis.Artifact, 1, { id: 1, body: '' } - log = Travis.Artifact.find(1) - - Ember.run -> - view = Travis.PreView.create() - view.set('log', log) - view.append() - - waits 50 - runs -> - payloads = [ - { number: 1, content: 'foo' } - { number: 1, content: 'bar', append: true } - ] - - # it should work even if we need to append to fragment in memory - view.appendLog(payloads) - - expect( view.$('#L1').parent().text().trim() ).toEqual '1foobar' - - # now, let's append more to this line, it's in DOM already - view.appendLog([ { number: 1, content: 'baz', append: true } ]) - - expect( view.$('#L1').parent().text().trim() ).toEqual '1foobarbaz' - - payloads = [ - { number: 1, content: 'foo', replace: true } - ] - # replace should work in DOM - view.appendLog(payloads) - expect( view.$('#L1').parent().text().trim() ).toEqual '1foo' - - payloads = [ - { number: 2, content: 'foo' } - { number: 2, content: 'bar', replace: true } - ] - # replace should work when element is in fragment - view.appendLog(payloads) - expect( view.$('#L2').parent().text().trim() ).toEqual '2bar' - - payloads = [ - { number: 3, content: '$ bundle install', fold: 'bundle' } - { number: 4, content: 'Installing rails', fold: 'bundle', foldContinuation: true } - ] - # folds should work properly with fragment - view.appendLog(payloads) - expect( view.$('.bundle #L3').parent().text().trim() ).toEqual '3$ bundle install' - expect( view.$('.bundle #L4').parent().text().trim() ).toEqual '4Installing rails' - expect( view.$('.bundle > p').length ).toEqual 2 - - payloads = [ - { number: 5, content: 'Installing travis', fold: 'bundle', foldContinuation: true } - ] - # folds should also work when already in DOM - view.appendLog(payloads) - expect( view.$('.bundle #L5').parent().text().trim() ).toEqual '5Installing travis' - expect( view.$('.bundle > p').length ).toEqual 3 - - # regular line append - view.appendLog([ { number: 6, content: 'next'} ]) - expect( view.$('#L6').parent().text().trim() ).toEqual '6next' - - # openFold when in fragment - payloads = [ - { number: 7, content: '$ install', fold: 'install' } - { number: 8, content: 'Installing foo', fold: 'install', foldContinuation: true } - { number: 9, content: 'error', openFold: true, fold: 'install', foldContinuation: true } - ] - # folds should work properly with fragment - view.appendLog(payloads) - expect( view.$('.install').hasClass('show-first-line') ).toEqual false - - # end fold when in fragment - payloads = [ - { number: 10, content: '$ install', fold: 'install2' } - { number: 11, content: 'Installing foo', fold: 'install2', foldEnd: true, foldContinuation: true } - ] - # folds should work properly with fragment - view.appendLog(payloads) - expect( view.$('.install2').hasClass('show-first-line') ).toEqual false diff --git a/assets/scripts/travis.coffee b/assets/scripts/travis.coffee index 2e52417d..21253131 100644 --- a/assets/scripts/travis.coffee +++ b/assets/scripts/travis.coffee @@ -38,6 +38,9 @@ window.Travis = Em.Application.extend(Ember.Evented, @_super.apply(this, arguments); + lookup: -> + @__container__.lookup.apply this, arguments + storeAfterSignInPath: (path) -> @get('auth').storeAfterSignInPath(path) diff --git a/assets/scripts/vendor/ember.js b/assets/scripts/vendor/ember.js index 35a9c6a5..524baafb 100644 --- a/assets/scripts/vendor/ember.js +++ b/assets/scripts/vendor/ember.js @@ -1,5 +1,5 @@ -// Version: v1.0.0-pre.4-209-g455440d -// Last commit: 455440d (2013-02-13 12:03:37 -0800) +// Version: v1.0.0-rc.1-109-g8cc5392 +// Last commit: 8cc5392 (2013-03-04 02:00:20 +0100) (function() { @@ -150,8 +150,8 @@ Ember.deprecateFunc = function(message, func) { })(); -// Version: v1.0.0-pre.4-209-g455440d -// Last commit: 455440d (2013-02-13 12:03:37 -0800) +// Version: v1.0.0-rc.1-109-g8cc5392 +// Last commit: 8cc5392 (2013-03-04 02:00:20 +0100) (function() { @@ -211,7 +211,7 @@ var define, requireModule; @class Ember @static - @version 1.0.0-pre.4 + @version 1.0.0-rc.1 */ if ('undefined' === typeof Ember) { @@ -238,10 +238,10 @@ Ember.toString = function() { return "Ember"; }; /** @property VERSION @type String - @default '1.0.0-pre.4' + @default '1.0.0-rc.1' @final */ -Ember.VERSION = '1.0.0-pre.4'; +Ember.VERSION = '1.0.0-rc.1'; /** Standard environmental variables. You can define these in a global `ENV` @@ -297,6 +297,15 @@ Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION ! */ Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES; +/** + Determines whether Ember logs info about version of used libraries + + @property LOG_VERSION + @type Boolean + @default true +*/ +Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true; + /** Empty function. Useful for some operations. @@ -651,7 +660,7 @@ Ember.generateGuid = function generateGuid(obj, prefix) { @method guidFor @for Ember - @param obj {Object} any object, string, number, Element, or primitive + @param {Object} obj any object, string, number, Element, or primitive @return {String} the unique guid for this instance. */ Ember.guidFor = function guidFor(obj) { @@ -1001,8 +1010,8 @@ var needsFinallyFix = (function() { @method tryFinally @for Ember - @param {Function} function The function to run the try callback - @param {Function} function The function to run the finally callback + @param {Function} tryable The function to run the try callback + @param {Function} finalizer The function to run the finally callback @param [binding] @return {anything} The return value is the that of the finalizer, unless that valueis undefined, in which case it is the return value @@ -1051,9 +1060,9 @@ if (needsFinallyFix) { @method tryCatchFinally @for Ember - @param {Function} function The function to run the try callback - @param {Function} function The function to run the catchable callback - @param {Function} function The function to run the finally callback + @param {Function} tryable The function to run the try callback + @param {Function} catchable The function to run the catchable callback + @param {Function} finalizer The function to run the finally callback @param [binding] @return {anything} The return value is the that of the finalizer, unless that value is undefined, in which case it is the return value @@ -1565,8 +1574,8 @@ OrderedSet.prototype = { /** @method forEach - @param {Function} function - @param target + @param {Function} fn + @param self */ forEach: function(fn, self) { // allow mutation during iteration @@ -2087,7 +2096,7 @@ Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now support @method trySet @for Ember @param {Object} obj The object to modify. - @param {String} keyName The property key to set + @param {String} path The property path to set @param {Object} value The value to set */ Ember.trySet = function(root, path, value) { @@ -2232,6 +2241,7 @@ Ember.defineProperty = function(obj, keyName, desc, data, meta) { } else { obj[keyName] = undefined; // make enumerable } + desc.setup(obj, keyName); } else { descs[keyName] = undefined; // shadow descriptor in proto if (desc == null) { @@ -2950,12 +2960,15 @@ Ember.watch = function(obj, keyName) { // can't watch length on Array - it is special... if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; } - var m = metaFor(obj), watching = m.watching; + var m = metaFor(obj), watching = m.watching, desc; // activate watching first time if (!watching[keyName]) { watching[keyName] = 1; if (isKeyName(keyName)) { + desc = m.descs[keyName]; + if (desc && desc.willWatch) { desc.willWatch(obj, keyName); } + if ('function' === typeof obj.willWatchProperty) { obj.willWatchProperty(keyName); } @@ -2990,12 +3003,15 @@ Ember.unwatch = function(obj, keyName) { // can't watch length on Array - it is special... if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; } - var m = metaFor(obj), watching = m.watching; + var m = metaFor(obj), watching = m.watching, desc; if (watching[keyName] === 1) { watching[keyName] = 0; if (isKeyName(keyName)) { + desc = m.descs[keyName]; + if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); } + if ('function' === typeof obj.didUnwatchProperty) { obj.didUnwatchProperty(keyName); } @@ -3404,6 +3420,25 @@ ComputedPropertyPrototype.meta = function(meta) { } }; +/* impl descriptor API */ +ComputedPropertyPrototype.willWatch = function(obj, keyName) { + // watch already creates meta for this instance + var meta = obj[META_KEY]; + Ember.assert('watch should have setup meta to be writable', meta.source === obj); + if (!(keyName in meta.cache)) { + addDependentKeys(this, obj, keyName, meta); + } +}; + +ComputedPropertyPrototype.didUnwatch = function(obj, keyName) { + var meta = obj[META_KEY]; + Ember.assert('unwatch should have setup meta to be writable', meta.source === obj); + if (!(keyName in meta.cache)) { + // unwatch already creates meta for this instance + removeDependentKeys(this, obj, keyName, meta); + } +}; + /* impl descriptor API */ ComputedPropertyPrototype.didChange = function(obj, keyName) { // _suspended is set via a CP.set to ensure we don't clear @@ -3412,7 +3447,9 @@ ComputedPropertyPrototype.didChange = function(obj, keyName) { var meta = metaFor(obj); if (keyName in meta.cache) { delete meta.cache[keyName]; - removeDependentKeys(this, obj, keyName, meta); + if (!meta.watching[keyName]) { + removeDependentKeys(this, obj, keyName, meta); + } } } }; @@ -3425,7 +3462,9 @@ ComputedPropertyPrototype.get = function(obj, keyName) { cache = meta.cache; if (keyName in cache) { return cache[keyName]; } ret = cache[keyName] = this.func.call(obj, keyName); - addDependentKeys(this, obj, keyName, meta); + if (!meta.watching[keyName]) { + addDependentKeys(this, obj, keyName, meta); + } } else { ret = this.func.call(obj, keyName); } @@ -3478,7 +3517,7 @@ ComputedPropertyPrototype.set = function(obj, keyName, value) { } if (cacheable) { - if (!hadCachedValue) { + if (!watched && !hadCachedValue) { addDependentKeys(this, obj, keyName, meta); } cache[keyName] = ret; @@ -3491,11 +3530,19 @@ ComputedPropertyPrototype.set = function(obj, keyName, value) { return ret; }; +/* called when property is defined */ +ComputedPropertyPrototype.setup = function(obj, keyName) { + var meta = obj[META_KEY]; + if (meta && meta.watching[keyName]) { + addDependentKeys(this, obj, keyName, metaFor(obj)); + } +}; + /* called before property is overridden */ ComputedPropertyPrototype.teardown = function(obj, keyName) { var meta = metaFor(obj); - if (keyName in meta.cache) { + if (meta.watching[keyName] || keyName in meta.cache) { removeDependentKeys(this, obj, keyName, meta); } @@ -3719,6 +3766,7 @@ function actionsDiff(obj, eventName, otherActions) { @param {String} eventName @param {Object|Function} targetOrMethod A target object or a function @param {Function|String} method A function or the name of a function to be called on `target` + @param {Boolean} once A flag whether a function should only be called once */ function addListener(obj, eventName, target, method, once) { Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName); @@ -3904,6 +3952,7 @@ function watchedEvents(obj) { @param obj @param {String} eventName @param {Array} params + @param {Array} actions @return true */ function sendEvent(obj, eventName, params, actions) { @@ -4515,6 +4564,21 @@ function scheduleOnce(queue, target, method, args) { // doFoo will only be executed once at the end of the RunLoop }); ``` + + Also note that passing an anonymous function to `Ember.run.once` will + not prevent additional calls with an identical anonymous function from + scheduling the items multiple times, e.g.: + + ```javascript + function scheduleIt() { + Ember.run.once(myContext, function() { console.log("Closure"); }); + } + scheduleIt(); + scheduleIt(); + // "Closure" will print twice, even though we're using `Ember.run.once`, + // because the function we pass to it is anonymous and won't match the + // previously scheduled operation. + ``` @method once @param {Object} [target] target of method to invoke @@ -4691,7 +4755,7 @@ Binding.prototype = { `get()` - see that method for more information. @method from - @param {String} propertyPath the property path to connect to + @param {String} path the property path to connect to @return {Ember.Binding} `this` */ from: function(path) { @@ -4709,7 +4773,7 @@ Binding.prototype = { `get()` - see that method for more information. @method to - @param {String|Tuple} propertyPath A property path or tuple + @param {String|Tuple} path A property path or tuple @return {Ember.Binding} `this` */ to: function(path) { @@ -5450,6 +5514,38 @@ Mixin.finishPartial = finishPartial; Ember.anyUnprocessedMixins = false; /** + 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`. + @method create @static @param arguments* @@ -5845,7 +5941,8 @@ define("rsvp", callbacks, callbackTuple, callback, binding, event; if (callbacks = allCallbacks[eventName]) { - for (var i=0, l=callbacks.length; i 'InnerHTML' - 'action_name'.capitalize() => 'Action_name' - 'css-class-name'.capitalize() => 'Css-class-name' - 'my favorite items'.capitalize() => 'My favorite items' + 'innerHTML'.capitalize() // 'InnerHTML' + 'action_name'.capitalize() // 'Action_name' + 'css-class-name'.capitalize() // 'Css-class-name' + 'my favorite items'.capitalize() // 'My favorite items' @method capitalize @param {String} str @@ -7305,10 +7456,10 @@ Ember.Enumerable = Ember.Mixin.create( ```javascript var arr = ["a", "b", "c"]; - arr.firstObject(); // "a" + arr.get('firstObject'); // "a" var arr = []; - arr.firstObject(); // undefined + arr.get('firstObject'); // undefined ``` @property firstObject @@ -7331,10 +7482,10 @@ Ember.Enumerable = Ember.Mixin.create( ```javascript var arr = ["a", "b", "c"]; - arr.lastObject(); // "c" + arr.get('lastObject'); // "c" var arr = []; - arr.lastObject(); // undefined + arr.get('lastObject'); // undefined ``` @property lastObject @@ -7467,7 +7618,7 @@ Ember.Enumerable = Ember.Mixin.create( @return {Array} The mapped array. */ map: function(callback, target) { - var ret = []; + var ret = Ember.A([]); this.forEach(function(x, idx, i) { ret[idx] = callback.call(target, x, idx,i); }); @@ -7517,7 +7668,7 @@ Ember.Enumerable = Ember.Mixin.create( @return {Array} A filtered array. */ filter: function(callback, target) { - var ret = []; + var ret = Ember.A([]); this.forEach(function(x, idx, i) { if (callback.call(target, x, idx, i)) ret.push(x); }); @@ -7806,7 +7957,7 @@ Ember.Enumerable = Ember.Mixin.create( @return {Array} return values from calling invoke. */ invoke: function(methodName) { - var args, ret = []; + var args, ret = Ember.A([]); if (arguments.length>1) args = a_slice.call(arguments, 1); this.forEach(function(x, idx) { @@ -7827,7 +7978,7 @@ Ember.Enumerable = Ember.Mixin.create( @return {Array} the enumerable as an array. */ toArray: function() { - var ret = []; + var ret = Ember.A([]); this.forEach(function(o, idx) { ret[idx] = o; }); return ret ; }, @@ -7861,7 +8012,7 @@ Ember.Enumerable = Ember.Mixin.create( */ without: function(value) { if (!this.contains(value)) return this; // nothing to do - var ret = [] ; + var ret = Ember.A([]); this.forEach(function(k) { if (k !== value) ret[ret.length] = k; }) ; @@ -7881,7 +8032,7 @@ Ember.Enumerable = Ember.Mixin.create( @return {Ember.Enumerable} */ uniq: function() { - var ret = []; + var ret = Ember.A([]); this.forEach(function(k){ if (a_indexOf(ret, k)<0) ret.push(k); }); @@ -7912,8 +8063,8 @@ Ember.Enumerable = Ember.Mixin.create( mixin. @method addEnumerableObserver - @param target {Object} - @param opts {Hash} + @param {Object} target + @param {Hash} [opts] */ addEnumerableObserver: function(target, opts) { var willChange = (opts && opts.willChange) || 'enumerableWillChange', @@ -7931,8 +8082,8 @@ Ember.Enumerable = Ember.Mixin.create( Removes a registered enumerable observer. @method removeEnumerableObserver - @param target {Object} - @param [opts] {Hash} + @param {Object} target + @param {Hash} [opts] */ removeEnumerableObserver: function(target, opts) { var willChange = (opts && opts.willChange) || 'enumerableWillChange', @@ -8049,7 +8200,7 @@ Ember.Enumerable = Ember.Mixin.create( // HELPERS // -var get = Ember.get, set = Ember.set, meta = Ember.meta, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor; +var get = Ember.get, set = Ember.set, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor; function none(obj) { return obj===null || obj===undefined; } @@ -8191,12 +8342,12 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot ``` @method slice - @param beginIndex {Integer} (Optional) index to begin slicing from. - @param endIndex {Integer} (Optional) index to end the slice at. + @param {Integer} beginIndex (Optional) index to begin slicing from. + @param {Integer} endIndex (Optional) index to end the slice at. @return {Array} New array with specified slice */ slice: function(beginIndex, endIndex) { - var ret = []; + var ret = Ember.A([]); var length = get(this, 'length') ; if (none(beginIndex)) beginIndex = 0 ; if (none(endIndex) || (endIndex > length)) endIndex = length ; @@ -8297,7 +8448,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot @method addArrayObserver @param {Object} target The observer object. @param {Hash} opts Optional hash of configuration options including - `willChange`, `didChange`, and a `context` option. + `willChange` and `didChange` option. @return {Ember.Array} receiver */ addArrayObserver: function(target, opts) { @@ -8319,6 +8470,8 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot @method removeArrayObserver @param {Object} target The object observing the array. + @param {Hash} opts Optional hash of configuration options including + `willChange` and `didChange` option. @return {Ember.Array} receiver */ removeArrayObserver: function(target, opts) { @@ -8535,7 +8688,7 @@ Ember.Copyable = Ember.Mixin.create( an exception. @method copy - @param deep {Boolean} if `true`, a deep copy of the object should be made + @param {Boolean} deep if `true`, a deep copy of the object should be made @return {Object} copy of receiver */ copy: Ember.required(Function), @@ -9096,7 +9249,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable, @submodule ember-runtime */ -var get = Ember.get, set = Ember.set, defineProperty = Ember.defineProperty; +var get = Ember.get, set = Ember.set; /** ## Overview @@ -9201,7 +9354,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { not defined upfront. @method get - @param {String} key The property to retrieve + @param {String} keyName The property to retrieve @return {Object} The property value or undefined. */ get: function(keyName) { @@ -9282,7 +9435,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { ``` @method set - @param {String} key The property to set + @param {String} keyName The property to set @param {Object} value The value to set or `null`. @return {Ember.Observable} */ @@ -9359,7 +9512,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { like. @method propertyWillChange - @param {String} key The property key that is about to change. + @param {String} keyName The property key that is about to change. @return {Ember.Observable} */ propertyWillChange: function(keyName){ @@ -10042,6 +10195,37 @@ CoreObject.PrototypeMixin = Mixin.create({ isInstance: true, + /** + An overridable method called when objects are instantiated. By default, + does nothing unless it is overridden during class definition. + + Example: + + ```javascript + App.Person = Ember.Object.extend({ + init: function() { + this._super(); + alert('Name is ' + this.get('name')); + } + }); + + var steve = App.Person.create({ + name: "Steve" + }); + + // alerts 'Name is Steve'. + ``` + + NOTE: If you do override `init` for a framework class like `Ember.View` or + `Ember.ArrayController`, be sure to call `this._super()` in your + `init` declaration! If you don't, Ember may not have an opportunity to + do important setup work, and you'll see strange behavior in your + application. + + ``` + + @method init + */ init: function() {}, /** @@ -10192,7 +10376,7 @@ CoreObject.PrototypeMixin = Mixin.create({ } }); teacher = App.Teacher.create() - teacher.toString(); // #=> "" + teacher.toString(); //=> "" @method toString @return {String} string representation @@ -10900,16 +11084,28 @@ var Namespace = Ember.Namespace = Ember.Object.extend({ Namespace.reopenClass({ NAMESPACES: [Ember], + NAMESPACES_BY_ID: {}, PROCESSED: false, - processAll: processAllNamespaces + 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; } @@ -11009,12 +11205,15 @@ function classToString() { } function processAllNamespaces() { - if (!Namespace.PROCESSED) { + var unprocessedNamespaces = !Namespace.PROCESSED, + unprocessedMixins = Ember.anyUnprocessedMixins; + + if (unprocessedNamespaces) { findNamespaces(); Namespace.PROCESSED = true; } - if (Ember.anyUnprocessedMixins) { + if (unprocessedNamespaces || unprocessedMixins) { var namespaces = Namespace.NAMESPACES, namespace; for (var i=0, l=namespaces.length; i0) { /** The NativeArray mixin contains the properties needed to to make the native Array support Ember.MutableArray and all of its dependent APIs. Unless you - have `Ember.EXTEND_PROTOTYPES or `Ember.EXTEND_PROTOTYPES.Array` set to + have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to false, this will be applied automatically. Otherwise you can apply the mixin at anytime by calling `Ember.NativeArray.activate`. @@ -12351,7 +12537,8 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, objectAtContent: function(idx) { var length = get(this, 'length'), - object = get(this,'arrangedContent').objectAt(idx); + arrangedContent = get(this,'arrangedContent'), + object = arrangedContent && arrangedContent.objectAt(idx); if (idx >= 0 && idx < length) { var controllerClass = this.lookupItemController(object); @@ -12371,20 +12558,20 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, arrangedContentDidChange: function() { this._super(); - this._resetSubContainers(); + this._resetSubControllers(); }, arrayContentDidChange: function(idx, removedCnt, addedCnt) { - var subContainers = get(this, 'subContainers'), - subContainersToRemove = subContainers.slice(idx, idx+removedCnt); + var subControllers = get(this, '_subControllers'), + subControllersToRemove = subControllers.slice(idx, idx+removedCnt); - forEach(subContainersToRemove, function(subContainer) { - if (subContainer) { subContainer.destroy(); } + forEach(subControllersToRemove, function(subController) { + if (subController) { subController.destroy(); } }); - replace(subContainers, idx, removedCnt, new Array(addedCnt)); + replace(subControllers, idx, removedCnt, new Array(addedCnt)); - // The shadow array of subcontainers must be updated before we trigger + // The shadow array of subcontrollers must be updated before we trigger // observers, otherwise observers will get the wrong subcontainer when // calling `objectAt` this._super(idx, removedCnt, addedCnt); @@ -12392,42 +12579,40 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, init: function() { this._super(); - this._resetSubContainers(); + if (!this.get('content')) { Ember.defineProperty(this, 'content', undefined, Ember.A()); } + this.set('_subControllers', Ember.A()); }, controllerAt: function(idx, object, controllerClass) { var container = get(this, 'container'), - subContainers = get(this, 'subContainers'), - subContainer = subContainers[idx], - controller; + subControllers = get(this, '_subControllers'), + subController = subControllers[idx]; - if (!subContainer) { - subContainer = subContainers[idx] = container.child(); + if (!subController) { + subController = container.lookup("controller:" + controllerClass, { singleton: false }); + subControllers[idx] = subController; } - controller = subContainer.lookup("controller:" + controllerClass); - if (!controller) { + if (!subController) { throw new Error('Could not resolve itemController: "' + controllerClass + '"'); } - controller.set('target', this); - controller.set('content', object); + subController.set('target', this); + subController.set('content', object); - return controller; + return subController; }, - subContainers: null, + _subControllers: null, - _resetSubContainers: function() { - var subContainers = get(this, 'subContainers'); + _resetSubControllers: function() { + var subControllers = get(this, '_subControllers'); - if (subContainers) { - forEach(subContainers, function(subContainer) { - if (subContainer) { subContainer.destroy(); } - }); - } + forEach(subControllers, function(subController) { + if (subController) { subController.destroy(); } + }); - this.set('subContainers', Ember.A()); + this.set('_subControllers', Ember.A()); } }); @@ -12504,15 +12689,16 @@ Ember.$ = jQuery; @module ember @submodule ember-views */ +if (Ember.$) { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents + var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend'); -// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents -var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend'); - -// Copies the `dataTransfer` property from a browser event object onto the -// jQuery event object for the specified events -Ember.EnumerableUtils.forEach(dragEvents, function(eventName) { - Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] }; -}); + // Copies the `dataTransfer` property from a browser event object onto the + // jQuery event object for the specified events + Ember.EnumerableUtils.forEach(dragEvents, function(eventName) { + Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] }; + }); +} })(); @@ -12529,7 +12715,8 @@ Ember.EnumerableUtils.forEach(dragEvents, function(eventName) { // Internet Explorer prior to 9 does not allow setting innerHTML if the first element // is a "zero-scope" element. This problem can be worked around by making // the first node an invisible text node. We, like Modernizr, use ­ -var needsShy = (function(){ + +var needsShy = this.document && (function(){ var testEl = document.createElement('div'); testEl.innerHTML = "
"; testEl.firstChild.innerHTML = ""; @@ -12539,7 +12726,7 @@ var needsShy = (function(){ // IE 8 (and likely earlier) likes to move whitespace preceeding // a script tag to appear after it. This means that we can // accidentally remove whitespace when updating a morph. -var movesWhitespace = (function() { +var movesWhitespace = this.document && (function() { var testEl = document.createElement('div'); testEl.innerHTML = "Test: Value"; return testEl.childNodes[0].nodeValue === 'Test:' && @@ -13012,7 +13199,7 @@ Ember._RenderBuffer.prototype = for (prop in props) { if (props.hasOwnProperty(prop)) { var value = props[prop]; - if (value) { + if (value || typeof(value) === 'number') { if (value === true) { buffer.push(' ' + prop + '="' + prop + '"'); } else { @@ -13371,8 +13558,9 @@ Ember.EventDispatcher = Ember.Object.extend( // Add a new named queue for rendering views that happens // after bindings have synced, and a queue for scheduling actions // that that should occur after view rendering. -var queues = Ember.run.queues; -queues.splice(Ember.$.inArray('actions', queues)+1, 0, 'render', 'afterRender'); +var queues = Ember.run.queues, + indexOf = Ember.ArrayPolyfills.indexOf; +queues.splice(indexOf.call(queues, 'actions')+1, 0, 'render', 'afterRender'); })(); @@ -13457,7 +13645,7 @@ var childViewsProperty = Ember.computed(function() { ret.replace = function (idx, removedCount, addedViews) { if (view instanceof Ember.ContainerView) { - Ember.deprecate("Manipulating a Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray."); + 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"); @@ -14716,7 +14904,6 @@ Ember.View = Ember.CoreView.extend( // JavaScript property changes. var observer = function() { elem = this.$(); - if (!elem) { return; } attributeValue = get(this, property); @@ -15584,10 +15771,18 @@ Ember.View = Ember.CoreView.extend( }, registerObserver: function(root, path, target, observer) { - Ember.addObserver(root, path, target, observer); + if (!observer && 'function' === typeof target) { + observer = target; + target = null; + } + var view = this, + stateCheckedObserver = function(){ + view.currentState.invokeObserver(this, observer); + }; + Ember.addObserver(root, path, target, stateCheckedObserver); this.one('willClearRender', function() { - Ember.removeObserver(root, path, target, observer); + Ember.removeObserver(root, path, target, stateCheckedObserver); }); } @@ -15618,17 +15813,24 @@ Ember.View = Ember.CoreView.extend( // once the view has been inserted into the DOM, legal manipulations // are done on the DOM element. +function notifyMutationListeners() { + Ember.run.once(Ember.View, 'notifyMutationListeners'); +} + var DOMManager = { prepend: function(view, html) { view.$().prepend(html); + notifyMutationListeners(); }, after: function(view, html) { view.$().after(html); + notifyMutationListeners(); }, html: function(view, html) { view.$().html(html); + notifyMutationListeners(); }, replace: function(view) { @@ -15638,15 +15840,18 @@ var DOMManager = { view._insertElementLater(function() { Ember.$(element).replaceWith(get(view, 'element')); + notifyMutationListeners(); }); }, remove: function(view) { view.$().remove(); + notifyMutationListeners(); }, empty: function(view) { view.$().empty(); + notifyMutationListeners(); } }; @@ -15761,6 +15966,20 @@ Ember.View.reopenClass({ } }); +var mutation = Ember.Object.extend(Ember.Evented).create(); + +Ember.View.addMutationListener = function(callback) { + mutation.on('change', callback); +}; + +Ember.View.removeMutationListener = function(callback) { + mutation.off('change', callback); +}; + +Ember.View.notifyMutationListeners = function() { + mutation.trigger('change'); +}; + /** Global views hash @@ -15841,7 +16060,8 @@ Ember.View.states._default = { return false; }, - rerender: Ember.K + rerender: Ember.K, + invokeObserver: Ember.K }; })(); @@ -15962,6 +16182,10 @@ Ember.merge(inBuffer, { } return value; + }, + + invokeObserver: function(target, observer) { + observer.call(target); } }); @@ -16049,6 +16273,10 @@ Ember.merge(hasElement, { } else { return true; // continue event propagation } + }, + + invokeObserver: function(target, observer) { + observer.call(target); } }); @@ -16637,7 +16865,7 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt; } else { viewClass = App.SongView; } - this._super(viewClass, attrs); + return this._super(viewClass, attrs); } }); ``` @@ -16920,15 +17148,15 @@ define("metamorph", var K = function(){}, guid = 0, - document = window.document, + document = this.document, // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges - supportsRange = ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, + supportsRange = document && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, // Internet Explorer prior to 9 does not allow setting innerHTML if the first element // is a "zero-scope" element. This problem can be worked around by making // the first node an invisible text node. We, like Modernizr, use ­ - needsShy = (function(){ + needsShy = document && (function(){ var testEl = document.createElement('div'); testEl.innerHTML = "
"; testEl.firstChild.innerHTML = ""; @@ -16939,7 +17167,7 @@ define("metamorph", // IE 8 (and likely earlier) likes to move whitespace preceeding // a script tag to appear after it. This means that we can // accidentally remove whitespace when updating a morph. - movesWhitespace = (function() { + movesWhitespace = document && (function() { var testEl = document.createElement('div'); testEl.innerHTML = "Test: Value"; return testEl.childNodes[0].nodeValue === 'Test:' && @@ -17383,8 +17611,12 @@ var objectCreate = Object.create || function(parent) { return new F(); }; -var Handlebars = this.Handlebars || Ember.imports.Handlebars; -Ember.assert("Ember Handlebars requires Handlebars 1.0.rc.2 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.rc\.[23456789]+/)); +var Handlebars = this.Handlebars || (Ember.imports && Ember.imports.Handlebars); +if(!Handlebars && typeof require === 'function') { + Handlebars = require('handlebars'); +} + +Ember.assert("Ember Handlebars requires Handlebars 1.0.rc.3 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0(\.0)?(\.|-)rc\.[23456789]+/)); /** Prepares the Handlebars templating library for use inside Ember's view @@ -18043,22 +18275,30 @@ Ember.Handlebars.resolvePaths = function(options) { var set = Ember.set, get = Ember.get; var Metamorph = requireModule('metamorph'); +function notifyMutationListeners() { + Ember.run.once(Ember.View, 'notifyMutationListeners'); +} + // DOMManager should just abstract dom manipulation between jquery and metamorph var DOMManager = { remove: function(view) { view.morph.remove(); + notifyMutationListeners(); }, prepend: function(view, html) { view.morph.prepend(html); + notifyMutationListeners(); }, after: function(view, html) { view.morph.after(html); + notifyMutationListeners(); }, html: function(view, html) { view.morph.html(html); + notifyMutationListeners(); }, // This is messed up. @@ -18081,11 +18321,13 @@ var DOMManager = { morph.replaceWith(buffer.string()); view.transitionTo('inDOM'); view.triggerRecursively('didInsertElement'); + notifyMutationListeners(); }); }, empty: function(view) { view.morph.html(""); + notifyMutationListeners(); } }; @@ -18977,7 +19219,9 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { // Add an observer to the view for when the property changes. // When the observer fires, find the element using the // unique data id and update the attribute to the new value. - if (path !== 'this') { + // Note: don't add observer when path is 'this' or path + // is whole keyword e.g. {{#each x in list}} ... {{bindAttr attr="x"}} + if (path !== 'this' && !(normalized.isKeyword && normalized.path === '' )) { view.registerObserver(normalized.root, normalized.path, invoker); } @@ -20000,7 +20244,7 @@ GroupedEach.prototype = { ```handlebars {{#view App.MyView }} - {{each view.items itemViewClass="App.AnItemView"}} + {{each view.items itemViewClass="App.AnItemView"}} {{/view}} ``` @@ -20031,7 +20275,7 @@ GroupedEach.prototype = {
Greetings Sara
``` - + ### Representing each item with a Controller. By default the controller lookup within an `{{#each}}` block will be the controller of the template where the `{{#each}}` was used. If each @@ -20039,10 +20283,10 @@ GroupedEach.prototype = { `itemController` option which references a controller by lookup name. Each item in the loop will be wrapped in an instance of this controller and the item itself will be set to the `content` property of that controller. - + This is useful in cases where properties of model objects need transformation or synthesis for display: - + ```javascript App.DeveloperController = Ember.ObjectController.extend({ isAvailableForHire: function(){ @@ -20050,17 +20294,17 @@ GroupedEach.prototype = { }.property('isEmployed', 'isSeekingWork') }) ``` - + ```handlebars - {{#each person in Developers itemController="developer"}} + {{#each person in developers itemController="developer"}} {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}} {{/each}} ``` - + @method each @for Ember.Handlebars.helpers @param [name] {String} name for item (used with `in`) - @param path {String} path + @param [path] {String} path @param [options] {Object} Handlebars key/value pairs of options @param [options.itemViewClass] {String} a path to a view class used for each item @param [options.itemController] {String} name of a controller to be created for each item @@ -20119,6 +20363,14 @@ Ember.Handlebars.registerHelper('each', function(path, options) { ``` + ```handlebars + {{#if isUser}} + {{template "user_info"}} + {{else}} + {{template "unlogged_user_info"}} + {{/if}} + ``` + This helper looks for templates in the global `Ember.TEMPLATES` hash. If you add `