diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 index 7761b057b86..2b4bd0e29cd 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 @@ -1,14 +1,5 @@ -/** - This controller supports the interface for listing screened email addresses in the admin section. - - @class AdminLogsScreenedEmailsController - @extends Ember.ArrayController - @namespace Discourse - @module Discourse -**/ export default Ember.ArrayController.extend(Discourse.Presence, { loading: false, - content: [], actions: { clearBlock: function(row){ @@ -23,7 +14,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, { var self = this; this.set('loading', true); Discourse.ScreenedEmail.findAll().then(function(result) { - self.set('content', result); + self.set('model', result); self.set('loading', false); }); } diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 index 5fd66c287d8..aeaaffeaf3c 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 @@ -1,28 +1,19 @@ -/** - This controller supports the interface for listing screened IP addresses in the admin section. - - @class AdminLogsScreenedIpAddressesController - @extends Ember.ArrayController - @namespace Discourse - @module Discourse -**/ export default Ember.ArrayController.extend(Discourse.Presence, { loading: false, - content: [], itemController: 'admin-log-screened-ip-address', show: function() { var self = this; this.set('loading', true); Discourse.ScreenedIpAddress.findAll().then(function(result) { - self.set('content', result); + self.set('model', result); self.set('loading', false); }); }, actions: { recordAdded: function(arg) { - this.get("content").unshiftObject(arg); + this.get("model").unshiftObject(arg); } } }); diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 index f2869bb8363..92cb40e4962 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 @@ -1,20 +1,11 @@ -/** - This controller supports the interface for listing screened URLs in the admin section. - - @class AdminLogsScreenedUrlsController - @extends Ember.ArrayController - @namespace Discourse - @module Discourse -**/ export default Ember.ArrayController.extend(Discourse.Presence, { loading: false, - content: [], show: function() { var self = this; this.set('loading', true); Discourse.ScreenedUrl.findAll().then(function(result) { - self.set('content', result); + self.set('model', result); self.set('loading', false); }); } diff --git a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 index 0c2ad34453b..76df4a3b60d 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 @@ -15,7 +15,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, { this.set('loading', true); Discourse.URL.set('queryParams', this.get('filters')); // TODO: doesn't work Discourse.StaffActionLog.findAll(this.get('filters')).then(function(result) { - self.set('content', result); + self.set('model', result); self.set('loading', false); }); }.observes('filters.action_name', 'filters.acting_user', 'filters.target_user', 'filters.subject'), diff --git a/app/assets/javascripts/admin/controllers/admin-users-list.js.es6 b/app/assets/javascripts/admin/controllers/admin-users-list.js.es6 index 36209fc2127..e3c237d9d27 100644 --- a/app/assets/javascripts/admin/controllers/admin-users-list.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-users-list.js.es6 @@ -10,7 +10,6 @@ export default Ember.ArrayController.extend(Discourse.Presence, { username: null, query: null, selectAll: false, - content: null, loading: false, mustApproveUsers: Discourse.computed.setting('must_approve_users'), @@ -27,7 +26,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, { **/ selectAllChanged: function() { var _this = this; - _.each(this.get('content'),function(user) { + _.each(this.get('model'),function(user) { user.set('selected', _this.get('selectAll')); }); }.observes('selectAll'), @@ -74,9 +73,9 @@ export default Ember.ArrayController.extend(Discourse.Presence, { @property selectedCount **/ selectedCount: function() { - if (this.blank('content')) return 0; - return this.get('content').filterProperty('selected').length; - }.property('content.@each.selected'), + if (this.blank('model')) return 0; + return this.get('model').filterProperty('selected').length; + }.property('model.@each.selected'), /** Do we have any selected users? @@ -95,7 +94,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, { adminUsersListController.set('loading', true); Discourse.AdminUser.findAll(this.get('query'), { filter: this.get('username'), show_emails: showEmails }).then(function (result) { - adminUsersListController.set('content', result); + adminUsersListController.set('model', result); adminUsersListController.set('loading', false); }); }, @@ -114,36 +113,28 @@ export default Ember.ArrayController.extend(Discourse.Presence, { this.set('query', term); }, - /** - Approve all the currently selected users. + actions: { + approveUsers: function() { + Discourse.AdminUser.bulkApprove(this.get('model').filterProperty('selected')); + this.refreshUsers(); + }, - @method approveUsers - **/ - approveUsers: function() { - Discourse.AdminUser.bulkApprove(this.get('content').filterProperty('selected')); - this.refreshUsers(); - }, + rejectUsers: function() { + var controller = this; + Discourse.AdminUser.bulkReject(this.get('model').filterProperty('selected')).then(function(result){ + var message = I18n.t("admin.users.reject_successful", {count: result.success}); + if (result.failed > 0) { + message += ' ' + I18n.t("admin.users.reject_failures", {count: result.failed}); + message += ' ' + I18n.t("admin.user.delete_forbidden", {count: Discourse.SiteSettings.delete_user_max_post_age}); + } + bootbox.alert(message); + controller.refreshUsers(); + }); + }, - /** - Reject all the currently selected users. - - @method rejectUsers - **/ - rejectUsers: function() { - var controller = this; - Discourse.AdminUser.bulkReject(this.get('content').filterProperty('selected')).then(function(result){ - var message = I18n.t("admin.users.reject_successful", {count: result.success}); - if (result.failed > 0) { - message += ' ' + I18n.t("admin.users.reject_failures", {count: result.failed}); - message += ' ' + I18n.t("admin.user.delete_forbidden", {count: Discourse.SiteSettings.delete_user_max_post_age}); - } - bootbox.alert(message); - controller.refreshUsers(); - }); - }, - - showEmails: function() { - this.refreshUsers(true); + showEmails: function() { + this.refreshUsers(true); + } } }); diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 7e027eff33c..548fb692ee0 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -48,7 +48,7 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, { if (arguments.length > 1) { postStream.set('show_deleted', value); } - return postStream.get('show_deleted') ? true : null; + return postStream.get('show_deleted') ? true : undefined; }.property('postStream.summary'), filter: function(key, value) { @@ -58,10 +58,18 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, { if (arguments.length > 1) { postStream.set('summary', value === "summary"); } - return postStream.get('summary') ? "summary" : null; + return postStream.get('summary') ? "summary" : undefined; }.property('postStream.summary'), - username_filters: Discourse.computed.queryAlias('postStream.streamFilters.username_filters'), + username_filters: function(key, value) { + var postStream = this.get('postStream'); + if (!postStream) { return; } + + if (arguments.length > 1) { + postStream.set('streamFilters.username_filters', value); + } + return postStream.get('streamFilters.username_filters'); + }.property('postStream.streamFilters.username_filters'), init: function() { this._super(); diff --git a/app/assets/javascripts/discourse/helpers/grouped-each.js.es6 b/app/assets/javascripts/discourse/helpers/grouped-each.js.es6 index b8d80ff400a..824ad38dba0 100644 --- a/app/assets/javascripts/discourse/helpers/grouped-each.js.es6 +++ b/app/assets/javascripts/discourse/helpers/grouped-each.js.es6 @@ -81,9 +81,13 @@ DiscourseGroupedEach.prototype = { data.insideEach = true; for (var i = 0; i < contentLength; i++) { var row = content.objectAt(i); + var keywords = Em.get(data, 'keywords'); + if (!keywords) { + keywords = {}; + Em.set(data, 'keywords', keywords); + } if (keyword) { - data.keywords = data.keywords || {}; - data.keywords[keyword] = row; + Em.set(keywords, keyword, row); } template(row, { data: data }); } diff --git a/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 b/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 index 675bc9a35c3..95a220b542b 100644 --- a/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 +++ b/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 @@ -3,7 +3,10 @@ export function renderAvatar(user, options) { if (user) { var username = Em.get(user, 'username'); - if (!username) username = Em.get(user, options.usernamePath); + if (!username) { + if (!options.usernamePath) { return ''; } + username = Em.get(user, options.usernamePath); + } var title; if (!options.ignoreTitle) { diff --git a/app/assets/javascripts/discourse/lib/computed.js b/app/assets/javascripts/discourse/lib/computed.js index 374c72d27c3..9cabb589909 100644 --- a/app/assets/javascripts/discourse/lib/computed.js +++ b/app/assets/javascripts/discourse/lib/computed.js @@ -112,24 +112,6 @@ Discourse.computed = { return computed.property.apply(computed, args); }, - /** - Creates a one way alias to a computed property, suitable for query params. - - @method queryAlias - @param {String} path to the alias - @param {String} defaultValue for the variable (omitted if equal) - **/ - queryAlias: function(path, defaultValue) { - return Em.computed(function(key, value) { - if (value) { - // Annoying but this ensures the parameter is present - } - var result = this.get(path); - if (typeof result !== "undefined" && result.toString() === defaultValue) { return; } - return result; - }).property(path); - }, - /** Creates a property from a SiteSetting. In the future the plan is for them to be able to update when changed. diff --git a/app/assets/javascripts/discourse/templates/components/private-message-map.hbs b/app/assets/javascripts/discourse/templates/components/private-message-map.hbs index 6b1dd73e7ba..54360aea35e 100644 --- a/app/assets/javascripts/discourse/templates/components/private-message-map.hbs +++ b/app/assets/javascripts/discourse/templates/components/private-message-map.hbs @@ -1,11 +1,11 @@

{{i18n private_message_info.title}}

- {{#grouped-each details.allowed_groups}} + {{#each details.allowed_groups}}
#{{unbound name}}
- {{/grouped-each}} - {{#grouped-each details.allowed_users}} + {{/each}} + {{#each details.allowed_users}}
{{#link-to 'user' this}} {{avatar this imageSize="small"}} @@ -17,7 +17,7 @@ {{/if}}
- {{/grouped-each}} + {{/each}}
{{#if details.can_invite_to}}
diff --git a/app/assets/javascripts/discourse/templates/components/topic-map.hbs b/app/assets/javascripts/discourse/templates/components/topic-map.hbs index 2ee9ad3a11f..1ea79eb44f2 100644 --- a/app/assets/javascripts/discourse/templates/components/topic-map.hbs +++ b/app/assets/javascripts/discourse/templates/components/topic-map.hbs @@ -46,7 +46,9 @@ {{/if}} {{#if showPosterAvatar}}
  • - {{#grouped-each participant in details.fewParticipants}}{{topic-participant participant=participant}}{{/grouped-each}} + {{#each details.fewParticipants}} + {{topic-participant participant=this}} + {{/each}}
  • {{/if}} @@ -54,14 +56,16 @@ {{#unless mapCollapsed}}
    - {{#grouped-each participant in details.participants}}{{topic-participant participant=participant}}{{/grouped-each}} + {{#each details.participants}} + {{topic-participant participant=this}} + {{/each}}
    {{#if infoLinks}}
    '); - }, - - willInsertElement: function() { - if (!this.get("height") || !this.get("rowHeight")) { - throw "A ListView must be created with a height and a rowHeight."; - } - this._super(); - }, - - /** - @private - - Sets inline styles of the view: - - height - - width - - position - - overflow - - -webkit-overflow - - overflow-scrolling - - Called while attributes binding. - - @property {Ember.ComputedProperty} style - */ - style: Ember.computed('height', 'width', function() { - var height, width, style, css; - - height = get(this, 'height'); - width = get(this, 'width'); - css = get(this, 'css'); - - style = ''; - - if (height) { style += 'height:' + height + 'px;'; } - if (width) { style += 'width:' + width + 'px;'; } - - for ( var rule in css ){ - if (css.hasOwnProperty(rule)) { - style += rule + ':' + css[rule] + ';'; + function willInsertElementIfNeeded(view) { + if (view.willInsertElement) { + view.willInsertElement(); } } - return style; - }), + function didInsertElementIfNeeded(view) { + if (view.didInsertElement) { + view.didInsertElement(); + } + } - /** - @private + function rerender() { + var element, buffer, context, hasChildViews; + element = get(this, 'element'); - Performs visual scrolling. Is overridden in Ember.ListView. + if (!element) { return; } - @method scrollTo - */ - scrollTo: function(y) { - throw 'must override to perform the visual scroll and effectively delegate to _scrollContentTo'; - }, + context = get(this, 'context'); - /** - @private - Internal method used to force scroll position + // releases action helpers in contents + // this means though that the ListItemView itself can't use classBindings or attributeBindings + // need support for rerender contents in ember + this.triggerRecursively('willClearRender'); - @method scrollTo - */ - _scrollTo: Ember.K, + if (this.lengthAfterRender > this.lengthBeforeRender) { + this.clearRenderedChildren(); + this._childViews.length = this.lengthBeforeRender; // triage bug in ember + } - /** - @private - @method _scrollContentTo - */ - _scrollContentTo: function(y) { - var startingIndex, endingIndex, - contentIndex, visibleEndingIndex, maxContentIndex, - contentIndexEnd, contentLength, scrollTop; + if (context) { + buffer = Ember.RenderBuffer(); + buffer = this.renderToBuffer(buffer); - scrollTop = max(0, y); + // check again for childViews, since rendering may have added some + hasChildViews = this._childViews.length > 0; - Ember.instrument('view._scrollContentTo', { - scrollTop: scrollTop, - content: get(this, 'content'), - startingIndex: this._startingIndex(), - endingIndex: min(max(get(this, 'content.length') - 1, 0), this._startingIndex() + this._numChildViewsForViewport()) - }, function () { - contentLength = get(this, 'content.length'); - set(this, 'scrollTop', scrollTop); + if (hasChildViews) { + this.invokeRecursively(willInsertElementIfNeeded, false); + } - maxContentIndex = max(contentLength - 1, 0); + element.innerHTML = buffer.innerString ? buffer.innerString() : backportedInnerString(buffer); - startingIndex = this._startingIndex(); - visibleEndingIndex = startingIndex + this._numChildViewsForViewport(); + set(this, 'element', element); - endingIndex = min(maxContentIndex, visibleEndingIndex); + var transitionTo = this._transitionTo ? this._transitionTo : this.transitionTo; - this.trigger('scrollYChanged', y); + transitionTo.call(this, 'inDOM'); - if (startingIndex === this._lastStartingIndex && - endingIndex === this._lastEndingIndex) { + if (hasChildViews) { + this.invokeRecursively(didInsertElementIfNeeded, false); + } + } else { + element.innerHTML = ''; // when there is no context, this view should be completely empty + } + } + + /** + The `Ember.ListItemView` view class renders a + [div](https://developer.mozilla.org/en/HTML/Element/div) HTML element + with `ember-list-item-view` class. It allows you to specify a custom item + handlebars template for `Ember.ListView`. + + Example: + + ```handlebars + + ``` + + ```javascript + App.ListView = Ember.ListView.extend({ + height: 500, + rowHeight: 20, + itemViewClass: Ember.ListItemView.extend({templateName: "row_item"}) + }); + ``` + + @extends Ember.View + @class ListItemView + @namespace Ember + */ + __exports__["default"] = Ember.View.extend(ListItemViewMixin, { + updateContext: function(newContext){ + var context = get(this, 'context'); + Ember.instrument('view.updateContext.render', this, function() { + if (context !== newContext) { + set(this, 'context', newContext); + if (newContext && newContext.isController) { + set(this, 'controller', newContext); + } + } + }, this); + }, + rerender: function () { + Ember.run.scheduleOnce('render', this, rerender); + }, + _contextDidChange: Ember.observer(rerender, 'context', 'controller') + }); + }); +define("list-view/list_item_view_mixin", + ["exports"], + function(__exports__) { + "use strict"; + // jshint validthis: true + + var get = Ember.get, set = Ember.set; + + function samePosition(a, b) { + return a && b && a.x === b.x && a.y === b.y; + } + + function positionElement() { + var element, position, _position; + + Ember.instrument('view.updateContext.positionElement', this, function() { + element = get(this, 'element'); + position = this.position; + _position = this._position; + + if (!position || !element) { return; } + + // TODO: avoid needing this by avoiding unnecessary + // calls to this method in the first place + if (samePosition(position, _position)) { return; } + Ember.run.schedule('render', this, this._parentView.applyTransform, element, position.x, position.y); + this._position = position; + }, this); + } + + __exports__["default"] = Ember.Mixin.create({ + init: function(){ + this._super(); + this.one('didInsertElement', positionElement); + }, + classNames: ['ember-list-item-view'], + _position: null, + updatePosition: function(position) { + this.position = position; + this._positionElement(); + }, + _positionElement: positionElement + }); + }); +define("list-view/list_view", + ["list-view/list_view_helper","list-view/list_view_mixin","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var ListViewHelper = __dependency1__["default"]; + var ListViewMixin = __dependency2__["default"]; + + var get = Ember.get, set = Ember.set; + + /** + The `Ember.ListView` view class renders a + [div](https://developer.mozilla.org/en/HTML/Element/div) HTML element, + with `ember-list-view` class. + + The context of each item element within the `Ember.ListView` are populated + from the objects in the `Element.ListView`'s `content` property. + + ### `content` as an Array of Objects + + The simplest version of an `Ember.ListView` takes an array of object as its + `content` property. The object will be used as the `context` each item element + inside the rendered `div`. + + Example: + + ```javascript + App.ContributorsRoute = Ember.Route.extend({ + model: function() { + return [{ name: 'Stefan Penner' }, { name: 'Alex Navasardyan' }, { name: 'Ray Cohen'}]; + } + }); + ``` + + ```handlebars + {{#ember-list items=contributors height=500 rowHeight=50}} + {{name}} + {{/ember-list}} + ``` + + Would result in the following HTML: + + ```html +
    +
    +
    + Stefan Penner +
    +
    + Alex Navasardyan +
    +
    + Rey Cohen +
    +
    +
    +
    + ``` + + By default `Ember.ListView` provides support for `height`, + `rowHeight`, `width`, `elementWidth`, `scrollTop` parameters. + + Note, that `height` and `rowHeight` are required parameters. + + ```handlebars + {{#ember-list items=this height=500 rowHeight=50}} + {{name}} + {{/ember-list}} + ``` + + If you would like to have multiple columns in your view layout, you can + set `width` and `elementWidth` parameters respectively. + + ```handlebars + {{#ember-list items=this height=500 rowHeight=50 width=500 elementWidth=80}} + {{name}} + {{/ember-list}} + ``` + + ### extending `Ember.ListView` + + Example: + + ```handlebars + {{view App.ListView contentBinding="content"}} + + + ``` + + ```javascript + App.ListView = Ember.ListView.extend({ + height: 500, + width: 500, + elementWidth: 80, + rowHeight: 20, + itemViewClass: Ember.ListItemView.extend({templateName: "row_item"}) + }); + ``` + + @extends Ember.ContainerView + @class ListView + @namespace Ember + */ + __exports__["default"] = Ember.ContainerView.extend(ListViewMixin, { + css: { + position: 'relative', + overflow: 'auto', + '-webkit-overflow-scrolling': 'touch', + 'overflow-scrolling': 'touch' + }, + + applyTransform: ListViewHelper.applyTransform, + + _scrollTo: function(scrollTop) { + var element = get(this, 'element'); + + if (element) { element.scrollTop = scrollTop; } + }, + + didInsertElement: function() { + var that = this; + var element = get(this, 'element'); + + this._updateScrollableHeight(); + + this._scroll = function(e) { that.scroll(e); }; + + Ember.$(element).on('scroll', this._scroll); + }, + + willDestroyElement: function() { + var element; + + element = get(this, 'element'); + + Ember.$(element).off('scroll', this._scroll); + }, + + scroll: function(e) { + this.scrollTo(e.target.scrollTop); + }, + + scrollTo: function(y){ + var element = get(this, 'element'); + this._scrollTo(y); + this._scrollContentTo(y); + }, + + totalHeightDidChange: Ember.observer(function () { + Ember.run.scheduleOnce('afterRender', this, this._updateScrollableHeight); + }, 'totalHeight'), + + _updateScrollableHeight: function () { + var height, state; + + // Support old and new Ember versions + state = this._state || this.state; + + if (state === 'inDOM') { + // if the list is currently displaying the emptyView, remove the height + if (this._isChildEmptyView()) { + height = ''; + } else { + height = get(this, 'totalHeight'); + } + + this.$('.ember-list-container').css({ + height: height + }); + } + } + }); + }); +define("list-view/list_view_helper", + ["exports"], + function(__exports__) { + "use strict"; + // TODO - remove this! + var el = document.createElement('div'), style = el.style; + + var propPrefixes = ['Webkit', 'Moz', 'O', 'ms']; + + function testProp(prop) { + if (prop in style) return prop; + var uppercaseProp = prop.charAt(0).toUpperCase() + prop.slice(1); + for (var i=0; i 0) { - // invoked by observer - Ember.run.schedule('afterRender', this, syncListContainerWidth); - } - }, 'columnCount'), - - /** - @private - - Computes max possible scrollTop value given the visible viewport - and scrollable container div height. - - @property {Ember.ComputedProperty} maxScrollTop - */ - maxScrollTop: Ember.computed('height', 'totalHeight', function(){ - var totalHeight, viewportHeight; - - totalHeight = get(this, 'totalHeight'); - viewportHeight = get(this, 'height'); - - return max(0, totalHeight - viewportHeight); - }), - - /** - @private - - Computes the number of views that would fit in the viewport area. - You must specify `height` and `rowHeight` parameters for the number of - views to be computed properly. - - @method _numChildViewsForViewport - */ - _numChildViewsForViewport: function() { - var height, rowHeight, paddingCount, columnCount; - - height = get(this, 'height'); - rowHeight = get(this, 'rowHeight'); - paddingCount = get(this, 'paddingCount'); - columnCount = get(this, 'columnCount'); - - return (ceil(height / rowHeight) * columnCount) + (paddingCount * columnCount); - }, - - /** - @private - - Computes the starting index of the item views array. - Takes `scrollTop` property of the element into account. - - Is used in `_syncChildViews`. - - @method _startingIndex - */ - _startingIndex: function() { - var scrollTop, rowHeight, columnCount, calculatedStartingIndex, - contentLength, largestStartingIndex; - - contentLength = get(this, 'content.length'); - scrollTop = get(this, 'scrollTop'); - rowHeight = get(this, 'rowHeight'); - columnCount = get(this, 'columnCount'); - - calculatedStartingIndex = floor(scrollTop / rowHeight) * columnCount; - - largestStartingIndex = max(contentLength - 1, 0); - - return min(calculatedStartingIndex, largestStartingIndex); - }, - - /** - @private - @event contentWillChange - */ - contentWillChange: Ember.beforeObserver(function() { - var content; - - content = get(this, 'content'); - - if (content) { - content.removeArrayObserver(this); - } - }, 'content'), - - /**), - @private - @event contentDidChange - */ - contentDidChange: Ember.observer(function() { - addContentArrayObserver.call(this); - syncChildViews.call(this); - }, 'content'), - - /** - @private - @property {Function} needsSyncChildViews - */ - needsSyncChildViews: Ember.observer(syncChildViews, 'height', 'width', 'columnCount'), - - /** - @private - - Returns a new item view. Takes `contentIndex` to set the context - of the returned view properly. - - @param {Number} contentIndex item index in the content array - @method _addItemView - */ - _addItemView: function(contentIndex){ - var itemViewClass, childView; - - itemViewClass = get(this, 'itemViewClass'); - childView = this.createChildView(itemViewClass); - - this.pushObject(childView); - }, - - /** - @private - - Intelligently manages the number of childviews. - - @method _syncChildViews - **/ - _syncChildViews: function(){ - var itemViewClass, startingIndex, childViewCount, - endingIndex, numberOfChildViews, numberOfChildViewsNeeded, - childViews, count, delta, index, childViewsLength, contentIndex; - - if (get(this, 'isDestroyed') || get(this, 'isDestroying')) { - return; - } - - childViewCount = this._childViewCount(); - childViews = this.positionOrderedChildViews(); - - startingIndex = this._startingIndex(); - endingIndex = startingIndex + childViewCount; - - numberOfChildViewsNeeded = childViewCount; - numberOfChildViews = childViews.length; - - delta = numberOfChildViewsNeeded - numberOfChildViews; - - if (delta === 0) { - // no change - } else if (delta > 0) { - // more views are needed - contentIndex = this._lastEndingIndex; - - for (count = 0; count < delta; count++, contentIndex++) { - this._addItemView(contentIndex); + function enableProfilingOutput() { + function before(name, time, payload) { + console.time(name); } - } else { - // less views are needed - forEach.call( - childViews.splice(numberOfChildViewsNeeded, numberOfChildViews), - removeAndDestroy, - this - ); - } - - this._scrollContentTo(get(this, 'scrollTop')); - - // if _scrollContentTo short-circuits, we still need - // to call _reuseChildren to get new views positioned - // and rendered correctly - this._reuseChildren(); - - this._lastStartingIndex = startingIndex; - this._lastEndingIndex = this._lastEndingIndex + delta; - }, - - /** - @private - @method _reuseChildren - */ - _reuseChildren: function(){ - var contentLength, childViews, childViewsLength, - startingIndex, endingIndex, childView, attrs, - contentIndex, visibleEndingIndex, maxContentIndex, - contentIndexEnd, scrollTop; - - scrollTop = get(this, 'scrollTop'); - contentLength = get(this, 'content.length'); - maxContentIndex = max(contentLength - 1, 0); - childViews = this._childViews; - childViewsLength = childViews.length; - - startingIndex = this._startingIndex(); - visibleEndingIndex = startingIndex + this._numChildViewsForViewport(); - - endingIndex = min(maxContentIndex, visibleEndingIndex); - - this.trigger('scrollContentTo', scrollTop); - - contentIndexEnd = min(visibleEndingIndex, startingIndex + childViewsLength); - - for (contentIndex = startingIndex; contentIndex < contentIndexEnd; contentIndex++) { - childView = childViews[contentIndex % childViewsLength]; - this._reuseChildForContentIndex(childView, contentIndex); - } - }, - - /** - @private - @method positionOrderedChildViews - */ - positionOrderedChildViews: function() { - return this._childViews.sort(sortByContentIndex); - }, - - arrayWillChange: Ember.K, - - /** - @private - @event arrayDidChange - */ - // TODO: refactor - arrayDidChange: function(content, start, removedCount, addedCount) { - var index, contentIndex; - - if (this.state === 'inDOM') { - // ignore if all changes are out of the visible change - if( start >= this._lastStartingIndex || start < this._lastEndingIndex) { - index = 0; - // ignore all changes not in the visible range - // this can re-position many, rather then causing a cascade of re-renders - forEach.call( - this.positionOrderedChildViews(), - function(childView) { - contentIndex = this._lastStartingIndex + index; - this._reuseChildForContentIndex(childView, contentIndex); - index++; - }, - this - ); + function after (name, time, payload) { + console.timeEnd(name); } - syncChildViews.call(this); + if (Ember.ENABLE_PROFILING) { + Ember.subscribe('view._scrollContentTo', { + before: before, + after: after + }); + Ember.subscribe('view.updateContext', { + before: before, + after: after + }); + } } - } -}); -})(); + /** + @class Ember.ListViewMixin + @namespace Ember + */ + __exports__["default"] = Ember.Mixin.create({ + itemViewClass: ReusableListItemView, + emptyViewClass: Ember.View, + classNames: ['ember-list-view'], + attributeBindings: ['style'], + classNameBindings: ['_isGrid:ember-list-view-grid:ember-list-view-list'], + domManager: domManager, + scrollTop: 0, + bottomPadding: 0, // TODO: maybe this can go away + _lastEndingIndex: 0, + paddingCount: 1, + _cachedPos: 0, + + _isGrid: Ember.computed('columnCount', function() { + return this.get('columnCount') > 1; + }).readOnly(), + + /** + @private + + Setup a mixin. + - adding observer to content array + - creating child views based on height and length of the content array + + @method init + */ + init: function() { + this._super(); + this._cachedHeights = [0]; + this.on('didInsertElement', this._syncListContainerWidth); + this.columnCountDidChange(); + this._syncChildViews(); + this._addContentArrayObserver(); + }, + + _addContentArrayObserver: Ember.beforeObserver(function() { + addContentArrayObserver.call(this); + }, 'content'), + + /** + Called on your view when it should push strings of HTML into a + `Ember.RenderBuffer`. + + Adds a [div](https://developer.mozilla.org/en-US/docs/HTML/Element/div) + with a required `ember-list-container` class. + + @method render + @param {Ember.RenderBuffer} buffer The render buffer + */ + render: function(buffer) { + buffer.push('
    '); + this._super(buffer); + buffer.push('
    '); + }, + + willInsertElement: function() { + if (!this.get("height") || !this.get("rowHeight")) { + throw new Error("A ListView must be created with a height and a rowHeight."); + } + this._super(); + }, + + /** + @private + + Sets inline styles of the view: + - height + - width + - position + - overflow + - -webkit-overflow + - overflow-scrolling + + Called while attributes binding. + + @property {Ember.ComputedProperty} style + */ + style: Ember.computed('height', 'width', function() { + var height, width, style, css; + + height = get(this, 'height'); + width = get(this, 'width'); + css = get(this, 'css'); + + style = ''; + + if (height) { + style += 'height:' + height + 'px;'; + } + + if (width) { + style += 'width:' + width + 'px;'; + } + + for ( var rule in css ) { + if (css.hasOwnProperty(rule)) { + style += rule + ':' + css[rule] + ';'; + } + } + + return style; + }), + + /** + @private + + Performs visual scrolling. Is overridden in Ember.ListView. + + @method scrollTo + */ + scrollTo: function(y) { + throw new Error('must override to perform the visual scroll and effectively delegate to _scrollContentTo'); + }, + + /** + @private + + Internal method used to force scroll position + + @method scrollTo + */ + _scrollTo: Ember.K, + + /** + @private + @method _scrollContentTo + */ + _scrollContentTo: function(y) { + var startingIndex, endingIndex, + contentIndex, visibleEndingIndex, maxContentIndex, + contentIndexEnd, contentLength, scrollTop, content; + + scrollTop = max(0, y); + + if (this.scrollTop === scrollTop) { + return; + } + + // allow a visual overscroll, but don't scroll the content. As we are doing needless + // recycyling, and adding unexpected nodes to the DOM. + var maxScrollTop = max(0, get(this, 'totalHeight') - get(this, 'height')); + scrollTop = min(scrollTop, maxScrollTop); + + content = get(this, 'content'); + contentLength = get(content, 'length'); + startingIndex = this._startingIndex(contentLength); + + Ember.instrument('view._scrollContentTo', { + scrollTop: scrollTop, + content: content, + startingIndex: startingIndex, + endingIndex: min(max(contentLength - 1, 0), startingIndex + this._numChildViewsForViewport()) + }, function () { + this.scrollTop = scrollTop; + + maxContentIndex = max(contentLength - 1, 0); + + startingIndex = this._startingIndex(); + visibleEndingIndex = startingIndex + this._numChildViewsForViewport(); + + endingIndex = min(maxContentIndex, visibleEndingIndex); + + if (startingIndex === this._lastStartingIndex && + endingIndex === this._lastEndingIndex) { + + this.trigger('scrollYChanged', y); + return; + } else { + + Ember.run(this, function() { + this._reuseChildren(); + + this._lastStartingIndex = startingIndex; + this._lastEndingIndex = endingIndex; + this.trigger('scrollYChanged', y); + }); + } + }, this); + + }, + + /** + @private + + Computes the height for a `Ember.ListView` scrollable container div. + You must specify `rowHeight` parameter for the height to be computed properly. + + @property {Ember.ComputedProperty} totalHeight + */ + totalHeight: Ember.computed('content.length', + 'rowHeight', + 'columnCount', + 'bottomPadding', function() { + if (typeof this.heightForIndex === 'function') { + return this._totalHeightWithHeightForIndex(); + } else { + return this._totalHeightWithStaticRowHeight(); + } + }), + + _doRowHeightDidChange: function() { + this._cachedHeights = [0]; + this._cachedPos = 0; + this._syncChildViews(); + }, + + _rowHeightDidChange: Ember.observer('rowHeight', function() { + Ember.run.once(this, this._doRowHeightDidChange); + }), + + _totalHeightWithHeightForIndex: function() { + var length = this.get('content.length'); + return this._cachedHeightLookup(length); + }, + + _totalHeightWithStaticRowHeight: function() { + var contentLength, rowHeight, columnCount, bottomPadding; + + contentLength = get(this, 'content.length'); + rowHeight = get(this, 'rowHeight'); + columnCount = get(this, 'columnCount'); + bottomPadding = get(this, 'bottomPadding'); + + return ((ceil(contentLength / columnCount)) * rowHeight) + bottomPadding; + }, + + /** + @private + @method _prepareChildForReuse + */ + _prepareChildForReuse: function(childView) { + childView.prepareForReuse(); + }, + + /** + @private + @method _reuseChildForContentIndex + */ + _reuseChildForContentIndex: function(childView, contentIndex) { + var content, context, newContext, childsCurrentContentIndex, position, enableProfiling, oldChildView; + + var contentViewClass = this.itemViewForIndex(contentIndex); + + if (childView.constructor !== contentViewClass) { + // rather then associative arrays, lets move childView + contentEntry maping to a Map + var i = this._childViews.indexOf(childView); + + childView.destroy(); + childView = this.createChildView(contentViewClass); + + this.insertAt(i, childView); + } + + content = get(this, 'content'); + enableProfiling = get(this, 'enableProfiling'); + position = this.positionForIndex(contentIndex); + childView.updatePosition(position); + + set(childView, 'contentIndex', contentIndex); + + if (enableProfiling) { + Ember.instrument('view._reuseChildForContentIndex', position, function() { + + }, this); + } + + newContext = content.objectAt(contentIndex); + childView.updateContext(newContext); + }, + + /** + @private + @method positionForIndex + */ + positionForIndex: function(index) { + if (typeof this.heightForIndex !== 'function') { + return this._singleHeightPosForIndex(index); + } + else { + return this._multiHeightPosForIndex(index); + } + }, + + _singleHeightPosForIndex: function(index) { + var elementWidth, width, columnCount, rowHeight, y, x; + + elementWidth = get(this, 'elementWidth') || 1; + width = get(this, 'width') || 1; + columnCount = get(this, 'columnCount'); + rowHeight = get(this, 'rowHeight'); + + y = (rowHeight * floor(index/columnCount)); + x = (index % columnCount) * elementWidth; + + return { + y: y, + x: x + }; + }, + + // 0 maps to 0, 1 maps to heightForIndex(i) + _multiHeightPosForIndex: function(index) { + var elementWidth, width, columnCount, rowHeight, y, x; + + elementWidth = get(this, 'elementWidth') || 1; + width = get(this, 'width') || 1; + columnCount = get(this, 'columnCount'); + + x = (index % columnCount) * elementWidth; + y = this._cachedHeightLookup(index); + + return { + x: x, + y: y + }; + }, + + _cachedHeightLookup: function(index) { + for (var i = this._cachedPos; i < index; i++) { + this._cachedHeights[i + 1] = this._cachedHeights[i] + this.heightForIndex(i); + } + this._cachedPos = i; + return this._cachedHeights[index]; + }, + + /** + @private + @method _childViewCount + */ + _childViewCount: function() { + var contentLength, childViewCountForHeight; + + contentLength = get(this, 'content.length'); + childViewCountForHeight = this._numChildViewsForViewport(); + + return min(contentLength, childViewCountForHeight); + }, + + /** + @private + + Returns a number of columns in the Ember.ListView (for grid layout). + + If you want to have a multi column layout, you need to specify both + `width` and `elementWidth`. + + If no `elementWidth` is specified, it returns `1`. Otherwise, it will + try to fit as many columns as possible for a given `width`. + + @property {Ember.ComputedProperty} columnCount + */ + columnCount: Ember.computed('width', 'elementWidth', function() { + var elementWidth, width, count; + + elementWidth = get(this, 'elementWidth'); + width = get(this, 'width'); + + if (elementWidth && width > elementWidth) { + count = floor(width / elementWidth); + } else { + count = 1; + } + + return count; + }), + + /** + @private + + Fires every time column count is changed. + + @event columnCountDidChange + */ + columnCountDidChange: Ember.observer(function() { + var ratio, currentScrollTop, proposedScrollTop, maxScrollTop, + scrollTop, lastColumnCount, newColumnCount, element; + + lastColumnCount = this._lastColumnCount; + + currentScrollTop = this.scrollTop; + newColumnCount = get(this, 'columnCount'); + maxScrollTop = get(this, 'maxScrollTop'); + element = get(this, 'element'); + + this._lastColumnCount = newColumnCount; + + if (lastColumnCount) { + ratio = (lastColumnCount / newColumnCount); + proposedScrollTop = currentScrollTop * ratio; + scrollTop = min(maxScrollTop, proposedScrollTop); + + this._scrollTo(scrollTop); + this.scrollTop = scrollTop; + } + + if (arguments.length > 0) { + // invoked by observer + Ember.run.schedule('afterRender', this, this._syncListContainerWidth); + } + }, 'columnCount'), + + /** + @private + + Computes max possible scrollTop value given the visible viewport + and scrollable container div height. + + @property {Ember.ComputedProperty} maxScrollTop + */ + maxScrollTop: Ember.computed('height', 'totalHeight', function(){ + var totalHeight, viewportHeight; + + totalHeight = get(this, 'totalHeight'); + viewportHeight = get(this, 'height'); + + return max(0, totalHeight - viewportHeight); + }), + + /** + @private + + Determines whether the emptyView is the current childView. + + @method _isChildEmptyView + */ + _isChildEmptyView: function() { + var emptyView = get(this, 'emptyView'); + + return emptyView && emptyView instanceof Ember.View && + this._childViews.length === 1 && this._childViews.indexOf(emptyView) === 0; + }, + + /** + @private + + Computes the number of views that would fit in the viewport area. + You must specify `height` and `rowHeight` parameters for the number of + views to be computed properly. + + @method _numChildViewsForViewport + */ + _numChildViewsForViewport: function() { + + if (this.heightForIndex) { + return this._numChildViewsForViewportWithMultiHeight(); + } else { + return this._numChildViewsForViewportWithoutMultiHeight(); + } + }, + + _numChildViewsForViewportWithoutMultiHeight: function() { + var height, rowHeight, paddingCount, columnCount; + + height = get(this, 'height'); + rowHeight = get(this, 'rowHeight'); + paddingCount = get(this, 'paddingCount'); + columnCount = get(this, 'columnCount'); + + return (ceil(height / rowHeight) * columnCount) + (paddingCount * columnCount); + }, + + _numChildViewsForViewportWithMultiHeight: function() { + var rowHeight, paddingCount, columnCount; + var scrollTop = this.scrollTop; + var viewportHeight = this.get('height'); + var length = this.get('content.length'); + var heightfromTop = 0; + var padding = get(this, 'paddingCount'); + + var startingIndex = this._calculatedStartingIndex(); + var currentHeight = 0; + + var offsetHeight = this._cachedHeightLookup(startingIndex); + for (var i = 0; i < length; i++) { + if (this._cachedHeightLookup(startingIndex + i + 1) - offsetHeight > viewportHeight) { + break; + } + } + + return i + padding + 1; + }, + /** + @private -(function() { -var get = Ember.get, set = Ember.set; + Computes the starting index of the item views array. + Takes `scrollTop` property of the element into account. -/** - The `Ember.ListView` view class renders a - [div](https://developer.mozilla.org/en/HTML/Element/div) HTML element, - with `ember-list-view` class. + Is used in `_syncChildViews`. - The context of each item element within the `Ember.ListView` are populated - from the objects in the `Element.ListView`'s `content` property. + @method _startingIndex + */ + _startingIndex: function(_contentLength) { + var scrollTop, rowHeight, columnCount, calculatedStartingIndex, + contentLength; - ### `content` as an Array of Objects + if (_contentLength === undefined) { + contentLength = get(this, 'content.length'); + } else { + contentLength = _contentLength; + } - The simplest version of an `Ember.ListView` takes an array of object as its - `content` property. The object will be used as the `context` each item element - inside the rendered `div`. + scrollTop = this.scrollTop; + rowHeight = get(this, 'rowHeight'); + columnCount = get(this, 'columnCount'); - Example: + if (this.heightForIndex) { + calculatedStartingIndex = this._calculatedStartingIndex(); + } else { + calculatedStartingIndex = floor(scrollTop / rowHeight) * columnCount; + } - ```javascript - App.contributors = [{ name: 'Stefan Penner' }, { name: 'Alex Navasardyan' }, { name: 'Rey Cohen'}]; - ``` + var viewsNeededForViewport = this._numChildViewsForViewport(); + var paddingCount = (1 * columnCount); + var largestStartingIndex = max(contentLength - viewsNeededForViewport, 0); - ```handlebars - {{#collection Ember.ListView contentBinding="App.contributors" height=500 rowHeight=50}} - {{name}} - {{/collection}} - ``` + return min(calculatedStartingIndex, largestStartingIndex); + }, - Would result in the following HTML: + _calculatedStartingIndex: function() { + var rowHeight, paddingCount, columnCount; + var scrollTop = this.scrollTop; + var viewportHeight = this.get('height'); + var length = this.get('content.length'); + var heightfromTop = 0; + var padding = get(this, 'paddingCount'); - ```html -
    -
    -
    - Stefan Penner -
    -
    - Alex Navasardyan -
    -
    - Rey Cohen -
    -
    -
    -
    - ``` + for (var i = 0; i < length; i++) { + if (this._cachedHeightLookup(i + 1) >= scrollTop) { + break; + } + } - By default `Ember.ListView` provides support for `height`, - `rowHeight`, `width`, `elementWidth`, `scrollTop` parameters. + return i; + }, - Note, that `height` and `rowHeight` are required parameters. + /** + @private + @event contentWillChange + */ + contentWillChange: Ember.beforeObserver(function() { + var content; - ```handlebars - {{#collection Ember.ListView contentBinding="App.contributors" height=500 rowHeight=50}} - {{name}} - {{/collection}} - ``` + content = get(this, 'content'); - If you would like to have multiple columns in your view layout, you can - set `width` and `elementWidth` parameters respectively. + if (content) { + content.removeArrayObserver(this); + } + }, 'content'), - ```handlebars - {{#collection Ember.ListView contentBinding="App.contributors" height=500 rowHeight=50 width=500 elementWidth=80}} - {{name}} - {{/collection}} - ``` + /**), + @private + @event contentDidChange + */ + contentDidChange: Ember.observer(function() { + addContentArrayObserver.call(this); + syncChildViews.call(this); + }, 'content'), - ### extending `Ember.ListView` + /** + @private + @property {Function} needsSyncChildViews + */ + needsSyncChildViews: Ember.observer(syncChildViews, 'height', 'width', 'columnCount'), - Example: + /** + @private - ```handlebars - {{view App.ListView contentBinding="content"}} + Returns a new item view. Takes `contentIndex` to set the context + of the returned view properly. - - ``` + @param {Number} contentIndex item index in the content array + @method _addItemView + */ + _addItemView: function(contentIndex){ + var itemViewClass, childView; - ```javascript - App.ListView = Ember.ListView.extend({ - height: 500, - width: 500, - elementWidth: 80, - rowHeight: 20, - itemViewClass: Ember.ListItemView.extend({templateName: "row_item"}) + itemViewClass = this.itemViewForIndex(contentIndex); + childView = this.createChildView(itemViewClass); + + this.pushObject(childView); + }, + + /** + @public + + Returns a view class for the provided contentIndex. If the view is + different then the one currently present it will remove the existing view + and replace it with an instance of the class provided + + @param {Number} contentIndex item index in the content array + @method _addItemView + @returns {Ember.View} ember view class for this index + */ + itemViewForIndex: function(contentIndex) { + return get(this, 'itemViewClass'); + }, + + /** + @public + + Returns a view class for the provided contentIndex. If the view is + different then the one currently present it will remove the existing view + and replace it with an instance of the class provided + + @param {Number} contentIndex item index in the content array + @method _addItemView + @returns {Ember.View} ember view class for this index + */ + heightForIndex: null, + + /** + @private + + Intelligently manages the number of childviews. + + @method _syncChildViews + **/ + _syncChildViews: function(){ + var childViews, childViewCount, + numberOfChildViews, numberOfChildViewsNeeded, + contentIndex, startingIndex, endingIndex, + contentLength, emptyView, count, delta; + + if (get(this, 'isDestroyed') || get(this, 'isDestroying')) { + return; + } + + contentLength = get(this, 'content.length'); + emptyView = get(this, 'emptyView'); + + childViewCount = this._childViewCount(); + childViews = this.positionOrderedChildViews(); + + if (this._isChildEmptyView()) { + removeEmptyView.call(this); + } + + startingIndex = this._startingIndex(); + endingIndex = startingIndex + childViewCount; + + numberOfChildViewsNeeded = childViewCount; + numberOfChildViews = childViews.length; + + delta = numberOfChildViewsNeeded - numberOfChildViews; + + if (delta === 0) { + // no change + } else if (delta > 0) { + // more views are needed + contentIndex = this._lastEndingIndex; + + for (count = 0; count < delta; count++, contentIndex++) { + this._addItemView(contentIndex); + } + } else { + // less views are needed + forEach.call( + childViews.splice(numberOfChildViewsNeeded, numberOfChildViews), + removeAndDestroy, + this + ); + } + + this._reuseChildren(); + + this._lastStartingIndex = startingIndex; + this._lastEndingIndex = this._lastEndingIndex + delta; + + if (contentLength === 0 || contentLength === undefined) { + addEmptyView.call(this); + } + }, + + /** + @private + + Applies an inline width style to the list container. + + @method _syncListContainerWidth + **/ + _syncListContainerWidth: function() { + var elementWidth, columnCount, containerWidth, element; + + elementWidth = get(this, 'elementWidth'); + columnCount = get(this, 'columnCount'); + containerWidth = elementWidth * columnCount; + element = this.$('.ember-list-container'); + + if (containerWidth && element) { + element.css('width', containerWidth); + } + }, + + /** + @private + @method _reuseChildren + */ + _reuseChildren: function(){ + var contentLength, childViews, childViewsLength, + startingIndex, endingIndex, childView, attrs, + contentIndex, visibleEndingIndex, maxContentIndex, + contentIndexEnd, scrollTop; + + scrollTop = this.scrollTop; + contentLength = get(this, 'content.length'); + maxContentIndex = max(contentLength - 1, 0); + childViews = this.getReusableChildViews(); + childViewsLength = childViews.length; + + startingIndex = this._startingIndex(); + visibleEndingIndex = startingIndex + this._numChildViewsForViewport(); + + endingIndex = min(maxContentIndex, visibleEndingIndex); + + contentIndexEnd = min(visibleEndingIndex, startingIndex + childViewsLength); + + for (contentIndex = startingIndex; contentIndex < contentIndexEnd; contentIndex++) { + childView = childViews[contentIndex % childViewsLength]; + this._reuseChildForContentIndex(childView, contentIndex); + } + }, + + /** + @private + @method getReusableChildViews + */ + getReusableChildViews: function() { + return this._childViews; + }, + + /** + @private + @method positionOrderedChildViews + */ + positionOrderedChildViews: function() { + return this.getReusableChildViews().sort(sortByContentIndex); + }, + + arrayWillChange: Ember.K, + + /** + @private + @event arrayDidChange + */ + // TODO: refactor + arrayDidChange: function(content, start, removedCount, addedCount) { + var index, contentIndex, state; + + if (this._isChildEmptyView()) { + removeEmptyView.call(this); + } + + // Support old and new Ember versions + state = this._state || this.state; + + if (state === 'inDOM') { + // ignore if all changes are out of the visible change + if (start >= this._lastStartingIndex || start < this._lastEndingIndex) { + index = 0; + // ignore all changes not in the visible range + // this can re-position many, rather then causing a cascade of re-renders + forEach.call( + this.positionOrderedChildViews(), + function(childView) { + contentIndex = this._lastStartingIndex + index; + this._reuseChildForContentIndex(childView, contentIndex); + index++; + }, + this + ); + } + + syncChildViews.call(this); + } + }, + + destroy: function () { + if (!this._super()) { return; } + + if (this._createdEmptyView) { + this._createdEmptyView.destroy(); + } + + return this; + } + }); }); - ``` +define("list-view/main", + ["list-view/reusable_list_item_view","list-view/virtual_list_view","list-view/list_item_view","list-view/helper","list-view/list_view","list-view/list_view_helper"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__) { + "use strict"; + var ReusableListItemView = __dependency1__["default"]; + var VirtualListView = __dependency2__["default"]; + var ListItemView = __dependency3__["default"]; + var EmberList = __dependency4__["default"]; + var ListView = __dependency5__["default"]; + var ListViewHelper = __dependency6__["default"]; - @extends Ember.ContainerView - @class ListView - @namespace Ember -*/ -Ember.ListView = Ember.ContainerView.extend(Ember.ListViewMixin, { - css: { - position: 'relative', - overflow: 'scroll', - '-webkit-overflow-scrolling': 'touch', - 'overflow-scrolling': 'touch' - }, + Ember.ReusableListItemView = ReusableListItemView; + Ember.VirtualListView = VirtualListView; + Ember.ListItemView = ListItemView; + Ember.ListView = ListView; + Ember.ListViewHelper = ListViewHelper; - applyTransform: Ember.ListViewHelper.applyTransform, + Ember.Handlebars.registerHelper('ember-list', EmberList); + }); +define("list-view/reusable_list_item_view", + ["list-view/list_item_view_mixin","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var ListItemViewMixin = __dependency1__["default"]; - _scrollTo: function(scrollTop) { - var element = get(this, 'element'); + var get = Ember.get, set = Ember.set; - if (element) { element.scrollTop = scrollTop; } - }, + __exports__["default"] = Ember.View.extend(ListItemViewMixin, { + init: function(){ + this._super(); + var context = Ember.ObjectProxy.create(); + this.set('context', context); + this._proxyContext = context; + }, + isVisible: Ember.computed('context.content', function(){ + return !!this.get('context.content'); + }), + updateContext: function(newContext){ + var context = get(this._proxyContext, 'content'), state; - didInsertElement: function() { - var that, element; + // Support old and new Ember versions + state = this._state || this.state; - that = this, - element = get(this, 'element'); + if (context !== newContext) { + if (state === 'inDOM') { + this.prepareForReuse(newContext); + } - this._updateScrollableHeight(); + set(this._proxyContext, 'content', newContext); - this._scroll = function(e) { that.scroll(e); }; - - Ember.$(element).on('scroll', this._scroll); - }, - - willDestroyElement: function() { - var element; - - element = get(this, 'element'); - - Ember.$(element).off('scroll', this._scroll); - }, - - scroll: function(e) { - Ember.run(this, this.scrollTo, e.target.scrollTop); - }, - - scrollTo: function(y){ - var element = get(this, 'element'); - this._scrollTo(y); - this._scrollContentTo(y); - }, - - totalHeightDidChange: Ember.observer(function () { - Ember.run.scheduleOnce('afterRender', this, this._updateScrollableHeight); - }, 'totalHeight'), - - _updateScrollableHeight: function () { - if (this.state === 'inDOM') { - this.$('.ember-list-container').css({ - height: get(this, 'totalHeight') - }); + if (newContext && newContext.isController) { + set(this, 'controller', newContext); + } + } + }, + prepareForReuse: Ember.K + }); + }); +define("list-view/virtual_list_scroller_events", + ["exports"], + function(__exports__) { + "use strict"; + // jshint validthis: true + var fieldRegex = /input|textarea|select/i, + hasTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch, + handleStart, handleMove, handleEnd, handleCancel, + startEvent, moveEvent, endEvent, cancelEvent; + if (hasTouch) { + startEvent = 'touchstart'; + handleStart = function (e) { + var touch = e.touches[0], + target = touch && touch.target; + // avoid e.preventDefault() on fields + if (target && fieldRegex.test(target.tagName)) { + return; + } + bindWindow(this.scrollerEventHandlers); + this.willBeginScroll(e.touches, e.timeStamp); + e.preventDefault(); + }; + moveEvent = 'touchmove'; + handleMove = function (e) { + this.continueScroll(e.touches, e.timeStamp); + }; + endEvent = 'touchend'; + handleEnd = function (e) { + // if we didn't end up scrolling we need to + // synthesize click since we did e.preventDefault() + // on touchstart + if (!this._isScrolling) { + synthesizeClick(e); + } + unbindWindow(this.scrollerEventHandlers); + this.endScroll(e.timeStamp); + }; + cancelEvent = 'touchcancel'; + handleCancel = function (e) { + unbindWindow(this.scrollerEventHandlers); + this.endScroll(e.timeStamp); + }; + } else { + startEvent = 'mousedown'; + handleStart = function (e) { + if (e.which !== 1) return; + var target = e.target; + // avoid e.preventDefault() on fields + if (target && fieldRegex.test(target.tagName)) { + return; + } + bindWindow(this.scrollerEventHandlers); + this.willBeginScroll([e], e.timeStamp); + e.preventDefault(); + }; + moveEvent = 'mousemove'; + handleMove = function (e) { + this.continueScroll([e], e.timeStamp); + }; + endEvent = 'mouseup'; + handleEnd = function (e) { + unbindWindow(this.scrollerEventHandlers); + this.endScroll(e.timeStamp); + }; + cancelEvent = 'mouseout'; + handleCancel = function (e) { + if (e.relatedTarget) return; + unbindWindow(this.scrollerEventHandlers); + this.endScroll(e.timeStamp); + }; } - } -}); -})(); - - - -(function() { -var fieldRegex = /input|textarea|select/i, - hasTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch, - handleStart, handleMove, handleEnd, handleCancel, - startEvent, moveEvent, endEvent, cancelEvent; -if (hasTouch) { - startEvent = 'touchstart'; - handleStart = function (e) { - var touch = e.touches[0], - target = touch && touch.target; - // avoid e.preventDefault() on fields - if (target && fieldRegex.test(target.tagName)) { - return; + function handleWheel(e) { + this.mouseWheel(e); + e.preventDefault(); } - bindWindow(this.scrollerEventHandlers); - this.willBeginScroll(e.touches, e.timeStamp); - e.preventDefault(); - }; - moveEvent = 'touchmove'; - handleMove = function (e) { - this.continueScroll(e.touches, e.timeStamp); - }; - endEvent = 'touchend'; - handleEnd = function (e) { - // if we didn't end up scrolling we need to - // synthesize click since we did e.preventDefault() - // on touchstart - if (!this._isScrolling) { - synthesizeClick(e); + + function bindElement(el, handlers) { + el.addEventListener(startEvent, handlers.start, false); + el.addEventListener('mousewheel', handlers.wheel, false); } - unbindWindow(this.scrollerEventHandlers); - this.endScroll(e.timeStamp); - }; - cancelEvent = 'touchcancel'; - handleCancel = function (e) { - unbindWindow(this.scrollerEventHandlers); - this.endScroll(e.timeStamp); - }; -} else { - startEvent = 'mousedown'; - handleStart = function (e) { - if (e.which !== 1) return; - var target = e.target; - // avoid e.preventDefault() on fields - if (target && fieldRegex.test(target.tagName)) { - return; + + function unbindElement(el, handlers) { + el.removeEventListener(startEvent, handlers.start, false); + el.removeEventListener('mousewheel', handlers.wheel, false); } - bindWindow(this.scrollerEventHandlers); - this.willBeginScroll([e], e.timeStamp); - e.preventDefault(); - }; - moveEvent = 'mousemove'; - handleMove = function (e) { - this.continueScroll([e], e.timeStamp); - }; - endEvent = 'mouseup'; - handleEnd = function (e) { - unbindWindow(this.scrollerEventHandlers); - this.endScroll(e.timeStamp); - }; - cancelEvent = 'mouseout'; - handleCancel = function (e) { - if (e.relatedTarget) return; - unbindWindow(this.scrollerEventHandlers); - this.endScroll(e.timeStamp); - }; -} -function handleWheel(e) { - this.mouseWheel(e); - e.preventDefault(); -} + function bindWindow(handlers) { + window.addEventListener(moveEvent, handlers.move, true); + window.addEventListener(endEvent, handlers.end, true); + window.addEventListener(cancelEvent, handlers.cancel, true); + } -function bindElement(el, handlers) { - el.addEventListener(startEvent, handlers.start, false); - el.addEventListener('mousewheel', handlers.wheel, false); -} + function unbindWindow(handlers) { + window.removeEventListener(moveEvent, handlers.move, true); + window.removeEventListener(endEvent, handlers.end, true); + window.removeEventListener(cancelEvent, handlers.cancel, true); + } -function unbindElement(el, handlers) { - el.removeEventListener(startEvent, handlers.start, false); - el.removeEventListener('mousewheel', handlers.wheel, false); -} - -function bindWindow(handlers) { - window.addEventListener(moveEvent, handlers.move, true); - window.addEventListener(endEvent, handlers.end, true); - window.addEventListener(cancelEvent, handlers.cancel, true); -} - -function unbindWindow(handlers) { - window.removeEventListener(moveEvent, handlers.move, true); - window.removeEventListener(endEvent, handlers.end, true); - window.removeEventListener(cancelEvent, handlers.cancel, true); -} - -Ember.VirtualListScrollerEvents = Ember.Mixin.create({ - init: function() { - this.on('didInsertElement', this, 'bindScrollerEvents'); - this.on('willDestroyElement', this, 'unbindScrollerEvents'); - this.scrollerEventHandlers = { - start: bind(this, handleStart), - move: bind(this, handleMove), - end: bind(this, handleEnd), - cancel: bind(this, handleCancel), - wheel: bind(this, handleWheel) - }; - return this._super(); - }, - bindScrollerEvents: function() { - var el = this.get('element'), - handlers = this.scrollerEventHandlers; - bindElement(el, handlers); - }, - unbindScrollerEvents: function() { - var el = this.get('element'), - handlers = this.scrollerEventHandlers; - unbindElement(el, handlers); - unbindWindow(handlers); - } -}); - -function bind(view, handler) { - return function (evt) { - handler.call(view, evt); - }; -} - -function synthesizeClick(e) { - var point = e.changedTouches[0], - target = point.target, - ev; - if (target && fieldRegex.test(target.tagName)) { - ev = document.createEvent('MouseEvents'); - ev.initMouseEvent('click', true, true, e.view, 1, point.screenX, point.screenY, point.clientX, point.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null); - return target.dispatchEvent(ev); - } -} - -})(); - - - -(function() { -/*global Scroller*/ -var max = Math.max, get = Ember.get, set = Ember.set; - -function updateScrollerDimensions(target) { - var width, height, totalHeight; - - target = target || this; - - width = get(target, 'width'); - height = get(target, 'height'); - totalHeight = get(target, 'totalHeight'); - - target.scroller.setDimensions(width, height, width, totalHeight); - target.trigger('scrollerDimensionsDidChange'); -} - -/** - VirtualListView - - @class VirtualListView - @namespace Ember -*/ -Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, Ember.VirtualListScrollerEvents, { - _isScrolling: false, - _mouseWheel: null, - css: { - position: 'relative', - overflow: 'hidden' - }, - - init: function(){ - this._super(); - this.setupScroller(); - }, - _scrollerTop: 0, - applyTransform: Ember.ListViewHelper.apply3DTransform, - - setupScroller: function(){ - var view, y; - - view = this; - - view.scroller = new Scroller(function(left, top, zoom) { - if (view.state !== 'inDOM') { return; } - - if (view.listContainerElement) { - view.applyTransform(view.listContainerElement, 0, -top); - view._scrollerTop = top; - view._scrollContentTo(top); - } - }, { - scrollingX: false, - scrollingComplete: function(){ - view.trigger('scrollingDidComplete'); + __exports__["default"] = Ember.Mixin.create({ + init: function() { + this.on('didInsertElement', this, 'bindScrollerEvents'); + this.on('willDestroyElement', this, 'unbindScrollerEvents'); + this.scrollerEventHandlers = { + start: bind(this, handleStart), + move: bind(this, handleMove), + end: bind(this, handleEnd), + cancel: bind(this, handleCancel), + wheel: bind(this, handleWheel) + }; + return this._super(); + }, + scrollElement: Ember.computed.oneWay('element').readOnly(), + bindScrollerEvents: function() { + var el = this.get('scrollElement'), + handlers = this.scrollerEventHandlers; + bindElement(el, handlers); + }, + unbindScrollerEvents: function() { + var el = this.get('scrollElement'), + handlers = this.scrollerEventHandlers; + unbindElement(el, handlers); + unbindWindow(handlers); } }); - view.trigger('didInitializeScroller'); - updateScrollerDimensions(view); - }, + function bind(view, handler) { + return function (evt) { + handler.call(view, evt); + }; + } - scrollerDimensionsNeedToChange: Ember.observer(function() { - Ember.run.once(this, updateScrollerDimensions); - }, 'width', 'height', 'totalHeight'), - - didInsertElement: function() { - this.listContainerElement = this.$('> .ember-list-container')[0]; - }, - - willBeginScroll: function(touches, timeStamp) { - this._isScrolling = false; - this.trigger('scrollingDidStart'); - - this.scroller.doTouchStart(touches, timeStamp); - }, - - continueScroll: function(touches, timeStamp) { - var startingScrollTop, endingScrollTop, event; - - if (this._isScrolling) { - this.scroller.doTouchMove(touches, timeStamp); - } else { - startingScrollTop = this._scrollerTop; - - this.scroller.doTouchMove(touches, timeStamp); - - endingScrollTop = this._scrollerTop; - - if (startingScrollTop !== endingScrollTop) { - event = Ember.$.Event("scrollerstart"); - Ember.$(touches[0].target).trigger(event); - - this._isScrolling = true; + function synthesizeClick(e) { + var point = e.changedTouches[0], + target = point.target, + ev; + if (target && fieldRegex.test(target.tagName)) { + ev = document.createEvent('MouseEvents'); + ev.initMouseEvent('click', true, true, e.view, 1, point.screenX, point.screenY, point.clientX, point.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null); + return target.dispatchEvent(ev); } } - }, + }); +define("list-view/virtual_list_view", + ["list-view/list_view_mixin","list-view/list_view_helper","list-view/virtual_list_scroller_events","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + /* + global Scroller + */ - endScroll: function(timeStamp) { - this.scroller.doTouchEnd(timeStamp); - }, + var ListViewMixin = __dependency1__["default"]; + var ListViewHelper = __dependency2__["default"]; + var VirtualListScrollerEvents = __dependency3__["default"]; - // api - scrollTo: function(y, animate) { - if (animate === undefined) { - animate = true; + var max = Math.max, get = Ember.get, set = Ember.set; + + function updateScrollerDimensions(target) { + var width, height, totalHeight; + + target = target || this; // jshint ignore:line + + width = get(target, 'width'); + height = get(target, 'height'); + totalHeight = get(target, 'totalHeight'); // jshint ignore:line + + target.scroller.setDimensions(width, height, width, totalHeight); + target.trigger('scrollerDimensionsDidChange'); } - this.scroller.scrollTo(0, y, animate, 1); - }, + /** + VirtualListView - // events - mouseWheel: function(e){ - var inverted, delta, candidatePosition; + @class VirtualListView + @namespace Ember + */ + __exports__["default"] = Ember.ContainerView.extend(ListViewMixin, VirtualListScrollerEvents, { + _isScrolling: false, + _mouseWheel: null, + css: { + position: 'relative', + overflow: 'hidden' + }, - inverted = e.webkitDirectionInvertedFromDevice; - delta = e.wheelDeltaY * (inverted ? 0.8 : -0.8); - candidatePosition = this.scroller.__scrollTop + delta; + init: function(){ + this._super(); + this.setupScroller(); + this.setupPullToRefresh(); + }, + _scrollerTop: 0, + applyTransform: ListViewHelper.apply3DTransform, - if ((candidatePosition >= 0) && (candidatePosition <= this.scroller.__maxScrollTop)) { - this.scroller.scrollBy(0, delta, true); - } + setupScroller: function(){ + var view, y; - return false; - } -}); + view = this; -})(); + view.scroller = new Scroller(function(left, top, zoom) { + // Support old and new Ember versions + var state = view._state || view.state; + if (state !== 'inDOM') { return; } + if (view.listContainerElement) { + view._scrollerTop = top; + view._scrollContentTo(top); + view.applyTransform(view.listContainerElement, 0, -top); + } + }, { + scrollingX: false, + scrollingComplete: function(){ + view.trigger('scrollingDidComplete'); + } + }); -(function() { + view.trigger('didInitializeScroller'); + updateScrollerDimensions(view); + }, + setupPullToRefresh: function() { + if (!this.pullToRefreshViewClass) { return; } + this._insertPullToRefreshView(); + this._activateScrollerPullToRefresh(); + }, + _insertPullToRefreshView: function(){ + this.pullToRefreshView = this.createChildView(this.pullToRefreshViewClass); + this.insertAt(0, this.pullToRefreshView); + var view = this; + this.pullToRefreshView.on('didInsertElement', function(){ + Ember.run.schedule('afterRender', this, function(){ + view.applyTransform(this.get('element'), 0, -1 * view.pullToRefreshViewHeight); + }); + }); + }, + _activateScrollerPullToRefresh: function(){ + var view = this; + function activatePullToRefresh(){ + view.pullToRefreshView.set('active', true); + view.trigger('activatePullToRefresh'); + } + function deactivatePullToRefresh() { + view.pullToRefreshView.set('active', false); + view.trigger('deactivatePullToRefresh'); + } + function startPullToRefresh() { + Ember.run(function(){ + view.pullToRefreshView.set('refreshing', true); -})(); + function finishRefresh(){ + if (view && !view.get('isDestroyed') && !view.get('isDestroying')) { + view.scroller.finishPullToRefresh(); + view.pullToRefreshView.set('refreshing', false); + } + } + view.startRefresh(finishRefresh); + }); + } + this.scroller.activatePullToRefresh( + this.pullToRefreshViewHeight, + activatePullToRefresh, + deactivatePullToRefresh, + startPullToRefresh + ); + }, + getReusableChildViews: function(){ + var firstView = this._childViews[0]; + if (firstView && firstView === this.pullToRefreshView) { + return this._childViews.slice(1); + } else { + return this._childViews; + } + }, + + scrollerDimensionsNeedToChange: Ember.observer(function() { + Ember.run.once(this, updateScrollerDimensions); + }, 'width', 'height', 'totalHeight'), + + didInsertElement: function() { + this.listContainerElement = this.$('> .ember-list-container')[0]; + }, + + willBeginScroll: function(touches, timeStamp) { + this._isScrolling = false; + this.trigger('scrollingDidStart'); + + this.scroller.doTouchStart(touches, timeStamp); + }, + + continueScroll: function(touches, timeStamp) { + var startingScrollTop, endingScrollTop, event; + + if (this._isScrolling) { + this.scroller.doTouchMove(touches, timeStamp); + } else { + startingScrollTop = this._scrollerTop; + + this.scroller.doTouchMove(touches, timeStamp); + + endingScrollTop = this._scrollerTop; + + if (startingScrollTop !== endingScrollTop) { + event = Ember.$.Event("scrollerstart"); + Ember.$(touches[0].target).trigger(event); + + this._isScrolling = true; + } + } + }, + + endScroll: function(timeStamp) { + this.scroller.doTouchEnd(timeStamp); + }, + + // api + scrollTo: function(y, animate) { + if (animate === undefined) { + animate = true; + } + + this.scroller.scrollTo(0, y, animate, 1); + }, + + // events + mouseWheel: function(e){ + var inverted, delta, candidatePosition; + + inverted = e.webkitDirectionInvertedFromDevice; + delta = e.wheelDeltaY * (inverted ? 0.8 : -0.8); + candidatePosition = this.scroller.__scrollTop + delta; + + if ((candidatePosition >= 0) && (candidatePosition <= this.scroller.__maxScrollTop)) { + this.scroller.scrollBy(0, delta, true); + e.stopPropagation(); + } + + return false; + } + }); + }); + requireModule('list-view/main'); +})(this); \ No newline at end of file diff --git a/vendor/assets/javascripts/production/ember.js b/vendor/assets/javascripts/production/ember.js index 497265a63a3..2744df8552d 100644 --- a/vendor/assets/javascripts/production/ember.js +++ b/vendor/assets/javascripts/production/ember.js @@ -5,10 +5,9 @@ * Portions Copyright 2008-2011 Apple Inc. All rights reserved. * @license Licensed under MIT license * See https://raw.github.com/emberjs/ember.js/master/LICENSE - * @version 1.6.0-beta.3+pre.2e4d67f6 + * @version 1.7.1 */ - (function() { var define, requireModule, require, requirejs, Ember; @@ -72,22 +71,10425 @@ var define, requireModule, require, requirejs, Ember; requirejs = require = requireModule = Ember.__loader.require; } })(); -(function() { + +define("backburner", + ["backburner/utils","backburner/deferred_action_queues","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var Utils = __dependency1__["default"]; + var DeferredActionQueues = __dependency2__.DeferredActionQueues; + + var slice = [].slice, + pop = [].pop, + each = Utils.each, + isString = Utils.isString, + isFunction = Utils.isFunction, + isNumber = Utils.isNumber, + timers = [], + global = this, + NUMBER = /\d+/; + + // In IE 6-8, try/finally doesn't work without a catch. + // Unfortunately, this is impossible to test for since wrapping it in a parent try/catch doesn't trigger the bug. + // This tests for another broken try/catch behavior that only exhibits in the same versions of IE. + var needsIETryCatchFix = (function(e,x){ + try{ x(); } + catch(e) { } // jshint ignore:line + return !!e; + })(); + + function isCoercableNumber(number) { + return isNumber(number) || NUMBER.test(number); + } + + function Backburner(queueNames, options) { + this.queueNames = queueNames; + this.options = options || {}; + if (!this.options.defaultQueue) { + this.options.defaultQueue = queueNames[0]; + } + this.instanceStack = []; + this._debouncees = []; + this._throttlers = []; + } + + Backburner.prototype = { + queueNames: null, + options: null, + currentInstance: null, + instanceStack: null, + + begin: function() { + var options = this.options, + onBegin = options && options.onBegin, + previousInstance = this.currentInstance; + + if (previousInstance) { + this.instanceStack.push(previousInstance); + } + + this.currentInstance = new DeferredActionQueues(this.queueNames, options); + if (onBegin) { + onBegin(this.currentInstance, previousInstance); + } + }, + + end: function() { + var options = this.options, + onEnd = options && options.onEnd, + currentInstance = this.currentInstance, + nextInstance = null; + + // Prevent double-finally bug in Safari 6.0.2 and iOS 6 + // This bug appears to be resolved in Safari 6.0.5 and iOS 7 + var finallyAlreadyCalled = false; + try { + currentInstance.flush(); + } finally { + if (!finallyAlreadyCalled) { + finallyAlreadyCalled = true; + + this.currentInstance = null; + + if (this.instanceStack.length) { + nextInstance = this.instanceStack.pop(); + this.currentInstance = nextInstance; + } + + if (onEnd) { + onEnd(currentInstance, nextInstance); + } + } + } + }, + + run: function(target, method /*, args */) { + var onError = getOnError(this.options); + + this.begin(); + + if (!method) { + method = target; + target = null; + } + + if (isString(method)) { + method = target[method]; + } + + var args = slice.call(arguments, 2); + + // guard against Safari 6's double-finally bug + var didFinally = false; + + if (onError) { + try { + return method.apply(target, args); + } catch(error) { + onError(error); + } finally { + if (!didFinally) { + didFinally = true; + this.end(); + } + } + } else { + try { + return method.apply(target, args); + } finally { + if (!didFinally) { + didFinally = true; + this.end(); + } + } + } + }, + + defer: function(queueName, target, method /* , args */) { + if (!method) { + method = target; + target = null; + } + + if (isString(method)) { + method = target[method]; + } + + var stack = this.DEBUG ? new Error() : undefined, + args = arguments.length > 3 ? slice.call(arguments, 3) : undefined; + if (!this.currentInstance) { createAutorun(this); } + return this.currentInstance.schedule(queueName, target, method, args, false, stack); + }, + + deferOnce: function(queueName, target, method /* , args */) { + if (!method) { + method = target; + target = null; + } + + if (isString(method)) { + method = target[method]; + } + + var stack = this.DEBUG ? new Error() : undefined, + args = arguments.length > 3 ? slice.call(arguments, 3) : undefined; + if (!this.currentInstance) { createAutorun(this); } + return this.currentInstance.schedule(queueName, target, method, args, true, stack); + }, + + setTimeout: function() { + var args = slice.call(arguments), + length = args.length, + method, wait, target, + methodOrTarget, methodOrWait, methodOrArgs; + + if (length === 0) { + return; + } else if (length === 1) { + method = args.shift(); + wait = 0; + } else if (length === 2) { + methodOrTarget = args[0]; + methodOrWait = args[1]; + + if (isFunction(methodOrWait) || isFunction(methodOrTarget[methodOrWait])) { + target = args.shift(); + method = args.shift(); + wait = 0; + } else if (isCoercableNumber(methodOrWait)) { + method = args.shift(); + wait = args.shift(); + } else { + method = args.shift(); + wait = 0; + } + } else { + var last = args[args.length - 1]; + + if (isCoercableNumber(last)) { + wait = args.pop(); + } else { + wait = 0; + } + + methodOrTarget = args[0]; + methodOrArgs = args[1]; + + if (isFunction(methodOrArgs) || (isString(methodOrArgs) && + methodOrTarget !== null && + methodOrArgs in methodOrTarget)) { + target = args.shift(); + method = args.shift(); + } else { + method = args.shift(); + } + } + + var executeAt = (+new Date()) + parseInt(wait, 10); + + if (isString(method)) { + method = target[method]; + } + + var onError = getOnError(this.options); + + function fn() { + if (onError) { + try { + method.apply(target, args); + } catch (e) { + onError(e); + } + } else { + method.apply(target, args); + } + } + + // find position to insert + var i = searchTimer(executeAt, timers); + + timers.splice(i, 0, executeAt, fn); + + updateLaterTimer(this, executeAt, wait); + + return fn; + }, + + throttle: function(target, method /* , args, wait, [immediate] */) { + var self = this, + args = arguments, + immediate = pop.call(args), + wait, + throttler, + index, + timer; + + if (isNumber(immediate) || isString(immediate)) { + wait = immediate; + immediate = true; + } else { + wait = pop.call(args); + } + + wait = parseInt(wait, 10); + + index = findThrottler(target, method, this._throttlers); + if (index > -1) { return this._throttlers[index]; } // throttled + + timer = global.setTimeout(function() { + if (!immediate) { + self.run.apply(self, args); + } + var index = findThrottler(target, method, self._throttlers); + if (index > -1) { + self._throttlers.splice(index, 1); + } + }, wait); + + if (immediate) { + self.run.apply(self, args); + } + + throttler = [target, method, timer]; + + this._throttlers.push(throttler); + + return throttler; + }, + + debounce: function(target, method /* , args, wait, [immediate] */) { + var self = this, + args = arguments, + immediate = pop.call(args), + wait, + index, + debouncee, + timer; + + if (isNumber(immediate) || isString(immediate)) { + wait = immediate; + immediate = false; + } else { + wait = pop.call(args); + } + + wait = parseInt(wait, 10); + // Remove debouncee + index = findDebouncee(target, method, this._debouncees); + + if (index > -1) { + debouncee = this._debouncees[index]; + this._debouncees.splice(index, 1); + clearTimeout(debouncee[2]); + } + + timer = global.setTimeout(function() { + if (!immediate) { + self.run.apply(self, args); + } + var index = findDebouncee(target, method, self._debouncees); + if (index > -1) { + self._debouncees.splice(index, 1); + } + }, wait); + + if (immediate && index === -1) { + self.run.apply(self, args); + } + + debouncee = [target, method, timer]; + + self._debouncees.push(debouncee); + + return debouncee; + }, + + cancelTimers: function() { + var clearItems = function(item) { + clearTimeout(item[2]); + }; + + each(this._throttlers, clearItems); + this._throttlers = []; + + each(this._debouncees, clearItems); + this._debouncees = []; + + if (this._laterTimer) { + clearTimeout(this._laterTimer); + this._laterTimer = null; + } + timers = []; + + if (this._autorun) { + clearTimeout(this._autorun); + this._autorun = null; + } + }, + + hasTimers: function() { + return !!timers.length || !!this._debouncees.length || !!this._throttlers.length || this._autorun; + }, + + cancel: function(timer) { + var timerType = typeof timer; + + if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce + return timer.queue.cancel(timer); + } else if (timerType === 'function') { // we're cancelling a setTimeout + for (var i = 0, l = timers.length; i < l; i += 2) { + if (timers[i + 1] === timer) { + timers.splice(i, 2); // remove the two elements + return true; + } + } + } else if (Object.prototype.toString.call(timer) === "[object Array]"){ // we're cancelling a throttle or debounce + return this._cancelItem(findThrottler, this._throttlers, timer) || + this._cancelItem(findDebouncee, this._debouncees, timer); + } else { + return; // timer was null or not a timer + } + }, + + _cancelItem: function(findMethod, array, timer){ + var item, + index; + + if (timer.length < 3) { return false; } + + index = findMethod(timer[0], timer[1], array); + + if(index > -1) { + + item = array[index]; + + if(item[2] === timer[2]){ + array.splice(index, 1); + clearTimeout(timer[2]); + return true; + } + } + + return false; + } + }; + + Backburner.prototype.schedule = Backburner.prototype.defer; + Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce; + Backburner.prototype.later = Backburner.prototype.setTimeout; + + if (needsIETryCatchFix) { + var originalRun = Backburner.prototype.run; + Backburner.prototype.run = wrapInTryCatch(originalRun); + + var originalEnd = Backburner.prototype.end; + Backburner.prototype.end = wrapInTryCatch(originalEnd); + } + + function wrapInTryCatch(func) { + return function () { + try { + return func.apply(this, arguments); + } catch (e) { + throw e; + } + }; + } + + function getOnError(options) { + return options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]); + } + + + function createAutorun(backburner) { + backburner.begin(); + backburner._autorun = global.setTimeout(function() { + backburner._autorun = null; + backburner.end(); + }); + } + + function updateLaterTimer(self, executeAt, wait) { + if (!self._laterTimer || executeAt < self._laterTimerExpiresAt) { + self._laterTimer = global.setTimeout(function() { + self._laterTimer = null; + self._laterTimerExpiresAt = null; + executeTimers(self); + }, wait); + self._laterTimerExpiresAt = executeAt; + } + } + + function executeTimers(self) { + var now = +new Date(), + time, fns, i, l; + + self.run(function() { + i = searchTimer(now, timers); + + fns = timers.splice(0, i); + + for (i = 1, l = fns.length; i < l; i += 2) { + self.schedule(self.options.defaultQueue, null, fns[i]); + } + }); + + if (timers.length) { + updateLaterTimer(self, timers[0], timers[0] - now); + } + } + + function findDebouncee(target, method, debouncees) { + return findItem(target, method, debouncees); + } + + function findThrottler(target, method, throttlers) { + return findItem(target, method, throttlers); + } + + function findItem(target, method, collection) { + var item, + index = -1; + + for (var i = 0, l = collection.length; i < l; i++) { + item = collection[i]; + if (item[0] === target && item[1] === method) { + index = i; + break; + } + } + + return index; + } + + function searchTimer(time, timers) { + var start = 0, + end = timers.length - 2, + middle, l; + + while (start < end) { + // since timers is an array of pairs 'l' will always + // be an integer + l = (end - start) / 2; + + // compensate for the index in case even number + // of pairs inside timers + middle = start + l - (l % 2); + + if (time >= timers[middle]) { + start = middle + 2; + } else { + end = middle; + } + } + + return (time >= timers[start]) ? start + 2 : start; + } + + __exports__.Backburner = Backburner; + }); +define("backburner/deferred_action_queues", + ["backburner/utils","backburner/queue","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var Utils = __dependency1__["default"]; + var Queue = __dependency2__.Queue; + + var each = Utils.each, + isString = Utils.isString; + + function DeferredActionQueues(queueNames, options) { + var queues = this.queues = {}; + this.queueNames = queueNames = queueNames || []; + + this.options = options; + + each(queueNames, function(queueName) { + queues[queueName] = new Queue(this, queueName, options); + }); + } + + DeferredActionQueues.prototype = { + queueNames: null, + queues: null, + options: null, + + schedule: function(queueName, target, method, args, onceFlag, stack) { + var queues = this.queues, + queue = queues[queueName]; + + if (!queue) { throw new Error("You attempted to schedule an action in a queue (" + queueName + ") that doesn't exist"); } + + if (onceFlag) { + return queue.pushUnique(target, method, args, stack); + } else { + return queue.push(target, method, args, stack); + } + }, + + invoke: function(target, method, args, _) { + if (args && args.length > 0) { + method.apply(target, args); + } else { + method.call(target); + } + }, + + invokeWithOnError: function(target, method, args, onError) { + try { + if (args && args.length > 0) { + method.apply(target, args); + } else { + method.call(target); + } + } catch(error) { + onError(error); + } + }, + + flush: function() { + var queues = this.queues, + queueNames = this.queueNames, + queueName, queue, queueItems, priorQueueNameIndex, + queueNameIndex = 0, numberOfQueues = queueNames.length, + options = this.options, + onError = options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]), + invoke = onError ? this.invokeWithOnError : this.invoke; + + outerloop: + while (queueNameIndex < numberOfQueues) { + queueName = queueNames[queueNameIndex]; + queue = queues[queueName]; + queueItems = queue._queueBeingFlushed = queue._queue.slice(); + queue._queue = []; + + var queueOptions = queue.options, // TODO: write a test for this + before = queueOptions && queueOptions.before, + after = queueOptions && queueOptions.after, + target, method, args, stack, + queueIndex = 0, numberOfQueueItems = queueItems.length; + + if (numberOfQueueItems && before) { before(); } + + while (queueIndex < numberOfQueueItems) { + target = queueItems[queueIndex]; + method = queueItems[queueIndex+1]; + args = queueItems[queueIndex+2]; + stack = queueItems[queueIndex+3]; // Debugging assistance + + if (isString(method)) { method = target[method]; } + + // method could have been nullified / canceled during flush + if (method) { + invoke(target, method, args, onError); + } + + queueIndex += 4; + } + + queue._queueBeingFlushed = null; + if (numberOfQueueItems && after) { after(); } + + if ((priorQueueNameIndex = indexOfPriorQueueWithActions(this, queueNameIndex)) !== -1) { + queueNameIndex = priorQueueNameIndex; + continue outerloop; + } + + queueNameIndex++; + } + } + }; + + function indexOfPriorQueueWithActions(daq, currentQueueIndex) { + var queueName, queue; + + for (var i = 0, l = currentQueueIndex; i <= l; i++) { + queueName = daq.queueNames[i]; + queue = daq.queues[queueName]; + if (queue._queue.length) { return i; } + } + + return -1; + } + + __exports__.DeferredActionQueues = DeferredActionQueues; + }); +define("backburner/queue", + ["exports"], + function(__exports__) { + "use strict"; + function Queue(daq, name, options) { + this.daq = daq; + this.name = name; + this.globalOptions = options; + this.options = options[name]; + this._queue = []; + } + + Queue.prototype = { + daq: null, + name: null, + options: null, + onError: null, + _queue: null, + + push: function(target, method, args, stack) { + var queue = this._queue; + queue.push(target, method, args, stack); + return {queue: this, target: target, method: method}; + }, + + pushUnique: function(target, method, args, stack) { + var queue = this._queue, currentTarget, currentMethod, i, l; + + for (i = 0, l = queue.length; i < l; i += 4) { + currentTarget = queue[i]; + currentMethod = queue[i+1]; + + if (currentTarget === target && currentMethod === method) { + queue[i+2] = args; // replace args + queue[i+3] = stack; // replace stack + return {queue: this, target: target, method: method}; + } + } + + queue.push(target, method, args, stack); + return {queue: this, target: target, method: method}; + }, + + // TODO: remove me, only being used for Ember.run.sync + flush: function() { + var queue = this._queue, + globalOptions = this.globalOptions, + options = this.options, + before = options && options.before, + after = options && options.after, + onError = globalOptions.onError || (globalOptions.onErrorTarget && globalOptions.onErrorTarget[globalOptions.onErrorMethod]), + target, method, args, stack, i, l = queue.length; + + if (l && before) { before(); } + for (i = 0; i < l; i += 4) { + target = queue[i]; + method = queue[i+1]; + args = queue[i+2]; + stack = queue[i+3]; // Debugging assistance + + // TODO: error handling + if (args && args.length > 0) { + if (onError) { + try { + method.apply(target, args); + } catch (e) { + onError(e); + } + } else { + method.apply(target, args); + } + } else { + if (onError) { + try { + method.call(target); + } catch(e) { + onError(e); + } + } else { + method.call(target); + } + } + } + if (l && after) { after(); } + + // check if new items have been added + if (queue.length > l) { + this._queue = queue.slice(l); + this.flush(); + } else { + this._queue.length = 0; + } + }, + + cancel: function(actionToCancel) { + var queue = this._queue, currentTarget, currentMethod, i, l; + + for (i = 0, l = queue.length; i < l; i += 4) { + currentTarget = queue[i]; + currentMethod = queue[i+1]; + + if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) { + queue.splice(i, 4); + return true; + } + } + + // if not found in current queue + // could be in the queue that is being flushed + queue = this._queueBeingFlushed; + if (!queue) { + return; + } + for (i = 0, l = queue.length; i < l; i += 4) { + currentTarget = queue[i]; + currentMethod = queue[i+1]; + + if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) { + // don't mess with array during flush + // just nullify the method + queue[i+1] = null; + return true; + } + } + } + }; + + __exports__.Queue = Queue; + }); +define("backburner/utils", + ["exports"], + function(__exports__) { + "use strict"; + __exports__["default"] = { + each: function(collection, callback) { + for (var i = 0; i < collection.length; i++) { + callback(collection[i]); + } + }, + + isString: function(suspect) { + return typeof suspect === 'string'; + }, + + isFunction: function(suspect) { + return typeof suspect === 'function'; + }, + + isNumber: function(suspect) { + return typeof suspect === 'number'; + } + }; + }); + +define("calculateVersion", + [], + function() { + "use strict"; + 'use strict'; + + var fs = require('fs'); + var path = require('path'); + + module.exports = function () { + var packageVersion = require('../package.json').version; + var output = [packageVersion]; + var gitPath = path.join(__dirname,'..','.git'); + var headFilePath = path.join(gitPath, 'HEAD'); + + if (packageVersion.indexOf('+') > -1) { + try { + if (fs.existsSync(headFilePath)) { + var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'}); + var branchName = headFile.split('/').slice(-1)[0].trim(); + var refPath = headFile.split(' ')[1]; + var branchSHA; + + if (refPath) { + var branchPath = path.join(gitPath, refPath.trim()); + branchSHA = fs.readFileSync(branchPath); + } else { + branchSHA = branchName; + } + + output.push(branchSHA.slice(0,10)); + } + } catch (err) { + console.error(err.stack); + } + return output.join('.'); + } else { + return packageVersion; + } + }; + }); +define("container", + ["container/container","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /* + Public api for the container is still in flux. + The public api, specified on the application namespace should be considered the stable api. + // @module container + @private + */ + + /* + Flag to enable/disable model factory injections (disabled by default) + If model factory injections are enabled, models should not be + accessed globally (only through `container.lookupFactory('model:modelName'))`); + */ + Ember.MODEL_FACTORY_INJECTIONS = false; + + if (Ember.ENV && typeof Ember.ENV.MODEL_FACTORY_INJECTIONS !== 'undefined') { + Ember.MODEL_FACTORY_INJECTIONS = !!Ember.ENV.MODEL_FACTORY_INJECTIONS; + } + + + var Container = __dependency1__["default"]; + + __exports__["default"] = Container; + }); +define("container/container", + ["container/inheriting_dict","ember-metal/core","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var InheritingDict = __dependency1__["default"]; + var Ember = __dependency2__["default"]; + // Ember.assert + + // A lightweight container that helps to assemble and decouple components. + // Public api for the container is still in flux. + // The public api, specified on the application namespace should be considered the stable api. + function Container(parent) { + this.parent = parent; + this.children = []; + + this.resolver = parent && parent.resolver || function() {}; + + this.registry = new InheritingDict(parent && parent.registry); + this.cache = new InheritingDict(parent && parent.cache); + this.factoryCache = new InheritingDict(parent && parent.factoryCache); + this.resolveCache = new InheritingDict(parent && parent.resolveCache); + this.typeInjections = new InheritingDict(parent && parent.typeInjections); + this.injections = {}; + + this.factoryTypeInjections = new InheritingDict(parent && parent.factoryTypeInjections); + this.factoryInjections = {}; + + this._options = new InheritingDict(parent && parent._options); + this._typeOptions = new InheritingDict(parent && parent._typeOptions); + } + + Container.prototype = { + + /** + @property parent + @type Container + @default null + */ + parent: null, + + /** + @property children + @type Array + @default [] + */ + children: null, + + /** + @property resolver + @type function + */ + resolver: null, + + /** + @property registry + @type InheritingDict + */ + registry: null, + + /** + @property cache + @type InheritingDict + */ + cache: null, + + /** + @property typeInjections + @type InheritingDict + */ + typeInjections: null, + + /** + @property injections + @type Object + @default {} + */ + injections: null, + + /** + @private + + @property _options + @type InheritingDict + @default null + */ + _options: null, + + /** + @private + + @property _typeOptions + @type InheritingDict + */ + _typeOptions: null, + + /** + Returns a new child of the current container. These children are configured + to correctly inherit from the current container. + + @method child + @return {Container} + */ + child: function() { + var container = new Container(this); + this.children.push(container); + return container; + }, + + /** + Sets a key-value pair on the current container. If a parent container, + has the same key, once set on a child, the parent and child will diverge + as expected. + + @method set + @param {Object} object + @param {String} key + @param {any} value + */ + set: function(object, key, value) { + object[key] = value; + }, + + /** + Registers a factory for later injection. + + Example: + + ```javascript + var container = new Container(); + + container.register('model:user', Person, {singleton: false }); + container.register('fruit:favorite', Orange); + container.register('communication:main', Email, {singleton: false}); + ``` + + @method register + @param {String} fullName + @param {Function} factory + @param {Object} options + */ + register: function(fullName, factory, options) { + + if (factory === undefined) { + throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`'); + } + + var normalizedName = this.normalize(fullName); + + if (this.cache.has(normalizedName)) { + throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.'); + } + + this.registry.set(normalizedName, factory); + this._options.set(normalizedName, options || {}); + }, + + /** + Unregister a fullName + + ```javascript + var container = new Container(); + container.register('model:user', User); + + container.lookup('model:user') instanceof User //=> true + + container.unregister('model:user') + container.lookup('model:user') === undefined //=> true + ``` + + @method unregister + @param {String} fullName + */ + unregister: function(fullName) { + + var normalizedName = this.normalize(fullName); + + this.registry.remove(normalizedName); + this.cache.remove(normalizedName); + this.factoryCache.remove(normalizedName); + this.resolveCache.remove(normalizedName); + this._options.remove(normalizedName); + }, + + /** + Given a fullName return the corresponding factory. + + By default `resolve` will retrieve the factory from + its container's registry. + + ```javascript + var container = new Container(); + container.register('api:twitter', Twitter); + + container.resolve('api:twitter') // => Twitter + ``` + + Optionally the container can be provided with a custom resolver. + If provided, `resolve` will first provide the custom resolver + the opportunity to resolve the fullName, otherwise it will fallback + to the registry. + + ```javascript + var container = new Container(); + container.resolver = function(fullName) { + // lookup via the module system of choice + }; + + // the twitter factory is added to the module system + container.resolve('api:twitter') // => Twitter + ``` + + @method resolve + @param {String} fullName + @return {Function} fullName's factory + */ + resolve: function(fullName) { + return resolve(this, this.normalize(fullName)); + }, + + /** + A hook that can be used to describe how the resolver will + attempt to find the factory. + + For example, the default Ember `.describe` returns the full + class name (including namespace) where Ember's resolver expects + to find the `fullName`. + + @method describe + @param {String} fullName + @return {string} described fullName + */ + describe: function(fullName) { + return fullName; + }, + + /** + A hook to enable custom fullName normalization behaviour + + @method normalize + @param {String} fullName + @return {string} normalized fullName + */ + normalize: function(fullName) { + return fullName; + }, + + /** + @method makeToString + + @param {any} factory + @param {string} fullName + @return {function} toString function + */ + makeToString: function(factory, fullName) { + return factory.toString(); + }, + + /** + Given a fullName return a corresponding instance. + + The default behaviour is for lookup to return a singleton instance. + The singleton is scoped to the container, allowing multiple containers + to all have their own locally scoped singletons. + + ```javascript + var container = new Container(); + container.register('api:twitter', Twitter); + + var twitter = container.lookup('api:twitter'); + + twitter instanceof Twitter; // => true + + // by default the container will return singletons + var twitter2 = container.lookup('api:twitter'); + twitter2 instanceof Twitter; // => true + + twitter === twitter2; //=> true + ``` + + If singletons are not wanted an optional flag can be provided at lookup. + + ```javascript + var container = new Container(); + container.register('api:twitter', Twitter); + + var twitter = container.lookup('api:twitter', { singleton: false }); + var twitter2 = container.lookup('api:twitter', { singleton: false }); + + twitter === twitter2; //=> false + ``` + + @method lookup + @param {String} fullName + @param {Object} options + @return {any} + */ + lookup: function(fullName, options) { + return lookup(this, this.normalize(fullName), options); + }, + + /** + Given a fullName return the corresponding factory. + + @method lookupFactory + @param {String} fullName + @return {any} + */ + lookupFactory: function(fullName) { + return factoryFor(this, this.normalize(fullName)); + }, + + /** + Given a fullName check if the container is aware of its factory + or singleton instance. + + @method has + @param {String} fullName + @return {Boolean} + */ + has: function(fullName) { + return has(this, this.normalize(fullName)); + }, + + /** + Allow registering options for all factories of a type. + + ```javascript + var container = new Container(); + + // if all of type `connection` must not be singletons + container.optionsForType('connection', { singleton: false }); + + container.register('connection:twitter', TwitterConnection); + container.register('connection:facebook', FacebookConnection); + + var twitter = container.lookup('connection:twitter'); + var twitter2 = container.lookup('connection:twitter'); + + twitter === twitter2; // => false + + var facebook = container.lookup('connection:facebook'); + var facebook2 = container.lookup('connection:facebook'); + + facebook === facebook2; // => false + ``` + + @method optionsForType + @param {String} type + @param {Object} options + */ + optionsForType: function(type, options) { + if (this.parent) { illegalChildOperation('optionsForType'); } + + this._typeOptions.set(type, options); + }, + + /** + @method options + @param {String} type + @param {Object} options + */ + options: function(type, options) { + this.optionsForType(type, options); + }, + + /** + Used only via `injection`. + + Provides a specialized form of injection, specifically enabling + all objects of one type to be injected with a reference to another + object. + + For example, provided each object of type `controller` needed a `router`. + one would do the following: + + ```javascript + var container = new Container(); + + container.register('router:main', Router); + container.register('controller:user', UserController); + container.register('controller:post', PostController); + + container.typeInjection('controller', 'router', 'router:main'); + + var user = container.lookup('controller:user'); + var post = container.lookup('controller:post'); + + user.router instanceof Router; //=> true + post.router instanceof Router; //=> true + + // both controllers share the same router + user.router === post.router; //=> true + ``` + + @private + @method typeInjection + @param {String} type + @param {String} property + @param {String} fullName + */ + typeInjection: function(type, property, fullName) { + if (this.parent) { illegalChildOperation('typeInjection'); } + + var fullNameType = fullName.split(':')[0]; + if(fullNameType === type) { + throw new Error('Cannot inject a `' + fullName + '` on other ' + type + '(s). Register the `' + fullName + '` as a different type and perform the typeInjection.'); + } + addTypeInjection(this.typeInjections, type, property, fullName); + }, + + /** + Defines injection rules. + + These rules are used to inject dependencies onto objects when they + are instantiated. + + Two forms of injections are possible: + + * Injecting one fullName on another fullName + * Injecting one fullName on a type + + Example: + + ```javascript + var container = new Container(); + + container.register('source:main', Source); + container.register('model:user', User); + container.register('model:post', Post); + + // injecting one fullName on another fullName + // eg. each user model gets a post model + container.injection('model:user', 'post', 'model:post'); + + // injecting one fullName on another type + container.injection('model', 'source', 'source:main'); + + var user = container.lookup('model:user'); + var post = container.lookup('model:post'); + + user.source instanceof Source; //=> true + post.source instanceof Source; //=> true + + user.post instanceof Post; //=> true + + // and both models share the same source + user.source === post.source; //=> true + ``` + + @method injection + @param {String} factoryName + @param {String} property + @param {String} injectionName + */ + injection: function(fullName, property, injectionName) { + if (this.parent) { illegalChildOperation('injection'); } + + validateFullName(injectionName); + var normalizedInjectionName = this.normalize(injectionName); + + if (fullName.indexOf(':') === -1) { + return this.typeInjection(fullName, property, normalizedInjectionName); + } + + var normalizedName = this.normalize(fullName); + + if (this.cache.has(normalizedName)) { + throw new Error("Attempted to register an injection for a type that has already been looked up. ('" + normalizedName + "', '" + property + "', '" + injectionName + "')"); + } + addInjection(this.injections, normalizedName, property, normalizedInjectionName); + }, + + + /** + Used only via `factoryInjection`. + + Provides a specialized form of injection, specifically enabling + all factory of one type to be injected with a reference to another + object. + + For example, provided each factory of type `model` needed a `store`. + one would do the following: + + ```javascript + var container = new Container(); + + container.register('store:main', SomeStore); + + container.factoryTypeInjection('model', 'store', 'store:main'); + + var store = container.lookup('store:main'); + var UserFactory = container.lookupFactory('model:user'); + + UserFactory.store instanceof SomeStore; //=> true + ``` + + @private + @method factoryTypeInjection + @param {String} type + @param {String} property + @param {String} fullName + */ + factoryTypeInjection: function(type, property, fullName) { + if (this.parent) { illegalChildOperation('factoryTypeInjection'); } + + addTypeInjection(this.factoryTypeInjections, type, property, this.normalize(fullName)); + }, + + /** + Defines factory injection rules. + + Similar to regular injection rules, but are run against factories, via + `Container#lookupFactory`. + + These rules are used to inject objects onto factories when they + are looked up. + + Two forms of injections are possible: + + * Injecting one fullName on another fullName + * Injecting one fullName on a type + + Example: + + ```javascript + var container = new Container(); + + container.register('store:main', Store); + container.register('store:secondary', OtherStore); + container.register('model:user', User); + container.register('model:post', Post); + + // injecting one fullName on another type + container.factoryInjection('model', 'store', 'store:main'); + + // injecting one fullName on another fullName + container.factoryInjection('model:post', 'secondaryStore', 'store:secondary'); + + var UserFactory = container.lookupFactory('model:user'); + var PostFactory = container.lookupFactory('model:post'); + var store = container.lookup('store:main'); + + UserFactory.store instanceof Store; //=> true + UserFactory.secondaryStore instanceof OtherStore; //=> false + + PostFactory.store instanceof Store; //=> true + PostFactory.secondaryStore instanceof OtherStore; //=> true + + // and both models share the same source instance + UserFactory.store === PostFactory.store; //=> true + ``` + + @method factoryInjection + @param {String} factoryName + @param {String} property + @param {String} injectionName + */ + factoryInjection: function(fullName, property, injectionName) { + if (this.parent) { illegalChildOperation('injection'); } + + var normalizedName = this.normalize(fullName); + var normalizedInjectionName = this.normalize(injectionName); + + validateFullName(injectionName); + + if (fullName.indexOf(':') === -1) { + return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName); + } + + + if (this.factoryCache.has(normalizedName)) { + throw new Error('Attempted to register a factoryInjection for a type that has already ' + + 'been looked up. (\'' + normalizedName + '\', \'' + property + '\', \'' + injectionName + '\')'); + } + + addInjection(this.factoryInjections, normalizedName, property, normalizedInjectionName); + }, + + /** + A depth first traversal, destroying the container, its descendant containers and all + their managed objects. + + @method destroy + */ + destroy: function() { + for (var i = 0, length = this.children.length; i < length; i++) { + this.children[i].destroy(); + } + + this.children = []; + + eachDestroyable(this, function(item) { + item.destroy(); + }); + + this.parent = undefined; + this.isDestroyed = true; + }, + + /** + @method reset + */ + reset: function() { + for (var i = 0, length = this.children.length; i < length; i++) { + resetCache(this.children[i]); + } + + resetCache(this); + } + }; + + function resolve(container, normalizedName) { + var cached = container.resolveCache.get(normalizedName); + if (cached) { return cached; } + + var resolved = container.resolver(normalizedName) || container.registry.get(normalizedName); + container.resolveCache.set(normalizedName, resolved); + + return resolved; + } + + function has(container, fullName){ + if (container.cache.has(fullName)) { + return true; + } + + return !!container.resolve(fullName); + } + + function lookup(container, fullName, options) { + options = options || {}; + + if (container.cache.has(fullName) && options.singleton !== false) { + return container.cache.get(fullName); + } + + var value = instantiate(container, fullName); + + if (value === undefined) { return; } + + if (isSingleton(container, fullName) && options.singleton !== false) { + container.cache.set(fullName, value); + } + + return value; + } + + function illegalChildOperation(operation) { + throw new Error(operation + ' is not currently supported on child containers'); + } + + function isSingleton(container, fullName) { + var singleton = option(container, fullName, 'singleton'); + + return singleton !== false; + } + + function buildInjections(container, injections) { + var hash = {}; + + if (!injections) { return hash; } + + var injection, injectable; + + for (var i = 0, length = injections.length; i < length; i++) { + injection = injections[i]; + injectable = lookup(container, injection.fullName); + + if (injectable !== undefined) { + hash[injection.property] = injectable; + } else { + throw new Error('Attempting to inject an unknown injection: `' + injection.fullName + '`'); + } + } + + return hash; + } + + function option(container, fullName, optionName) { + var options = container._options.get(fullName); + + if (options && options[optionName] !== undefined) { + return options[optionName]; + } + + var type = fullName.split(':')[0]; + options = container._typeOptions.get(type); + + if (options) { + return options[optionName]; + } + } + + function factoryFor(container, fullName) { + var cache = container.factoryCache; + if (cache.has(fullName)) { + return cache.get(fullName); + } + var factory = container.resolve(fullName); + if (factory === undefined) { return; } + + var type = fullName.split(':')[0]; + if (!factory || typeof factory.extend !== 'function' || (!Ember.MODEL_FACTORY_INJECTIONS && type === 'model')) { + // TODO: think about a 'safe' merge style extension + // for now just fallback to create time injection + return factory; + } else { + var injections = injectionsFor(container, fullName); + var factoryInjections = factoryInjectionsFor(container, fullName); + + factoryInjections._toString = container.makeToString(factory, fullName); + + var injectedFactory = factory.extend(injections); + injectedFactory.reopenClass(factoryInjections); + + cache.set(fullName, injectedFactory); + + return injectedFactory; + } + } + + function injectionsFor(container, fullName) { + var splitName = fullName.split(':'), + type = splitName[0], + injections = []; + + injections = injections.concat(container.typeInjections.get(type) || []); + injections = injections.concat(container.injections[fullName] || []); + + injections = buildInjections(container, injections); + injections._debugContainerKey = fullName; + injections.container = container; + + return injections; + } + + function factoryInjectionsFor(container, fullName) { + var splitName = fullName.split(':'), + type = splitName[0], + factoryInjections = []; + + factoryInjections = factoryInjections.concat(container.factoryTypeInjections.get(type) || []); + factoryInjections = factoryInjections.concat(container.factoryInjections[fullName] || []); + + factoryInjections = buildInjections(container, factoryInjections); + factoryInjections._debugContainerKey = fullName; + + return factoryInjections; + } + + function instantiate(container, fullName) { + var factory = factoryFor(container, fullName); + + if (option(container, fullName, 'instantiate') === false) { + return factory; + } + + if (factory) { + if (typeof factory.create !== 'function') { + throw new Error('Failed to create an instance of \'' + fullName + '\'. ' + + 'Most likely an improperly defined class or an invalid module export.'); + } + + if (typeof factory.extend === 'function') { + // assume the factory was extendable and is already injected + return factory.create(); + } else { + // assume the factory was extendable + // to create time injections + // TODO: support new'ing for instantiation and merge injections for pure JS Functions + return factory.create(injectionsFor(container, fullName)); + } + } + } + + function eachDestroyable(container, callback) { + container.cache.eachLocal(function(key, value) { + if (option(container, key, 'instantiate') === false) { return; } + callback(value); + }); + } + + function resetCache(container) { + container.cache.eachLocal(function(key, value) { + if (option(container, key, 'instantiate') === false) { return; } + value.destroy(); + }); + container.cache.dict = {}; + } + + function addTypeInjection(rules, type, property, fullName) { + var injections = rules.get(type); + + if (!injections) { + injections = []; + rules.set(type, injections); + } + + injections.push({ + property: property, + fullName: fullName + }); + } + + var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/; + function validateFullName(fullName) { + if (!VALID_FULL_NAME_REGEXP.test(fullName)) { + throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName); + } + return true; + } + + function addInjection(rules, factoryName, property, injectionName) { + var injections = rules[factoryName] = rules[factoryName] || []; + injections.push({ property: property, fullName: injectionName }); + } + + __exports__["default"] = Container; + }); +define("container/inheriting_dict", + ["exports"], + function(__exports__) { + "use strict"; + // A safe and simple inheriting object. + function InheritingDict(parent) { + this.parent = parent; + this.dict = {}; + } + + InheritingDict.prototype = { + + /** + @property parent + @type InheritingDict + @default null + */ + + parent: null, + + /** + Object used to store the current nodes data. + + @property dict + @type Object + @default Object + */ + dict: null, + + /** + Retrieve the value given a key, if the value is present at the current + level use it, otherwise walk up the parent hierarchy and try again. If + no matching key is found, return undefined. + + @method get + @param {String} key + @return {any} + */ + get: function(key) { + var dict = this.dict; + + if (dict.hasOwnProperty(key)) { + return dict[key]; + } + + if (this.parent) { + return this.parent.get(key); + } + }, + + /** + Set the given value for the given key, at the current level. + + @method set + @param {String} key + @param {Any} value + */ + set: function(key, value) { + this.dict[key] = value; + }, + + /** + Delete the given key + + @method remove + @param {String} key + */ + remove: function(key) { + delete this.dict[key]; + }, + + /** + Check for the existence of given a key, if the key is present at the current + level return true, otherwise walk up the parent hierarchy and try again. If + no matching key is found, return false. + + @method has + @param {String} key + @return {Boolean} + */ + has: function(key) { + var dict = this.dict; + + if (dict.hasOwnProperty(key)) { + return true; + } + + if (this.parent) { + return this.parent.has(key); + } + + return false; + }, + + /** + Iterate and invoke a callback for each local key-value pair. + + @method eachLocal + @param {Function} callback + @param {Object} binding + */ + eachLocal: function(callback, binding) { + var dict = this.dict; + + for (var prop in dict) { + if (dict.hasOwnProperty(prop)) { + callback.call(binding, prop, dict[prop]); + } + } + } + }; + + __exports__["default"] = InheritingDict; + }); +define("ember-application", + ["ember-metal/core","ember-runtime/system/lazy_load","ember-application/system/dag","ember-application/system/resolver","ember-application/system/application","ember-application/ext/controller"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__) { + "use strict"; + var Ember = __dependency1__["default"]; + var runLoadHooks = __dependency2__.runLoadHooks; + + /** + Ember Application + + @module ember + @submodule ember-application + @requires ember-views, ember-routing + */ + + var DAG = __dependency3__["default"]; + var Resolver = __dependency4__.Resolver; + var DefaultResolver = __dependency4__["default"]; + var Application = __dependency5__["default"]; + // side effect of extending ControllerMixin + + Ember.Application = Application; + Ember.DAG = DAG; + Ember.Resolver = Resolver; + Ember.DefaultResolver = DefaultResolver; + + runLoadHooks('Ember.Application', Application); + }); +define("ember-application/ext/controller", + ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/error","ember-metal/utils","ember-metal/computed","ember-runtime/mixins/controller","ember-routing/system/controller_for","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-application + */ + + var Ember = __dependency1__["default"]; + // Ember.assert + var get = __dependency2__.get; + var set = __dependency3__.set; + var EmberError = __dependency4__["default"]; + var inspect = __dependency5__.inspect; + var computed = __dependency6__.computed; + var ControllerMixin = __dependency7__["default"]; + var meta = __dependency5__.meta; + var controllerFor = __dependency8__["default"]; + + function verifyNeedsDependencies(controller, container, needs) { + var dependency, i, l, missing = []; + + for (i=0, l=needs.length; i 1 ? 'they' : 'it') + " could not be found"); + } + } + + var defaultControllersComputedProperty = computed(function() { + var controller = this; + + return { + needs: get(controller, 'needs'), + container: get(controller, 'container'), + unknownProperty: function(controllerName) { + var needs = this.needs, + dependency, i, l; + for (i=0, l=needs.length; i 0) { + + if (this.container) { + verifyNeedsDependencies(this, this.container, needs); + } + + // if needs then initialize controllers proxy + get(this, 'controllers'); + } + + this._super.apply(this, arguments); + }, + + /** + @method controllerFor + @see {Ember.Route#controllerFor} + @deprecated Use `needs` instead + */ + controllerFor: function(controllerName) { + return controllerFor(get(this, 'container'), controllerName); + }, + + /** + Stores the instances of other controllers available from within + this controller. Any controller listed by name in the `needs` + property will be accessible by name through this property. + + ```javascript + App.CommentsController = Ember.ArrayController.extend({ + needs: ['post'], + postTitle: function(){ + var currentPost = this.get('controllers.post'); // instance of App.PostController + return currentPost.get('title'); + }.property('controllers.post.title') + }); + ``` + + @see {Ember.ControllerMixin#needs} + @property {Object} controllers + @default null + */ + controllers: defaultControllersComputedProperty + }); + + __exports__["default"] = ControllerMixin; + }); +define("ember-application/system/application", + ["ember-metal","ember-metal/property_get","ember-metal/property_set","ember-runtime/system/lazy_load","ember-application/system/dag","ember-runtime/system/namespace","ember-runtime/mixins/deferred","ember-application/system/resolver","ember-metal/platform","ember-metal/run_loop","ember-metal/utils","container/container","ember-runtime/controllers/controller","ember-metal/enumerable_utils","ember-runtime/controllers/object_controller","ember-runtime/controllers/array_controller","ember-views/system/event_dispatcher","ember-views/system/jquery","ember-routing/system/route","ember-routing/system/router","ember-routing/location/hash_location","ember-routing/location/history_location","ember-routing/location/auto_location","ember-routing/location/none_location","ember-routing/system/cache","ember-metal/core","ember-handlebars-compiler","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __dependency21__, __dependency22__, __dependency23__, __dependency24__, __dependency25__, __dependency26__, __dependency27__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-application + */ + + var Ember = __dependency1__["default"]; + // Ember.FEATURES, Ember.deprecate, Ember.assert, Ember.libraries, LOG_VERSION, Namespace, BOOTED + var get = __dependency2__.get; + var set = __dependency3__.set; + var runLoadHooks = __dependency4__.runLoadHooks; + var DAG = __dependency5__["default"]; + var Namespace = __dependency6__["default"]; + var DeferredMixin = __dependency7__["default"]; + var DefaultResolver = __dependency8__["default"]; + var create = __dependency9__.create; + var run = __dependency10__["default"]; + var canInvoke = __dependency11__.canInvoke; + var Container = __dependency12__["default"]; + var Controller = __dependency13__["default"]; + var EnumerableUtils = __dependency14__["default"]; + var ObjectController = __dependency15__["default"]; + var ArrayController = __dependency16__["default"]; + var EventDispatcher = __dependency17__["default"]; + //import ContainerDebugAdapter from "ember-extension-support/container_debug_adapter"; + var jQuery = __dependency18__["default"]; + var Route = __dependency19__["default"]; + var Router = __dependency20__["default"]; + var HashLocation = __dependency21__["default"]; + var HistoryLocation = __dependency22__["default"]; + var AutoLocation = __dependency23__["default"]; + var NoneLocation = __dependency24__["default"]; + var BucketCache = __dependency25__["default"]; + + var K = __dependency26__.K; + var EmberHandlebars = __dependency27__["default"]; + + var ContainerDebugAdapter; + + /** + An instance of `Ember.Application` is the starting point for every Ember + application. It helps to instantiate, initialize and coordinate the many + objects that make up your app. + + Each Ember app has one and only one `Ember.Application` object. In fact, the + very first thing you should do in your application is create the instance: + + ```javascript + window.App = Ember.Application.create(); + ``` + + Typically, the application object is the only global variable. All other + classes in your app should be properties on the `Ember.Application` instance, + which highlights its first role: a global namespace. + + For example, if you define a view class, it might look like this: + + ```javascript + App.MyView = Ember.View.extend(); + ``` + + By default, calling `Ember.Application.create()` will automatically initialize + your application by calling the `Ember.Application.initialize()` method. If + you need to delay initialization, you can call your app's `deferReadiness()` + method. When you are ready for your app to be initialized, call its + `advanceReadiness()` method. + + You can define a `ready` method on the `Ember.Application` instance, which + will be run by Ember when the application is initialized. + + Because `Ember.Application` inherits from `Ember.Namespace`, any classes + you create will have useful string representations when calling `toString()`. + See the `Ember.Namespace` documentation for more information. + + While you can think of your `Ember.Application` as a container that holds the + other classes in your application, there are several other responsibilities + going on under-the-hood that you may want to understand. + + ### Event Delegation + + Ember uses a technique called _event delegation_. This allows the framework + to set up a global, shared event listener instead of requiring each view to + do it manually. For example, instead of each view registering its own + `mousedown` listener on its associated element, Ember sets up a `mousedown` + listener on the `body`. + + If a `mousedown` event occurs, Ember will look at the target of the event and + start walking up the DOM node tree, finding corresponding views and invoking + their `mouseDown` method as it goes. + + `Ember.Application` has a number of default events that it listens for, as + well as a mapping from lowercase events to camel-cased view method names. For + example, the `keypress` event causes the `keyPress` method on the view to be + called, the `dblclick` event causes `doubleClick` to be called, and so on. + + If there is a bubbling browser event that Ember does not listen for by + default, you can specify custom events and their corresponding view method + names by setting the application's `customEvents` property: + + ```javascript + App = Ember.Application.create({ + customEvents: { + // add support for the paste event + paste: "paste" + } + }); + ``` + + By default, the application sets up these event listeners on the document + body. However, in cases where you are embedding an Ember application inside + an existing page, you may want it to set up the listeners on an element + inside the body. + + For example, if only events inside a DOM element with the ID of `ember-app` + should be delegated, set your application's `rootElement` property: + + ```javascript + window.App = Ember.Application.create({ + rootElement: '#ember-app' + }); + ``` + + The `rootElement` can be either a DOM element or a jQuery-compatible selector + string. Note that *views appended to the DOM outside the root element will + not receive events.* If you specify a custom root element, make sure you only + append views inside it! + + To learn more about the advantages of event delegation and the Ember view + layer, and a list of the event listeners that are setup by default, visit the + [Ember View Layer guide](http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_event-delegation). + + ### Initializers + + Libraries on top of Ember can add initializers, like so: + + ```javascript + Ember.Application.initializer({ + name: 'api-adapter', + + initialize: function(container, application) { + application.register('api-adapter:main', ApiAdapter); + } + }); + ``` + + Initializers provide an opportunity to access the container, which + organizes the different components of an Ember application. Additionally + they provide a chance to access the instantiated application. Beyond + being used for libraries, initializers are also a great way to organize + dependency injection or setup in your own application. + + ### Routing + + In addition to creating your application's router, `Ember.Application` is + also responsible for telling the router when to start routing. Transitions + between routes can be logged with the `LOG_TRANSITIONS` flag, and more + detailed intra-transition logging can be logged with + the `LOG_TRANSITIONS_INTERNAL` flag: + + ```javascript + window.App = Ember.Application.create({ + LOG_TRANSITIONS: true, // basic logging of successful transitions + LOG_TRANSITIONS_INTERNAL: true // detailed logging of all routing steps + }); + ``` + + By default, the router will begin trying to translate the current URL into + application state once the browser emits the `DOMContentReady` event. If you + need to defer routing, you can call the application's `deferReadiness()` + method. Once routing can begin, call the `advanceReadiness()` method. + + If there is any setup required before routing begins, you can implement a + `ready()` method on your app that will be invoked immediately before routing + begins. + ``` + + @class Application + @namespace Ember + @extends Ember.Namespace + */ + + var Application = Namespace.extend(DeferredMixin, { + _suppressDeferredDeprecation: true, + + /** + The root DOM element of the Application. This can be specified as an + element or a + [jQuery-compatible selector string](http://api.jquery.com/category/selectors/). + + This is the element that will be passed to the Application's, + `eventDispatcher`, which sets up the listeners for event delegation. Every + view in your application should be a child of the element you specify here. + + @property rootElement + @type DOMElement + @default 'body' + */ + rootElement: 'body', + + /** + The `Ember.EventDispatcher` responsible for delegating events to this + application's views. + + The event dispatcher is created by the application at initialization time + and sets up event listeners on the DOM element described by the + application's `rootElement` property. + + See the documentation for `Ember.EventDispatcher` for more information. + + @property eventDispatcher + @type Ember.EventDispatcher + @default null + */ + eventDispatcher: null, + + /** + The DOM events for which the event dispatcher should listen. + + By default, the application's `Ember.EventDispatcher` listens + for a set of standard DOM events, such as `mousedown` and + `keyup`, and delegates them to your application's `Ember.View` + instances. + + If you would like additional bubbling events to be delegated to your + views, set your `Ember.Application`'s `customEvents` property + to a hash containing the DOM event name as the key and the + corresponding view method name as the value. For example: + + ```javascript + App = Ember.Application.create({ + customEvents: { + // add support for the paste event + paste: "paste" + } + }); + ``` + + @property customEvents + @type Object + @default null + */ + customEvents: null, + + // Start off the number of deferrals at 1. This will be + // decremented by the Application's own `initialize` method. + _readinessDeferrals: 1, + + init: function() { + if (!this.$) { this.$ = jQuery; } + this.__container__ = this.buildContainer(); + + this.Router = this.defaultRouter(); + + this._super(); + + this.scheduleInitialize(); + + Ember.libraries.registerCoreLibrary('Handlebars', EmberHandlebars.VERSION); + Ember.libraries.registerCoreLibrary('jQuery', jQuery().jquery); + + if ( Ember.LOG_VERSION ) { + Ember.LOG_VERSION = false; // we only need to see this once per Application#init + + var nameLengths = EnumerableUtils.map(Ember.libraries, function(item) { + return get(item, "name.length"); + }); + + var maxNameLength = Math.max.apply(this, nameLengths); + + Ember.libraries.each(function(name, version) { + var spaces = new Array(maxNameLength - name.length + 1).join(" "); + }); + } + }, + + /** + Build the container for the current application. + + Also register a default application view in case the application + itself does not. + + @private + @method buildContainer + @return {Ember.Container} the configured container + */ + buildContainer: function() { + var container = this.__container__ = Application.buildContainer(this); + + return container; + }, + + /** + If the application has not opted out of routing and has not explicitly + defined a router, supply a default router for the application author + to configure. + + This allows application developers to do: + + ```javascript + var App = Ember.Application.create(); + + App.Router.map(function() { + this.resource('posts'); + }); + ``` + + @private + @method defaultRouter + @return {Ember.Router} the default router + */ + + defaultRouter: function() { + if (this.Router === false) { return; } + var container = this.__container__; + + if (this.Router) { + container.unregister('router:main'); + container.register('router:main', this.Router); + } + + return container.lookupFactory('router:main'); + }, + + /** + Automatically initialize the application once the DOM has + become ready. + + The initialization itself is scheduled on the actions queue + which ensures that application loading finishes before + booting. + + If you are asynchronously loading code, you should call + `deferReadiness()` to defer booting, and then call + `advanceReadiness()` once all of your code has finished + loading. + + @private + @method scheduleInitialize + */ + scheduleInitialize: function() { + var self = this; + + if (!this.$ || this.$.isReady) { + run.schedule('actions', self, '_initialize'); + } else { + this.$().ready(function runInitialize() { + run(self, '_initialize'); + }); + } + }, + + /** + Use this to defer readiness until some condition is true. + + Example: + + ```javascript + App = Ember.Application.create(); + App.deferReadiness(); + + jQuery.getJSON("/auth-token", function(token) { + App.token = token; + App.advanceReadiness(); + }); + ``` + + This allows you to perform asynchronous setup logic and defer + booting your application until the setup has finished. + + However, if the setup requires a loading UI, it might be better + to use the router for this purpose. + + @method deferReadiness + */ + deferReadiness: function() { + this._readinessDeferrals++; + }, + + /** + Call `advanceReadiness` after any asynchronous setup logic has completed. + Each call to `deferReadiness` must be matched by a call to `advanceReadiness` + or the application will never become ready and routing will not begin. + + @method advanceReadiness + @see {Ember.Application#deferReadiness} + */ + advanceReadiness: function() { + this._readinessDeferrals--; + + if (this._readinessDeferrals === 0) { + run.once(this, this.didBecomeReady); + } + }, + + /** + Registers a factory that can be used for dependency injection (with + `App.inject`) or for service lookup. Each factory is registered with + a full name including two parts: `type:name`. + + A simple example: + + ```javascript + var App = Ember.Application.create(); + App.Orange = Ember.Object.extend(); + App.register('fruit:favorite', App.Orange); + ``` + + Ember will resolve factories from the `App` namespace automatically. + For example `App.CarsController` will be discovered and returned if + an application requests `controller:cars`. + + An example of registering a controller with a non-standard name: + + ```javascript + var App = Ember.Application.create(), + Session = Ember.Controller.extend(); + + App.register('controller:session', Session); + + // The Session controller can now be treated like a normal controller, + // despite its non-standard name. + App.ApplicationController = Ember.Controller.extend({ + needs: ['session'] + }); + ``` + + Registered factories are **instantiated** by having `create` + called on them. Additionally they are **singletons**, each time + they are looked up they return the same instance. + + Some examples modifying that default behavior: + + ```javascript + var App = Ember.Application.create(); + + App.Person = Ember.Object.extend(); + App.Orange = Ember.Object.extend(); + App.Email = Ember.Object.extend(); + App.session = Ember.Object.create(); + + App.register('model:user', App.Person, {singleton: false }); + App.register('fruit:favorite', App.Orange); + App.register('communication:main', App.Email, {singleton: false}); + App.register('session', App.session, {instantiate: false}); + ``` + + @method register + @param fullName {String} type:name (e.g., 'model:user') + @param factory {Function} (e.g., App.Person) + @param options {Object} (optional) disable instantiation or singleton usage + **/ + register: function() { + var container = this.__container__; + container.register.apply(container, arguments); + }, + + /** + Define a dependency injection onto a specific factory or all factories + of a type. + + When Ember instantiates a controller, view, or other framework component + it can attach a dependency to that component. This is often used to + provide services to a set of framework components. + + An example of providing a session object to all controllers: + + ```javascript + var App = Ember.Application.create(), + Session = Ember.Object.extend({ isAuthenticated: false }); + + // A factory must be registered before it can be injected + App.register('session:main', Session); + + // Inject 'session:main' onto all factories of the type 'controller' + // with the name 'session' + App.inject('controller', 'session', 'session:main'); + + App.IndexController = Ember.Controller.extend({ + isLoggedIn: Ember.computed.alias('session.isAuthenticated') + }); + ``` + + Injections can also be performed on specific factories. + + ```javascript + App.inject(, , ) + App.inject('route', 'source', 'source:main') + App.inject('route:application', 'email', 'model:email') + ``` + + It is important to note that injections can only be performed on + classes that are instantiated by Ember itself. Instantiating a class + directly (via `create` or `new`) bypasses the dependency injection + system. + + Ember-Data instantiates its models in a unique manner, and consequently + injections onto models (or all models) will not work as expected. Injections + on models can be enabled by setting `Ember.MODEL_FACTORY_INJECTIONS` + to `true`. + + @method inject + @param factoryNameOrType {String} + @param property {String} + @param injectionName {String} + **/ + inject: function() { + var container = this.__container__; + container.injection.apply(container, arguments); + }, + + /** + Calling initialize manually is not supported. + + Please see Ember.Application#advanceReadiness and + Ember.Application#deferReadiness. + + @private + @deprecated + @method initialize + **/ + initialize: function() { + }, + + /** + Initialize the application. This happens automatically. + + Run any initializers and run the application load hook. These hooks may + choose to defer readiness. For example, an authentication hook might want + to defer readiness until the auth token has been retrieved. + + @private + @method _initialize + */ + _initialize: function() { + if (this.isDestroyed) { return; } + + // At this point, the App.Router must already be assigned + if (this.Router) { + var container = this.__container__; + container.unregister('router:main'); + container.register('router:main', this.Router); + } + + this.runInitializers(); + runLoadHooks('application', this); + + // At this point, any initializers or load hooks that would have wanted + // to defer readiness have fired. In general, advancing readiness here + // will proceed to didBecomeReady. + this.advanceReadiness(); + + return this; + }, + + /** + Reset the application. This is typically used only in tests. It cleans up + the application in the following order: + + 1. Deactivate existing routes + 2. Destroy all objects in the container + 3. Create a new application container + 4. Re-route to the existing url + + Typical Example: + + ```javascript + + var App; + + run(function() { + App = Ember.Application.create(); + }); + + module("acceptance test", { + setup: function() { + App.reset(); + } + }); + + test("first test", function() { + // App is freshly reset + }); + + test("first test", function() { + // App is again freshly reset + }); + ``` + + Advanced Example: + + Occasionally you may want to prevent the app from initializing during + setup. This could enable extra configuration, or enable asserting prior + to the app becoming ready. + + ```javascript + + var App; + + run(function() { + App = Ember.Application.create(); + }); + + module("acceptance test", { + setup: function() { + run(function() { + App.reset(); + App.deferReadiness(); + }); + } + }); + + test("first test", function() { + ok(true, 'something before app is initialized'); + + run(function() { + App.advanceReadiness(); + }); + ok(true, 'something after app is initialized'); + }); + ``` + + @method reset + **/ + reset: function() { + this._readinessDeferrals = 1; + + function handleReset() { + var router = this.__container__.lookup('router:main'); + router.reset(); + + run(this.__container__, 'destroy'); + + this.buildContainer(); + + run.schedule('actions', this, function() { + this._initialize(); + }); + } + + run.join(this, handleReset); + }, + + /** + @private + @method runInitializers + */ + runInitializers: function() { + var initializers = get(this.constructor, 'initializers'); + var container = this.__container__; + var graph = new DAG(); + var namespace = this; + var name, initializer; + + for (name in initializers) { + initializer = initializers[name]; + graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after); + } + + graph.topsort(function (vertex) { + var initializer = vertex.value; + initializer(container, namespace); + }); + }, + + /** + @private + @method didBecomeReady + */ + didBecomeReady: function() { + this.setupEventDispatcher(); + this.ready(); // user hook + this.startRouting(); + + if (!Ember.testing) { + // Eagerly name all classes that are already loaded + Ember.Namespace.processAll(); + Ember.BOOTED = true; + } + + this.resolve(this); + }, + + /** + Setup up the event dispatcher to receive events on the + application's `rootElement` with any registered + `customEvents`. + + @private + @method setupEventDispatcher + */ + setupEventDispatcher: function() { + var customEvents = get(this, 'customEvents'); + var rootElement = get(this, 'rootElement'); + var dispatcher = this.__container__.lookup('event_dispatcher:main'); + + set(this, 'eventDispatcher', dispatcher); + dispatcher.setup(customEvents, rootElement); + }, + + /** + If the application has a router, use it to route to the current URL, and + trigger a new call to `route` whenever the URL changes. + + @private + @method startRouting + @property router {Ember.Router} + */ + startRouting: function() { + var router = this.__container__.lookup('router:main'); + if (!router) { return; } + + router.startRouting(); + }, + + handleURL: function(url) { + var router = this.__container__.lookup('router:main'); + + router.handleURL(url); + }, + + /** + Called when the Application has become ready. + The call will be delayed until the DOM has become ready. + + @event ready + */ + ready: K, + + /** + @deprecated Use 'Resolver' instead + Set this to provide an alternate class to `Ember.DefaultResolver` + + + @property resolver + */ + resolver: null, + + /** + Set this to provide an alternate class to `Ember.DefaultResolver` + + @property resolver + */ + Resolver: null, + + willDestroy: function() { + Ember.BOOTED = false; + // Ensure deactivation of routes before objects are destroyed + this.__container__.lookup('router:main').reset(); + + this.__container__.destroy(); + }, + + initializer: function(options) { + this.constructor.initializer(options); + }, + + /** + @method then + @private + @deprecated + */ + then: function() { + + this._super.apply(this, arguments); + } + }); + + Application.reopenClass({ + initializers: {}, + + /** + Initializer receives an object which has the following attributes: + `name`, `before`, `after`, `initialize`. The only required attribute is + `initialize, all others are optional. + + * `name` allows you to specify under which name the initializer is registered. + This must be a unique name, as trying to register two initializers with the + same name will result in an error. + + ```javascript + Ember.Application.initializer({ + name: 'namedInitializer', + initialize: function(container, application) { + Ember.debug("Running namedInitializer!"); + } + }); + ``` + + * `before` and `after` are used to ensure that this initializer is ran prior + or after the one identified by the value. This value can be a single string + or an array of strings, referencing the `name` of other initializers. + + An example of ordering initializers, we create an initializer named `first`: + + ```javascript + Ember.Application.initializer({ + name: 'first', + initialize: function(container, application) { + Ember.debug("First initializer!"); + } + }); + + // DEBUG: First initializer! + ``` + + We add another initializer named `second`, specifying that it should run + after the initializer named `first`: + + ```javascript + Ember.Application.initializer({ + name: 'second', + after: 'first', + + initialize: function(container, application) { + Ember.debug("Second initializer!"); + } + }); + + // DEBUG: First initializer! + // DEBUG: Second initializer! + ``` + + Afterwards we add a further initializer named `pre`, this time specifying + that it should run before the initializer named `first`: + + ```javascript + Ember.Application.initializer({ + name: 'pre', + before: 'first', + + initialize: function(container, application) { + Ember.debug("Pre initializer!"); + } + }); + + // DEBUG: Pre initializer! + // DEBUG: First initializer! + // DEBUG: Second initializer! + ``` + + Finally we add an initializer named `post`, specifying it should run after + both the `first` and the `second` initializers: + + ```javascript + Ember.Application.initializer({ + name: 'post', + after: ['first', 'second'], + + initialize: function(container, application) { + Ember.debug("Post initializer!"); + } + }); + + // DEBUG: Pre initializer! + // DEBUG: First initializer! + // DEBUG: Second initializer! + // DEBUG: Post initializer! + ``` + + * `initialize` is a callback function that receives two arguments, `container` + and `application` on which you can operate. + + Example of using `container` to preload data into the store: + + ```javascript + Ember.Application.initializer({ + name: "preload-data", + + initialize: function(container, application) { + var store = container.lookup('store:main'); + store.pushPayload(preloadedData); + } + }); + ``` + + Example of using `application` to register an adapter: + + ```javascript + Ember.Application.initializer({ + name: 'api-adapter', + + initialize: function(container, application) { + application.register('api-adapter:main', ApiAdapter); + } + }); + ``` + + @method initializer + @param initializer {Object} + */ + initializer: function(initializer) { + // If this is the first initializer being added to a subclass, we are going to reopen the class + // to make sure we have a new `initializers` object, which extends from the parent class' using + // prototypal inheritance. Without this, attempting to add initializers to the subclass would + // pollute the parent class as well as other subclasses. + if (this.superclass.initializers !== undefined && this.superclass.initializers === this.initializers) { + this.reopenClass({ + initializers: create(this.initializers) + }); + } + + + this.initializers[initializer.name] = initializer; + }, + + /** + This creates a container with the default Ember naming conventions. + + It also configures the container: + + * registered views are created every time they are looked up (they are + not singletons) + * registered templates are not factories; the registered value is + returned directly. + * the router receives the application as its `namespace` property + * all controllers receive the router as their `target` and `controllers` + properties + * all controllers receive the application as their `namespace` property + * the application view receives the application controller as its + `controller` property + * the application view receives the application template as its + `defaultTemplate` property + + @private + @method buildContainer + @static + @param {Ember.Application} namespace the application to build the + container for. + @return {Ember.Container} the built container + */ + buildContainer: function(namespace) { + var container = new Container(); + + container.set = set; + container.resolver = resolverFor(namespace); + container.normalize = container.resolver.normalize; + container.describe = container.resolver.describe; + container.makeToString = container.resolver.makeToString; + + container.optionsForType('component', { singleton: false }); + container.optionsForType('view', { singleton: false }); + container.optionsForType('template', { instantiate: false }); + container.optionsForType('helper', { instantiate: false }); + + container.register('application:main', namespace, { instantiate: false }); + + container.register('controller:basic', Controller, { instantiate: false }); + container.register('controller:object', ObjectController, { instantiate: false }); + container.register('controller:array', ArrayController, { instantiate: false }); + container.register('route:basic', Route, { instantiate: false }); + container.register('event_dispatcher:main', EventDispatcher); + + container.register('router:main', Router); + container.injection('router:main', 'namespace', 'application:main'); + + container.register('location:auto', AutoLocation); + container.register('location:hash', HashLocation); + container.register('location:history', HistoryLocation); + container.register('location:none', NoneLocation); + + container.injection('controller', 'target', 'router:main'); + container.injection('controller', 'namespace', 'application:main'); + + container.register('-bucket-cache:main', BucketCache); + container.injection('router', '_bucketCache', '-bucket-cache:main'); + container.injection('route', '_bucketCache', '-bucket-cache:main'); + container.injection('controller', '_bucketCache', '-bucket-cache:main'); + + container.injection('route', 'router', 'router:main'); + container.injection('location', 'rootURL', '-location-setting:root-url'); + + // DEBUGGING + container.register('resolver-for-debugging:main', container.resolver.__resolver__, { instantiate: false }); + container.injection('container-debug-adapter:main', 'resolver', 'resolver-for-debugging:main'); + container.injection('data-adapter:main', 'containerDebugAdapter', 'container-debug-adapter:main'); + // Custom resolver authors may want to register their own ContainerDebugAdapter with this key + + // ES6TODO: resolve this via import once ember-application package is ES6'ed + if (!ContainerDebugAdapter) { ContainerDebugAdapter = requireModule('ember-extension-support/container_debug_adapter')['default']; } + container.register('container-debug-adapter:main', ContainerDebugAdapter); + + return container; + } + }); + + /** + This function defines the default lookup rules for container lookups: + + * templates are looked up on `Ember.TEMPLATES` + * other names are looked up on the application after classifying the name. + For example, `controller:post` looks up `App.PostController` by default. + * if the default lookup fails, look for registered classes on the container + + This allows the application to register default injections in the container + that could be overridden by the normal naming convention. + + @private + @method resolverFor + @param {Ember.Namespace} namespace the namespace to look for classes + @return {*} the resolved value for a given lookup + */ + function resolverFor(namespace) { + if (namespace.get('resolver')) { + } + + var ResolverClass = namespace.get('resolver') || namespace.get('Resolver') || DefaultResolver; + var resolver = ResolverClass.create({ + namespace: namespace + }); + + function resolve(fullName) { + return resolver.resolve(fullName); + } + + resolve.describe = function(fullName) { + return resolver.lookupDescription(fullName); + }; + + resolve.makeToString = function(factory, fullName) { + return resolver.makeToString(factory, fullName); + }; + + resolve.normalize = function(fullName) { + if (resolver.normalize) { + return resolver.normalize(fullName); + } else { + return fullName; + } + }; + + resolve.__resolver__ = resolver; + + return resolve; + } + + __exports__["default"] = Application; + }); +define("ember-application/system/dag", + ["ember-metal/error","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var EmberError = __dependency1__["default"]; + + function visit(vertex, fn, visited, path) { + var name = vertex.name; + var vertices = vertex.incoming; + var names = vertex.incomingNames; + var len = names.length; + var i; + + if (!visited) { + visited = {}; + } + if (!path) { + path = []; + } + if (visited.hasOwnProperty(name)) { + return; + } + path.push(name); + visited[name] = true; + for (i = 0; i < len; i++) { + visit(vertices[names[i]], fn, visited, path); + } + fn(vertex, path); + path.pop(); + } + + + /** + * DAG stands for Directed acyclic graph. + * + * It is used to build a graph of dependencies checking that there isn't circular + * dependencies. p.e Registering initializers with a certain precedence order. + * + * @class DAG + * @constructor + */ + function DAG() { + this.names = []; + this.vertices = {}; + } + + /** + * Adds a vertex entry to the graph unless it is already added. + * + * @private + * @method add + * @param {String} name The name of the vertex to add + */ + DAG.prototype.add = function(name) { + if (!name) { return; } + if (this.vertices.hasOwnProperty(name)) { + return this.vertices[name]; + } + var vertex = { + name: name, incoming: {}, incomingNames: [], hasOutgoing: false, value: null + }; + this.vertices[name] = vertex; + this.names.push(name); + return vertex; + }; + + /** + * Adds a vertex to the graph and sets its value. + * + * @private + * @method map + * @param {String} name The name of the vertex. + * @param value The value to put in the vertex. + */ + DAG.prototype.map = function(name, value) { + this.add(name).value = value; + }; + + /** + * Connects the vertices with the given names, adding them to the graph if + * necesary, only if this does not produce is any circular dependency. + * + * @private + * @method addEdge + * @param {String} fromName The name the vertex where the edge starts. + * @param {String} toName The name the vertex where the edge ends. + */ + DAG.prototype.addEdge = function(fromName, toName) { + if (!fromName || !toName || fromName === toName) { + return; + } + var from = this.add(fromName), to = this.add(toName); + if (to.incoming.hasOwnProperty(fromName)) { + return; + } + function checkCycle(vertex, path) { + if (vertex.name === toName) { + throw new EmberError("cycle detected: " + toName + " <- " + path.join(" <- ")); + } + } + visit(from, checkCycle); + from.hasOutgoing = true; + to.incoming[fromName] = from; + to.incomingNames.push(fromName); + }; + + /** + * Visits all the vertex of the graph calling the given function with each one, + * ensuring that the vertices are visited respecting their precedence. + * + * @method topsort + * @param {Function} fn The function to be invoked on each vertex. + */ + DAG.prototype.topsort = function(fn) { + var visited = {}; + var vertices = this.vertices; + var names = this.names; + var len = names.length; + var i, vertex; + + for (i = 0; i < len; i++) { + vertex = vertices[names[i]]; + if (!vertex.hasOutgoing) { + visit(vertex, fn, visited); + } + } + }; + + /** + * Adds a vertex with the given name and value to the graph and joins it with the + * vertices referenced in _before_ and _after_. If there isn't vertices with those + * names, they are added too. + * + * If either _before_ or _after_ are falsy/empty, the added vertex will not have + * an incoming/outgoing edge. + * + * @method addEdges + * @param {String} name The name of the vertex to be added. + * @param value The value of that vertex. + * @param before An string or array of strings with the names of the vertices before + * which this vertex must be visited. + * @param after An string or array of strings with the names of the vertex after + * which this vertex must be visited. + * + */ + DAG.prototype.addEdges = function(name, value, before, after) { + var i; + this.map(name, value); + if (before) { + if (typeof before === 'string') { + this.addEdge(name, before); + } else { + for (i = 0; i < before.length; i++) { + this.addEdge(name, before[i]); + } + } + } + if (after) { + if (typeof after === 'string') { + this.addEdge(after, name); + } else { + for (i = 0; i < after.length; i++) { + this.addEdge(after[i], name); + } + } + } + }; + + __exports__["default"] = DAG; + }); +define("ember-application/system/resolver", + ["ember-metal/core","ember-metal/property_get","ember-metal/logger","ember-runtime/system/string","ember-runtime/system/object","ember-runtime/system/namespace","ember-handlebars","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-application + */ + + var Ember = __dependency1__["default"]; + // Ember.TEMPLATES, Ember.assert + var get = __dependency2__.get; + var Logger = __dependency3__["default"]; + var classify = __dependency4__.classify; + var capitalize = __dependency4__.capitalize; + var decamelize = __dependency4__.decamelize; + var EmberObject = __dependency5__["default"]; + var Namespace = __dependency6__["default"]; + var EmberHandlebars = __dependency7__["default"]; + + var Resolver = EmberObject.extend({ + /** + This will be set to the Application instance when it is + created. + + @property namespace + */ + namespace: null, + normalize: Ember.required(Function), + resolve: Ember.required(Function), + parseName: Ember.required(Function), + lookupDescription: Ember.required(Function), + makeToString: Ember.required(Function), + resolveOther: Ember.required(Function), + _logLookup: Ember.required(Function) + }); + __exports__.Resolver = Resolver; + /** + The DefaultResolver defines the default lookup rules to resolve + container lookups before consulting the container for registered + items: + + * templates are looked up on `Ember.TEMPLATES` + * other names are looked up on the application after converting + the name. For example, `controller:post` looks up + `App.PostController` by default. + * there are some nuances (see examples below) + + ### How Resolving Works + + The container calls this object's `resolve` method with the + `fullName` argument. + + It first parses the fullName into an object using `parseName`. + + Then it checks for the presence of a type-specific instance + method of the form `resolve[Type]` and calls it if it exists. + For example if it was resolving 'template:post', it would call + the `resolveTemplate` method. + + Its last resort is to call the `resolveOther` method. + + The methods of this object are designed to be easy to override + in a subclass. For example, you could enhance how a template + is resolved like so: + + ```javascript + App = Ember.Application.create({ + Resolver: Ember.DefaultResolver.extend({ + resolveTemplate: function(parsedName) { + var resolvedTemplate = this._super(parsedName); + if (resolvedTemplate) { return resolvedTemplate; } + return Ember.TEMPLATES['not_found']; + } + }) + }); + ``` + + Some examples of how names are resolved: + + ``` + 'template:post' //=> Ember.TEMPLATES['post'] + 'template:posts/byline' //=> Ember.TEMPLATES['posts/byline'] + 'template:posts.byline' //=> Ember.TEMPLATES['posts/byline'] + 'template:blogPost' //=> Ember.TEMPLATES['blogPost'] + // OR + // Ember.TEMPLATES['blog_post'] + 'controller:post' //=> App.PostController + 'controller:posts.index' //=> App.PostsIndexController + 'controller:blog/post' //=> Blog.PostController + 'controller:basic' //=> Ember.Controller + 'route:post' //=> App.PostRoute + 'route:posts.index' //=> App.PostsIndexRoute + 'route:blog/post' //=> Blog.PostRoute + 'route:basic' //=> Ember.Route + 'view:post' //=> App.PostView + 'view:posts.index' //=> App.PostsIndexView + 'view:blog/post' //=> Blog.PostView + 'view:basic' //=> Ember.View + 'foo:post' //=> App.PostFoo + 'model:post' //=> App.Post + ``` + + @class DefaultResolver + @namespace Ember + @extends Ember.Object + */ + + __exports__["default"] = EmberObject.extend({ + /** + This will be set to the Application instance when it is + created. + + @property namespace + */ + namespace: null, + + normalize: function(fullName) { + var split = fullName.split(':', 2), + type = split[0], + name = split[1]; + + + if (type !== 'template') { + var result = name; + + if (result.indexOf('.') > -1) { + result = result.replace(/\.(.)/g, function(m) { return m.charAt(1).toUpperCase(); }); + } + + if (name.indexOf('_') > -1) { + result = result.replace(/_(.)/g, function(m) { return m.charAt(1).toUpperCase(); }); + } + + return type + ':' + result; + } else { + return fullName; + } + }, + + + /** + This method is called via the container's resolver method. + It parses the provided `fullName` and then looks up and + returns the appropriate template or class. + + @method resolve + @param {String} fullName the lookup string + @return {Object} the resolved factory + */ + resolve: function(fullName) { + var parsedName = this.parseName(fullName), + resolveMethodName = parsedName.resolveMethodName, + resolved; + + if (!(parsedName.name && parsedName.type)) { + throw new TypeError('Invalid fullName: `' + fullName + '`, must be of the form `type:name` '); + } + + if (this[resolveMethodName]) { + resolved = this[resolveMethodName](parsedName); + } + + if (!resolved) { + resolved = this.resolveOther(parsedName); + } + + if (parsedName.root && parsedName.root.LOG_RESOLVER) { + this._logLookup(resolved, parsedName); + } + + return resolved; + }, + /** + Convert the string name of the form 'type:name' to + a Javascript object with the parsed aspects of the name + broken out. + + @protected + @param {String} fullName the lookup string + @method parseName + */ + parseName: function(fullName) { + var nameParts = fullName.split(':'), + type = nameParts[0], fullNameWithoutType = nameParts[1], + name = fullNameWithoutType, + namespace = get(this, 'namespace'), + root = namespace; + + if (type !== 'template' && name.indexOf('/') !== -1) { + var parts = name.split('/'); + name = parts[parts.length - 1]; + var namespaceName = capitalize(parts.slice(0, -1).join('.')); + root = Namespace.byName(namespaceName); + + } + + return { + fullName: fullName, + type: type, + fullNameWithoutType: fullNameWithoutType, + name: name, + root: root, + resolveMethodName: 'resolve' + classify(type) + }; + }, + + /** + Returns a human-readable description for a fullName. Used by the + Application namespace in assertions to describe the + precise name of the class that Ember is looking for, rather than + container keys. + + @protected + @param {String} fullName the lookup string + @method lookupDescription + */ + lookupDescription: function(fullName) { + var parsedName = this.parseName(fullName); + + if (parsedName.type === 'template') { + return 'template at ' + parsedName.fullNameWithoutType.replace(/\./g, '/'); + } + + var description = parsedName.root + '.' + classify(parsedName.name); + if (parsedName.type !== 'model') { description += classify(parsedName.type); } + + return description; + }, + + makeToString: function(factory, fullName) { + return factory.toString(); + }, + /** + Given a parseName object (output from `parseName`), apply + the conventions expected by `Ember.Router` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method useRouterNaming + */ + useRouterNaming: function(parsedName) { + parsedName.name = parsedName.name.replace(/\./g, '_'); + if (parsedName.name === 'basic') { + parsedName.name = ''; + } + }, + /** + Look up the template in Ember.TEMPLATES + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveTemplate + */ + resolveTemplate: function(parsedName) { + var templateName = parsedName.fullNameWithoutType.replace(/\./g, '/'); + + if (Ember.TEMPLATES[templateName]) { + return Ember.TEMPLATES[templateName]; + } + + templateName = decamelize(templateName); + if (Ember.TEMPLATES[templateName]) { + return Ember.TEMPLATES[templateName]; + } + }, + /** + Lookup the view using `resolveOther` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveView + */ + resolveView: function(parsedName) { + this.useRouterNaming(parsedName); + return this.resolveOther(parsedName); + }, + /** + Lookup the controller using `resolveOther` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveController + */ + resolveController: function(parsedName) { + this.useRouterNaming(parsedName); + return this.resolveOther(parsedName); + }, + /** + Lookup the route using `resolveOther` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveRoute + */ + resolveRoute: function(parsedName) { + this.useRouterNaming(parsedName); + return this.resolveOther(parsedName); + }, + + /** + Lookup the model on the Application namespace + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveModel + */ + resolveModel: function(parsedName) { + var className = classify(parsedName.name); + var factory = get(parsedName.root, className); + + if (factory) { return factory; } + }, + /** + Look up the specified object (from parsedName) on the appropriate + namespace (usually on the Application) + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveHelper + */ + resolveHelper: function(parsedName) { + return this.resolveOther(parsedName) || EmberHandlebars.helpers[parsedName.fullNameWithoutType]; + }, + /** + Look up the specified object (from parsedName) on the appropriate + namespace (usually on the Application) + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveOther + */ + resolveOther: function(parsedName) { + var className = classify(parsedName.name) + classify(parsedName.type); + var factory = get(parsedName.root, className); + if (factory) { return factory; } + }, + + /** + @method _logLookup + @param {Boolean} found + @param {Object} parsedName + @private + */ + _logLookup: function(found, parsedName) { + var symbol, padding; + + if (found) { symbol = '[✓]'; } + else { symbol = '[ ]'; } + + if (parsedName.fullName.length > 60) { + padding = '.'; + } else { + padding = new Array(60 - parsedName.fullName.length).join('.'); + } + + Logger.info(symbol, parsedName.fullName, padding, this.lookupDescription(parsedName.fullName)); + } + }); + }); +define("ember-extension-support", + ["ember-metal/core","ember-extension-support/data_adapter","ember-extension-support/container_debug_adapter"], + function(__dependency1__, __dependency2__, __dependency3__) { + "use strict"; + /** + Ember Extension Support + + @module ember + @submodule ember-extension-support + @requires ember-application + */ + + var Ember = __dependency1__["default"]; + var DataAdapter = __dependency2__["default"]; + var ContainerDebugAdapter = __dependency3__["default"]; + + Ember.DataAdapter = DataAdapter; + Ember.ContainerDebugAdapter = ContainerDebugAdapter; + }); +define("ember-extension-support/container_debug_adapter", + ["ember-metal/core","ember-runtime/system/native_array","ember-metal/utils","ember-runtime/system/string","ember-runtime/system/namespace","ember-runtime/system/object","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var emberA = __dependency2__.A; + var typeOf = __dependency3__.typeOf; + var dasherize = __dependency4__.dasherize; + var classify = __dependency4__.classify; + var Namespace = __dependency5__["default"]; + var EmberObject = __dependency6__["default"]; + + /** + @module ember + @submodule ember-extension-support + */ + + /** + The `ContainerDebugAdapter` helps the container and resolver interface + with tools that debug Ember such as the + [Ember Extension](https://github.com/tildeio/ember-extension) + for Chrome and Firefox. + + This class can be extended by a custom resolver implementer + to override some of the methods with library-specific code. + + The methods likely to be overridden are: + + * `canCatalogEntriesByType` + * `catalogEntriesByType` + + The adapter will need to be registered + in the application's container as `container-debug-adapter:main` + + Example: + + ```javascript + Application.initializer({ + name: "containerDebugAdapter", + + initialize: function(container, application) { + application.register('container-debug-adapter:main', require('app/container-debug-adapter')); + } + }); + ``` + + @class ContainerDebugAdapter + @namespace Ember + @extends EmberObject + @since 1.5.0 + */ + __exports__["default"] = EmberObject.extend({ + /** + The container of the application being debugged. + This property will be injected + on creation. + + @property container + @default null + */ + container: null, + + /** + The resolver instance of the application + being debugged. This property will be injected + on creation. + + @property resolver + @default null + */ + resolver: null, + + /** + Returns true if it is possible to catalog a list of available + classes in the resolver for a given type. + + @method canCatalogEntriesByType + @param {string} type The type. e.g. "model", "controller", "route" + @return {boolean} whether a list is available for this type. + */ + canCatalogEntriesByType: function(type) { + if (type === 'model' || type === 'template') return false; + return true; + }, + + /** + Returns the available classes a given type. + + @method catalogEntriesByType + @param {string} type The type. e.g. "model", "controller", "route" + @return {Array} An array of strings. + */ + catalogEntriesByType: function(type) { + var namespaces = emberA(Namespace.NAMESPACES), types = emberA(), self = this; + var typeSuffixRegex = new RegExp(classify(type) + "$"); + + namespaces.forEach(function(namespace) { + if (namespace !== Ember) { + for (var key in namespace) { + if (!namespace.hasOwnProperty(key)) { continue; } + if (typeSuffixRegex.test(key)) { + var klass = namespace[key]; + if (typeOf(klass) === 'class') { + types.push(dasherize(key.replace(typeSuffixRegex, ''))); + } + } + } + } + }); + return types; + } + }); + }); +define("ember-extension-support/data_adapter", + ["ember-metal/core","ember-metal/property_get","ember-metal/run_loop","ember-runtime/system/string","ember-runtime/system/namespace","ember-runtime/system/object","ember-runtime/system/native_array","ember-application/system/application","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + var get = __dependency2__.get; + var run = __dependency3__["default"]; + var dasherize = __dependency4__.dasherize; + var Namespace = __dependency5__["default"]; + var EmberObject = __dependency6__["default"]; + var emberA = __dependency7__.A; + var Application = __dependency8__["default"]; + + /** + @module ember + @submodule ember-extension-support + */ + + /** + The `DataAdapter` helps a data persistence library + interface with tools that debug Ember such + as the [Ember Extension](https://github.com/tildeio/ember-extension) + for Chrome and Firefox. + + This class will be extended by a persistence library + which will override some of the methods with + library-specific code. + + The methods likely to be overridden are: + + * `getFilters` + * `detect` + * `columnsForType` + * `getRecords` + * `getRecordColumnValues` + * `getRecordKeywords` + * `getRecordFilterValues` + * `getRecordColor` + * `observeRecord` + + The adapter will need to be registered + in the application's container as `dataAdapter:main` + + Example: + + ```javascript + Application.initializer({ + name: "data-adapter", + + initialize: function(container, application) { + application.register('data-adapter:main', DS.DataAdapter); + } + }); + ``` + + @class DataAdapter + @namespace Ember + @extends EmberObject + */ + __exports__["default"] = EmberObject.extend({ + init: function() { + this._super(); + this.releaseMethods = emberA(); + }, + + /** + The container of the application being debugged. + This property will be injected + on creation. + + @property container + @default null + @since 1.3.0 + */ + container: null, + + + /** + The container-debug-adapter which is used + to list all models. + + @property containerDebugAdapter + @default undefined + @since 1.5.0 + **/ + containerDebugAdapter: undefined, + + /** + Number of attributes to send + as columns. (Enough to make the record + identifiable). + + @private + @property attributeLimit + @default 3 + @since 1.3.0 + */ + attributeLimit: 3, + + /** + Stores all methods that clear observers. + These methods will be called on destruction. + + @private + @property releaseMethods + @since 1.3.0 + */ + releaseMethods: emberA(), + + /** + Specifies how records can be filtered. + Records returned will need to have a `filterValues` + property with a key for every name in the returned array. + + @public + @method getFilters + @return {Array} List of objects defining filters. + The object should have a `name` and `desc` property. + */ + getFilters: function() { + return emberA(); + }, + + /** + Fetch the model types and observe them for changes. + + @public + @method watchModelTypes + + @param {Function} typesAdded Callback to call to add types. + Takes an array of objects containing wrapped types (returned from `wrapModelType`). + + @param {Function} typesUpdated Callback to call when a type has changed. + Takes an array of objects containing wrapped types. + + @return {Function} Method to call to remove all observers + */ + watchModelTypes: function(typesAdded, typesUpdated) { + var modelTypes = this.getModelTypes(), + self = this, typesToSend, releaseMethods = emberA(); + + typesToSend = modelTypes.map(function(type) { + var klass = type.klass; + var wrapped = self.wrapModelType(klass, type.name); + releaseMethods.push(self.observeModelType(klass, typesUpdated)); + return wrapped; + }); + + typesAdded(typesToSend); + + var release = function() { + releaseMethods.forEach(function(fn) { fn(); }); + self.releaseMethods.removeObject(release); + }; + this.releaseMethods.pushObject(release); + return release; + }, + + _nameToClass: function(type) { + if (typeof type === 'string') { + type = this.container.lookupFactory('model:' + type); + } + return type; + }, + + /** + Fetch the records of a given type and observe them for changes. + + @public + @method watchRecords + + @param {Function} recordsAdded Callback to call to add records. + Takes an array of objects containing wrapped records. + The object should have the following properties: + columnValues: {Object} key and value of a table cell + object: {Object} the actual record object + + @param {Function} recordsUpdated Callback to call when a record has changed. + Takes an array of objects containing wrapped records. + + @param {Function} recordsRemoved Callback to call when a record has removed. + Takes the following parameters: + index: the array index where the records were removed + count: the number of records removed + + @return {Function} Method to call to remove all observers + */ + watchRecords: function(type, recordsAdded, recordsUpdated, recordsRemoved) { + var self = this, releaseMethods = emberA(), records = this.getRecords(type), release; + + var recordUpdated = function(updatedRecord) { + recordsUpdated([updatedRecord]); + }; + + var recordsToSend = records.map(function(record) { + releaseMethods.push(self.observeRecord(record, recordUpdated)); + return self.wrapRecord(record); + }); + + + var contentDidChange = function(array, idx, removedCount, addedCount) { + for (var i = idx; i < idx + addedCount; i++) { + var record = array.objectAt(i); + var wrapped = self.wrapRecord(record); + releaseMethods.push(self.observeRecord(record, recordUpdated)); + recordsAdded([wrapped]); + } + + if (removedCount) { + recordsRemoved(idx, removedCount); + } + }; + + var observer = { didChange: contentDidChange, willChange: Ember.K }; + records.addArrayObserver(self, observer); + + release = function() { + releaseMethods.forEach(function(fn) { fn(); }); + records.removeArrayObserver(self, observer); + self.releaseMethods.removeObject(release); + }; + + recordsAdded(recordsToSend); + + this.releaseMethods.pushObject(release); + return release; + }, + + /** + Clear all observers before destruction + @private + @method willDestroy + */ + willDestroy: function() { + this._super(); + this.releaseMethods.forEach(function(fn) { + fn(); + }); + }, + + /** + Detect whether a class is a model. + + Test that against the model class + of your persistence library + + @private + @method detect + @param {Class} klass The class to test + @return boolean Whether the class is a model class or not + */ + detect: function(klass) { + return false; + }, + + /** + Get the columns for a given model type. + + @private + @method columnsForType + @param {Class} type The model type + @return {Array} An array of columns of the following format: + name: {String} name of the column + desc: {String} Humanized description (what would show in a table column name) + */ + columnsForType: function(type) { + return emberA(); + }, + + /** + Adds observers to a model type class. + + @private + @method observeModelType + @param {Class} type The model type class + @param {Function} typesUpdated Called when a type is modified. + @return {Function} The function to call to remove observers + */ + + observeModelType: function(type, typesUpdated) { + var self = this, records = this.getRecords(type); + + var onChange = function() { + typesUpdated([self.wrapModelType(type)]); + }; + var observer = { + didChange: function() { + run.scheduleOnce('actions', this, onChange); + }, + willChange: Ember.K + }; + + records.addArrayObserver(this, observer); + + var release = function() { + records.removeArrayObserver(self, observer); + }; + + return release; + }, + + + /** + Wraps a given model type and observes changes to it. + + @private + @method wrapModelType + @param {Class} type A model class + @param {String} Optional name of the class + @return {Object} contains the wrapped type and the function to remove observers + Format: + type: {Object} the wrapped type + The wrapped type has the following format: + name: {String} name of the type + count: {Integer} number of records available + columns: {Columns} array of columns to describe the record + object: {Class} the actual Model type class + release: {Function} The function to remove observers + */ + wrapModelType: function(type, name) { + var release, records = this.getRecords(type), + typeToSend, self = this; + + typeToSend = { + name: name || type.toString(), + count: get(records, 'length'), + columns: this.columnsForType(type), + object: type + }; + + + return typeToSend; + }, + + + /** + Fetches all models defined in the application. + + @private + @method getModelTypes + @return {Array} Array of model types + */ + getModelTypes: function() { + var types, self = this, + containerDebugAdapter = this.get('containerDebugAdapter'); + + if (containerDebugAdapter.canCatalogEntriesByType('model')) { + types = containerDebugAdapter.catalogEntriesByType('model'); + } else { + types = this._getObjectsOnNamespaces(); + } + + // New adapters return strings instead of classes + types = emberA(types).map(function(name) { + return { + klass: self._nameToClass(name), + name: name + }; + }); + types = emberA(types).filter(function(type) { + return self.detect(type.klass); + }); + + return emberA(types); + }, + + /** + Loops over all namespaces and all objects + attached to them + + @private + @method _getObjectsOnNamespaces + @return {Array} Array of model type strings + */ + _getObjectsOnNamespaces: function() { + var namespaces = emberA(Namespace.NAMESPACES), + types = emberA(), + self = this; + + namespaces.forEach(function(namespace) { + for (var key in namespace) { + if (!namespace.hasOwnProperty(key)) { continue; } + // Even though we will filter again in `getModelTypes`, + // we should not call `lookupContainer` on non-models + // (especially when `Ember.MODEL_FACTORY_INJECTIONS` is `true`) + if (!self.detect(namespace[key])) { continue; } + var name = dasherize(key); + if (!(namespace instanceof Application) && namespace.toString()) { + name = namespace + '/' + name; + } + types.push(name); + } + }); + return types; + }, + + /** + Fetches all loaded records for a given type. + + @private + @method getRecords + @return {Array} An array of records. + This array will be observed for changes, + so it should update when new records are added/removed. + */ + getRecords: function(type) { + return emberA(); + }, + + /** + Wraps a record and observers changes to it. + + @private + @method wrapRecord + @param {Object} record The record instance. + @return {Object} The wrapped record. Format: + columnValues: {Array} + searchKeywords: {Array} + */ + wrapRecord: function(record) { + var recordToSend = { object: record }, columnValues = {}, self = this; + + recordToSend.columnValues = this.getRecordColumnValues(record); + recordToSend.searchKeywords = this.getRecordKeywords(record); + recordToSend.filterValues = this.getRecordFilterValues(record); + recordToSend.color = this.getRecordColor(record); + + return recordToSend; + }, + + /** + Gets the values for each column. + + @private + @method getRecordColumnValues + @return {Object} Keys should match column names defined + by the model type. + */ + getRecordColumnValues: function(record) { + return {}; + }, + + /** + Returns keywords to match when searching records. + + @private + @method getRecordKeywords + @return {Array} Relevant keywords for search. + */ + getRecordKeywords: function(record) { + return emberA(); + }, + + /** + Returns the values of filters defined by `getFilters`. + + @private + @method getRecordFilterValues + @param {Object} record The record instance + @return {Object} The filter values + */ + getRecordFilterValues: function(record) { + return {}; + }, + + /** + Each record can have a color that represents its state. + + @private + @method getRecordColor + @param {Object} record The record instance + @return {String} The record's color + Possible options: black, red, blue, green + */ + getRecordColor: function(record) { + return null; + }, + + /** + Observes all relevant properties and re-sends the wrapped record + when a change occurs. + + @private + @method observerRecord + @param {Object} record The record instance + @param {Function} recordUpdated The callback to call when a record is updated. + @return {Function} The function to call to remove all observers. + */ + observeRecord: function(record, recordUpdated) { + return function(){}; + } + }); + }); +define("ember-extension-support/initializers", + [], + function() { + "use strict"; + + }); +define("ember-handlebars-compiler", + ["ember-metal/core","exports"], + function(__dependency1__, __exports__) { + "use strict"; + /* global Handlebars:true */ + + /** + @module ember + @submodule ember-handlebars-compiler + */ + + var Ember = __dependency1__["default"]; + + // ES6Todo: you'll need to import debugger once debugger is es6'd. + if (typeof Ember.assert === 'undefined') { Ember.assert = function(){}; } + if (typeof Ember.FEATURES === 'undefined') { Ember.FEATURES = { isEnabled: function(){} }; } + + var objectCreate = Object.create || function(parent) { + function F() {} + F.prototype = parent; + return new F(); + }; + + // set up for circular references later + var View, Component; + + // ES6Todo: when ember-debug is es6'ed import this. + // var emberAssert = Ember.assert; + var Handlebars = (Ember.imports && Ember.imports.Handlebars) || (this && this.Handlebars); + if (!Handlebars && typeof require === 'function') { + Handlebars = require('handlebars'); + } + + + + /** + Prepares the Handlebars templating library for use inside Ember's view + system. + + The `Ember.Handlebars` object is the standard Handlebars library, extended to + use Ember's `get()` method instead of direct property access, which allows + computed properties to be used inside templates. + + To create an `Ember.Handlebars` template, call `Ember.Handlebars.compile()`. + This will return a function that can be used by `Ember.View` for rendering. + + @class Handlebars + @namespace Ember + */ + var EmberHandlebars = Ember.Handlebars = objectCreate(Handlebars); + + /** + Register a bound helper or custom view helper. + + ## Simple bound helper example + + ```javascript + Ember.Handlebars.helper('capitalize', function(value) { + return value.toUpperCase(); + }); + ``` + + The above bound helper can be used inside of templates as follows: + + ```handlebars + {{capitalize name}} + ``` + + In this case, when the `name` property of the template's context changes, + the rendered value of the helper will update to reflect this change. + + For more examples of bound helpers, see documentation for + `Ember.Handlebars.registerBoundHelper`. + + ## Custom view helper example + + Assuming a view subclass named `App.CalendarView` were defined, a helper + for rendering instances of this view could be registered as follows: + + ```javascript + Ember.Handlebars.helper('calendar', App.CalendarView): + ``` + + The above bound helper can be used inside of templates as follows: + + ```handlebars + {{calendar}} + ``` + + Which is functionally equivalent to: + + ```handlebars + {{view App.CalendarView}} + ``` + + Options in the helper will be passed to the view in exactly the same + manner as with the `view` helper. + + @method helper + @for Ember.Handlebars + @param {String} name + @param {Function|Ember.View} function or view class constructor + @param {String} dependentKeys* + */ + EmberHandlebars.helper = function(name, value) { + if (!View) { View = requireModule('ember-views/views/view')['default']; } // ES6TODO: stupid circular dep + if (!Component) { Component = requireModule('ember-views/views/component')['default']; } // ES6TODO: stupid circular dep + + + if (View.detect(value)) { + EmberHandlebars.registerHelper(name, EmberHandlebars.makeViewHelper(value)); + } else { + EmberHandlebars.registerBoundHelper.apply(null, arguments); + } + }; + + /** + Returns a helper function that renders the provided ViewClass. + + Used internally by Ember.Handlebars.helper and other methods + involving helper/component registration. + + @private + @method makeViewHelper + @for Ember.Handlebars + @param {Function} ViewClass view class constructor + @since 1.2.0 + */ + EmberHandlebars.makeViewHelper = function(ViewClass) { + return function(options) { + return EmberHandlebars.helpers.view.call(this, ViewClass, options); + }; + }; + + /** + @class helpers + @namespace Ember.Handlebars + */ + EmberHandlebars.helpers = objectCreate(Handlebars.helpers); + + /** + Override the the opcode compiler and JavaScript compiler for Handlebars. + + @class Compiler + @namespace Ember.Handlebars + @private + @constructor + */ + EmberHandlebars.Compiler = function() {}; + + // Handlebars.Compiler doesn't exist in runtime-only + if (Handlebars.Compiler) { + EmberHandlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype); + } + + EmberHandlebars.Compiler.prototype.compiler = EmberHandlebars.Compiler; + + /** + @class JavaScriptCompiler + @namespace Ember.Handlebars + @private + @constructor + */ + EmberHandlebars.JavaScriptCompiler = function() {}; + + // Handlebars.JavaScriptCompiler doesn't exist in runtime-only + if (Handlebars.JavaScriptCompiler) { + EmberHandlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype); + EmberHandlebars.JavaScriptCompiler.prototype.compiler = EmberHandlebars.JavaScriptCompiler; + } + + + EmberHandlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars"; + + EmberHandlebars.JavaScriptCompiler.prototype.initializeBuffer = function() { + return "''"; + }; + + /** + Override the default buffer for Ember Handlebars. By default, Handlebars + creates an empty String at the beginning of each invocation and appends to + it. Ember's Handlebars overrides this to append to a single shared buffer. + + @private + @method appendToBuffer + @param string {String} + */ + EmberHandlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) { + return "data.buffer.push("+string+");"; + }; + + // Hacks ahead: + // Handlebars presently has a bug where the `blockHelperMissing` hook + // doesn't get passed the name of the missing helper name, but rather + // gets passed the value of that missing helper evaluated on the current + // context, which is most likely `undefined` and totally useless. + // + // So we alter the compiled template function to pass the name of the helper + // instead, as expected. + // + // This can go away once the following is closed: + // https://github.com/wycats/handlebars.js/issues/634 + + var DOT_LOOKUP_REGEX = /helpers\.(.*?)\)/, + BRACKET_STRING_LOOKUP_REGEX = /helpers\['(.*?)'/, + INVOCATION_SPLITTING_REGEX = /(.*blockHelperMissing\.call\(.*)(stack[0-9]+)(,.*)/; + + EmberHandlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation = function(source) { + var helperInvocation = source[source.length - 1], + helperName = (DOT_LOOKUP_REGEX.exec(helperInvocation) || BRACKET_STRING_LOOKUP_REGEX.exec(helperInvocation))[1], + matches = INVOCATION_SPLITTING_REGEX.exec(helperInvocation); + + source[source.length - 1] = matches[1] + "'" + helperName + "'" + matches[3]; + }; + + var stringifyBlockHelperMissing = EmberHandlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation; + + var originalBlockValue = EmberHandlebars.JavaScriptCompiler.prototype.blockValue; + EmberHandlebars.JavaScriptCompiler.prototype.blockValue = function() { + originalBlockValue.apply(this, arguments); + stringifyBlockHelperMissing(this.source); + }; + + var originalAmbiguousBlockValue = EmberHandlebars.JavaScriptCompiler.prototype.ambiguousBlockValue; + EmberHandlebars.JavaScriptCompiler.prototype.ambiguousBlockValue = function() { + originalAmbiguousBlockValue.apply(this, arguments); + stringifyBlockHelperMissing(this.source); + }; + + /** + Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that + all simple mustaches in Ember's Handlebars will also set up an observer to + keep the DOM up to date when the underlying property changes. + + @private + @method mustache + @for Ember.Handlebars.Compiler + @param mustache + */ + EmberHandlebars.Compiler.prototype.mustache = function(mustache) { + if (!(mustache.params.length || mustache.hash)) { + var id = new Handlebars.AST.IdNode([{ part: '_triageMustache' }]); + + // Update the mustache node to include a hash value indicating whether the original node + // was escaped. This will allow us to properly escape values when the underlying value + // changes and we need to re-render the value. + if (!mustache.escaped) { + mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]); + mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]); + } + mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped); + } + + return Handlebars.Compiler.prototype.mustache.call(this, mustache); + }; + + /** + Used for precompilation of Ember Handlebars templates. This will not be used + during normal app execution. + + @method precompile + @for Ember.Handlebars + @static + @param {String} string The template to precompile + @param {Boolean} asObject optional parameter, defaulting to true, of whether or not the + compiled template should be returned as an Object or a String + */ + EmberHandlebars.precompile = function(string, asObject) { + var ast = Handlebars.parse(string); + + var options = { + knownHelpers: { + action: true, + unbound: true, + 'bind-attr': true, + template: true, + view: true, + _triageMustache: true + }, + data: true, + stringParams: true + }; + + asObject = asObject === undefined ? true : asObject; + + var environment = new EmberHandlebars.Compiler().compile(ast, options); + return new EmberHandlebars.JavaScriptCompiler().compile(environment, options, undefined, asObject); + }; + + // We don't support this for Handlebars runtime-only + if (Handlebars.compile) { + /** + The entry point for Ember Handlebars. This replaces the default + `Handlebars.compile` and turns on template-local data and String + parameters. + + @method compile + @for Ember.Handlebars + @static + @param {String} string The template to compile + @return {Function} + */ + EmberHandlebars.compile = function(string) { + var ast = Handlebars.parse(string); + var options = { data: true, stringParams: true }; + var environment = new EmberHandlebars.Compiler().compile(ast, options); + var templateSpec = new EmberHandlebars.JavaScriptCompiler().compile(environment, options, undefined, true); + + var template = EmberHandlebars.template(templateSpec); + template.isMethod = false; //Make sure we don't wrap templates with ._super + + return template; + }; + } + + __exports__["default"] = EmberHandlebars; + }); +define("ember-handlebars", + ["ember-handlebars-compiler","ember-metal/core","ember-runtime/system/lazy_load","ember-handlebars/loader","ember-handlebars/ext","ember-handlebars/string","ember-handlebars/helpers/shared","ember-handlebars/helpers/binding","ember-handlebars/helpers/collection","ember-handlebars/helpers/view","ember-handlebars/helpers/unbound","ember-handlebars/helpers/debug","ember-handlebars/helpers/each","ember-handlebars/helpers/template","ember-handlebars/helpers/partial","ember-handlebars/helpers/yield","ember-handlebars/helpers/loc","ember-handlebars/controls/checkbox","ember-handlebars/controls/select","ember-handlebars/controls/text_area","ember-handlebars/controls/text_field","ember-handlebars/controls/text_support","ember-handlebars/controls","ember-handlebars/component_lookup","ember-handlebars/views/handlebars_bound_view","ember-handlebars/views/metamorph_view","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __dependency21__, __dependency22__, __dependency23__, __dependency24__, __dependency25__, __dependency26__, __exports__) { + "use strict"; + var EmberHandlebars = __dependency1__["default"]; + var Ember = __dependency2__["default"]; + // to add to globals + + var runLoadHooks = __dependency3__.runLoadHooks; + var bootstrap = __dependency4__["default"]; + + var normalizePath = __dependency5__.normalizePath; + var template = __dependency5__.template; + var makeBoundHelper = __dependency5__.makeBoundHelper; + var registerBoundHelper = __dependency5__.registerBoundHelper; + var resolveHash = __dependency5__.resolveHash; + var resolveParams = __dependency5__.resolveParams; + var getEscaped = __dependency5__.getEscaped; + var handlebarsGet = __dependency5__.handlebarsGet; + var evaluateUnboundHelper = __dependency5__.evaluateUnboundHelper; + var helperMissingHelper = __dependency5__.helperMissingHelper; + var blockHelperMissingHelper = __dependency5__.blockHelperMissingHelper; + + + // side effect of extending StringUtils of htmlSafe + + var resolvePaths = __dependency7__["default"]; + var bind = __dependency8__.bind; + var _triageMustacheHelper = __dependency8__._triageMustacheHelper; + var resolveHelper = __dependency8__.resolveHelper; + var bindHelper = __dependency8__.bindHelper; + var boundIfHelper = __dependency8__.boundIfHelper; + var unboundIfHelper = __dependency8__.unboundIfHelper; + var withHelper = __dependency8__.withHelper; + var ifHelper = __dependency8__.ifHelper; + var unlessHelper = __dependency8__.unlessHelper; + var bindAttrHelper = __dependency8__.bindAttrHelper; + var bindAttrHelperDeprecated = __dependency8__.bindAttrHelperDeprecated; + var bindClasses = __dependency8__.bindClasses; + + var collectionHelper = __dependency9__["default"]; + var ViewHelper = __dependency10__.ViewHelper; + var viewHelper = __dependency10__.viewHelper; + var unboundHelper = __dependency11__["default"]; + var logHelper = __dependency12__.logHelper; + var debuggerHelper = __dependency12__.debuggerHelper; + var EachView = __dependency13__.EachView; + var GroupedEach = __dependency13__.GroupedEach; + var eachHelper = __dependency13__.eachHelper; + var templateHelper = __dependency14__["default"]; + var partialHelper = __dependency15__["default"]; + var yieldHelper = __dependency16__["default"]; + var locHelper = __dependency17__["default"]; + + + var Checkbox = __dependency18__["default"]; + var Select = __dependency19__.Select; + var SelectOption = __dependency19__.SelectOption; + var SelectOptgroup = __dependency19__.SelectOptgroup; + var TextArea = __dependency20__["default"]; + var TextField = __dependency21__["default"]; + var TextSupport = __dependency22__["default"]; + var inputHelper = __dependency23__.inputHelper; + var textareaHelper = __dependency23__.textareaHelper; + + + var ComponentLookup = __dependency24__["default"]; + var _HandlebarsBoundView = __dependency25__._HandlebarsBoundView; + var SimpleHandlebarsView = __dependency25__.SimpleHandlebarsView; + var _wrapMap = __dependency26__._wrapMap; + var _SimpleMetamorphView = __dependency26__._SimpleMetamorphView; + var _MetamorphView = __dependency26__._MetamorphView; + var _Metamorph = __dependency26__._Metamorph; + + + /** + Ember Handlebars + + @module ember + @submodule ember-handlebars + @requires ember-views + */ + + // Ember.Handlebars.Globals + EmberHandlebars.bootstrap = bootstrap; + EmberHandlebars.template = template; + EmberHandlebars.makeBoundHelper = makeBoundHelper; + EmberHandlebars.registerBoundHelper = registerBoundHelper; + EmberHandlebars.resolveHash = resolveHash; + EmberHandlebars.resolveParams = resolveParams; + EmberHandlebars.resolveHelper = resolveHelper; + EmberHandlebars.get = handlebarsGet; + EmberHandlebars.getEscaped = getEscaped; + EmberHandlebars.evaluateUnboundHelper = evaluateUnboundHelper; + EmberHandlebars.bind = bind; + EmberHandlebars.bindClasses = bindClasses; + EmberHandlebars.EachView = EachView; + EmberHandlebars.GroupedEach = GroupedEach; + EmberHandlebars.resolvePaths = resolvePaths; + EmberHandlebars.ViewHelper = ViewHelper; + EmberHandlebars.normalizePath = normalizePath; + + + // Ember Globals + Ember.Handlebars = EmberHandlebars; + Ember.ComponentLookup = ComponentLookup; + Ember._SimpleHandlebarsView = SimpleHandlebarsView; + Ember._HandlebarsBoundView = _HandlebarsBoundView; + Ember._SimpleMetamorphView = _SimpleMetamorphView; + Ember._MetamorphView = _MetamorphView; + Ember._Metamorph = _Metamorph; + Ember._metamorphWrapMap = _wrapMap; + Ember.TextSupport = TextSupport; + Ember.Checkbox = Checkbox; + Ember.Select = Select; + Ember.SelectOption = SelectOption; + Ember.SelectOptgroup = SelectOptgroup; + Ember.TextArea = TextArea; + Ember.TextField = TextField; + Ember.TextSupport = TextSupport; + + // register helpers + EmberHandlebars.registerHelper('helperMissing', helperMissingHelper); + EmberHandlebars.registerHelper('blockHelperMissing', blockHelperMissingHelper); + EmberHandlebars.registerHelper('bind', bindHelper); + EmberHandlebars.registerHelper('boundIf', boundIfHelper); + EmberHandlebars.registerHelper('_triageMustache', _triageMustacheHelper); + EmberHandlebars.registerHelper('unboundIf', unboundIfHelper); + EmberHandlebars.registerHelper('with', withHelper); + EmberHandlebars.registerHelper('if', ifHelper); + EmberHandlebars.registerHelper('unless', unlessHelper); + EmberHandlebars.registerHelper('bind-attr', bindAttrHelper); + EmberHandlebars.registerHelper('bindAttr', bindAttrHelperDeprecated); + EmberHandlebars.registerHelper('collection', collectionHelper); + EmberHandlebars.registerHelper("log", logHelper); + EmberHandlebars.registerHelper("debugger", debuggerHelper); + EmberHandlebars.registerHelper("each", eachHelper); + EmberHandlebars.registerHelper("loc", locHelper); + EmberHandlebars.registerHelper("partial", partialHelper); + EmberHandlebars.registerHelper("template", templateHelper); + EmberHandlebars.registerHelper("yield", yieldHelper); + EmberHandlebars.registerHelper("view", viewHelper); + EmberHandlebars.registerHelper("unbound", unboundHelper); + EmberHandlebars.registerHelper("input", inputHelper); + EmberHandlebars.registerHelper("textarea", textareaHelper); + + // run load hooks + runLoadHooks('Ember.Handlebars', EmberHandlebars); + + __exports__["default"] = EmberHandlebars; + }); +define("ember-handlebars/component_lookup", + ["ember-runtime/system/object","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var EmberObject = __dependency1__["default"]; + + var ComponentLookup = EmberObject.extend({ + lookupFactory: function(name, container) { + + container = container || this.container; + + var fullName = 'component:' + name, + templateFullName = 'template:components/' + name, + templateRegistered = container && container.has(templateFullName); + + if (templateRegistered) { + container.injection(fullName, 'layout', templateFullName); + } + + var Component = container.lookupFactory(fullName); + + // Only treat as a component if either the component + // or a template has been registered. + if (templateRegistered || Component) { + if (!Component) { + container.register(fullName, Ember.Component); + Component = container.lookupFactory(fullName); + } + return Component; + } + } + }); + + __exports__["default"] = ComponentLookup; + }); +define("ember-handlebars/controls", + ["ember-handlebars/controls/checkbox","ember-handlebars/controls/text_field","ember-handlebars/controls/text_area","ember-metal/core","ember-handlebars-compiler","ember-handlebars/ext","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { + "use strict"; + var Checkbox = __dependency1__["default"]; + var TextField = __dependency2__["default"]; + var TextArea = __dependency3__["default"]; + + var Ember = __dependency4__["default"]; + // Ember.assert + // var emberAssert = Ember.assert; + + var EmberHandlebars = __dependency5__["default"]; + var handlebarsGet = __dependency6__.handlebarsGet; + var helpers = EmberHandlebars.helpers; + /** + @module ember + @submodule ember-handlebars-compiler + */ + + function _resolveOption(context, options, key) { + if (options.hashTypes[key] === "ID") { + return handlebarsGet(context, options.hash[key], options); + } else { + return options.hash[key]; + } + } + + /** + + The `{{input}}` helper inserts an HTML `` tag into the template, + with a `type` value of either `text` or `checkbox`. If no `type` is provided, + `text` will be the default value applied. The attributes of `{{input}}` + match those of the native HTML tag as closely as possible for these two types. + + ## Use as text field + An `{{input}}` with no `type` or a `type` of `text` will render an HTML text input. + The following HTML attributes can be set via the helper: + + + + + + + + + + + + +
    `readonly``required``autofocus`
    `value``placeholder``disabled`
    `size``tabindex``maxlength`
    `name``min``max`
    `pattern``accept``autocomplete`
    `autosave``formaction``formenctype`
    `formmethod``formnovalidate``formtarget`
    `height``inputmode``multiple`
    `step``width``form`
    `selectionDirection``spellcheck` 
    + + + When set to a quoted string, these values will be directly applied to the HTML + element. When left unquoted, these values will be bound to a property on the + template's current rendering context (most typically a controller instance). + + ## Unbound: + + ```handlebars + {{input value="http://www.facebook.com"}} + ``` + + + ```html + + ``` + + ## Bound: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + firstName: "Stanley", + entryNotAllowed: true + }); + ``` + + + ```handlebars + {{input type="text" value=firstName disabled=entryNotAllowed size="50"}} + ``` + + + ```html + + ``` + + ## Actions + + The helper can send multiple actions based on user events. + + The action property defines the action which is send when + the user presses the return key. + + ```handlebars + {{input action="submit"}} + ``` + + The helper allows some user events to send actions. + + * `enter` + * `insert-newline` + * `escape-press` + * `focus-in` + * `focus-out` + * `key-press` + + For example, if you desire an action to be sent when the input is blurred, + you only need to setup the action name to the event name property. + + ```handlebars + {{input focus-in="alertMessage"}} + ``` + + See more about [Text Support Actions](/api/classes/Ember.TextField.html) + + ## Extension + + Internally, `{{input type="text"}}` creates an instance of `Ember.TextField`, passing + arguments from the helper to `Ember.TextField`'s `create` method. You can extend the + capabilities of text inputs in your applications by reopening this class. For example, + if you are building a Bootstrap project where `data-*` attributes are used, you + can add one to the `TextField`'s `attributeBindings` property: + + + ```javascript + Ember.TextField.reopen({ + attributeBindings: ['data-error'] + }); + ``` + + Keep in mind when writing `Ember.TextField` subclasses that `Ember.TextField` + itself extends `Ember.Component`, meaning that it does NOT inherit + the `controller` of the parent view. + + See more about [Ember components](/api/classes/Ember.Component.html) + + + ## Use as checkbox + + An `{{input}}` with a `type` of `checkbox` will render an HTML checkbox input. + The following HTML attributes can be set via the helper: + + * `checked` + * `disabled` + * `tabindex` + * `indeterminate` + * `name` + * `autofocus` + * `form` + + + When set to a quoted string, these values will be directly applied to the HTML + element. When left unquoted, these values will be bound to a property on the + template's current rendering context (most typically a controller instance). + + ## Unbound: + + ```handlebars + {{input type="checkbox" name="isAdmin"}} + ``` + + ```html + + ``` + + ## Bound: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + isAdmin: true + }); + ``` + + + ```handlebars + {{input type="checkbox" checked=isAdmin }} + ``` + + + ```html + + ``` + + ## Extension + + Internally, `{{input type="checkbox"}}` creates an instance of `Ember.Checkbox`, passing + arguments from the helper to `Ember.Checkbox`'s `create` method. You can extend the + capablilties of checkbox inputs in your applications by reopening this class. For example, + if you wanted to add a css class to all checkboxes in your application: + + + ```javascript + Ember.Checkbox.reopen({ + classNames: ['my-app-checkbox'] + }); + ``` + + + @method input + @for Ember.Handlebars.helpers + @param {Hash} options + */ + function inputHelper(options) { + + var hash = options.hash, + types = options.hashTypes, + inputType = _resolveOption(this, options, 'type'), + onEvent = hash.on; + + if (inputType === 'checkbox') { + delete hash.type; + delete types.type; + + + return helpers.view.call(this, Checkbox, options); + } else { + delete hash.on; + + hash.onEvent = onEvent || 'enter'; + return helpers.view.call(this, TextField, options); + } + } + + __exports__.inputHelper = inputHelper;/** + `{{textarea}}` inserts a new instance of ` + ``` + + Bound: + + In the following example, the `writtenWords` property on `App.ApplicationController` + will be updated live as the user types 'Lots of text that IS bound' into + the text area of their browser's window. + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + writtenWords: "Lots of text that IS bound" + }); + ``` + + ```handlebars + {{textarea value=writtenWords}} + ``` + + Would result in the following HTML: + + ```html + + ``` + + If you wanted a one way binding between the text area and a div tag + somewhere else on your screen, you could use `Ember.computed.oneWay`: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + writtenWords: "Lots of text that IS bound", + outputWrittenWords: Ember.computed.oneWay("writtenWords") + }); + ``` + + ```handlebars + {{textarea value=writtenWords}} + +
    + {{outputWrittenWords}} +
    + ``` + + Would result in the following HTML: + + ```html + + + <-- the following div will be updated in real time as you type --> + +
    + Lots of text that IS bound +
    + ``` + + Finally, this example really shows the power and ease of Ember when two + properties are bound to eachother via `Ember.computed.alias`. Type into + either text area box and they'll both stay in sync. Note that + `Ember.computed.alias` costs more in terms of performance, so only use it when + your really binding in both directions: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + writtenWords: "Lots of text that IS bound", + twoWayWrittenWords: Ember.computed.alias("writtenWords") + }); + ``` + + ```handlebars + {{textarea value=writtenWords}} + {{textarea value=twoWayWrittenWords}} + ``` + + ```html + + + <-- both updated in real time --> + + + ``` + + ## Actions + + The helper can send multiple actions based on user events. + + The action property defines the action which is send when + the user presses the return key. + + ```handlebars + {{input action="submit"}} + ``` + + The helper allows some user events to send actions. + + * `enter` + * `insert-newline` + * `escape-press` + * `focus-in` + * `focus-out` + * `key-press` + + For example, if you desire an action to be sent when the input is blurred, + you only need to setup the action name to the event name property. + + ```handlebars + {{textarea focus-in="alertMessage"}} + ``` + + See more about [Text Support Actions](/api/classes/Ember.TextArea.html) + + ## Extension + + Internally, `{{textarea}}` creates an instance of `Ember.TextArea`, passing + arguments from the helper to `Ember.TextArea`'s `create` method. You can + extend the capabilities of text areas in your application by reopening this + class. For example, if you are building a Bootstrap project where `data-*` + attributes are used, you can globally add support for a `data-*` attribute + on all `{{textarea}}`s' in your app by reopening `Ember.TextArea` or + `Ember.TextSupport` and adding it to the `attributeBindings` concatenated + property: + + ```javascript + Ember.TextArea.reopen({ + attributeBindings: ['data-error'] + }); + ``` + + Keep in mind when writing `Ember.TextArea` subclasses that `Ember.TextArea` + itself extends `Ember.Component`, meaning that it does NOT inherit + the `controller` of the parent view. + + See more about [Ember components](/api/classes/Ember.Component.html) + + @method textarea + @for Ember.Handlebars.helpers + @param {Hash} options + */ + function textareaHelper(options) { + + var hash = options.hash, + types = options.hashTypes; + + return helpers.view.call(this, TextArea, options); + } + + __exports__.textareaHelper = textareaHelper; + }); +define("ember-handlebars/controls/checkbox", + ["ember-metal/property_get","ember-metal/property_set","ember-views/views/view","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var get = __dependency1__.get; + var set = __dependency2__.set; + var View = __dependency3__["default"]; + + /** + @module ember + @submodule ember-handlebars + */ + + /** + The internal class used to create text inputs when the `{{input}}` + helper is used with `type` of `checkbox`. + + See [handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details. + + ## Direct manipulation of `checked` + + The `checked` attribute of an `Ember.Checkbox` object should always be set + through the Ember object or by interacting with its rendered element + representation via the mouse, keyboard, or touch. Updating the value of the + checkbox via jQuery will result in the checked value of the object and its + element losing synchronization. + + ## Layout and LayoutName properties + + Because HTML `input` elements are self closing `layout` and `layoutName` + properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s + layout section for more information. + + @class Checkbox + @namespace Ember + @extends Ember.View + */ + __exports__["default"] = View.extend({ + instrumentDisplay: '{{input type="checkbox"}}', + + classNames: ['ember-checkbox'], + + tagName: 'input', + + attributeBindings: [ + 'type', + 'checked', + 'indeterminate', + 'disabled', + 'tabindex', + 'name', + 'autofocus', + 'required', + 'form' + ], + + type: 'checkbox', + checked: false, + disabled: false, + indeterminate: false, + + init: function() { + this._super(); + this.on('change', this, this._updateElementValue); + }, + + didInsertElement: function() { + this._super(); + get(this, 'element').indeterminate = !!get(this, 'indeterminate'); + }, + + _updateElementValue: function() { + set(this, 'checked', this.$().prop('checked')); + } + }); + }); +define("ember-handlebars/controls/select", + ["ember-handlebars-compiler","ember-metal/enumerable_utils","ember-metal/property_get","ember-metal/property_set","ember-views/views/view","ember-views/views/collection_view","ember-metal/utils","ember-metal/is_none","ember-metal/computed","ember-runtime/system/native_array","ember-metal/mixin","ember-metal/properties","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-handlebars + */ + + var EmberHandlebars = __dependency1__["default"]; + + var forEach = __dependency2__.forEach; + var indexOf = __dependency2__.indexOf; + var indexesOf = __dependency2__.indexesOf; + var replace = __dependency2__.replace; + + var get = __dependency3__.get; + var set = __dependency4__.set; + var View = __dependency5__["default"]; + var CollectionView = __dependency6__["default"]; + var isArray = __dependency7__.isArray; + var isNone = __dependency8__["default"]; + var computed = __dependency9__.computed; + var emberA = __dependency10__.A; + var observer = __dependency11__.observer; + var defineProperty = __dependency12__.defineProperty; + + var precompileTemplate = EmberHandlebars.compile; + + var SelectOption = View.extend({ + instrumentDisplay: 'Ember.SelectOption', + + tagName: 'option', + attributeBindings: ['value', 'selected'], + + defaultTemplate: function(context, options) { + options = { data: options.data, hash: {} }; + EmberHandlebars.helpers.bind.call(context, "view.label", options); + }, + + init: function() { + this.labelPathDidChange(); + this.valuePathDidChange(); + + this._super(); + }, + + selected: computed(function() { + var content = get(this, 'content'), + selection = get(this, 'parentView.selection'); + if (get(this, 'parentView.multiple')) { + return selection && indexOf(selection, content.valueOf()) > -1; + } else { + // Primitives get passed through bindings as objects... since + // `new Number(4) !== 4`, we use `==` below + return content == selection; // jshint ignore:line + } + }).property('content', 'parentView.selection'), + + labelPathDidChange: observer('parentView.optionLabelPath', function() { + var labelPath = get(this, 'parentView.optionLabelPath'); + + if (!labelPath) { return; } + + defineProperty(this, 'label', computed(function() { + return get(this, labelPath); + }).property(labelPath)); + }), + + valuePathDidChange: observer('parentView.optionValuePath', function() { + var valuePath = get(this, 'parentView.optionValuePath'); + + if (!valuePath) { return; } + + defineProperty(this, 'value', computed(function() { + return get(this, valuePath); + }).property(valuePath)); + }) + }); + + var SelectOptgroup = CollectionView.extend({ + instrumentDisplay: 'Ember.SelectOptgroup', + + tagName: 'optgroup', + attributeBindings: ['label'], + + selectionBinding: 'parentView.selection', + multipleBinding: 'parentView.multiple', + optionLabelPathBinding: 'parentView.optionLabelPath', + optionValuePathBinding: 'parentView.optionValuePath', + + itemViewClassBinding: 'parentView.optionView' + }); + + /** + The `Ember.Select` view class renders a + [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element, + allowing the user to choose from a list of options. + + The text and `value` property of each ` + + + ``` + + The `value` attribute of the selected `"); + return buffer; + } + + function program3(depth0,data) { + + var stack1; + stack1 = helpers.each.call(depth0, "view.groupedContent", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(4, program4, data),contexts:[depth0],types:["ID"],data:data}); + if(stack1 || stack1 === 0) { data.buffer.push(stack1); } + else { data.buffer.push(''); } + } + function program4(depth0,data) { + + + data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.groupView", {hash:{ + 'content': ("content"), + 'label': ("label") + },hashTypes:{'content': "ID",'label': "ID"},hashContexts:{'content': depth0,'label': depth0},contexts:[depth0],types:["ID"],data:data}))); + } + + function program6(depth0,data) { + + var stack1; + stack1 = helpers.each.call(depth0, "view.content", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(7, program7, data),contexts:[depth0],types:["ID"],data:data}); + if(stack1 || stack1 === 0) { data.buffer.push(stack1); } + else { data.buffer.push(''); } + } + function program7(depth0,data) { + + + data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", {hash:{ + 'content': ("") + },hashTypes:{'content': "ID"},hashContexts:{'content': depth0},contexts:[depth0],types:["ID"],data:data}))); + } + + stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],data:data}); + if(stack1 || stack1 === 0) { data.buffer.push(stack1); } + stack1 = helpers['if'].call(depth0, "view.optionGroupPath", {hash:{},hashTypes:{},hashContexts:{},inverse:self.program(6, program6, data),fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],data:data}); + if(stack1 || stack1 === 0) { data.buffer.push(stack1); } + return buffer; + + }), + attributeBindings: ['multiple', 'disabled', 'tabindex', 'name', 'required', 'autofocus', + 'form', 'size'], + + /** + The `multiple` attribute of the select element. Indicates whether multiple + options can be selected. + + @property multiple + @type Boolean + @default false + */ + multiple: false, + + /** + The `disabled` attribute of the select element. Indicates whether + the element is disabled from interactions. + + @property disabled + @type Boolean + @default false + */ + disabled: false, + + /** + The `required` attribute of the select element. Indicates whether + a selected option is required for form validation. + + @property required + @type Boolean + @default false + @since 1.5.0 + */ + required: false, + + /** + The list of options. + + If `optionLabelPath` and `optionValuePath` are not overridden, this should + be a list of strings, which will serve simultaneously as labels and values. + + Otherwise, this should be a list of objects. For instance: + + ```javascript + Ember.Select.create({ + content: Ember.A([ + { id: 1, firstName: 'Yehuda' }, + { id: 2, firstName: 'Tom' } + ]), + optionLabelPath: 'content.firstName', + optionValuePath: 'content.id' + }); + ``` + + @property content + @type Array + @default null + */ + content: null, + + /** + When `multiple` is `false`, the element of `content` that is currently + selected, if any. + + When `multiple` is `true`, an array of such elements. + + @property selection + @type Object or Array + @default null + */ + selection: null, + + /** + In single selection mode (when `multiple` is `false`), value can be used to + get the current selection's value or set the selection by it's value. + + It is not currently supported in multiple selection mode. + + @property value + @type String + @default null + */ + value: computed(function(key, value) { + if (arguments.length === 2) { return value; } + var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''); + return valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection'); + }).property('selection'), + + /** + If given, a top-most dummy option will be rendered to serve as a user + prompt. + + @property prompt + @type String + @default null + */ + prompt: null, + + /** + The path of the option labels. See [content](/api/classes/Ember.Select.html#property_content). + + @property optionLabelPath + @type String + @default 'content' + */ + optionLabelPath: 'content', + + /** + The path of the option values. See [content](/api/classes/Ember.Select.html#property_content). + + @property optionValuePath + @type String + @default 'content' + */ + optionValuePath: 'content', + + /** + The path of the option group. + When this property is used, `content` should be sorted by `optionGroupPath`. + + @property optionGroupPath + @type String + @default null + */ + optionGroupPath: null, + + /** + The view class for optgroup. + + @property groupView + @type Ember.View + @default Ember.SelectOptgroup + */ + groupView: SelectOptgroup, + + groupedContent: computed(function() { + var groupPath = get(this, 'optionGroupPath'); + var groupedContent = emberA(); + var content = get(this, 'content') || []; + + forEach(content, function(item) { + var label = get(item, groupPath); + + if (get(groupedContent, 'lastObject.label') !== label) { + groupedContent.pushObject({ + label: label, + content: emberA() + }); + } + + get(groupedContent, 'lastObject.content').push(item); + }); + + return groupedContent; + }).property('optionGroupPath', 'content.@each'), + + /** + The view class for option. + + @property optionView + @type Ember.View + @default Ember.SelectOption + */ + optionView: SelectOption, + + _change: function() { + if (get(this, 'multiple')) { + this._changeMultiple(); + } else { + this._changeSingle(); + } + }, + + selectionDidChange: observer('selection.@each', function() { + var selection = get(this, 'selection'); + if (get(this, 'multiple')) { + if (!isArray(selection)) { + set(this, 'selection', emberA([selection])); + return; + } + this._selectionDidChangeMultiple(); + } else { + this._selectionDidChangeSingle(); + } + }), + + valueDidChange: observer('value', function() { + var content = get(this, 'content'), + value = get(this, 'value'), + valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''), + selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection')), + selection; + + if (value !== selectedValue) { + selection = content ? content.find(function(obj) { + return value === (valuePath ? get(obj, valuePath) : obj); + }) : null; + + this.set('selection', selection); + } + }), + + + _triggerChange: function() { + var selection = get(this, 'selection'); + var value = get(this, 'value'); + + if (!isNone(selection)) { this.selectionDidChange(); } + if (!isNone(value)) { this.valueDidChange(); } + + this._change(); + }, + + _changeSingle: function() { + var selectedIndex = this.$()[0].selectedIndex, + content = get(this, 'content'), + prompt = get(this, 'prompt'); + + if (!content || !get(content, 'length')) { return; } + if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; } + + if (prompt) { selectedIndex -= 1; } + set(this, 'selection', content.objectAt(selectedIndex)); + }, + + + _changeMultiple: function() { + var options = this.$('option:selected'), + prompt = get(this, 'prompt'), + offset = prompt ? 1 : 0, + content = get(this, 'content'), + selection = get(this, 'selection'); + + if (!content) { return; } + if (options) { + var selectedIndexes = options.map(function() { + return this.index - offset; + }).toArray(); + var newSelection = content.objectsAt(selectedIndexes); + + if (isArray(selection)) { + replace(selection, 0, get(selection, 'length'), newSelection); + } else { + set(this, 'selection', newSelection); + } + } + }, + + _selectionDidChangeSingle: function() { + var el = this.get('element'); + if (!el) { return; } + + var content = get(this, 'content'), + selection = get(this, 'selection'), + selectionIndex = content ? indexOf(content, selection) : -1, + prompt = get(this, 'prompt'); + + if (prompt) { selectionIndex += 1; } + if (el) { el.selectedIndex = selectionIndex; } + }, + + _selectionDidChangeMultiple: function() { + var content = get(this, 'content'), + selection = get(this, 'selection'), + selectedIndexes = content ? indexesOf(content, selection) : [-1], + prompt = get(this, 'prompt'), + offset = prompt ? 1 : 0, + options = this.$('option'), + adjusted; + + if (options) { + options.each(function() { + adjusted = this.index > -1 ? this.index - offset : -1; + this.selected = indexOf(selectedIndexes, adjusted) > -1; + }); + } + }, + + init: function() { + this._super(); + this.on("didInsertElement", this, this._triggerChange); + this.on("change", this, this._change); + } + }); + + __exports__["default"] = Select; + __exports__.Select = Select; + __exports__.SelectOption = SelectOption; + __exports__.SelectOptgroup = SelectOptgroup; + }); +define("ember-handlebars/controls/text_area", + ["ember-metal/property_get","ember-views/views/component","ember-handlebars/controls/text_support","ember-metal/mixin","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + + /** + @module ember + @submodule ember-handlebars + */ + var get = __dependency1__.get; + var Component = __dependency2__["default"]; + var TextSupport = __dependency3__["default"]; + var observer = __dependency4__.observer; + + /** + The internal class used to create textarea element when the `{{textarea}}` + helper is used. + + See [handlebars.helpers.textarea](/api/classes/Ember.Handlebars.helpers.html#method_textarea) for usage details. + + ## Layout and LayoutName properties + + Because HTML `textarea` elements do not contain inner HTML the `layout` and + `layoutName` properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s + layout section for more information. + + @class TextArea + @namespace Ember + @extends Ember.Component + @uses Ember.TextSupport + */ + __exports__["default"] = Component.extend(TextSupport, { + instrumentDisplay: '{{textarea}}', + + classNames: ['ember-text-area'], + + tagName: "textarea", + attributeBindings: ['rows', 'cols', 'name', 'selectionEnd', 'selectionStart', 'wrap'], + rows: null, + cols: null, + + _updateElementValue: observer('value', function() { + // We do this check so cursor position doesn't get affected in IE + var value = get(this, 'value'), + $el = this.$(); + if ($el && value !== $el.val()) { + $el.val(value); + } + }), + + init: function() { + this._super(); + this.on("didInsertElement", this, this._updateElementValue); + } + }); + }); +define("ember-handlebars/controls/text_field", + ["ember-metal/property_get","ember-metal/property_set","ember-views/views/component","ember-handlebars/controls/text_support","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-handlebars + */ + + var get = __dependency1__.get; + var set = __dependency2__.set; + var Component = __dependency3__["default"]; + var TextSupport = __dependency4__["default"]; + + /** + + The internal class used to create text inputs when the `{{input}}` + helper is used with `type` of `text`. + + See [Handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details. + + ## Layout and LayoutName properties + + Because HTML `input` elements are self closing `layout` and `layoutName` + properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s + layout section for more information. + + @class TextField + @namespace Ember + @extends Ember.Component + @uses Ember.TextSupport + */ + __exports__["default"] = Component.extend(TextSupport, { + instrumentDisplay: '{{input type="text"}}', + + classNames: ['ember-text-field'], + tagName: "input", + attributeBindings: ['type', 'value', 'size', 'pattern', 'name', 'min', 'max', + 'accept', 'autocomplete', 'autosave', 'formaction', + 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', + 'height', 'inputmode', 'list', 'multiple', 'step', + 'width'], + + /** + The `value` attribute of the input element. As the user inputs text, this + property is updated live. + + @property value + @type String + @default "" + */ + value: "", + + /** + The `type` attribute of the input element. + + @property type + @type String + @default "text" + */ + type: "text", + + /** + The `size` of the text field in characters. + + @property size + @type String + @default null + */ + size: null, + + /** + The `pattern` attribute of input element. + + @property pattern + @type String + @default null + */ + pattern: null, + + /** + The `min` attribute of input element used with `type="number"` or `type="range"`. + + @property min + @type String + @default null + @since 1.4.0 + */ + min: null, + + /** + The `max` attribute of input element used with `type="number"` or `type="range"`. + + @property max + @type String + @default null + @since 1.4.0 + */ + max: null + }); + }); +define("ember-handlebars/controls/text_support", + ["ember-metal/property_get","ember-metal/property_set","ember-metal/mixin","ember-runtime/mixins/target_action_support","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + /** + @module ember + @submodule ember-handlebars + */ + + var get = __dependency1__.get; + var set = __dependency2__.set; + var Mixin = __dependency3__.Mixin; + var TargetActionSupport = __dependency4__["default"]; + + /** + Shared mixin used by `Ember.TextField` and `Ember.TextArea`. + + @class TextSupport + @namespace Ember + @uses Ember.TargetActionSupport + @extends Ember.Mixin + @private + */ + var TextSupport = Mixin.create(TargetActionSupport, { + value: "", + + attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly', + 'autofocus', 'form', 'selectionDirection', 'spellcheck', 'required', + 'title', 'autocapitalize', 'autocorrect'], + placeholder: null, + disabled: false, + maxlength: null, + + init: function() { + this._super(); + this.on("focusOut", this, this._elementValueDidChange); + this.on("change", this, this._elementValueDidChange); + this.on("paste", this, this._elementValueDidChange); + this.on("cut", this, this._elementValueDidChange); + this.on("input", this, this._elementValueDidChange); + this.on("keyUp", this, this.interpretKeyEvents); + }, + + /** + The action to be sent when the user presses the return key. + + This is similar to the `{{action}}` helper, but is fired when + the user presses the return key when editing a text field, and sends + the value of the field as the context. + + @property action + @type String + @default null + */ + action: null, + + /** + The event that should send the action. + + Options are: + + * `enter`: the user pressed enter + * `keyPress`: the user pressed a key + + @property onEvent + @type String + @default enter + */ + onEvent: 'enter', + + /** + Whether they `keyUp` event that triggers an `action` to be sent continues + propagating to other views. + + By default, when the user presses the return key on their keyboard and + the text field has an `action` set, the action will be sent to the view's + controller and the key event will stop propagating. + + If you would like parent views to receive the `keyUp` event even after an + action has been dispatched, set `bubbles` to true. + + @property bubbles + @type Boolean + @default false + */ + bubbles: false, + + interpretKeyEvents: function(event) { + var map = TextSupport.KEY_EVENTS; + var method = map[event.keyCode]; + + this._elementValueDidChange(); + if (method) { return this[method](event); } + }, + + _elementValueDidChange: function() { + set(this, 'value', this.$().val()); + }, + + /** + Called when the user inserts a new line. + + Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13. + Uses sendAction to send the `enter` action. + + @method insertNewline + @param {Event} event + */ + insertNewline: function(event) { + sendAction('enter', this, event); + sendAction('insert-newline', this, event); + }, + + /** + Called when the user hits escape. + + Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 27. + Uses sendAction to send the `escape-press` action. + + @method cancel + @param {Event} event + */ + cancel: function(event) { + sendAction('escape-press', this, event); + }, + + /** + Called when the text area is focused. + + Uses sendAction to send the `focus-in` action. + + @method focusIn + @param {Event} event + */ + focusIn: function(event) { + sendAction('focus-in', this, event); + }, + + /** + Called when the text area is blurred. + + Uses sendAction to send the `focus-out` action. + + @method focusOut + @param {Event} event + */ + focusOut: function(event) { + sendAction('focus-out', this, event); + }, + + /** + Called when the user presses a key. Enabled by setting + the `onEvent` property to `keyPress`. + + Uses sendAction to send the `key-press` action. + + @method keyPress + @param {Event} event + */ + keyPress: function(event) { + sendAction('key-press', this, event); + } + + }); + + TextSupport.KEY_EVENTS = { + 13: 'insertNewline', + 27: 'cancel' + }; + + // In principle, this shouldn't be necessary, but the legacy + // sendAction semantics for TextField are different from + // the component semantics so this method normalizes them. + function sendAction(eventName, view, event) { + var action = get(view, eventName), + on = get(view, 'onEvent'), + value = get(view, 'value'); + + // back-compat support for keyPress as an event name even though + // it's also a method name that consumes the event (and therefore + // incompatible with sendAction semantics). + if (on === eventName || (on === 'keyPress' && eventName === 'key-press')) { + view.sendAction('action', value); + } + + view.sendAction(eventName, value); + + if (action || on === eventName) { + if(!get(view, 'bubbles')) { + event.stopPropagation(); + } + } + } + + __exports__["default"] = TextSupport; + }); +define("ember-handlebars/ext", + ["ember-metal/core","ember-runtime/system/string","ember-handlebars-compiler","ember-metal/property_get","ember-metal/binding","ember-metal/error","ember-metal/mixin","ember-metal/is_empty","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.FEATURES, Ember.assert, Ember.Handlebars, Ember.lookup + // var emberAssert = Ember.assert; + + var fmt = __dependency2__.fmt; + + var EmberHandlebars = __dependency3__["default"]; + var helpers = EmberHandlebars.helpers; + + var get = __dependency4__.get; + var isGlobalPath = __dependency5__.isGlobalPath; + var EmberError = __dependency6__["default"]; + var IS_BINDING = __dependency7__.IS_BINDING; + + // late bound via requireModule because of circular dependencies. + var resolveHelper, + SimpleHandlebarsView; + + var isEmpty = __dependency8__["default"]; + + var slice = [].slice, originalTemplate = EmberHandlebars.template; + + /** + If a path starts with a reserved keyword, returns the root + that should be used. + + @private + @method normalizePath + @for Ember + @param root {Object} + @param path {String} + @param data {Hash} + */ + function normalizePath(root, path, data) { + var keywords = (data && data.keywords) || {}, + keyword, isKeyword; + + // Get the first segment of the path. For example, if the + // path is "foo.bar.baz", returns "foo". + keyword = path.split('.', 1)[0]; + + // Test to see if the first path is a keyword that has been + // passed along in the view's data hash. If so, we will treat + // that object as the new root. + if (keywords.hasOwnProperty(keyword)) { + // Look up the value in the template's data hash. + root = keywords[keyword]; + isKeyword = true; + + // Handle cases where the entire path is the reserved + // word. In that case, return the object itself. + if (path === keyword) { + path = ''; + } else { + // Strip the keyword from the path and look up + // the remainder from the newly found root. + path = path.substr(keyword.length+1); + } + } + + return { root: root, path: path, isKeyword: isKeyword }; + } + + + /** + Lookup both on root and on window. If the path starts with + a keyword, the corresponding object will be looked up in the + template's data hash and used to resolve the path. + + @method get + @for Ember.Handlebars + @param {Object} root The object to look up the property on + @param {String} path The path to be lookedup + @param {Object} options The template's option hash + */ + function handlebarsGet(root, path, options) { + var data = options && options.data, + normalizedPath = normalizePath(root, path, data), + value; + + + root = normalizedPath.root; + path = normalizedPath.path; + + value = get(root, path); + + if (value === undefined && root !== Ember.lookup && isGlobalPath(path)) { + value = get(Ember.lookup, path); + } + + + return value; + } + + /** + This method uses `Ember.Handlebars.get` to lookup a value, then ensures + that the value is escaped properly. + + If `unescaped` is a truthy value then the escaping will not be performed. + + @method getEscaped + @for Ember.Handlebars + @param {Object} root The object to look up the property on + @param {String} path The path to be lookedup + @param {Object} options The template's option hash + @since 1.4.0 + */ + function getEscaped(root, path, options) { + var result = handlebarsGet(root, path, options); + + if (result === null || result === undefined) { + result = ""; + } else if (!(result instanceof Handlebars.SafeString)) { + result = String(result); + } + if (!options.hash.unescaped){ + result = Handlebars.Utils.escapeExpression(result); + } + + return result; + } + + __exports__.getEscaped = getEscaped;function resolveParams(context, params, options) { + var resolvedParams = [], types = options.types, param, type; + + for (var i=0, l=params.length; i{{user.name}} + +
    +
    {{user.role.label}}
    + {{user.role.id}} + +

    {{user.role.description}}

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

    {{description}}

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

    Howdy Dave

    +

    Howdy Mary

    +

    Howdy Sara

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

    Sorry, nobody is available for this task.

    + {{/each}} + ``` + ### Specifying a View class for items + If you provide an `itemViewClass` option that references a view class + with its own `template` you can omit the block. + + The following template: + + ```handlebars + {{#view App.MyView }} + {{each view.items itemViewClass="App.AnItemView"}} + {{/view}} + ``` + + And application code + + ```javascript + App = Ember.Application.create({ + MyView: Ember.View.extend({ + items: [ + Ember.Object.create({name: 'Dave'}), + Ember.Object.create({name: 'Mary'}), + Ember.Object.create({name: 'Sara'}) + ] + }) + }); + + App.AnItemView = Ember.View.extend({ + template: Ember.Handlebars.compile("Greetings {{name}}") + }); + ``` + + Will result in the HTML structure below + + ```html +
    +
    Greetings Dave
    +
    Greetings Mary
    +
    Greetings Sara
    +
    + ``` + + If an `itemViewClass` is defined on the helper, and therefore the helper is not + being used as a block, an `emptyViewClass` can also be provided optionally. + The `emptyViewClass` will match the behavior of the `{{else}}` condition + described above. That is, the `emptyViewClass` will render if the collection + is empty. + + ### Representing each item with a Controller. + By default the controller lookup within an `{{#each}}` block will be + the controller of the template where the `{{#each}}` was used. If each + item needs to be presented by a custom controller you can provide a + `itemController` option which references a controller by lookup name. + Each item in the loop will be wrapped in an instance of this controller + and the item itself will be set to the `model` property of that controller. + + This is useful in cases where properties of model objects need transformation + or synthesis for display: + + ```javascript + App.DeveloperController = Ember.ObjectController.extend({ + isAvailableForHire: function() { + return !this.get('model.isEmployed') && this.get('model.isSeekingWork'); + }.property('isEmployed', 'isSeekingWork') + }) + ``` + + ```handlebars + {{#each person in developers itemController="developer"}} + {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}} + {{/each}} + ``` + + Each itemController will receive a reference to the current controller as + a `parentController` property. + + ### (Experimental) Grouped Each + + When used in conjunction with the experimental [group helper](https://github.com/emberjs/group-helper), + you can inform Handlebars to re-render an entire group of items instead of + re-rendering them one at a time (in the event that they are changed en masse + or an item is added/removed). + + ```handlebars + {{#group}} + {{#each people}} + {{firstName}} {{lastName}} + {{/each}} + {{/group}} + ``` + + This can be faster than the normal way that Handlebars re-renders items + in some cases. + + If for some reason you have a group with more than one `#each`, you can make + one of the collections be updated in normal (non-grouped) fashion by setting + the option `groupedRows=true` (counter-intuitive, I know). + + For example, + + ```handlebars + {{dealershipName}} + + {{#group}} + {{#each dealers}} + {{firstName}} {{lastName}} + {{/each}} + + {{#each car in cars groupedRows=true}} + {{car.make}} {{car.model}} {{car.color}} + {{/each}} + {{/group}} + ``` + Any change to `dealershipName` or the `dealers` collection will cause the + entire group to be re-rendered. However, changes to the `cars` collection + will be re-rendered individually (as normal). + + Note that `group` behavior is also disabled by specifying an `itemViewClass`. + + @method each + @for Ember.Handlebars.helpers + @param [name] {String} name for item (used with `in`) + @param [path] {String} path + @param [options] {Object} Handlebars key/value pairs of options + @param [options.itemViewClass] {String} a path to a view class used for each item + @param [options.itemController] {String} name of a controller to be created for each item + @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper + */ + function eachHelper(path, options) { + var ctx, helperName = 'each'; + + if (arguments.length === 4) { + + var keywordName = arguments[0]; + + + options = arguments[3]; + path = arguments[2]; + + helperName += ' ' + keywordName + ' in ' + path; + + if (path === '') { path = "this"; } + + options.hash.keyword = keywordName; + + } else if (arguments.length === 1) { + options = path; + path = 'this'; + } else { + helperName += ' ' + path; + } + + options.hash.dataSourceBinding = path; + // Set up emptyView as a metamorph with no tag + //options.hash.emptyViewClass = Ember._MetamorphView; + + // can't rely on this default behavior when use strict + ctx = this || window; + + options.helperName = options.helperName || helperName; + + if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) { + new GroupedEach(ctx, path, options).render(); + } else { + // ES6TODO: figure out how to do this without global lookup. + return helpers.collection.call(ctx, 'Ember.Handlebars.EachView', options); + } + } + + __exports__.EachView = EachView; + __exports__.GroupedEach = GroupedEach; + __exports__.eachHelper = eachHelper; + }); +define("ember-handlebars/helpers/loc", + ["ember-runtime/system/string","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var loc = __dependency1__.loc; + + /** + @module ember + @submodule ember-handlebars + */ + + // ES6TODO: + // Pretty sure this can be expressed as + // var locHelper EmberStringUtils.loc ? + + /** + Calls [Ember.String.loc](/api/classes/Ember.String.html#method_loc) with the + provided string. + + This is a convenient way to localize text. For example: + + ```html + + ``` + + Take note that `"welcome"` is a string and not an object + reference. + + See [Ember.String.loc](/api/classes/Ember.String.html#method_loc) for how to + set up localized string references. + + @method loc + @for Ember.Handlebars.helpers + @param {String} str The string to format + @see {Ember.String#loc} + */ + __exports__["default"] = function locHelper(str) { + return loc(str); + } + }); +define("ember-handlebars/helpers/partial", + ["ember-metal/core","ember-metal/is_none","ember-handlebars/ext","ember-handlebars/helpers/binding","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + var Ember = __dependency1__["default"]; + // Ember.assert + // var emberAssert = Ember.assert; + + var isNone = __dependency2__.isNone; + var handlebarsGet = __dependency3__.handlebarsGet; + var bind = __dependency4__.bind; + + /** + @module ember + @submodule ember-handlebars + */ + + /** + The `partial` helper renders another template without + changing the template context: + + ```handlebars + {{foo}} + {{partial "nav"}} + ``` + + The above example template will render a template named + "_nav", which has the same context as the parent template + it's rendered into, so if the "_nav" template also referenced + `{{foo}}`, it would print the same thing as the `{{foo}}` + in the above example. + + If a "_nav" template isn't found, the `partial` helper will + fall back to a template named "nav". + + ## Bound template names + + The parameter supplied to `partial` can also be a path + to a property containing a template name, e.g.: + + ```handlebars + {{partial someTemplateName}} + ``` + + The above example will look up the value of `someTemplateName` + on the template context (e.g. a controller) and use that + value as the name of the template to render. If the resolved + value is falsy, nothing will be rendered. If `someTemplateName` + changes, the partial will be re-rendered using the new template + name. + + ## Setting the partial's context with `with` + + The `partial` helper can be used in conjunction with the `with` + helper to set a context that will be used by the partial: + + ```handlebars + {{#with currentUser}} + {{partial "user_info"}} + {{/with}} + ``` + + @method partial + @for Ember.Handlebars.helpers + @param {String} partialName the name of the template to render minus the leading underscore + */ + + __exports__["default"] = function partialHelper(name, options) { + + var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; + + options.helperName = options.helperName || 'partial'; + + if (options.types[0] === "ID") { + // Helper was passed a property path; we need to + // create a binding that will re-render whenever + // this property changes. + options.fn = function(context, fnOptions) { + var partialName = handlebarsGet(context, name, fnOptions); + renderPartial(context, partialName, fnOptions); + }; + + return bind.call(context, name, options, true, exists); + } else { + // Render the partial right into parent template. + renderPartial(context, name, options); + } + } + + function exists(value) { + return !isNone(value); + } + + function renderPartial(context, name, options) { + var nameParts = name.split("/"); + var lastPart = nameParts[nameParts.length - 1]; + + nameParts[nameParts.length - 1] = "_" + lastPart; + + var view = options.data.view; + var underscoredName = nameParts.join("/"); + var template = view.templateForName(underscoredName); + var deprecatedTemplate = !template && view.templateForName(name); + + + template = template || deprecatedTemplate; + + template(context, { data: options.data }); + } + }); +define("ember-handlebars/helpers/shared", + ["ember-handlebars/ext","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var handlebarsGet = __dependency1__.handlebarsGet; + + __exports__["default"] = function resolvePaths(options) { + var ret = [], + contexts = options.contexts, + roots = options.roots, + data = options.data; + + for (var i=0, l=contexts.length; i + {{#with loggedInUser}} + Last Login: {{lastLogin}} + User Info: {{template "user_info"}} + {{/with}} + + ``` + + ```html + + ``` + + ```handlebars + {{#if isUser}} + {{template "user_info"}} + {{else}} + {{template "unlogged_user_info"}} + {{/if}} + ``` + + This helper looks for templates in the global `Ember.TEMPLATES` hash. If you + add ` - ``` - - Take note that `"welcome"` is a string and not an object - reference. - - @method loc - @for Ember.Handlebars.helpers - @param {String} str The string to format - */ - function locHelper(str) { - return loc(str); - } - - __exports__["default"] = locHelper; - }); -define("ember-handlebars/helpers/partial", - ["ember-metal/core","ember-metal/is_none","ember-handlebars/ext","ember-handlebars/helpers/binding","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { - "use strict"; - var Ember = __dependency1__["default"]; - // Ember.assert - // var emberAssert = Ember.assert; - - var isNone = __dependency2__.isNone; - var handlebarsGet = __dependency3__.handlebarsGet; - var bind = __dependency4__.bind; - - /** - @module ember - @submodule ember-handlebars - */ - - /** - The `partial` helper renders another template without - changing the template context: - - ```handlebars - {{foo}} - {{partial "nav"}} - ``` - - The above example template will render a template named - "_nav", which has the same context as the parent template - it's rendered into, so if the "_nav" template also referenced - `{{foo}}`, it would print the same thing as the `{{foo}}` - in the above example. - - If a "_nav" template isn't found, the `partial` helper will - fall back to a template named "nav". - - ## Bound template names - - The parameter supplied to `partial` can also be a path - to a property containing a template name, e.g.: - - ```handlebars - {{partial someTemplateName}} - ``` - - The above example will look up the value of `someTemplateName` - on the template context (e.g. a controller) and use that - value as the name of the template to render. If the resolved - value is falsy, nothing will be rendered. If `someTemplateName` - changes, the partial will be re-rendered using the new template - name. - - ## Setting the partial's context with `with` - - The `partial` helper can be used in conjunction with the `with` - helper to set a context that will be used by the partial: - - ```handlebars - {{#with currentUser}} - {{partial "user_info"}} - {{/with}} - ``` - - @method partial - @for Ember.Handlebars.helpers - @param {String} partialName the name of the template to render minus the leading underscore - */ - - function partialHelper(name, options) { - - var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; - - if (options.types[0] === "ID") { - // Helper was passed a property path; we need to - // create a binding that will re-render whenever - // this property changes. - options.fn = function(context, fnOptions) { - var partialName = handlebarsGet(context, name, fnOptions); - renderPartial(context, partialName, fnOptions); - }; - - return bind.call(context, name, options, true, exists); - } else { - // Render the partial right into parent template. - renderPartial(context, name, options); - } - } - - function exists(value) { - return !isNone(value); - } - - function renderPartial(context, name, options) { - var nameParts = name.split("/"), - lastPart = nameParts[nameParts.length - 1]; - - nameParts[nameParts.length - 1] = "_" + lastPart; - - var view = options.data.view, - underscoredName = nameParts.join("/"), - template = view.templateForName(underscoredName), - deprecatedTemplate = !template && view.templateForName(name); - - - template = template || deprecatedTemplate; - - template(context, { data: options.data }); - } - - __exports__["default"] = partialHelper; - }); -define("ember-handlebars/helpers/shared", - ["ember-handlebars/ext","exports"], - function(__dependency1__, __exports__) { - "use strict"; - var handlebarsGet = __dependency1__.handlebarsGet; - - function resolvePaths(options) { - var ret = [], - contexts = options.contexts, - roots = options.roots, - data = options.data; - - for (var i=0, l=contexts.length; i - {{#with loggedInUser}} - Last Login: {{lastLogin}} - User Info: {{template "user_info"}} - {{/with}} - - ``` - - ```html - - ``` - - ```handlebars - {{#if isUser}} - {{template "user_info"}} - {{else}} - {{template "unlogged_user_info"}} - {{/if}} - ``` - - This helper looks for templates in the global `Ember.TEMPLATES` hash. If you - add ` - ``` - - ```javascript - App.ListView = Ember.ListView.extend({ - height: 500, - rowHeight: 20, - itemViewClass: Ember.ListItemView.extend({templateName: "row_item"}) - }); - ``` - - @extends Ember.View - @class ListItemView - @namespace Ember -*/ -Ember.ListItemView = Ember.View.extend(Ember.ListItemViewMixin, { - updateContext: function(newContext){ - var context = get(this, 'context'); - Ember.instrument('view.updateContext.render', this, function() { - if (context !== newContext) { - this.set('context', newContext); - if (newContext instanceof Ember.ObjectController) { - this.set('controller', newContext); - } - } - }, this); - }, - rerender: function () { Ember.run.scheduleOnce('render', this, rerender); }, - _contextDidChange: Ember.observer(rerender, 'context', 'controller') -}); - -})(); - - - -(function() { -var get = Ember.get, set = Ember.set; - -Ember.ReusableListItemView = Ember.View.extend(Ember.ListItemViewMixin, { - init: function(){ - this._super(); - this.set('context', Ember.ObjectProxy.create()); - }, - isVisible: Ember.computed('context.content', function(){ - return !!this.get('context.content'); - }), - updateContext: function(newContext){ - var context = get(this, 'context.content'); - if (context !== newContext) { - if (this.state === 'inDOM') { - this.prepareForReuse(newContext); - } - set(this, 'context.content', newContext); - } - }, - prepareForReuse: Ember.K -}); - -})(); - - - -(function() { -var el = document.createElement('div'), style = el.style; - -var propPrefixes = ['Webkit', 'Moz', 'O', 'ms']; - -function testProp(prop) { - if (prop in style) return prop; - var uppercaseProp = prop.charAt(0).toUpperCase() + prop.slice(1); - for (var i=0; i'); - this._super(buffer); - buffer.push(''); - }, - - willInsertElement: function() { - if (!this.get("height") || !this.get("rowHeight")) { - throw "A ListView must be created with a height and a rowHeight."; - } - this._super(); - }, - - /** - @private - - Sets inline styles of the view: - - height - - width - - position - - overflow - - -webkit-overflow - - overflow-scrolling - - Called while attributes binding. - - @property {Ember.ComputedProperty} style - */ - style: Ember.computed('height', 'width', function() { - var height, width, style, css; - - height = get(this, 'height'); - width = get(this, 'width'); - css = get(this, 'css'); - - style = ''; - - if (height) { style += 'height:' + height + 'px;'; } - if (width) { style += 'width:' + width + 'px;'; } - - for ( var rule in css ){ - if (css.hasOwnProperty(rule)) { - style += rule + ':' + css[rule] + ';'; - } - } - - return style; - }), - - /** - @private - - Performs visual scrolling. Is overridden in Ember.ListView. - - @method scrollTo - */ - scrollTo: function(y) { - throw 'must override to perform the visual scroll and effectively delegate to _scrollContentTo'; - }, - - /** - @private - - Internal method used to force scroll position - - @method scrollTo - */ - _scrollTo: Ember.K, - - /** - @private - @method _scrollContentTo - */ - _scrollContentTo: function(y) { - var startingIndex, endingIndex, - contentIndex, visibleEndingIndex, maxContentIndex, - contentIndexEnd, contentLength, scrollTop; - - scrollTop = max(0, y); - - Ember.instrument('view._scrollContentTo', { - scrollTop: scrollTop, - content: get(this, 'content'), - startingIndex: this._startingIndex(), - endingIndex: min(max(get(this, 'content.length') - 1, 0), this._startingIndex() + this._numChildViewsForViewport()) - }, function () { - contentLength = get(this, 'content.length'); - set(this, 'scrollTop', scrollTop); - - maxContentIndex = max(contentLength - 1, 0); - - startingIndex = this._startingIndex(); - visibleEndingIndex = startingIndex + this._numChildViewsForViewport(); - - endingIndex = min(maxContentIndex, visibleEndingIndex); - - this.trigger('scrollYChanged', y); - - if (startingIndex === this._lastStartingIndex && - endingIndex === this._lastEndingIndex) { - return; - } - - this._reuseChildren(); - - this._lastStartingIndex = startingIndex; - this._lastEndingIndex = endingIndex; - }, this); - }, - - /** - @private - - Computes the height for a `Ember.ListView` scrollable container div. - You must specify `rowHeight` parameter for the height to be computed properly. - - @property {Ember.ComputedProperty} totalHeight - */ - totalHeight: Ember.computed('content.length', 'rowHeight', 'columnCount', 'bottomPadding', function() { - var contentLength, rowHeight, columnCount, bottomPadding; - - contentLength = get(this, 'content.length'); - rowHeight = get(this, 'rowHeight'); - columnCount = get(this, 'columnCount'); - bottomPadding = get(this, 'bottomPadding'); - - return ((ceil(contentLength / columnCount)) * rowHeight) + bottomPadding; - }), - - /** - @private - @method _prepareChildForReuse - */ - _prepareChildForReuse: function(childView) { - childView.prepareForReuse(); - }, - - /** - @private - @method _reuseChildForContentIndex - */ - _reuseChildForContentIndex: function(childView, contentIndex) { - var content, context, newContext, childsCurrentContentIndex, position, enableProfiling; - - content = get(this, 'content'); - enableProfiling = get(this, 'enableProfiling'); - position = this.positionForIndex(contentIndex); - set(childView, 'position', position); - - set(childView, 'contentIndex', contentIndex); - - if (enableProfiling) { - Ember.instrument('view._reuseChildForContentIndex', position, function(){}, this); - } - - newContext = content.objectAt(contentIndex); - childView.updateContext(newContext); - }, - - /** - @private - @method positionForIndex - */ - positionForIndex: function(index){ - var elementWidth, width, columnCount, rowHeight, y, x; - - elementWidth = get(this, 'elementWidth') || 1; - width = get(this, 'width') || 1; - columnCount = get(this, 'columnCount'); - rowHeight = get(this, 'rowHeight'); - - y = (rowHeight * floor(index/columnCount)); - x = (index % columnCount) * elementWidth; - - return { - y: y, - x: x - }; - }, - - /** - @private - @method _childViewCount - */ - _childViewCount: function() { - var contentLength, childViewCountForHeight; - - contentLength = get(this, 'content.length'); - childViewCountForHeight = this._numChildViewsForViewport(); - - return min(contentLength, childViewCountForHeight); - }, - - /** - @private - - Returns a number of columns in the Ember.ListView (for grid layout). - - If you want to have a multi column layout, you need to specify both - `width` and `elementWidth`. - - If no `elementWidth` is specified, it returns `1`. Otherwise, it will - try to fit as many columns as possible for a given `width`. - - @property {Ember.ComputedProperty} columnCount - */ - columnCount: Ember.computed('width', 'elementWidth', function() { - var elementWidth, width, count; - - elementWidth = get(this, 'elementWidth'); - width = get(this, 'width'); - - if (elementWidth) { - count = floor(width / elementWidth); - } else { - count = 1; - } - - return count; - }), - - /** - @private - - Fires every time column count is changed. - - @event columnCountDidChange - */ - columnCountDidChange: Ember.observer(function(){ - var ratio, currentScrollTop, proposedScrollTop, maxScrollTop, - scrollTop, lastColumnCount, newColumnCount, element; - - lastColumnCount = this._lastColumnCount; - - currentScrollTop = get(this, 'scrollTop'); - newColumnCount = get(this, 'columnCount'); - maxScrollTop = get(this, 'maxScrollTop'); - element = get(this, 'element'); - - this._lastColumnCount = newColumnCount; - - if (lastColumnCount) { - ratio = (lastColumnCount / newColumnCount); - proposedScrollTop = currentScrollTop * ratio; - scrollTop = min(maxScrollTop, proposedScrollTop); - - this._scrollTo(scrollTop); - set(this, 'scrollTop', scrollTop); - } - - if (arguments.length > 0) { - // invoked by observer - Ember.run.schedule('afterRender', this, syncListContainerWidth); - } - }, 'columnCount'), - - /** - @private - - Computes max possible scrollTop value given the visible viewport - and scrollable container div height. - - @property {Ember.ComputedProperty} maxScrollTop - */ - maxScrollTop: Ember.computed('height', 'totalHeight', function(){ - var totalHeight, viewportHeight; - - totalHeight = get(this, 'totalHeight'); - viewportHeight = get(this, 'height'); - - return max(0, totalHeight - viewportHeight); - }), - - /** - @private - - Computes the number of views that would fit in the viewport area. - You must specify `height` and `rowHeight` parameters for the number of - views to be computed properly. - - @method _numChildViewsForViewport - */ - _numChildViewsForViewport: function() { - var height, rowHeight, paddingCount, columnCount; - - height = get(this, 'height'); - rowHeight = get(this, 'rowHeight'); - paddingCount = get(this, 'paddingCount'); - columnCount = get(this, 'columnCount'); - - return (ceil(height / rowHeight) * columnCount) + (paddingCount * columnCount); - }, - - /** - @private - - Computes the starting index of the item views array. - Takes `scrollTop` property of the element into account. - - Is used in `_syncChildViews`. - - @method _startingIndex - */ - _startingIndex: function() { - var scrollTop, rowHeight, columnCount, calculatedStartingIndex, - contentLength, largestStartingIndex; - - contentLength = get(this, 'content.length'); - scrollTop = get(this, 'scrollTop'); - rowHeight = get(this, 'rowHeight'); - columnCount = get(this, 'columnCount'); - - calculatedStartingIndex = floor(scrollTop / rowHeight) * columnCount; - - largestStartingIndex = max(contentLength - 1, 0); - - return min(calculatedStartingIndex, largestStartingIndex); - }, - - /** - @private - @event contentWillChange - */ - contentWillChange: Ember.beforeObserver(function() { - var content; - - content = get(this, 'content'); - - if (content) { - content.removeArrayObserver(this); - } - }, 'content'), - - /**), - @private - @event contentDidChange - */ - contentDidChange: Ember.observer(function() { - addContentArrayObserver.call(this); - syncChildViews.call(this); - }, 'content'), - - /** - @private - @property {Function} needsSyncChildViews - */ - needsSyncChildViews: Ember.observer(syncChildViews, 'height', 'width', 'columnCount'), - - /** - @private - - Returns a new item view. Takes `contentIndex` to set the context - of the returned view properly. - - @param {Number} contentIndex item index in the content array - @method _addItemView - */ - _addItemView: function(contentIndex){ - var itemViewClass, childView; - - itemViewClass = get(this, 'itemViewClass'); - childView = this.createChildView(itemViewClass); - - this.pushObject(childView); - }, - - /** - @private - - Intelligently manages the number of childviews. - - @method _syncChildViews - **/ - _syncChildViews: function(){ - var itemViewClass, startingIndex, childViewCount, - endingIndex, numberOfChildViews, numberOfChildViewsNeeded, - childViews, count, delta, index, childViewsLength, contentIndex; - - if (get(this, 'isDestroyed') || get(this, 'isDestroying')) { - return; - } - - childViewCount = this._childViewCount(); - childViews = this.positionOrderedChildViews(); - - startingIndex = this._startingIndex(); - endingIndex = startingIndex + childViewCount; - - numberOfChildViewsNeeded = childViewCount; - numberOfChildViews = childViews.length; - - delta = numberOfChildViewsNeeded - numberOfChildViews; - - if (delta === 0) { - // no change - } else if (delta > 0) { - // more views are needed - contentIndex = this._lastEndingIndex; - - for (count = 0; count < delta; count++, contentIndex++) { - this._addItemView(contentIndex); - } - - } else { - // less views are needed - forEach.call( - childViews.splice(numberOfChildViewsNeeded, numberOfChildViews), - removeAndDestroy, - this - ); - } - - this._scrollContentTo(get(this, 'scrollTop')); - - // if _scrollContentTo short-circuits, we still need - // to call _reuseChildren to get new views positioned - // and rendered correctly - this._reuseChildren(); - - this._lastStartingIndex = startingIndex; - this._lastEndingIndex = this._lastEndingIndex + delta; - }, - - /** - @private - @method _reuseChildren - */ - _reuseChildren: function(){ - var contentLength, childViews, childViewsLength, - startingIndex, endingIndex, childView, attrs, - contentIndex, visibleEndingIndex, maxContentIndex, - contentIndexEnd, scrollTop; - - scrollTop = get(this, 'scrollTop'); - contentLength = get(this, 'content.length'); - maxContentIndex = max(contentLength - 1, 0); - childViews = this._childViews; - childViewsLength = childViews.length; - - startingIndex = this._startingIndex(); - visibleEndingIndex = startingIndex + this._numChildViewsForViewport(); - - endingIndex = min(maxContentIndex, visibleEndingIndex); - - this.trigger('scrollContentTo', scrollTop); - - contentIndexEnd = min(visibleEndingIndex, startingIndex + childViewsLength); - - for (contentIndex = startingIndex; contentIndex < contentIndexEnd; contentIndex++) { - childView = childViews[contentIndex % childViewsLength]; - this._reuseChildForContentIndex(childView, contentIndex); - } - }, - - /** - @private - @method positionOrderedChildViews - */ - positionOrderedChildViews: function() { - return this._childViews.sort(sortByContentIndex); - }, - - arrayWillChange: Ember.K, - - /** - @private - @event arrayDidChange - */ - // TODO: refactor - arrayDidChange: function(content, start, removedCount, addedCount) { - var index, contentIndex; - - if (this.state === 'inDOM') { - // ignore if all changes are out of the visible change - if( start >= this._lastStartingIndex || start < this._lastEndingIndex) { - index = 0; - // ignore all changes not in the visible range - // this can re-position many, rather then causing a cascade of re-renders - forEach.call( - this.positionOrderedChildViews(), - function(childView) { - contentIndex = this._lastStartingIndex + index; - this._reuseChildForContentIndex(childView, contentIndex); - index++; - }, - this - ); - } - - syncChildViews.call(this); - } - } -}); - -})(); - - - -(function() { -var get = Ember.get, set = Ember.set; - -/** - The `Ember.ListView` view class renders a - [div](https://developer.mozilla.org/en/HTML/Element/div) HTML element, - with `ember-list-view` class. - - The context of each item element within the `Ember.ListView` are populated - from the objects in the `Element.ListView`'s `content` property. - - ### `content` as an Array of Objects - - The simplest version of an `Ember.ListView` takes an array of object as its - `content` property. The object will be used as the `context` each item element - inside the rendered `div`. - - Example: - - ```javascript - App.contributors = [{ name: 'Stefan Penner' }, { name: 'Alex Navasardyan' }, { name: 'Rey Cohen'}]; - ``` - - ```handlebars - {{#collection Ember.ListView contentBinding="App.contributors" height=500 rowHeight=50}} - {{name}} - {{/collection}} - ``` - - Would result in the following HTML: - - ```html -
    -
    -
    - Stefan Penner -
    -
    - Alex Navasardyan -
    -
    - Rey Cohen -
    -
    -
    -
    - ``` - - By default `Ember.ListView` provides support for `height`, - `rowHeight`, `width`, `elementWidth`, `scrollTop` parameters. - - Note, that `height` and `rowHeight` are required parameters. - - ```handlebars - {{#collection Ember.ListView contentBinding="App.contributors" height=500 rowHeight=50}} - {{name}} - {{/collection}} - ``` - - If you would like to have multiple columns in your view layout, you can - set `width` and `elementWidth` parameters respectively. - - ```handlebars - {{#collection Ember.ListView contentBinding="App.contributors" height=500 rowHeight=50 width=500 elementWidth=80}} - {{name}} - {{/collection}} - ``` - - ### extending `Ember.ListView` - - Example: - - ```handlebars - {{view App.ListView contentBinding="content"}} - - - ``` - - ```javascript - App.ListView = Ember.ListView.extend({ - height: 500, - width: 500, - elementWidth: 80, - rowHeight: 20, - itemViewClass: Ember.ListItemView.extend({templateName: "row_item"}) - }); - ``` - - @extends Ember.ContainerView - @class ListView - @namespace Ember -*/ -Ember.ListView = Ember.ContainerView.extend(Ember.ListViewMixin, { - css: { - position: 'relative', - overflow: 'scroll', - '-webkit-overflow-scrolling': 'touch', - 'overflow-scrolling': 'touch' - }, - - applyTransform: Ember.ListViewHelper.applyTransform, - - _scrollTo: function(scrollTop) { - var element = get(this, 'element'); - - if (element) { element.scrollTop = scrollTop; } - }, - - didInsertElement: function() { - var that, element; - - that = this, - element = get(this, 'element'); - - this._updateScrollableHeight(); - - this._scroll = function(e) { that.scroll(e); }; - - Ember.$(element).on('scroll', this._scroll); - }, - - willDestroyElement: function() { - var element; - - element = get(this, 'element'); - - Ember.$(element).off('scroll', this._scroll); - }, - - scroll: function(e) { - Ember.run(this, this.scrollTo, e.target.scrollTop); - }, - - scrollTo: function(y){ - var element = get(this, 'element'); - this._scrollTo(y); - this._scrollContentTo(y); - }, - - totalHeightDidChange: Ember.observer(function () { - Ember.run.scheduleOnce('afterRender', this, this._updateScrollableHeight); - }, 'totalHeight'), - - _updateScrollableHeight: function () { - if (this.state === 'inDOM') { - this.$('.ember-list-container').css({ - height: get(this, 'totalHeight') - }); - } - } -}); - -})(); - - - -(function() { -var fieldRegex = /input|textarea|select/i, - hasTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch, - handleStart, handleMove, handleEnd, handleCancel, - startEvent, moveEvent, endEvent, cancelEvent; -if (hasTouch) { - startEvent = 'touchstart'; - handleStart = function (e) { - var touch = e.touches[0], - target = touch && touch.target; - // avoid e.preventDefault() on fields - if (target && fieldRegex.test(target.tagName)) { - return; - } - bindWindow(this.scrollerEventHandlers); - this.willBeginScroll(e.touches, e.timeStamp); - e.preventDefault(); - }; - moveEvent = 'touchmove'; - handleMove = function (e) { - this.continueScroll(e.touches, e.timeStamp); - }; - endEvent = 'touchend'; - handleEnd = function (e) { - // if we didn't end up scrolling we need to - // synthesize click since we did e.preventDefault() - // on touchstart - if (!this._isScrolling) { - synthesizeClick(e); - } - unbindWindow(this.scrollerEventHandlers); - this.endScroll(e.timeStamp); - }; - cancelEvent = 'touchcancel'; - handleCancel = function (e) { - unbindWindow(this.scrollerEventHandlers); - this.endScroll(e.timeStamp); - }; -} else { - startEvent = 'mousedown'; - handleStart = function (e) { - if (e.which !== 1) return; - var target = e.target; - // avoid e.preventDefault() on fields - if (target && fieldRegex.test(target.tagName)) { - return; - } - bindWindow(this.scrollerEventHandlers); - this.willBeginScroll([e], e.timeStamp); - e.preventDefault(); - }; - moveEvent = 'mousemove'; - handleMove = function (e) { - this.continueScroll([e], e.timeStamp); - }; - endEvent = 'mouseup'; - handleEnd = function (e) { - unbindWindow(this.scrollerEventHandlers); - this.endScroll(e.timeStamp); - }; - cancelEvent = 'mouseout'; - handleCancel = function (e) { - if (e.relatedTarget) return; - unbindWindow(this.scrollerEventHandlers); - this.endScroll(e.timeStamp); - }; -} - -function handleWheel(e) { - this.mouseWheel(e); - e.preventDefault(); -} - -function bindElement(el, handlers) { - el.addEventListener(startEvent, handlers.start, false); - el.addEventListener('mousewheel', handlers.wheel, false); -} - -function unbindElement(el, handlers) { - el.removeEventListener(startEvent, handlers.start, false); - el.removeEventListener('mousewheel', handlers.wheel, false); -} - -function bindWindow(handlers) { - window.addEventListener(moveEvent, handlers.move, true); - window.addEventListener(endEvent, handlers.end, true); - window.addEventListener(cancelEvent, handlers.cancel, true); -} - -function unbindWindow(handlers) { - window.removeEventListener(moveEvent, handlers.move, true); - window.removeEventListener(endEvent, handlers.end, true); - window.removeEventListener(cancelEvent, handlers.cancel, true); -} - -Ember.VirtualListScrollerEvents = Ember.Mixin.create({ - init: function() { - this.on('didInsertElement', this, 'bindScrollerEvents'); - this.on('willDestroyElement', this, 'unbindScrollerEvents'); - this.scrollerEventHandlers = { - start: bind(this, handleStart), - move: bind(this, handleMove), - end: bind(this, handleEnd), - cancel: bind(this, handleCancel), - wheel: bind(this, handleWheel) - }; - return this._super(); - }, - bindScrollerEvents: function() { - var el = this.get('element'), - handlers = this.scrollerEventHandlers; - bindElement(el, handlers); - }, - unbindScrollerEvents: function() { - var el = this.get('element'), - handlers = this.scrollerEventHandlers; - unbindElement(el, handlers); - unbindWindow(handlers); - } -}); - -function bind(view, handler) { - return function (evt) { - handler.call(view, evt); - }; -} - -function synthesizeClick(e) { - var point = e.changedTouches[0], - target = point.target, - ev; - if (target && fieldRegex.test(target.tagName)) { - ev = document.createEvent('MouseEvents'); - ev.initMouseEvent('click', true, true, e.view, 1, point.screenX, point.screenY, point.clientX, point.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null); - return target.dispatchEvent(ev); - } -} - -})(); - - - -(function() { -/*global Scroller*/ -var max = Math.max, get = Ember.get, set = Ember.set; - -function updateScrollerDimensions(target) { - var width, height, totalHeight; - - target = target || this; - - width = get(target, 'width'); - height = get(target, 'height'); - totalHeight = get(target, 'totalHeight'); - - target.scroller.setDimensions(width, height, width, totalHeight); - target.trigger('scrollerDimensionsDidChange'); -} - -/** - VirtualListView - - @class VirtualListView - @namespace Ember -*/ -Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, Ember.VirtualListScrollerEvents, { - _isScrolling: false, - _mouseWheel: null, - css: { - position: 'relative', - overflow: 'hidden' - }, - - init: function(){ - this._super(); - this.setupScroller(); - }, - _scrollerTop: 0, - applyTransform: Ember.ListViewHelper.apply3DTransform, - - setupScroller: function(){ - var view, y; - - view = this; - - view.scroller = new Scroller(function(left, top, zoom) { - if (view.state !== 'inDOM') { return; } - - if (view.listContainerElement) { - view.applyTransform(view.listContainerElement, 0, -top); - view._scrollerTop = top; - view._scrollContentTo(top); - } - }, { - scrollingX: false, - scrollingComplete: function(){ - view.trigger('scrollingDidComplete'); - } - }); - - view.trigger('didInitializeScroller'); - updateScrollerDimensions(view); - }, - - scrollerDimensionsNeedToChange: Ember.observer(function() { - Ember.run.once(this, updateScrollerDimensions); - }, 'width', 'height', 'totalHeight'), - - didInsertElement: function() { - this.listContainerElement = this.$('> .ember-list-container')[0]; - }, - - willBeginScroll: function(touches, timeStamp) { - this._isScrolling = false; - this.trigger('scrollingDidStart'); - - this.scroller.doTouchStart(touches, timeStamp); - }, - - continueScroll: function(touches, timeStamp) { - var startingScrollTop, endingScrollTop, event; - - if (this._isScrolling) { - this.scroller.doTouchMove(touches, timeStamp); - } else { - startingScrollTop = this._scrollerTop; - - this.scroller.doTouchMove(touches, timeStamp); - - endingScrollTop = this._scrollerTop; - - if (startingScrollTop !== endingScrollTop) { - event = Ember.$.Event("scrollerstart"); - Ember.$(touches[0].target).trigger(event); - - this._isScrolling = true; - } - } - }, - - endScroll: function(timeStamp) { - this.scroller.doTouchEnd(timeStamp); - }, - - // api - scrollTo: function(y, animate) { - if (animate === undefined) { - animate = true; - } - - this.scroller.scrollTo(0, y, animate, 1); - }, - - // events - mouseWheel: function(e){ - var inverted, delta, candidatePosition; - - inverted = e.webkitDirectionInvertedFromDevice; - delta = e.wheelDeltaY * (inverted ? 0.8 : -0.8); - candidatePosition = this.scroller.__scrollTop + delta; - - if ((candidatePosition >= 0) && (candidatePosition <= this.scroller.__maxScrollTop)) { - this.scroller.scrollBy(0, delta, true); - } - - return false; - } -}); - -})(); - - - -(function() { - -})(); - - - -if (typeof location !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1')) { - Ember.Logger.warn("You are running a production build of Ember on localhost and won't receive detailed error messages. "+ - "If you want full error messages please use the non-minified build provided on the Ember website."); -} +// ========================================================================== +// Project: Ember ListView +// Copyright: ©2012-2013 Erik Bryn, Yapp Inc., and contributors. +// License: Licensed under MIT license +// Version: 0.0.5 +// ========================================================================== + +(function(e){var t,i,n,r;(function(){var e;if(!Array.isArray){e=function(e){return Object.prototype.toString.call(e)==="[object Array]"}}else{e=Array.isArray}var s={},o={},l={};var h=false;t=function(t,i,n){if(!e(i)){n=i;i=[]}s[t]={deps:i,callback:n}};function a(e,t,i){var r=e.length;var s=new Array(r);var o;var l;for(var h=0,a=r;hthis.lengthBeforeRender){this.clearRenderedChildren();this._childViews.length=this.lengthBeforeRender}if(i){t=Ember.RenderBuffer();t=this.renderToBuffer(t);h=this._childViews.length>0;if(h){this.invokeRecursively(o,false)}e.innerHTML=t.innerString?t.innerString():s(t);r(this,"element",e);var a=this._transitionTo?this._transitionTo:this.transitionTo;a.call(this,"inDOM");if(h){this.invokeRecursively(l,false)}}else{e.innerHTML=""}}t["default"]=Ember.View.extend(i,{updateContext:function(e){var t=n(this,"context");Ember.instrument("view.updateContext.render",this,function(){if(t!==e){r(this,"context",e);if(e&&e.isController){r(this,"controller",e)}}},this)},rerender:function(){Ember.run.scheduleOnce("render",this,h)},_contextDidChange:Ember.observer(h,"context","controller")})});t("list-view/list_item_view_mixin",["exports"],function(e){"use strict";var t=Ember.get,i=Ember.set;function n(e,t){return e&&t&&e.x===t.x&&e.y===t.y}function r(){var e,i,r;Ember.instrument("view.updateContext.positionElement",this,function(){e=t(this,"element");i=this.position;r=this._position;if(!i||!e){return}if(n(i,r)){return}Ember.run.schedule("render",this,this._parentView.applyTransform,e,i.x,i.y);this._position=i},this)}e["default"]=Ember.Mixin.create({init:function(){this._super();this.one("didInsertElement",r)},classNames:["ember-list-item-view"],_position:null,updatePosition:function(e){this.position=e;this._positionElement()},_positionElement:r})});t("list-view/list_view",["list-view/list_view_helper","list-view/list_view_mixin","exports"],function(e,t,i){"use strict";var n=e["default"];var r=t["default"];var s=Ember.get,o=Ember.set;i["default"]=Ember.ContainerView.extend(r,{css:{position:"relative",overflow:"auto","-webkit-overflow-scrolling":"touch","overflow-scrolling":"touch"},applyTransform:n.applyTransform,_scrollTo:function(e){var t=s(this,"element");if(t){t.scrollTop=e}},didInsertElement:function(){var e=this;var t=s(this,"element");this._updateScrollableHeight();this._scroll=function(t){e.scroll(t)};Ember.$(t).on("scroll",this._scroll)},willDestroyElement:function(){var e;e=s(this,"element");Ember.$(e).off("scroll",this._scroll)},scroll:function(e){this.scrollTo(e.target.scrollTop)},scrollTo:function(e){var t=s(this,"element");this._scrollTo(e);this._scrollContentTo(e)},totalHeightDidChange:Ember.observer(function(){Ember.run.scheduleOnce("afterRender",this,this._updateScrollableHeight)},"totalHeight"),_updateScrollableHeight:function(){var e,t;t=this._state||this.state;if(t==="inDOM"){if(this._isChildEmptyView()){e=""}else{e=s(this,"totalHeight")}this.$(".ember-list-container").css({height:e})}}})});t("list-view/list_view_helper",["exports"],function(e){"use strict";var t=document.createElement("div"),i=t.style;var n=["Webkit","Moz","O","ms"];function r(e){if(e in i)return e;var t=e.charAt(0).toUpperCase()+e.slice(1);for(var r=0;r1}).readOnly(),init:function(){this._super();this._cachedHeights=[0];this.on("didInsertElement",this._syncListContainerWidth);this.columnCountDidChange();this._syncChildViews();this._addContentArrayObserver()},_addContentArrayObserver:Ember.beforeObserver(function(){c.call(this)},"content"),render:function(e){e.push('
    ');this._super(e);e.push("
    ")},willInsertElement:function(){if(!this.get("height")||!this.get("rowHeight")){throw new Error("A ListView must be created with a height and a rowHeight.")}this._super()},style:Ember.computed("height","width",function(){var e,t,i,r;e=n(this,"height");t=n(this,"width");r=n(this,"css");i="";if(e){i+="height:"+e+"px;"}if(t){i+="width:"+t+"px;"}for(var s in r){if(r.hasOwnProperty(s)){i+=s+":"+r[s]+";"}}return i}),scrollTo:function(e){throw new Error("must override to perform the visual scroll and effectively delegate to _scrollContentTo")},_scrollTo:Ember.K,_scrollContentTo:function(e){var t,i,r,l,h,a,c,u,d;u=o(0,e);if(this.scrollTop===u){return}var f=o(0,n(this,"totalHeight")-n(this,"height"));u=s(u,f);d=n(this,"content");c=n(d,"length");t=this._startingIndex(c);Ember.instrument("view._scrollContentTo",{scrollTop:u,content:d,startingIndex:t,endingIndex:s(o(c-1,0),t+this._numChildViewsForViewport())},function(){this.scrollTop=u;h=o(c-1,0);t=this._startingIndex();l=t+this._numChildViewsForViewport();i=s(h,l);if(t===this._lastStartingIndex&&i===this._lastEndingIndex){this.trigger("scrollYChanged",e);return}else{Ember.run(this,function(){this._reuseChildren();this._lastStartingIndex=t;this._lastEndingIndex=i;this.trigger("scrollYChanged",e)})}},this)},totalHeight:Ember.computed("content.length","rowHeight","columnCount","bottomPadding",function(){if(typeof this.heightForIndex==="function"){return this._totalHeightWithHeightForIndex()}else{return this._totalHeightWithStaticRowHeight()}}),_doRowHeightDidChange:function(){this._cachedHeights=[0];this._cachedPos=0;this._syncChildViews()},_rowHeightDidChange:Ember.observer("rowHeight",function(){Ember.run.once(this,this._doRowHeightDidChange)}),_totalHeightWithHeightForIndex:function(){var e=this.get("content.length");return this._cachedHeightLookup(e)},_totalHeightWithStaticRowHeight:function(){var e,t,i,r;e=n(this,"content.length");t=n(this,"rowHeight");i=n(this,"columnCount");r=n(this,"bottomPadding");return h(e/i)*t+r},_prepareChildForReuse:function(e){e.prepareForReuse()},_reuseChildForContentIndex:function(e,t){var i,s,o,l,h,a,c;var u=this.itemViewForIndex(t);if(e.constructor!==u){var d=this._childViews.indexOf(e);e.destroy();e=this.createChildView(u);this.insertAt(d,e)}i=n(this,"content");a=n(this,"enableProfiling");h=this.positionForIndex(t);e.updatePosition(h);r(e,"contentIndex",t);if(a){Ember.instrument("view._reuseChildForContentIndex",h,function(){},this)}o=i.objectAt(t);e.updateContext(o)},positionForIndex:function(e){if(typeof this.heightForIndex!=="function"){return this._singleHeightPosForIndex(e)}else{return this._multiHeightPosForIndex(e)}},_singleHeightPosForIndex:function(e){var t,i,r,s,o,h;t=n(this,"elementWidth")||1;i=n(this,"width")||1;r=n(this,"columnCount");s=n(this,"rowHeight");o=s*l(e/r);h=e%r*t;return{y:o,x:h}},_multiHeightPosForIndex:function(e){var t,i,r,s,o,l;t=n(this,"elementWidth")||1;i=n(this,"width")||1;r=n(this,"columnCount");l=e%r*t;o=this._cachedHeightLookup(e);return{x:l,y:o}},_cachedHeightLookup:function(e){for(var t=this._cachedPos;te){i=l(t/e)}else{i=1}return i}),columnCountDidChange:Ember.observer(function(){var e,t,i,r,o,l,h,a;l=this._lastColumnCount;t=this.scrollTop;h=n(this,"columnCount");r=n(this,"maxScrollTop");a=n(this,"element");this._lastColumnCount=h;if(l){e=l/h;i=t*e;o=s(r,i);this._scrollTo(o);this.scrollTop=o}if(arguments.length>0){Ember.run.schedule("afterRender",this,this._syncListContainerWidth)}},"columnCount"),maxScrollTop:Ember.computed("height","totalHeight",function(){var e,t;e=n(this,"totalHeight");t=n(this,"height");return o(0,e-t)}),_isChildEmptyView:function(){var e=n(this,"emptyView");return e&&e instanceof Ember.View&&this._childViews.length===1&&this._childViews.indexOf(e)===0},_numChildViewsForViewport:function(){if(this.heightForIndex){return this._numChildViewsForViewportWithMultiHeight()}else{return this._numChildViewsForViewportWithoutMultiHeight()}},_numChildViewsForViewportWithoutMultiHeight:function(){var e,t,i,r;e=n(this,"height");t=n(this,"rowHeight");i=n(this,"paddingCount");r=n(this,"columnCount");return h(e/t)*r+i*r},_numChildViewsForViewportWithMultiHeight:function(){var e,t,i;var r=this.scrollTop;var s=this.get("height");var o=this.get("content.length");var l=0;var h=n(this,"paddingCount");var a=this._calculatedStartingIndex();var c=0;var u=this._cachedHeightLookup(a);for(var d=0;ds){break}}return d+h+1},_startingIndex:function(e){var t,i,r,h,a;if(e===undefined){a=n(this,"content.length")}else{a=e}t=this.scrollTop;i=n(this,"rowHeight");r=n(this,"columnCount");if(this.heightForIndex){h=this._calculatedStartingIndex()}else{h=l(t/i)*r}var c=this._numChildViewsForViewport();var u=1*r;var d=o(a-c,0);return s(h,d)},_calculatedStartingIndex:function(){var e,t,i;var r=this.scrollTop;var s=this.get("height");var o=this.get("content.length");var l=0;var h=n(this,"paddingCount");for(var a=0;a=r){break}}return a},contentWillChange:Ember.beforeObserver(function(){var e;e=n(this,"content");if(e){e.removeArrayObserver(this)}},"content"),contentDidChange:Ember.observer(function(){c.call(this);d.call(this)},"content"),needsSyncChildViews:Ember.observer(d,"height","width","columnCount"),_addItemView:function(e){var t,i;t=this.itemViewForIndex(e);i=this.createChildView(t);this.pushObject(i)},itemViewForIndex:function(e){return n(this,"itemViewClass")},heightForIndex:null,_syncChildViews:function(){var e,t,i,r,s,o,l,h,c,d,f;if(n(this,"isDestroyed")||n(this,"isDestroying")){return}h=n(this,"content.length");c=n(this,"emptyView");t=this._childViewCount();e=this.positionOrderedChildViews();if(this._isChildEmptyView()){v.call(this)}o=this._startingIndex();l=o+t;r=t;i=e.length;f=r-i;if(f===0){}else if(f>0){s=this._lastEndingIndex;for(d=0;d=this._lastStartingIndex||t .ember-list-container")[0]},willBeginScroll:function(e,t){this._isScrolling=false;this.trigger("scrollingDidStart");this.scroller.doTouchStart(e,t)},continueScroll:function(e,t){var i,n,r;if(this._isScrolling){this.scroller.doTouchMove(e,t)}else{i=this._scrollerTop;this.scroller.doTouchMove(e,t);n=this._scrollerTop;if(i!==n){r=Ember.$.Event("scrollerstart");Ember.$(e[0].target).trigger(r);this._isScrolling=true}}},endScroll:function(e){this.scroller.doTouchEnd(e)},scrollTo:function(e,t){if(t===undefined){t=true}this.scroller.scrollTo(0,e,t,1)},mouseWheel:function(e){var t,i,n;t=e.webkitDirectionInvertedFromDevice;i=e.wheelDeltaY*(t?.8:-.8);n=this.scroller.__scrollTop+i;if(n>=0&&n<=this.scroller.__maxScrollTop){this.scroller.scrollBy(0,i,true);e.stopPropagation()}return false}})});i("list-view/main")})(this); \ No newline at end of file