Easier to group bindings. Perf improvements.

This commit is contained in:
Robin Ward 2013-08-02 13:17:28 -04:00
parent 1f53d5dcac
commit ef82b66e95
19 changed files with 284 additions and 254 deletions

View File

@ -10,22 +10,22 @@
</thead> </thead>
{{#if model.length}} {{#if model.length}}
{{#group}} {{#groupedEach model}}
{{#collection contentBinding="model" tagName="tbody" itemTagName="tr"}} <tr>
<td>{{date created_at}}</td> <td>{{date created_at}}</td>
<td> <td>
{{#if user}} {{#if user}}
{{#linkTo 'adminUser' user}}{{avatar user imageSize="tiny"}}{{/linkTo}} {{#linkTo 'adminUser' user}}{{avatar user imageSize="tiny"}}{{/linkTo}}
{{#linkTo 'adminUser' user}}{{user.username}}{{/linkTo}} {{#linkTo 'adminUser' user}}{{user.username}}{{/linkTo}}
{{else}} {{else}}
&mdash; &mdash;
{{/if}} {{/if}}
</td> </td>
<td><a href='mailto:{{unbound to_address}}'>{{to_address}}</a></td> <td><a href='mailto:{{unbound to_address}}'>{{to_address}}</a></td>
<td>{{email_type}}</td> <td>{{email_type}}</td>
<td>{{reply_key}}</td> <td>{{reply_key}}</td>
{{/collection}} </tr>
{{/group}} {{/groupedEach}}
{{/if}} {{/if}}
</table> </table>

View File

@ -23,10 +23,8 @@ require_asset ("./external/handlebars.js")
if Rails.env.development? if Rails.env.development?
require_asset ("./external_development/ember.js") require_asset ("./external_development/ember.js")
require_asset ("./external_development/group-helper.js")
else else
require_asset ("./external_production/ember.js") require_asset ("./external_production/ember.js")
require_asset ("./external_production/group-helper.js")
end end
require_asset ("./main_include.js") require_asset ("./main_include.js")

View File

@ -0,0 +1,111 @@
var DiscourseGroupedEach = function(context, path, options) {
var self = this,
normalized = Ember.Handlebars.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();
});
};
DiscourseGroupedEach.prototype = {
contentWillChange: function() {
this.removeArrayObservers();
},
contentDidChange: function() {
this.content = this.lookupContent();
this.addArrayObservers();
this.rerenderContainingView();
},
contentArrayWillChange: Ember.K,
contentArrayDidChange: function() {
this.rerenderContainingView();
},
lookupContent: function() {
return Ember.Handlebars.get(this.normalizedRoot, this.normalizedPath, this.options);
},
addArrayObservers: function() {
this.content.addArrayObserver(this, {
willChange: 'contentArrayWillChange',
didChange: 'contentArrayDidChange'
});
},
removeArrayObservers: function() {
this.content.removeArrayObserver(this, {
willChange: 'contentArrayWillChange',
didChange: 'contentArrayDidChange'
});
},
addContentObservers: function() {
Ember.addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange);
Ember.addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange);
},
removeContentObservers: function() {
Ember.removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange);
Ember.removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange);
},
render: function() {
var content = this.content,
contentLength = Em.get(content, 'length'),
data = this.options.data,
template = this.template;
data.insideEach = true;
data.insideGroup = true;
for (var i = 0; i < contentLength; i++) {
template(content.objectAt(i), { data: data });
}
},
rerenderContainingView: function() {
Ember.run.scheduleOnce('render', this.containingView, 'rerender');
},
destroy: function() {
this.removeContentObservers();
this.removeArrayObservers();
}
};
Ember.Handlebars.registerHelper('groupedEach', function(path, options) {
if (arguments.length === 4) {
Ember.assert("If you pass more than one argument to the groupedEach helper, it must be in the form #groupedEach foo in bar", arguments[1] === "in");
var keywordName = arguments[0];
options = arguments[3];
path = arguments[2];
if (path === '') { path = "this"; }
options.hash.keyword = keywordName;
}
if (arguments.length === 1) {
options = path;
path = 'this';
}
options.hash.dataSourceBinding = path;
new DiscourseGroupedEach(this, path, options).render();
});

View File

@ -10,8 +10,8 @@
<th class='num activity' colspan='2'>{{i18n activity}}</th> <th class='num activity' colspan='2'>{{i18n activity}}</th>
</tr> </tr>
{{#group}} {{#groupedEach view.topics}}
{{#collection contentBinding="view.topics" tagName="tbody" itemTagName="tr"}} <tr>
<td class='main-link'> <td class='main-link'>
<a class='title' href="{{unbound lastReadUrl}}">{{{unbound fancy_title}}}</a> <a class='title' href="{{unbound lastReadUrl}}">{{{unbound fancy_title}}}</a>
{{#if unread}} {{#if unread}}
@ -21,35 +21,35 @@
<a href="{{unbound lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new_posts count="new_posts"}}'>{{unbound new_posts}}</a> <a href="{{unbound lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new_posts count="new_posts"}}'>{{unbound new_posts}}</a>
{{/if}} {{/if}}
{{#if unseen}} {{#if unseen}}
<a href="{{lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new}}'><i class='icon icon-asterisk'></i></a> <a href="{{unbound lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new}}'><i class='icon icon-asterisk'></i></a>
{{/if}} {{/if}}
</td> </td>
<td class='category'> <td class='category'>
{{categoryLink category}} {{categoryLink category}}
</td> </td>
<td class='num posts'><a href="{{lastReadUrl}}" class='badge-posts'>{{number posts_count numberKey="posts_long"}}</a></td> <td class='num posts'><a href="{{unbound lastReadUrl}}" class='badge-posts'>{{number posts_count numberKey="posts_long"}}</a></td>
<td class='num likes'> <td class='num likes'>
{{#if like_count}} {{#if like_count}}
<a href='{{url}}{{#if has_best_of}}?filter=best_of{{/if}}'>{{like_count}} <i class='icon-heart'></i></a> <a href='{{unbound url}}{{#if has_best_of}}?filter=best_of{{/if}}'>{{unbound like_count}} <i class='icon-heart'></i></a>
{{/if}} {{/if}}
</td> </td>
<td {{bindAttr class=":num :views viewsHeat"}}>{{number views numberKey="views_long"}}</td> <td {{bindAttr class=":num :views viewsHeat"}}>{{number views numberKey="views_long"}}</td>
{{#if bumped}} {{#if bumped}}
<td class='num activity'> <td class='num activity'>
<a href="{{url}}" {{{bindAttr class=":age ageCold"}}} title='{{i18n first_post}}: {{{unboundDate created_at}}}' >{{unboundAge created_at}}</a> <a href="{{unbound url}}" {{{bindAttr class=":age ageCold"}}} title='{{i18n first_post}}: {{{unboundDate created_at}}}' >{{unboundAge created_at}}</a>
</td> </td>
<td class='num activity last'> <td class='num activity last'>
<a href="{{lastPostUrl}}" class='age' title='{{i18n last_post}}: {{{unboundDate bumped_at}}}'>{{unboundAge bumped_at}}</a> <a href="{{unbound lastPostUrl}}" class='age' title='{{i18n last_post}}: {{{unboundDate bumped_at}}}'>{{unboundAge bumped_at}}</a>
</td> </td>
{{else}} {{else}}
<td class='num activity'> <td class='num activity'>
<a href="{{url}}" class='age' title='{{i18n first_post}}: {{{unboundDate created_at}}}'>{{unboundAge created_at}}</a> <a href="{{unbound url}}" class='age' title='{{i18n first_post}}: {{{unboundDate created_at}}}'>{{unboundAge created_at}}</a>
</td> </td>
<td class="activity"></td> <td class="activity"></td>
{{/if}} {{/if}}
{{/collection}} </tr>
{{/group}} {{/groupedEach}}
</table> </table>

View File

@ -1,76 +1,76 @@
{{#if controller.currentUser.id}} {{#if controller.currentUser.id}}
<td class='star'> <td class='star'>
<a {{bindAttr class=":star :icon-star starred:starred"}} {{action toggleStar this}} href='#' {{bindAttr title="favoriteTooltip"}}></a> <a {{bindAttr class=":star :icon-star starred:starred"}} {{action toggleStar this}} href='#' {{bindAttr title="favoriteTooltip"}}></a>
</td> </td>
{{/if}}
<td class='main-link clearfix'>
{{#if controller.rankDetailsVisible}}
<div class='rank-details'>
<p>{{rank_details.hot_topic_type}}</p>
<p>
({{float rank_details.random_bias}} * {{float rank_details.random_multiplier}}) + ({{float rank_details.days_ago_bias}} * {{float rank_details.days_ago_multiplier}}) = <b>{{float rank_details.ranking_score}}</b>
<i class='icon icon-beaker score' {{action showRankDetails this}} title='{{i18n rank_details.show}}'></i>
</p>
</div>
{{/if}} {{/if}}
<td class='main-link clearfix'> {{topicStatus topic=this}}
{{{topicLink this}}}
{{#if controller.rankDetailsVisible}} {{#if unread}}
<div class='rank-details'> <a href="{{lastReadUrl}}" class='badge unread badge-notification' title='{{i18n topic.unread_posts count="unread"}}'>{{unread}}</a>
<p>{{rank_details.hot_topic_type}}</p>
<p>
({{float rank_details.random_bias}} * {{float rank_details.random_multiplier}}) + ({{float rank_details.days_ago_bias}} * {{float rank_details.days_ago_multiplier}}) = <b>{{float rank_details.ranking_score}}</b>
<i class='icon icon-beaker score' {{action showRankDetails this}} title='{{i18n rank_details.show}}'></i>
</p>
</div>
{{/if}}
{{topicStatus topic=this}}
{{{topicLink this}}}
{{#if unread}}
<a href="{{lastReadUrl}}" class='badge unread badge-notification' title='{{i18n topic.unread_posts count="unread"}}'>{{unread}}</a>
{{/if}}
{{#if displayNewPosts}}
<a href="{{lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new_posts count="displayNewPosts"}}'>{{displayNewPosts}}</a>
{{/if}}
{{#if unseen}}
<a href="{{lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new}}'><i class='icon icon-asterisk'></i></a>
{{/if}}
{{#if hasExcerpt}}
<div class="topic-excerpt">
{{excerpt}}
{{#if excerptTruncated}}
{{#unless canClearPin}}<a href="{{lastReadUrl}}">{{i18n read_more}}</a>{{/unless}}
{{/if}}
{{#if canClearPin}}
<a href="#" {{action clearPin this}} title="{{unbound i18n topic.clear_pin.help}}">{{i18n topic.clear_pin.title}}</a>
{{/if}}
</div>
{{/if}}
</td>
<td class='category'>
{{categoryLink category}}
</td>
<td class='posters'>
{{#each posters}}
<a href="{{user.path}}" class="{{unbound extras}}">{{avatar this usernamePath="user.username" imageSize="small"}}</a>
{{/each}}
</td>
<td class='num posts'><a href="{{lastReadUrl}}" class='badge-posts'>{{number posts_count numberKey="posts_long"}}</a></td>
<td class='num likes'>
{{#if like_count}}
<a href='{{url}}{{#if has_best_of}}?filter=best_of{{/if}}' title='{{i18n topic.likes count="like_count"}}'>{{number like_count numberKey="likes_long"}} <i class='icon-heart'></i></a>
{{/if}}
</td>
<td {{bindAttr class=":num :views viewsHeat"}}>{{number views numberKey="views_long"}}</td>
{{#if bumped}}
<td class='num activity'>
<a href="{{url}}" {{{bindAttr class=":age ageCold"}}} title='{{i18n first_post}}: {{{unboundDate created_at}}}' >{{unboundAge created_at}}</a>
</td>
<td class='num activity last'>
<a href="{{lastPostUrl}}" class='age' title='{{i18n last_post}}: {{{unboundDate bumped_at}}}'>{{unboundAge bumped_at}}</a>
</td>
{{else}}
<td class='num activity'>
<a href="{{url}}" class='age' title='{{i18n first_post}}: {{{unboundDate created_at}}}'>{{unboundAge created_at}}</a>
</td>
<td class='activity'></td>
{{/if}} {{/if}}
{{#if displayNewPosts}}
<a href="{{lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new_posts count="displayNewPosts"}}'>{{displayNewPosts}}</a>
{{/if}}
{{#if unseen}}
<a href="{{lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new}}'><i class='icon icon-asterisk'></i></a>
{{/if}}
{{#if hasExcerpt}}
<div class="topic-excerpt">
{{excerpt}}
{{#if excerptTruncated}}
{{#unless canClearPin}}<a href="{{lastReadUrl}}">{{i18n read_more}}</a>{{/unless}}
{{/if}}
{{#if canClearPin}}
<a href="#" {{action clearPin this}} title="{{i18n topic.clear_pin.help}}">{{i18n topic.clear_pin.title}}</a>
{{/if}}
</div>
{{/if}}
</td>
<td class='category'>
{{categoryLink category}}
</td>
<td class='posters'>
{{#each posters}}
<a href="{{user.path}}" class="{{extras}}">{{avatar this usernamePath="user.username" imageSize="small"}}</a>
{{/each}}
</td>
<td class='num posts'><a href="{{lastReadUrl}}" class='badge-posts'>{{number posts_count numberKey="posts_long"}}</a></td>
<td class='num likes'>
{{#if like_count}}
<a href='{{url}}{{#if has_best_of}}?filter=best_of{{/if}}' title='{{i18n topic.likes count="like_count"}}'>{{number like_count numberKey="likes_long"}} <i class='icon-heart'></i></a>
{{/if}}
</td>
<td {{bindAttr class=":num :views viewsHeat"}}>{{number views numberKey="views_long"}}</td>
{{#if bumped}}
<td class='num activity'>
<a href="{{url}}" {{{bindAttr class=":age ageCold"}}} title='{{i18n first_post}}: {{{unboundDate created_at}}}' >{{unboundAge created_at}}</a>
</td>
<td class='num activity last'>
<a href="{{lastPostUrl}}" class='age' title='{{i18n last_post}}: {{{unboundDate bumped_at}}}'>{{unboundAge bumped_at}}</a>
</td>
{{else}}
<td class='num activity'>
<a href="{{url}}" class='age' title='{{i18n first_post}}: {{{unboundDate created_at}}}'>{{unboundAge created_at}}</a>
</td>
<td class='activity'></td>
{{/if}}

View File

@ -41,9 +41,7 @@
</tbody> </tbody>
{{/if}} {{/if}}
{{#group}} {{collection contentBinding="topics" tagName="tbody" itemViewClass="Discourse.TopicListItemView"}}
{{collection contentBinding="topics" tagName="tbody" itemViewClass="Discourse.TopicListItemView"}}
{{/group}}
</table> </table>
{{/if}} {{/if}}

View File

@ -47,7 +47,7 @@
</div> </div>
</div> </div>
{{/unless}} {{/unless}}
{{view Discourse.RawDivView class="cooked" contentBinding="cooked"}} <div class='cooked'>{{{cooked}}}</div>
{{view Discourse.PostMenuView postBinding="this" postViewBinding="view"}} {{view Discourse.PostMenuView postBinding="this" postViewBinding="view"}}
</div> </div>
{{view Discourse.RepliesView contentBinding="replies" postViewBinding="view"}} {{view Discourse.RepliesView contentBinding="replies" postViewBinding="view"}}

View File

@ -6,15 +6,10 @@
@namespace Discourse @namespace Discourse
@module Discourse @module Discourse
**/ **/
Discourse.EmbeddedPostView = Discourse.View.extend({ Discourse.EmbeddedPostView = Discourse.GroupedView.extend({
templateName: 'embedded_post', templateName: 'embedded_post',
classNames: ['reply'], classNames: ['reply'],
init: function() {
this._super();
this.set('context', this.get('content'));
},
didInsertElement: function() { didInsertElement: function() {
Discourse.ScreenTrack.instance().track(this.get('elementId'), this.get('post.post_number')); Discourse.ScreenTrack.instance().track(this.get('elementId'), this.get('post.post_number'));
}, },

View File

@ -2,11 +2,11 @@
This view handles the rendering of a topic in a list This view handles the rendering of a topic in a list
@class TopicListItemView @class TopicListItemView
@extends Discourse.View @extends Discourse.GroupedView
@namespace Discourse @namespace Discourse
@module Discourse @module Discourse
**/ **/
Discourse.TopicListItemView = Discourse.View.extend({ Discourse.TopicListItemView = Discourse.GroupedView.extend({
tagName: 'tr', tagName: 'tr',
templateName: 'list/topic_list_item', templateName: 'list/topic_list_item',
classNameBindings: ['content.archived', ':topic-list-item', 'content.hasExcerpt:has-excerpt'], classNameBindings: ['content.archived', ':topic-list-item', 'content.hasExcerpt:has-excerpt'],
@ -14,11 +14,6 @@ Discourse.TopicListItemView = Discourse.View.extend({
'data-topic-id': Em.computed.alias('content.id'), 'data-topic-id': Em.computed.alias('content.id'),
init: function() {
this._super();
this.set('context', this.get('content'));
},
highlight: function() { highlight: function() {
var $topic = this.$(); var $topic = this.$();
var originalCol = $topic.css('backgroundColor'); var originalCol = $topic.css('backgroundColor');

View File

@ -6,7 +6,7 @@
@namespace Discourse @namespace Discourse
@module Discourse @module Discourse
**/ **/
Discourse.OptionBooleanView = Discourse.View.extend({ Discourse.OptionBooleanView = Discourse.GroupedView.extend({
classNames: ['archetype-option'], classNames: ['archetype-option'],
composerControllerBinding: 'Discourse.router.composerController', composerControllerBinding: 'Discourse.router.composerController',
templateName: "modal/option_boolean", templateName: "modal/option_boolean",
@ -16,12 +16,7 @@ Discourse.OptionBooleanView = Discourse.View.extend({
metaData = this.get('parentView.metaData'); metaData = this.get('parentView.metaData');
metaData.set(this.get('content.key'), this.get('checked') ? 'true' : 'false'); metaData.set(this.get('content.key'), this.get('checked') ? 'true' : 'false');
return this.get('controller.controllers.composer').saveDraft(); return this.get('controller.controllers.composer').saveDraft();
}).observes('checked'), }).observes('checked')
init: function() {
this._super();
return this.set('context', this.get('content'));
}
}); });

View File

@ -2,11 +2,11 @@
This view renders a post. This view renders a post.
@class PostView @class PostView
@extends Discourse.View @extends Discourse.GroupedView
@namespace Discourse @namespace Discourse
@module Discourse @module Discourse
**/ **/
Discourse.PostView = Discourse.View.extend({ Discourse.PostView = Discourse.GroupedView.extend({
classNames: ['topic-post', 'clearfix'], classNames: ['topic-post', 'clearfix'],
templateName: 'post', templateName: 'post',
classNameBindings: ['postTypeClass', classNameBindings: ['postTypeClass',
@ -28,18 +28,12 @@ Discourse.PostView = Discourse.View.extend({
}); });
}.observes('post.cooked'), }.observes('post.cooked'),
init: function() {
this._super();
this.set('context', this.get('content'));
},
mouseUp: function(e) { mouseUp: function(e) {
if (this.get('controller.multiSelect') && (e.metaKey || e.ctrlKey)) { if (this.get('controller.multiSelect') && (e.metaKey || e.ctrlKey)) {
this.get('controller').selectPost(this.get('post')); this.get('controller').selectPost(this.get('post'));
} }
}, },
selected: function() { selected: function() {
var selectedPosts = this.get('controller.selectedPosts'); var selectedPosts = this.get('controller.selectedPosts');
if (!selectedPosts) return false; if (!selectedPosts) return false;

View File

@ -1,11 +0,0 @@
// used to render a div with unescaped contents
Discourse.RawDivView = Ember.View.extend({
shouldRerender: Discourse.View.renderIfChanged('content'),
render: function(buffer) {
buffer.push(this.get('content'));
}
});

View File

@ -8,17 +8,11 @@
**/ **/
Discourse.SearchResultsTypeView = Ember.CollectionView.extend({ Discourse.SearchResultsTypeView = Ember.CollectionView.extend({
tagName: 'ul', tagName: 'ul',
itemViewClass: Ember.View.extend({ itemViewClass: Discourse.GroupedView.extend({
tagName: 'li', tagName: 'li',
classNameBindings: ['selected'], classNameBindings: ['selected'],
templateName: Discourse.computed.fmt('parentView.type', "search/%@_result"), templateName: Discourse.computed.fmt('parentView.type', "search/%@_result"),
selected: Discourse.computed.propertyEqual('content.index', 'controller.selectedIndex'), selected: Discourse.computed.propertyEqual('content.index', 'controller.selectedIndex')
init: function() {
this._super();
this.set('context', this.get('content'));
}
}) })
}); });

View File

@ -13,13 +13,7 @@ Discourse.UserStreamView = Ember.CollectionView.extend(Discourse.LoadMore, {
eyelineSelector: '#user-activity .user-stream .item', eyelineSelector: '#user-activity .user-stream .item',
classNames: ['user-stream'], classNames: ['user-stream'],
itemViewClass: Ember.View.extend({ itemViewClass: Discourse.GroupedView.extend({ templateName: 'user/stream_item' }),
templateName: 'user/stream_item',
init: function() {
this._super();
this.set('context', this.get('content'));
}
}),
loadMore: function() { loadMore: function() {
var userStreamView = this; var userStreamView = this;

View File

@ -9,6 +9,20 @@
**/ **/
Discourse.View = Ember.View.extend(Discourse.Presence, {}); Discourse.View = Ember.View.extend(Discourse.Presence, {});
Discourse.GroupedView = Ember.View.extend(Discourse.Presence, {
init: function() {
this._super();
this.set('context', this.get('content'));
var templateData = this.get('templateData');
if (templateData) {
this.set('templateData.insideGroup', true);
}
}
});
Discourse.View.reopenClass({ Discourse.View.reopenClass({
/** /**

View File

@ -1999,7 +1999,7 @@ function suspendListener(obj, eventName, target, method, callback) {
Suspends multiple listeners during a callback. Suspends multiple listeners during a callback.
@method suspendListeners @method suspendListeners
@for Ember @for Ember
@param obj @param obj
@ -2066,7 +2066,7 @@ function watchedEvents(obj) {
is skipped, and once listeners are removed. A listener without is skipped, and once listeners are removed. A listener without
a target is executed on the passed object. If an array of actions a target is executed on the passed object. If an array of actions
is not passed, the actions stored on the passed object are invoked. is not passed, the actions stored on the passed object are invoked.
@method sendEvent @method sendEvent
@for Ember @for Ember
@param obj @param obj
@ -2794,14 +2794,14 @@ Map.create = function() {
Map.prototype = { Map.prototype = {
/** /**
This property will change as the number of objects in the map changes. This property will change as the number of objects in the map changes.
@property length @property length
@type number @type number
@default 0 @default 0
*/ */
length: 0, length: 0,
/** /**
Retrieve the value associated with a given key. Retrieve the value associated with a given key.
@ -4395,7 +4395,7 @@ Ember.computed.alias = function(dependentKey) {
@return {Ember.ComputedProperty} computed property which creates an @return {Ember.ComputedProperty} computed property which creates an
one way computed property to the original value for property. one way computed property to the original value for property.
Where `computed.alias` aliases `get` and `set`, and allows for bidirectional Where `computed.alias` aliases `get` and `set`, and allows for bidirectional
data flow, `computed.oneWay` only provides an aliased `get`. The `set` will data flow, `computed.oneWay` only provides an aliased `get`. The `set` will
not mutate the upstream property, rather causes the current property to not mutate the upstream property, rather causes the current property to
become the value set. This causes the downstream property to permentantly become the value set. This causes the downstream property to permentantly
@ -23878,7 +23878,7 @@ helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {};
var buffer = '', stack1, hashTypes, hashContexts, escapeExpression=this.escapeExpression, self=this; var buffer = '', stack1, hashTypes, hashContexts, escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) { function program1(depth0,data) {
var buffer = '', hashTypes, hashContexts; var buffer = '', hashTypes, hashContexts;
data.buffer.push("<option value=\"\">"); data.buffer.push("<option value=\"\">");
hashTypes = {}; hashTypes = {};
@ -23889,7 +23889,7 @@ function program1(depth0,data) {
} }
function program3(depth0,data) { function program3(depth0,data) {
var stack1, hashTypes, hashContexts; var stack1, hashTypes, hashContexts;
hashTypes = {}; hashTypes = {};
hashContexts = {}; hashContexts = {};
@ -23898,7 +23898,7 @@ function program3(depth0,data) {
else { data.buffer.push(''); } else { data.buffer.push(''); }
} }
function program4(depth0,data) { function program4(depth0,data) {
var hashContexts, hashTypes; var hashContexts, hashTypes;
hashContexts = {'contentBinding': depth0,'labelBinding': depth0}; hashContexts = {'contentBinding': depth0,'labelBinding': depth0};
hashTypes = {'contentBinding': "ID",'labelBinding': "ID"}; hashTypes = {'contentBinding': "ID",'labelBinding': "ID"};
@ -23909,7 +23909,7 @@ function program4(depth0,data) {
} }
function program6(depth0,data) { function program6(depth0,data) {
var stack1, hashTypes, hashContexts; var stack1, hashTypes, hashContexts;
hashTypes = {}; hashTypes = {};
hashContexts = {}; hashContexts = {};
@ -23918,7 +23918,7 @@ function program6(depth0,data) {
else { data.buffer.push(''); } else { data.buffer.push(''); }
} }
function program7(depth0,data) { function program7(depth0,data) {
var hashContexts, hashTypes; var hashContexts, hashTypes;
hashContexts = {'contentBinding': depth0}; hashContexts = {'contentBinding': depth0};
hashTypes = {'contentBinding': "STRING"}; hashTypes = {'contentBinding': "STRING"};
@ -23936,7 +23936,7 @@ function program7(depth0,data) {
stack1 = helpers['if'].call(depth0, "view.optionGroupPath", {hash:{},inverse:self.program(6, program6, data),fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}); stack1 = helpers['if'].call(depth0, "view.optionGroupPath", {hash:{},inverse:self.program(6, program6, data),fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data});
if(stack1 || stack1 === 0) { data.buffer.push(stack1); } if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
return buffer; return buffer;
}), }),
attributeBindings: ['multiple', 'disabled', 'tabindex', 'name'], attributeBindings: ['multiple', 'disabled', 'tabindex', 'name'],
@ -24939,12 +24939,12 @@ define("router",
A Transition is a thennable (a promise-like object) that represents A Transition is a thennable (a promise-like object) that represents
an attempt to transition to another route. It can be aborted, either an attempt to transition to another route. It can be aborted, either
explicitly via `abort` or by attempting another transition while a explicitly via `abort` or by attempting another transition while a
previous one is still underway. An aborted transition can also previous one is still underway. An aborted transition can also
be `retry()`d later. be `retry()`d later.
*/ */
function Transition(router, promise) { function Transition(router, promise) {
this.router = router; this.router = router;
this.promise = promise; this.promise = promise;
this.data = {}; this.data = {};
@ -24968,9 +24968,9 @@ define("router",
The Transition's internal promise. Calling `.then` on this property The Transition's internal promise. Calling `.then` on this property
is that same as calling `.then` on the Transition object itself, but is that same as calling `.then` on the Transition object itself, but
this property is exposed for when you want to pass around a this property is exposed for when you want to pass around a
Transition's promise, but not the Transition object itself, since Transition's promise, but not the Transition object itself, since
Transition object can be externally `abort`ed, while the promise Transition object can be externally `abort`ed, while the promise
cannot. cannot.
*/ */
promise: null, promise: null,
@ -24984,12 +24984,12 @@ define("router",
data: null, data: null,
/** /**
A standard promise hook that resolves if the transition A standard promise hook that resolves if the transition
succeeds and rejects if it fails/redirects/aborts. succeeds and rejects if it fails/redirects/aborts.
Forwards to the internal `promise` property which you can Forwards to the internal `promise` property which you can
use in situations where you want to pass around a thennable, use in situations where you want to pass around a thennable,
but not the Transition itself. but not the Transition itself.
@param {Function} success @param {Function} success
@param {Function} failure @param {Function} failure
@ -25000,18 +25000,18 @@ define("router",
/** /**
Aborts the Transition. Note you can also implicitly abort a transition Aborts the Transition. Note you can also implicitly abort a transition
by initiating another transition while a previous one is underway. by initiating another transition while a previous one is underway.
*/ */
abort: function() { abort: function() {
if (this.isAborted) { return this; } if (this.isAborted) { return this; }
log(this.router, this.sequence, this.targetName + ": transition was aborted"); log(this.router, this.sequence, this.targetName + ": transition was aborted");
this.isAborted = true; this.isAborted = true;
this.router.activeTransition = null; this.router.activeTransition = null;
return this; return this;
}, },
/** /**
Retries a previously-aborted transition (making sure to abort the Retries a previously-aborted transition (making sure to abort the
transition if it's still active). Returns a new transition that transition if it's still active). Returns a new transition that
represents the new attempt to transition. represents the new attempt to transition.
*/ */
@ -25025,7 +25025,7 @@ define("router",
}, },
/** /**
Sets the URL-changing method to be employed at the end of a Sets the URL-changing method to be employed at the end of a
successful transition. By default, a new Transition will just successful transition. By default, a new Transition will just
use `updateURL`, but passing 'replace' to this method will use `updateURL`, but passing 'replace' to this method will
cause the URL to update using 'replaceWith' instead. Omitting cause the URL to update using 'replaceWith' instead. Omitting
@ -25058,12 +25058,12 @@ define("router",
handlers for failed transitions. handlers for failed transitions.
*/ */
Router.UnrecognizedURLError = function(message) { Router.UnrecognizedURLError = function(message) {
this.message = (message || "UnrecognizedURLError"); this.message = (message || "UnrecognizedURLError");
this.name = "UnrecognizedURLError"; this.name = "UnrecognizedURLError";
}; };
Router.TransitionAborted = function(message) { Router.TransitionAborted = function(message) {
this.message = (message || "TransitionAborted"); this.message = (message || "TransitionAborted");
this.name = "TransitionAborted"; this.name = "TransitionAborted";
}; };
@ -25230,8 +25230,8 @@ define("router",
if (isParam(object)) { if (isParam(object)) {
var recogHandler = recogHandlers[i], name = recogHandler.names[0]; var recogHandler = recogHandlers[i], name = recogHandler.names[0];
if (object.toString() !== this.currentParams[name]) { return false; } if (object.toString() !== this.currentParams[name]) { return false; }
} else if (handlerInfo.context !== object) { } else if (handlerInfo.context !== object) {
return false; return false;
} }
} }
} }
@ -25262,7 +25262,7 @@ define("router",
*/ */
function getMatchPoint(router, handlers, objects, inputParams) { function getMatchPoint(router, handlers, objects, inputParams) {
var matchPoint = handlers.length, var matchPoint = handlers.length,
providedModels = {}, i, providedModels = {}, i,
currentHandlerInfos = router.currentHandlerInfos || [], currentHandlerInfos = router.currentHandlerInfos || [],
params = {}, params = {},
@ -25273,9 +25273,9 @@ define("router",
objects = slice.call(objects); objects = slice.call(objects);
merge(params, inputParams); merge(params, inputParams);
for (i = handlers.length - 1; i >= 0; i--) { for (i = handlers.length - 1; i >= 0; i--) {
var handlerObj = handlers[i], var handlerObj = handlers[i],
handlerName = handlerObj.handler, handlerName = handlerObj.handler,
oldHandlerInfo = currentHandlerInfos[i], oldHandlerInfo = currentHandlerInfos[i],
hasChanged = false; hasChanged = false;
@ -25313,7 +25313,7 @@ define("router",
handlerParams[handlerName][name] = params[name] = params[name] || oldParams[name]; handlerParams[handlerName][name] = params[name] = params[name] || oldParams[name];
} }
} }
} }
if (hasChanged) { matchPoint = i; } if (hasChanged) { matchPoint = i; }
} }
@ -25341,7 +25341,7 @@ define("router",
// Use model from previous transition attempt, preferably the resolved one. // Use model from previous transition attempt, preferably the resolved one.
return (paramName && activeTransition.providedModels[handlerName]) || return (paramName && activeTransition.providedModels[handlerName]) ||
activeTransition.resolvedModels[handlerName]; activeTransition.resolvedModels[handlerName];
} }
} }
function isParam(object) { function isParam(object) {
@ -25511,7 +25511,7 @@ define("router",
if (handler.setup) { handler.setup(context); } if (handler.setup) { handler.setup(context); }
checkAbort(transition); checkAbort(transition);
} catch(e) { } catch(e) {
if (!(e instanceof Router.TransitionAborted)) { if (!(e instanceof Router.TransitionAborted)) {
// Trigger the `error` event starting from this failed handler. // Trigger the `error` event starting from this failed handler.
trigger(currentHandlerInfos.concat(handlerInfo), true, ['error', e, transition]); trigger(currentHandlerInfos.concat(handlerInfo), true, ['error', e, transition]);
} }
@ -25661,11 +25661,11 @@ define("router",
wasTransitioning = false; wasTransitioning = false;
// Check if there's already a transition underway. // Check if there's already a transition underway.
if (router.activeTransition) { if (router.activeTransition) {
if (transitionsIdentical(router.activeTransition, targetName, providedModelsArray)) { if (transitionsIdentical(router.activeTransition, targetName, providedModelsArray)) {
return router.activeTransition; return router.activeTransition;
} }
router.activeTransition.abort(); router.activeTransition.abort();
wasTransitioning = true; wasTransitioning = true;
} }
@ -25721,8 +25721,8 @@ define("router",
@private @private
Accepts handlers in Recognizer format, either returned from Accepts handlers in Recognizer format, either returned from
recognize() or handlersFor(), and returns unified recognize() or handlersFor(), and returns unified
`HandlerInfo`s. `HandlerInfo`s.
*/ */
function generateHandlerInfos(router, recogHandlers) { function generateHandlerInfos(router, recogHandlers) {
var handlerInfos = []; var handlerInfos = [];
@ -25786,7 +25786,7 @@ define("router",
router.currentParams = params; router.currentParams = params;
var urlMethod = transition.urlMethod; var urlMethod = transition.urlMethod;
if (urlMethod) { if (urlMethod) {
var url = router.recognizer.generate(handlerName, params); var url = router.recognizer.generate(handlerName, params);
if (urlMethod === 'replace') { if (urlMethod === 'replace') {
@ -25862,12 +25862,12 @@ define("router",
log(router, seq, handlerName + ": handling error: " + reason); log(router, seq, handlerName + ": handling error: " + reason);
// An error was thrown / promise rejected, so fire an // An error was thrown / promise rejected, so fire an
// `error` event from this handler info up to root. // `error` event from this handler info up to root.
trigger(handlerInfos.slice(0, index + 1), true, ['error', reason, transition]); trigger(handlerInfos.slice(0, index + 1), true, ['error', reason, transition]);
if (handler.error) { if (handler.error) {
handler.error(reason, transition); handler.error(reason, transition);
} }
// Propagate the original error. // Propagate the original error.
@ -25917,7 +25917,7 @@ define("router",
Throws a TransitionAborted if the provided transition has been aborted. Throws a TransitionAborted if the provided transition has been aborted.
*/ */
function checkAbort(transition) { function checkAbort(transition) {
if (transition.isAborted) { if (transition.isAborted) {
log(transition.router, transition.sequence, "detected abort."); log(transition.router, transition.sequence, "detected abort.");
throw new Router.TransitionAborted(); throw new Router.TransitionAborted();
} }
@ -25947,7 +25947,7 @@ define("router",
} }
/** /**
@private @private
*/ */
function log(router, sequence, msg) { function log(router, sequence, msg) {
@ -26006,7 +26006,7 @@ define("router",
// Use custom serialize if it exists. // Use custom serialize if it exists.
if (handler.serialize) { if (handler.serialize) {
return handler.serialize(model, names); return handler.serialize(model, names);
} }
if (names.length !== 1) { return; } if (names.length !== 1) { return; }
@ -28551,7 +28551,7 @@ Ember.ControllerMixin.reopen({
Optionally supply a model for the route in question. The model Optionally supply a model for the route in question. The model
will be serialized into the URL using the `serialize` hook of will be serialized into the URL using the `serialize` hook of
the route: the route:
```javascript ```javascript
aController.transitionToRoute('blogPost', aPost); aController.transitionToRoute('blogPost', aPost);
``` ```
@ -28675,7 +28675,7 @@ Ember.View.reopen({
// Add a new named queue after the 'actions' queue (where RSVP promises // Add a new named queue after the 'actions' queue (where RSVP promises
// resolve), which is used in router transitions to prevent unnecessary // resolve), which is used in router transitions to prevent unnecessary
// loading state entry if all context promises resolve on the // loading state entry if all context promises resolve on the
// 'actions' queue first. // 'actions' queue first.
var queues = Ember.run.queues, var queues = Ember.run.queues,
@ -32144,7 +32144,7 @@ function chain(app, promise, fn) {
* using your app. * using your app.
* *
* Example: * Example:
* *
* ``` * ```
* visit('posts/index').then(function() { * visit('posts/index').then(function() {
* // assert something * // assert something
@ -32152,7 +32152,7 @@ function chain(app, promise, fn) {
* ``` * ```
* *
* @method visit * @method visit
* @param {String} url the name of the route * @param {String} url the name of the route
* @returns {RSVP.Promise} * @returns {RSVP.Promise}
*/ */
helper('visit', visit); helper('visit', visit);

View File

@ -1,23 +0,0 @@
(function() {
var get = Ember.get, set = Ember.set, EmberHandlebars = Ember.Handlebars;
EmberHandlebars.registerHelper('group', function(options) {
var data = options.data,
fn = options.fn,
view = data.view,
childView;
childView = view.createChildView(Ember._MetamorphView, {
context: get(view, 'context'),
template: function(context, options) {
options.data.insideGroup = true;
return fn(context, options);
}
});
view.appendChild(childView);
});
})();

View File

@ -1,23 +0,0 @@
(function() {
var get = Ember.get, set = Ember.set, EmberHandlebars = Ember.Handlebars;
EmberHandlebars.registerHelper('group', function(options) {
var data = options.data,
fn = options.fn,
view = data.view,
childView;
childView = view.createChildView(Ember._MetamorphView, {
context: get(view, 'context'),
template: function(context, options) {
options.data.insideGroup = true;
return fn(context, options);
}
});
view.appendChild(childView);
});
})();

View File

@ -13,7 +13,6 @@
//= require ../../app/assets/javascripts/external/jquery.ui.widget.js //= require ../../app/assets/javascripts/external/jquery.ui.widget.js
//= require ../../app/assets/javascripts/external/handlebars.js //= require ../../app/assets/javascripts/external/handlebars.js
//= require ../../app/assets/javascripts/external_development/ember.js //= require ../../app/assets/javascripts/external_development/ember.js
//= require ../../app/assets/javascripts/external_development/group-helper.js
//= require ../../app/assets/javascripts/locales/i18n //= require ../../app/assets/javascripts/locales/i18n
//= require ../../app/assets/javascripts/discourse/helpers/i18n_helpers //= require ../../app/assets/javascripts/discourse/helpers/i18n_helpers