Added some macros to simplify code

This commit is contained in:
Robin Ward
2013-07-10 14:56:00 -04:00
parent 1fef617818
commit 8888ae4b40
17 changed files with 82 additions and 82 deletions

View File

@ -0,0 +1,17 @@
Discourse.computed = {
/**
Returns whether two properties are equal to each other.
@method propertyEqual
@params {String} p1 the first property
@params {String} p2 the second property
@return {Function} computedProperty function
**/
propertyEqual: function(p1, p2) {
return Ember.computed(function() {
return this.get(p1) === this.get(p2);
}).property(p1, p2);
}
}

View File

@ -37,14 +37,11 @@ Ember.Handlebars.registerHelper('i18n', function(property, options) {
Ember.Handlebars.registerHelper('countI18n', function(key, options) { Ember.Handlebars.registerHelper('countI18n', function(key, options) {
var view = Discourse.View.extend({ var view = Discourse.View.extend({
tagName: 'span', tagName: 'span',
shouldRerender: Discourse.View.renderIfChanged('countChanged'),
render: function(buffer) { render: function(buffer) {
buffer.push(I18n.t(key, { count: this.get('count') })); buffer.push(I18n.t(key, { count: this.get('count') }));
}, }
countChanged: function() {
this.rerender();
}.observes('count')
}); });
return Ember.Handlebars.helpers.view.call(this, view, options); return Ember.Handlebars.helpers.view.call(this, view, options);

View File

@ -60,7 +60,7 @@
<div class='span5 gutter'> <div class='span5 gutter'>
{{collection contentBinding="internalLinks" itemViewClass="Discourse.PostLinkView" tagName="ul" classNames="post-links"}} {{collection contentBinding="internalLinks" itemViewClass="Discourse.PostLinkView" tagName="ul" classNames="post-links"}}
{{#if controller.details.can_reply_as_new_topic}} {{#if topic.details.can_reply_as_new_topic}}
<a href='#' class='reply-new' {{action replyAsNewTopic this}}><i class='icon icon-plus'></i>{{i18n post.reply_as_new_topic}}</a> <a href='#' class='reply-new' {{action replyAsNewTopic this}}><i class='icon icon-plus'></i>{{i18n post.reply_as_new_topic}}</a>
{{/if}} {{/if}}
</div> </div>

View File

@ -11,18 +11,14 @@ Discourse.ActionsHistoryView = Discourse.View.extend({
tagName: 'section', tagName: 'section',
classNameBindings: [':post-actions', 'hidden'], classNameBindings: [':post-actions', 'hidden'],
hidden: (function() { hidden: Em.computed.empty('content'),
return this.blank('content');
}).property('content.@each'),
usersChanged: (function() { shouldRerender: Discourse.View.renderIfChanged('content.@each', 'content.users.@each'),
return this.rerender();
}).observes('content.@each', 'content.users.@each'),
// This was creating way too many bound ifs and subviews in the handlebars version. // This was creating way too many bound ifs and subviews in the handlebars version.
render: function(buffer) { render: function(buffer) {
if (!this.present('content')) return; if (!this.present('content')) return;
return this.get('content').forEach(function(c) { return this.get('content').forEach(function(c) {
var actionString, iconsHtml; var actionString, iconsHtml;
buffer.push("<div class='post-action'>"); buffer.push("<div class='post-action'>");

View File

@ -10,6 +10,8 @@ Discourse.DropdownButtonView = Discourse.View.extend({
classNames: ['btn-group'], classNames: ['btn-group'],
attributeBindings: ['data-not-implemented'], attributeBindings: ['data-not-implemented'],
shouldRerender: Discourse.View.renderIfChanged('text', 'longDescription'),
didInsertElement: function(e) { didInsertElement: function(e) {
// If there's a click handler, call it // If there's a click handler, call it
if (this.clicked) { if (this.clicked) {
@ -26,10 +28,6 @@ Discourse.DropdownButtonView = Discourse.View.extend({
this.$('ul li').off('click.dropdown-button'); this.$('ul li').off('click.dropdown-button');
}, },
textChanged: function() {
this.rerender();
}.observes('text', 'longDescription'),
render: function(buffer) { render: function(buffer) {
buffer.push("<h4 class='title'>" + this.get('title') + "</h4>"); buffer.push("<h4 class='title'>" + this.get('title') + "</h4>");
buffer.push("<button class='btn standard dropdown-toggle' data-toggle='dropdown'>"); buffer.push("<button class='btn standard dropdown-toggle' data-toggle='dropdown'>");

View File

@ -10,9 +10,7 @@ Discourse.FavoriteButton = Discourse.ButtonView.extend({
textKey: 'favorite.title', textKey: 'favorite.title',
helpKeyBinding: 'controller.content.favoriteTooltipKey', helpKeyBinding: 'controller.content.favoriteTooltipKey',
favoriteChanged: function() { shouldRerender: Discourse.View.renderIfChanged('controller.content.starred'),
this.rerender();
}.observes('controller.content.starred'),
click: function() { click: function() {
this.get('controller').toggleStar(); this.get('controller').toggleStar();

View File

@ -10,6 +10,8 @@
Discourse.HotnessView = Discourse.View.extend({ Discourse.HotnessView = Discourse.View.extend({
classNames: ['hotness-control'], classNames: ['hotness-control'],
shouldRerender: Discourse.View.renderIfChanged('hotness'),
render: function(buffer) { render: function(buffer) {
// Our scale goes to 11! // Our scale goes to 11!
for (var i=1; i<12; i++) { for (var i=1; i<12; i++) {
@ -21,15 +23,6 @@ Discourse.HotnessView = Discourse.View.extend({
} }
}, },
/**
Trigger a re-render whenever the hotness changes
@observer hotnessChanged
**/
hotnessChanged: function() {
this.rerender();
}.observes('hotness'),
/** /**
When the user clicks on a hotness value button, change it. When the user clicks on a hotness value button, change it.

View File

@ -9,17 +9,9 @@
Discourse.InputTipView = Discourse.View.extend({ Discourse.InputTipView = Discourse.View.extend({
classNameBindings: [':tip', 'good', 'bad'], classNameBindings: [':tip', 'good', 'bad'],
good: function() { shouldRerender: Discourse.View.renderIfChanged('validation'),
return !this.get('validation.failed'); bad: Em.computed.alias('validation.failed'),
}.property('validation'), good: Em.computed.not('bad'),
bad: function() {
return this.get('validation.failed');
}.property('validation'),
triggerRender: function() {
return this.rerender();
}.observes('validation'),
render: function(buffer) { render: function(buffer) {
var reason = this.get('validation.reason'); var reason = this.get('validation.reason');

View File

@ -8,9 +8,13 @@
**/ **/
Discourse.NavItemView = Discourse.View.extend({ Discourse.NavItemView = Discourse.View.extend({
tagName: 'li', tagName: 'li',
classNameBindings: ['isActive', 'content.hasIcon:has-icon'], classNameBindings: ['active', 'content.hasIcon:has-icon'],
attributeBindings: ['title'], attributeBindings: ['title'],
countBinding: Ember.Binding.oneWay('content.count'),
hidden: Em.computed.not('content.visible'),
count: Ember.computed.alias('content.count'),
shouldRerender: Discourse.View.renderIfChanged('count'),
active: Discourse.computed.propertyEqual('contentNameSlug', 'controller.filterMode'),
title: function() { title: function() {
var categoryName, extra, name; var categoryName, extra, name;
@ -23,16 +27,13 @@ Discourse.NavItemView = Discourse.View.extend({
return I18n.t("filters." + name + ".help", extra); return I18n.t("filters." + name + ".help", extra);
}.property("content.filter"), }.property("content.filter"),
isActive: function() { contentNameSlug: function() {
if (this.get("content.name").toLowerCase().replace(' ','-') === this.get("controller.filterMode")) return "active"; return this.get("content.name").toLowerCase().replace(' ','-');
return ""; }.property('content.name'),
}.property("content.name", "controller.filterMode"),
hidden: Em.computed.not('content.visible'), // active: function() {
// return (this.get("contentNameSlug") === this.get("controller.filterMode"));
countChanged: function(){ // }.property("contentNameSlug", "controller.filterMode"),
this.rerender();
}.observes('count'),
name: function() { name: function() {
var categoryName, extra, name; var categoryName, extra, name;

View File

@ -10,6 +10,17 @@ Discourse.PostMenuView = Discourse.View.extend({
tagName: 'section', tagName: 'section',
classNames: ['post-menu-area', 'clearfix'], classNames: ['post-menu-area', 'clearfix'],
shouldRerender: Discourse.View.renderIfChanged(
'post.deleted_at',
'post.flagsAvailable.@each',
'post.url',
'post.bookmarked',
'post.reply_count',
'post.showRepliesBelow',
'post.can_delete',
'post.read',
'post.topic.last_read_post_number'),
render: function(buffer) { render: function(buffer) {
var post = this.get('post'); var post = this.get('post');
buffer.push("<nav class='post-controls'>"); buffer.push("<nav class='post-controls'>");
@ -35,12 +46,6 @@ Discourse.PostMenuView = Discourse.View.extend({
handler.call(this); handler.call(this);
}, },
// Trigger re-rendering
needsToRender: function() {
this.rerender();
}.observes('post.deleted_at', 'post.flagsAvailable.@each', 'post.url', 'post.bookmarked', 'post.reply_count',
'post.showRepliesBelow', 'post.can_delete', 'post.read', 'post.topic.last_read_post_number'),
// Replies Button // Replies Button
renderReplies: function(post, buffer) { renderReplies: function(post, buffer) {
if (!post.get('showRepliesBelow')) return; if (!post.get('showRepliesBelow')) return;

View File

@ -2,12 +2,10 @@
Discourse.RawDivView = Ember.View.extend({ Discourse.RawDivView = Ember.View.extend({
shouldRerender: Discourse.View.renderIfChanged('content'),
render: function(buffer) { render: function(buffer) {
buffer.push(this.get('content')); buffer.push(this.get('content'));
}, }
contentChanged: function() {
this.rerender();
}.observes('content')
}); });

View File

@ -11,9 +11,7 @@ Discourse.TopicClosingView = Discourse.View.extend({
elementId: 'topic-closing-info', elementId: 'topic-closing-info',
delayedRerender: null, delayedRerender: null,
contentChanged: function() { shouldRerender: Discourse.View.renderIfChanged('topic.details.auto_close_at'),
this.rerender();
}.observes('topic.details.auto_close_at'),
render: function(buffer) { render: function(buffer) {
if (!this.present('topic.details.auto_close_at')) return; if (!this.present('topic.details.auto_close_at')) return;
@ -49,12 +47,12 @@ Discourse.TopicClosingView = Discourse.View.extend({
buffer.push('</h3>'); buffer.push('</h3>');
// TODO Sam: concerned this can cause a heavy rerender loop // TODO Sam: concerned this can cause a heavy rerender loop
this.delayedRerender = Em.run.later(this, this.rerender, rerenderDelay); this.set('delayedRerender', Em.run.later(this, this.rerender, rerenderDelay));
}, },
willDestroyElement: function() { willDestroyElement: function() {
if( this.delayedRerender ) { if( this.delayedRerender ) {
Em.run.cancel(this.delayedRerender); Em.run.cancel(this.get('delayedRerender'));
} }
} }
}); });

View File

@ -17,9 +17,7 @@ Discourse.TopicStatusView = Discourse.View.extend({
return false; return false;
}.property('topic.closed', 'topic.pinned', 'topic.visible'), }.property('topic.closed', 'topic.pinned', 'topic.visible'),
statusChanged: function() { shouldRerender: Discourse.View.renderIfChanged('topic.closed', 'topic.pinned', 'topic.visible'),
this.rerender();
}.observes('topic.closed', 'topic.pinned', 'topic.visible'),
renderIcon: function(buffer, name, key) { renderIcon: function(buffer, name, key) {
var title = I18n.t("topic_statuses." + key + ".help"); var title = I18n.t("topic_statuses." + key + ".help");
@ -27,8 +25,7 @@ Discourse.TopicStatusView = Discourse.View.extend({
}, },
render: function(buffer) { render: function(buffer) {
if (!this.get('hasDisplayableStatus')) { return; }
if (!this.get('hasDisplayableStatus')) return;
// Allow a plugin to add a custom icon to a topic // Allow a plugin to add a custom icon to a topic
this.trigger('addCustomIcon', buffer); this.trigger('addCustomIcon', buffer);

View File

@ -26,12 +26,10 @@ Discourse.TopicSummaryView = Discourse.ContainerView.extend({
return allLinks.slice(0, Discourse.TopicSummaryView.LINKS_SHOWN); return allLinks.slice(0, Discourse.TopicSummaryView.LINKS_SHOWN);
}.property('topic.details.links', 'allLinksShown'), }.property('topic.details.links', 'allLinksShown'),
newPostCreated: function() { shouldRerender: Discourse.View.renderIfChanged('topic.posts_count'),
this.rerender();
}.observes('topic.posts_count'),
hidden: function() { hidden: function() {
if (this.get('post.post_number') !== 1) return true; if (!this.get('post.firstPost')) return true;
if (this.get('controller.content.archetype') === 'private_message') return false; if (this.get('controller.content.archetype') === 'private_message') return false;
if (this.get('controller.content.archetype') !== 'regular') return true; if (this.get('controller.content.archetype') !== 'regular') return true;
return this.get('controller.content.posts_count') < 2; return this.get('controller.content.posts_count') < 2;

View File

@ -11,10 +11,7 @@ Discourse.ActivityFilterView = Discourse.View.extend({
classNameBindings: ['active'], classNameBindings: ['active'],
stream: Em.computed.alias('controller.content'), stream: Em.computed.alias('controller.content'),
shouldRerender: Discourse.View.renderIfChanged('count'),
countChanged: function(){
this.rerender();
}.observes('count'),
active: function() { active: function() {
var content = this.get('content'); var content = this.get('content');

View File

@ -26,6 +26,21 @@ Discourse.View.reopenClass({
Discourse.Utilities.normalizeHash(hash, types); Discourse.Utilities.normalizeHash(hash, types);
return Ember.Handlebars.helpers.view.call(this, helperClass, options); return Ember.Handlebars.helpers.view.call(this, helperClass, options);
}); });
},
/**
Returns an observer that will re-render if properties change. This is useful for
views where rendering is done to a buffer manually and need to know when to trigger
a new render call.
@method renderIfChanged
@params {String} propertyNames*
@return {Function} observer
**/
renderIfChanged: function() {
var args = Array.prototype.slice.call(arguments, 0);
args.unshift(function () { this.rerender(); });
return Ember.observer.apply(this, args);
} }
}); });

View File

@ -40,7 +40,8 @@ class PostSerializer < BasicPostSerializer
:hidden, :hidden,
:hidden_reason_id, :hidden_reason_id,
:deleted_at, :deleted_at,
:trust_level :trust_level,
:deleted_by
def moderator? def moderator?
@ -72,7 +73,6 @@ class PostSerializer < BasicPostSerializer
end end
def link_counts def link_counts
return @single_post_link_counts if @single_post_link_counts.present? return @single_post_link_counts if @single_post_link_counts.present?
# TODO: This could be better, just porting the old one over # TODO: This could be better, just porting the old one over