mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -05:00
FIX: Fix rerendering issues on some components.
This commit is contained in:
parent
7069ee16e2
commit
5fd3f2547c
18 changed files with 96 additions and 162 deletions
|
@ -1,23 +1,16 @@
|
|||
/**
|
||||
This component handles rendering of what actions have been taken on a post. It uses
|
||||
buffer rendering for performance rather than a template.
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
@class ActionsHistoryComponent
|
||||
@extends Em.Component
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Em.Component.extend({
|
||||
export default Em.Component.extend(StringBuffer, {
|
||||
tagName: 'section',
|
||||
classNameBindings: [':post-actions', 'hidden'],
|
||||
actionsHistory: Em.computed.alias('post.actionsHistory'),
|
||||
emptyHistory: Em.computed.empty('actionsHistory'),
|
||||
hidden: Em.computed.and('emptyHistory', 'post.notDeleted'),
|
||||
shouldRerender: Discourse.View.renderIfChanged('actionsHistory.@each', 'actionsHistory.users.length', 'post.deleted'),
|
||||
|
||||
rerenderTriggers: ['actionsHistory.@each', 'actionsHistory.users.length', 'post.deleted'],
|
||||
|
||||
// This was creating way too many bound ifs and subviews in the handlebars version.
|
||||
render: function(buffer) {
|
||||
|
||||
renderString: function(buffer) {
|
||||
if (!this.get('emptyHistory')) {
|
||||
this.get('actionsHistory').forEach(function(c) {
|
||||
buffer.push("<div class='post-action'>");
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
export default Ember.Component.extend({
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
shouldRerender: Discourse.View.renderIfChanged("site.isReadOnly"),
|
||||
export default Ember.Component.extend(StringBuffer, {
|
||||
rerenderTriggers: ['site.isReadOnly'],
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
var notices = [];
|
||||
|
||||
if (this.site.get("isReadOnly")) {
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
/**
|
||||
This view handles rendering of a navigation item
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
@class NavigationItemComponent
|
||||
@extends Ember.Component
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Ember.Component.extend({
|
||||
export default Ember.Component.extend(StringBuffer, {
|
||||
tagName: 'li',
|
||||
classNameBindings: ['active', 'content.hasIcon:has-icon'],
|
||||
attributeBindings: ['title'],
|
||||
hidden: Em.computed.not('content.visible'),
|
||||
shouldRerender: Discourse.View.renderIfChanged('content.count'),
|
||||
rerenderTriggers: ['content.count'],
|
||||
|
||||
title: function() {
|
||||
var categoryName = this.get('content.categoryName'),
|
||||
|
@ -42,7 +36,7 @@ export default Ember.Component.extend({
|
|||
return I18n.t("filters." + name + ".title", extra);
|
||||
}.property('content.count'),
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
var content = this.get('content');
|
||||
buffer.push("<a href='" + content.get('href') + "'>");
|
||||
if (content.get('hasIcon')) {
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
var MAX_SHOWN = 5;
|
||||
|
||||
export default Em.Component.extend({
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
export default Em.Component.extend(StringBuffer, {
|
||||
classNameBindings: [':gutter'],
|
||||
|
||||
rerenderTriggers: ['expanded'],
|
||||
|
||||
// Roll up links to avoid duplicates
|
||||
collapsed: function() {
|
||||
var seen = {},
|
||||
|
@ -21,7 +25,7 @@ export default Em.Component.extend({
|
|||
return result;
|
||||
}.property('links'),
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
var links = this.get('collapsed'),
|
||||
toRender = links,
|
||||
collapsed = !this.get('expanded');
|
||||
|
@ -62,10 +66,6 @@ export default Em.Component.extend({
|
|||
}
|
||||
},
|
||||
|
||||
_rerenderIfNeeded: function() {
|
||||
this.rerender();
|
||||
}.observes('expanded'),
|
||||
|
||||
click: function(e) {
|
||||
var $target = $(e.target);
|
||||
if ($target.hasClass('toggle-more')) {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
export default Ember.Component.extend({
|
||||
tagName: 'h2',
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
_shouldRerender: Discourse.View.renderIfChanged('period.title'),
|
||||
render: function(buffer) {
|
||||
export default Ember.Component.extend(StringBuffer, {
|
||||
tagName: 'h2',
|
||||
rerenderTriggers: ['period.title'],
|
||||
|
||||
renderString: function(buffer) {
|
||||
buffer.push("<i class='fa fa-calendar-o'></i> " + this.get('period.title'));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
// Creates a link
|
||||
function link(buffer, prop, url, cssClass, i18nKey, text) {
|
||||
|
@ -7,12 +8,12 @@ function link(buffer, prop, url, cssClass, i18nKey, text) {
|
|||
buffer.push("<a href='" + url + "' class='badge " + cssClass + " badge-notification' title='" + title + "'>" + (text || prop) + "</a>\n");
|
||||
}
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Ember.Component.extend(StringBuffer, {
|
||||
tagName: 'span',
|
||||
classNameBindings: [':topic-post-badges'],
|
||||
_shouldRerender: Discourse.View.renderIfChanged('url', 'unread', 'newPosts', 'unseen'),
|
||||
rerenderTriggers: ['url', 'unread', 'newPosts', 'unseen'],
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
var url = this.get('url');
|
||||
|
||||
link(buffer, this.get('unread'), url, 'unread', 'unread_posts');
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
/**
|
||||
This view is for rendering an icon representing the status of a topic
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
@class TopicStatusComponent
|
||||
@extends Ember.Component
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Ember.Component.extend({
|
||||
export default Ember.Component.extend(StringBuffer, {
|
||||
classNames: ['topic-statuses'],
|
||||
|
||||
hasDisplayableStatus: Em.computed.or('topic.archived','topic.closed', 'topic.pinned', 'topic.unpinned', 'topic.invisible', 'topic.archetypeObject.notDefault', 'topic.is_warning'),
|
||||
shouldRerender: Discourse.View.renderIfChanged('topic.archived', 'topic.closed', 'topic.pinned', 'topic.visible', 'topic.unpinned', 'topic.is_warning'),
|
||||
rerenderTriggers: ['topic.archived', 'topic.closed', 'topic.pinned', 'topic.visible', 'topic.unpinned', 'topic.is_warning'],
|
||||
|
||||
didInsertElement: function(){
|
||||
watchClick: function(){
|
||||
var self = this;
|
||||
|
||||
this.$('a').click(function(){
|
||||
|
@ -27,13 +21,13 @@ export default Ember.Component.extend({
|
|||
|
||||
return false;
|
||||
});
|
||||
},
|
||||
}.on('didInsertElement'),
|
||||
|
||||
canAct: function() {
|
||||
return Discourse.User.current() && !this.get('disableActions');
|
||||
}.property('disableActions'),
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
if (!this.get('hasDisplayableStatus')) { return; }
|
||||
|
||||
var self = this;
|
||||
|
@ -41,7 +35,7 @@ export default Ember.Component.extend({
|
|||
var renderIconIf = function(conditionProp, name, key, actionable) {
|
||||
if (!self.get(conditionProp)) { return; }
|
||||
var title = Handlebars.Utils.escapeExpression(I18n.t("topic_statuses." + key + ".help"));
|
||||
var startTag = actionable ? "a href='#'" : "span";
|
||||
var startTag = actionable ? "a href" : "span";
|
||||
var endTag = actionable ? "a" : "span";
|
||||
|
||||
buffer.push("<" + startTag + " title='" + title + "' class='topic-status'><i class='fa fa-" + name + "'></i></" + endTag + ">");
|
||||
|
|
|
@ -49,14 +49,13 @@ Ember.Handlebars.registerBoundHelper("boundI18n", function(property, options) {
|
|||
@for Handlebars
|
||||
**/
|
||||
Ember.Handlebars.registerHelper('countI18n', function(key, options) {
|
||||
var view = Discourse.View.extend({
|
||||
var view = Discourse.View.extend(Discourse.StringBuffer, {
|
||||
tagName: 'span',
|
||||
shouldRerender: Discourse.View.renderIfChanged('count', 'suffix'),
|
||||
rerenderTriggers: ['count', 'suffix'],
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
buffer.push(I18n.t(key + (this.get('suffix') || ''), { count: this.get('count') }));
|
||||
}
|
||||
|
||||
});
|
||||
return Ember.Handlebars.helpers.view.call(this, view, options);
|
||||
});
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
export default Ember.Component.extend({
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
export default Ember.Component.extend(StringBuffer, {
|
||||
tagName: 'li',
|
||||
classNameBindings: ['active', 'noGlyph'],
|
||||
|
||||
shouldRerender: Discourse.View.renderIfChanged('content.count', 'count'),
|
||||
rerenderTriggers: ['content.count', 'count'],
|
||||
noGlyph: Em.computed.empty('icon'),
|
||||
|
||||
active: function() {
|
||||
|
@ -37,7 +39,7 @@ export default Ember.Component.extend({
|
|||
return this.get('content.description') || I18n.t("user.filters.all");
|
||||
}.property('content.description'),
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
buffer.push("<a href='" + this.get('url') + "'>");
|
||||
var icon = this.get('icon');
|
||||
if (icon) {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.View.extend({
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
export default Discourse.View.extend(StringBuffer, {
|
||||
tagName: 'button',
|
||||
classNameBindings: [':btn', ':standard', 'dropDownToggle'],
|
||||
attributeBindings: ['title', 'data-toggle', 'data-share-url'],
|
||||
|
@ -12,7 +14,7 @@ export default Discourse.View.extend({
|
|||
return I18n.t(this.get('textKey'));
|
||||
}.property('textKey'),
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
if (this.renderIcon) {
|
||||
this.renderIcon(buffer);
|
||||
}
|
||||
|
|
|
@ -1,41 +1,45 @@
|
|||
export default Discourse.View.extend({
|
||||
classNameBindings: [':btn-group', 'hidden'],
|
||||
shouldRerender: Discourse.View.renderIfChanged('text', 'longDescription'),
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
didInsertElement: function() {
|
||||
var self = this;
|
||||
export default Discourse.View.extend(StringBuffer, {
|
||||
classNameBindings: [':btn-group', 'hidden'],
|
||||
rerenderTriggers: ['text', 'longDescription'],
|
||||
|
||||
_bindClick: function() {
|
||||
// If there's a click handler, call it
|
||||
if (self.clicked) {
|
||||
self.$('ul li').on('click.dropdown-button', function(e) {
|
||||
if (this.clicked) {
|
||||
var self = this;
|
||||
this.$().on('click.dropdown-button', 'ul li', function(e) {
|
||||
e.preventDefault();
|
||||
if ($(e.currentTarget).data('id') !== self.get('activeItem'))
|
||||
if ($(e.currentTarget).data('id') !== self.get('activeItem')) {
|
||||
self.clicked($(e.currentTarget).data('id'));
|
||||
}
|
||||
self.$('.dropdown-toggle').dropdown('toggle');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
},
|
||||
}.on('didInsertElement'),
|
||||
|
||||
willDestroyElement: function() {
|
||||
this.$('ul li').off('click.dropdown-button');
|
||||
},
|
||||
_unbindClick: function() {
|
||||
this.$().off('click.dropdown-button', 'ul li');
|
||||
}.on('willDestroyElement'),
|
||||
|
||||
render: function(buffer) {
|
||||
var self = this;
|
||||
renderString: function(buffer) {
|
||||
|
||||
buffer.push("<h4 class='title'>" + self.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(self.get('text'));
|
||||
buffer.push(this.get('text'));
|
||||
buffer.push("</button>");
|
||||
buffer.push("<ul class='dropdown-menu'>");
|
||||
|
||||
_.each(self.get('dropDownContent'), function(row) {
|
||||
var self = this;
|
||||
this.get('dropDownContent').forEach(function(row) {
|
||||
var id = row.id,
|
||||
title = row.title,
|
||||
iconClass = row.styleClasses,
|
||||
description = row.description,
|
||||
className = (self.get('activeItem') === id? 'disabled': '');
|
||||
className = (self.get('activeItem') === id ? 'disabled': '');
|
||||
|
||||
buffer.push("<li data-id=\"" + id + "\" class=\"" + className + "\"><a href='#'>");
|
||||
buffer.push("<li data-id=\"" + id + "\" class=\"" + className + "\"><a href>");
|
||||
buffer.push("<span class='icon " + iconClass + "'></span>");
|
||||
buffer.push("<div><span class='title'>" + title + "</span>");
|
||||
buffer.push("<span>" + description + "</span></div>");
|
||||
|
@ -44,7 +48,7 @@ export default Discourse.View.extend({
|
|||
|
||||
buffer.push("</ul>");
|
||||
|
||||
var desc = self.get('longDescription');
|
||||
var desc = this.get('longDescription');
|
||||
if (desc) {
|
||||
buffer.push("<p>");
|
||||
buffer.push(desc);
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
/**
|
||||
This view handles rendering a tip when a field on a form is invalid
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
@class InputTipView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Discourse.View.extend({
|
||||
export default Discourse.View.extend(StringBuffer, {
|
||||
classNameBindings: [':tip', 'good', 'bad'],
|
||||
rerenderTriggers: ['validation'],
|
||||
|
||||
shouldRerender: Discourse.View.renderIfChanged('validation'),
|
||||
bad: Em.computed.alias('validation.failed'),
|
||||
good: Em.computed.not('bad'),
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
var reason = this.get('validation.reason');
|
||||
if (reason) {
|
||||
var icon = this.get('good') ? 'fa-check' : 'fa-times';
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
// Helper class for rendering a button
|
||||
export var Button = function(action, label, icon, opts) {
|
||||
this.action = action;
|
||||
|
@ -28,11 +30,11 @@ Button.prototype.render = function(buffer) {
|
|||
|
||||
var hiddenButtons;
|
||||
|
||||
export default Discourse.View.extend({
|
||||
export default Discourse.View.extend(StringBuffer, {
|
||||
tagName: 'section',
|
||||
classNames: ['post-menu-area', 'clearfix'],
|
||||
|
||||
shouldRerender: Discourse.View.renderIfChanged(
|
||||
rerenderTriggers: [
|
||||
'post.deleted_at',
|
||||
'post.flagsAvailable.@each',
|
||||
'post.reply_count',
|
||||
|
@ -43,13 +45,13 @@ export default Discourse.View.extend({
|
|||
'post.topic.deleted_at',
|
||||
'post.replies.length',
|
||||
'post.wiki',
|
||||
'collapsed'),
|
||||
'collapsed'],
|
||||
|
||||
_collapsedByDefault: function() {
|
||||
this.set('collapsed', true);
|
||||
}.on('init'),
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
var post = this.get('post');
|
||||
|
||||
buffer.push("<nav class='post-controls'>");
|
||||
|
|
|
@ -6,7 +6,7 @@ export default ButtonView.extend({
|
|||
helpKeyBinding: 'controller.starTooltipKey',
|
||||
attributeBindings: ['disabled'],
|
||||
|
||||
shouldRerender: Discourse.View.renderIfChanged('controller.starred'),
|
||||
rerenderTriggers: ['controller.starred'],
|
||||
|
||||
click: function() {
|
||||
this.get('controller').send('toggleStar');
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
/**
|
||||
This view is used for rendering the notification that a topic will
|
||||
automatically close.
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
@class TopicClosingView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Discourse.View.extend({
|
||||
export default Discourse.View.extend(StringBuffer, {
|
||||
elementId: 'topic-closing-info',
|
||||
delayedRerender: null,
|
||||
|
||||
shouldRerender: Discourse.View.renderIfChanged('topic.closed', 'topic.details.{auto_close_at,auto_close_based_on_last_post,auto_close_hours}'),
|
||||
rerenderTriggers: ['topic.closed',
|
||||
'topic.details.auto_close_at',
|
||||
'topic.details.auto_close_based_on_last_post',
|
||||
'topic.details.auto_close_hours'],
|
||||
|
||||
render: function(buffer) {
|
||||
renderString: function(buffer) {
|
||||
if (!this.present('topic.details.auto_close_at')) return;
|
||||
if (this.get("topic.closed")) return;
|
||||
|
||||
|
@ -36,8 +32,7 @@ export default Discourse.View.extend({
|
|||
}
|
||||
|
||||
var basedOnLastPost = this.get("topic.details.auto_close_based_on_last_post");
|
||||
var key = basedOnLastPost ? 'topic.auto_close_notice_based_on_last_post' : 'topic.auto_close_notice'
|
||||
|
||||
var key = basedOnLastPost ? 'topic.auto_close_notice_based_on_last_post' : 'topic.auto_close_notice';
|
||||
var autoCloseHours = this.get("topic.details.auto_close_hours") || 0;
|
||||
|
||||
buffer.push('<h3><i class="fa fa-clock-o"></i> ');
|
||||
|
|
|
@ -6,7 +6,10 @@ import DiscourseContainerView from 'discourse/views/container';
|
|||
|
||||
export default DiscourseContainerView.extend({
|
||||
classNameBindings: ['hidden', ':topic-map'],
|
||||
shouldRerender: Discourse.View.renderIfChanged('topic.posts_count'),
|
||||
|
||||
_postsChanged: function() {
|
||||
Ember.run.once(this, 'rerender');
|
||||
}.observes('topic.posts_count'),
|
||||
|
||||
hidden: function() {
|
||||
if (!this.get('post.firstPost')) return true;
|
||||
|
|
|
@ -40,16 +40,8 @@ Discourse.View.reopenClass({
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
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() {
|
||||
Em.warn("`rerenderIfChanged` is deprecated. Use the `StringBuffer` mixin with `rerenderTriggers` instead.");
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
args.unshift(function () {
|
||||
Ember.run.once(this, 'rerender');
|
||||
|
|
|
@ -30,47 +30,3 @@ test("registerHelper: enables embedding a child view in a parent view via dedica
|
|||
equal(parentView.$("#child").length, 1, "child view registered as helper is appended to the parent view");
|
||||
equal(parentView.$("#child").text(), "foo", "child view registered as helper gets parameters provided during helper invocation in parent's template");
|
||||
});
|
||||
|
||||
test("renderIfChanged: rerenders the whole view template when one of registered view fields changes", function() {
|
||||
var view, rerenderSpy;
|
||||
|
||||
var viewRerendersOnceWhen = function(message, changeCallback) {
|
||||
rerenderSpy.reset();
|
||||
Ember.run(function() { changeCallback(); });
|
||||
ok(rerenderSpy.calledOnce, "view rerenders when " + message);
|
||||
};
|
||||
|
||||
var viewDoesNotRerenderWhen = function(message, changeCallback) {
|
||||
rerenderSpy.reset();
|
||||
Ember.run(function() { changeCallback(); });
|
||||
ok(!rerenderSpy.called, "view does not rerender when " + message);
|
||||
};
|
||||
|
||||
|
||||
view = Ember.View.extend({
|
||||
shouldRerender: Discourse.View.renderIfChanged("simple", "complex.@each.nested")
|
||||
}).create({
|
||||
simple: "initial value",
|
||||
complex: [Ember.Object.create({nested: "initial value"})],
|
||||
unregistered: "initial value"
|
||||
});
|
||||
|
||||
rerenderSpy = sinon.spy(view, "rerender");
|
||||
|
||||
Ember.run(function() {
|
||||
view.appendTo("#qunit-fixture");
|
||||
});
|
||||
|
||||
|
||||
viewRerendersOnceWhen("a simple field (holding a string) changes", function() {
|
||||
view.set("simple", "updated value");
|
||||
});
|
||||
|
||||
viewRerendersOnceWhen("a nested sub-field of a complex field (holding an array of objects) changes", function() {
|
||||
view.get("complex").objectAt(0).set("nested", "updated value");
|
||||
});
|
||||
|
||||
viewDoesNotRerenderWhen("unregistered field changes", function() {
|
||||
view.set("unregistered", "updated value");
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue