mirror of
https://github.com/codeninjasllc/discourse.git
synced 2025-02-25 07:54:11 -05:00
Vendor the CloakedView stuff
This commit is contained in:
parent
3a6ba907d9
commit
3a2ae97668
4 changed files with 274 additions and 241 deletions
|
@ -1,164 +0,0 @@
|
||||||
/**
|
|
||||||
Display a list of cloaked items
|
|
||||||
|
|
||||||
@class CloakedContainerView
|
|
||||||
@extends Discourse.View
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
Discourse.CloakedCollectionView = Ember.CollectionView.extend(Discourse.Scrolling, {
|
|
||||||
topVisible: null,
|
|
||||||
bottomVisible: null,
|
|
||||||
|
|
||||||
init: function() {
|
|
||||||
var cloakView = this.get('cloakView'),
|
|
||||||
idProperty = this.get('idProperty') || 'id';
|
|
||||||
|
|
||||||
this.set('slackRatio', Discourse.Capabilities.currentProp('slackRatio'));
|
|
||||||
this.set('itemViewClass', Discourse.CloakedView.extend({
|
|
||||||
classNames: [cloakView + '-cloak'],
|
|
||||||
cloaks: Em.String.classify(cloakView) + 'View',
|
|
||||||
defaultHeight: this.get('defaultHeight') || 100,
|
|
||||||
|
|
||||||
init: function() {
|
|
||||||
this._super();
|
|
||||||
this.set('elementId', cloakView + '-cloak-' + this.get('content.' + idProperty));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._super();
|
|
||||||
Ember.run.next(this, 'scrolled');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
If the topmost visible view changed, we will notify the controller if it has an appropriate hook.
|
|
||||||
|
|
||||||
@method _topVisibleChanged
|
|
||||||
@observes topVisible
|
|
||||||
**/
|
|
||||||
_topVisibleChanged: function() {
|
|
||||||
var controller = this.get('controller');
|
|
||||||
if (controller.topVisibleChanged) { controller.topVisibleChanged(this.get('topVisible')); }
|
|
||||||
}.observes('topVisible'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
If the bottommost visible view changed, we will notify the controller if it has an appropriate hook.
|
|
||||||
|
|
||||||
@method _bottomVisible
|
|
||||||
@observes bottomVisible
|
|
||||||
**/
|
|
||||||
_bottomVisible: function() {
|
|
||||||
var controller = this.get('controller');
|
|
||||||
if (controller.bottomVisibleChanged) { controller.bottomVisibleChanged(this.get('bottomVisible')); }
|
|
||||||
}.observes('bottomVisible'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
Binary search for finding the topmost view on screen.
|
|
||||||
|
|
||||||
@method findTopView
|
|
||||||
@param {Array} childViews the childViews to search through
|
|
||||||
@param {Number} windowTop The top of the viewport to search against
|
|
||||||
@param {Number} min The minimum index to search through of the child views
|
|
||||||
@param {Number} max The max index to search through of the child views
|
|
||||||
@returns {Number} the index into childViews of the topmost view
|
|
||||||
**/
|
|
||||||
findTopView: function(childViews, viewportTop, min, max) {
|
|
||||||
if (max < min) { return min; }
|
|
||||||
|
|
||||||
var mid = Math.floor((min + max) / 2),
|
|
||||||
$view = childViews[mid].$(),
|
|
||||||
viewBottom = $view.offset().top + $view.height();
|
|
||||||
|
|
||||||
if (viewBottom > viewportTop) {
|
|
||||||
return this.findTopView(childViews, viewportTop, min, mid-1);
|
|
||||||
} else {
|
|
||||||
return this.findTopView(childViews, viewportTop, mid+1, max);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
Determine what views are onscreen and cloak/uncloak them as necessary.
|
|
||||||
|
|
||||||
@method scrolled
|
|
||||||
**/
|
|
||||||
scrolled: function() {
|
|
||||||
var childViews = this.get('childViews'),
|
|
||||||
toUncloak = [],
|
|
||||||
$w = $(window),
|
|
||||||
windowHeight = $w.height(),
|
|
||||||
windowTop = $w.scrollTop(),
|
|
||||||
slack = Math.round(windowHeight * this.get('slackRatio')),
|
|
||||||
viewportTop = windowTop - slack,
|
|
||||||
windowBottom = windowTop + windowHeight,
|
|
||||||
viewportBottom = windowBottom + slack,
|
|
||||||
topView = this.findTopView(childViews, viewportTop, 0, childViews.length-1),
|
|
||||||
bodyHeight = $('body').height(),
|
|
||||||
bottomView = topView,
|
|
||||||
onscreen = [];
|
|
||||||
|
|
||||||
if (windowBottom > bodyHeight) { windowBottom = bodyHeight; }
|
|
||||||
if (viewportBottom > bodyHeight) { viewportBottom = bodyHeight; }
|
|
||||||
|
|
||||||
// Find the bottom view and what's onscreen
|
|
||||||
while (bottomView < childViews.length) {
|
|
||||||
var view = childViews[bottomView],
|
|
||||||
$view = view.$(),
|
|
||||||
viewTop = $view.offset().top,
|
|
||||||
viewBottom = viewTop + $view.height();
|
|
||||||
|
|
||||||
if (viewTop > viewportBottom) { break; }
|
|
||||||
toUncloak.push(view);
|
|
||||||
|
|
||||||
if (viewBottom > windowTop && viewTop <= windowBottom) {
|
|
||||||
onscreen.push(view.get('content'));
|
|
||||||
}
|
|
||||||
|
|
||||||
bottomView++;
|
|
||||||
}
|
|
||||||
if (bottomView >= childViews.length) { bottomView = childViews.length - 1; }
|
|
||||||
|
|
||||||
// If our controller has a `sawObjects` method, pass the on screen objects to it.
|
|
||||||
var controller = this.get('controller');
|
|
||||||
if (onscreen.length) {
|
|
||||||
this.setProperties({topVisible: onscreen[0], bottomVisible: onscreen[onscreen.length-1]});
|
|
||||||
if (controller && controller.sawObjects) {
|
|
||||||
Em.run.schedule('afterRender', function() {
|
|
||||||
controller.sawObjects(onscreen);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.setProperties({topVisible: null, bottomVisible: null});
|
|
||||||
}
|
|
||||||
|
|
||||||
var toCloak = childViews.slice(0, topView).concat(childViews.slice(bottomView+1)),
|
|
||||||
loadingView = childViews[bottomView + 1];
|
|
||||||
|
|
||||||
Em.run.schedule('afterRender', function() {
|
|
||||||
toUncloak.forEach(function (v) { v.uncloak(); });
|
|
||||||
toCloak.forEach(function (v) { v.cloak(); });
|
|
||||||
});
|
|
||||||
|
|
||||||
for (var j=bottomView; j<childViews.length; j++) {
|
|
||||||
var checkView = childViews[j];
|
|
||||||
if (!checkView.get('containedView')) {
|
|
||||||
if (!checkView.get('loading')) {
|
|
||||||
checkView.$().html("<div class='spinner'>" + I18n.t('loading') + "</div>");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
didInsertElement: function() {
|
|
||||||
this.bindScrolling({debounce: 10});
|
|
||||||
},
|
|
||||||
|
|
||||||
willDestroyElement: function() {
|
|
||||||
this.unbindScrolling();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
Discourse.View.registerHelper('cloaked-collection', Discourse.CloakedCollectionView);
|
|
|
@ -1,77 +0,0 @@
|
||||||
/**
|
|
||||||
A cloaked view is one that removes its content when scrolled off the screen
|
|
||||||
|
|
||||||
@class CloakedView
|
|
||||||
@extends Discourse.View
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
Discourse.CloakedView = Discourse.View.extend({
|
|
||||||
attributeBindings: ['style'],
|
|
||||||
|
|
||||||
init: function() {
|
|
||||||
this._super();
|
|
||||||
this.uncloak();
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Triggers the set up for rendering a view that is cloaked.
|
|
||||||
|
|
||||||
@method uncloak
|
|
||||||
*/
|
|
||||||
uncloak: function() {
|
|
||||||
var containedView = this.get('containedView');
|
|
||||||
if (!containedView) {
|
|
||||||
this.setProperties({
|
|
||||||
style: null,
|
|
||||||
loading: false,
|
|
||||||
containedView: this.createChildView(Discourse[this.get('cloaks')], { content: this.get('content') })
|
|
||||||
});
|
|
||||||
|
|
||||||
this.rerender();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
Removes the view from the DOM and tears down all observers.
|
|
||||||
|
|
||||||
@method cloak
|
|
||||||
*/
|
|
||||||
cloak: function() {
|
|
||||||
var containedView = this.get('containedView'),
|
|
||||||
self = this;
|
|
||||||
|
|
||||||
if (containedView && this.get('state') === 'inDOM') {
|
|
||||||
var style = 'height: ' + this.$().height() + 'px;';
|
|
||||||
this.set('style', style);
|
|
||||||
this.$().prop('style', style);
|
|
||||||
|
|
||||||
// We need to remove the container after the height of the element has taken
|
|
||||||
// effect.
|
|
||||||
Ember.run.schedule('afterRender', function() {
|
|
||||||
self.set('containedView', null);
|
|
||||||
containedView.willDestroyElement();
|
|
||||||
containedView.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Render the cloaked view if applicable.
|
|
||||||
|
|
||||||
@method render
|
|
||||||
*/
|
|
||||||
render: function(buffer) {
|
|
||||||
var containedView = this.get('containedView');
|
|
||||||
if (containedView && containedView.get('state') !== 'inDOM') {
|
|
||||||
containedView.renderToBuffer(buffer);
|
|
||||||
containedView.transitionTo('inDOM');
|
|
||||||
Em.run.schedule('afterRender', function() {
|
|
||||||
containedView.didInsertElement();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
|
@ -32,6 +32,7 @@
|
||||||
//= require rsvp.js
|
//= require rsvp.js
|
||||||
//= require show-html.js
|
//= require show-html.js
|
||||||
//= require lock-on.js
|
//= require lock-on.js
|
||||||
|
//= require ember-cloaking
|
||||||
|
|
||||||
//= require ./discourse/helpers/i18n_helpers
|
//= require ./discourse/helpers/i18n_helpers
|
||||||
//= require ./discourse/mixins/ajax
|
//= require ./discourse/mixins/ajax
|
||||||
|
|
273
vendor/assets/javascripts/ember-cloaking.js
vendored
Normal file
273
vendor/assets/javascripts/ember-cloaking.js
vendored
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Display a list of cloaked items
|
||||||
|
|
||||||
|
@class CloakedContainerView
|
||||||
|
@extends Ember.View
|
||||||
|
@namespace Ember
|
||||||
|
**/
|
||||||
|
Ember.CloakedCollectionView = Ember.CollectionView.extend({
|
||||||
|
topVisible: null,
|
||||||
|
bottomVisible: null,
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
var cloakView = this.get('cloakView'),
|
||||||
|
idProperty = this.get('idProperty') || 'id';
|
||||||
|
|
||||||
|
// Set the slack ratio differently to allow for more or less slack in preloading
|
||||||
|
var slackRatio = parseFloat(this.get('slackRatio'));
|
||||||
|
if (!slackRatio) { this.set('slackRatio', 1.0); }
|
||||||
|
|
||||||
|
this.set('itemViewClass', Ember.CloakedView.extend({
|
||||||
|
classNames: [cloakView + '-cloak'],
|
||||||
|
cloaks: cloakView,
|
||||||
|
defaultHeight: this.get('defaultHeight') || 100,
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.set('elementId', cloakView + '-cloak-' + this.get('content.' + idProperty));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._super();
|
||||||
|
Ember.run.next(this, 'scrolled');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
If the topmost visible view changed, we will notify the controller if it has an appropriate hook.
|
||||||
|
|
||||||
|
@method _topVisibleChanged
|
||||||
|
@observes topVisible
|
||||||
|
**/
|
||||||
|
_topVisibleChanged: function() {
|
||||||
|
var controller = this.get('controller');
|
||||||
|
if (controller.topVisibleChanged) { controller.topVisibleChanged(this.get('topVisible')); }
|
||||||
|
}.observes('topVisible'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
If the bottommost visible view changed, we will notify the controller if it has an appropriate hook.
|
||||||
|
|
||||||
|
@method _bottomVisible
|
||||||
|
@observes bottomVisible
|
||||||
|
**/
|
||||||
|
_bottomVisible: function() {
|
||||||
|
var controller = this.get('controller');
|
||||||
|
if (controller.bottomVisibleChanged) { controller.bottomVisibleChanged(this.get('bottomVisible')); }
|
||||||
|
}.observes('bottomVisible'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
Binary search for finding the topmost view on screen.
|
||||||
|
|
||||||
|
@method findTopView
|
||||||
|
@param {Array} childViews the childViews to search through
|
||||||
|
@param {Number} windowTop The top of the viewport to search against
|
||||||
|
@param {Number} min The minimum index to search through of the child views
|
||||||
|
@param {Number} max The max index to search through of the child views
|
||||||
|
@returns {Number} the index into childViews of the topmost view
|
||||||
|
**/
|
||||||
|
findTopView: function(childViews, viewportTop, min, max) {
|
||||||
|
if (max < min) { return min; }
|
||||||
|
|
||||||
|
var mid = Math.floor((min + max) / 2),
|
||||||
|
$view = childViews[mid].$(),
|
||||||
|
viewBottom = $view.offset().top + $view.height();
|
||||||
|
|
||||||
|
if (viewBottom > viewportTop) {
|
||||||
|
return this.findTopView(childViews, viewportTop, min, mid-1);
|
||||||
|
} else {
|
||||||
|
return this.findTopView(childViews, viewportTop, mid+1, max);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Determine what views are onscreen and cloak/uncloak them as necessary.
|
||||||
|
|
||||||
|
@method scrolled
|
||||||
|
**/
|
||||||
|
scrolled: function() {
|
||||||
|
var childViews = this.get('childViews');
|
||||||
|
if (childViews.length === 0) { return; }
|
||||||
|
|
||||||
|
var toUncloak = [],
|
||||||
|
$w = $(window),
|
||||||
|
windowHeight = $w.height(),
|
||||||
|
windowTop = $w.scrollTop(),
|
||||||
|
slack = Math.round(windowHeight * this.get('slackRatio')),
|
||||||
|
viewportTop = windowTop - slack,
|
||||||
|
windowBottom = windowTop + windowHeight,
|
||||||
|
viewportBottom = windowBottom + slack,
|
||||||
|
topView = this.findTopView(childViews, viewportTop, 0, childViews.length-1),
|
||||||
|
bodyHeight = $('body').height(),
|
||||||
|
bottomView = topView,
|
||||||
|
onscreen = [];
|
||||||
|
|
||||||
|
if (windowBottom > bodyHeight) { windowBottom = bodyHeight; }
|
||||||
|
if (viewportBottom > bodyHeight) { viewportBottom = bodyHeight; }
|
||||||
|
|
||||||
|
// Find the bottom view and what's onscreen
|
||||||
|
while (bottomView < childViews.length) {
|
||||||
|
var view = childViews[bottomView],
|
||||||
|
$view = view.$(),
|
||||||
|
viewTop = $view.offset().top,
|
||||||
|
viewBottom = viewTop + $view.height();
|
||||||
|
|
||||||
|
if (viewTop > viewportBottom) { break; }
|
||||||
|
toUncloak.push(view);
|
||||||
|
|
||||||
|
if (viewBottom > windowTop && viewTop <= windowBottom) {
|
||||||
|
onscreen.push(view.get('content'));
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomView++;
|
||||||
|
}
|
||||||
|
if (bottomView >= childViews.length) { bottomView = childViews.length - 1; }
|
||||||
|
|
||||||
|
// If our controller has a `sawObjects` method, pass the on screen objects to it.
|
||||||
|
var controller = this.get('controller');
|
||||||
|
if (onscreen.length) {
|
||||||
|
this.setProperties({topVisible: onscreen[0], bottomVisible: onscreen[onscreen.length-1]});
|
||||||
|
if (controller && controller.sawObjects) {
|
||||||
|
Em.run.schedule('afterRender', function() {
|
||||||
|
controller.sawObjects(onscreen);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setProperties({topVisible: null, bottomVisible: null});
|
||||||
|
}
|
||||||
|
|
||||||
|
var toCloak = childViews.slice(0, topView).concat(childViews.slice(bottomView+1)),
|
||||||
|
loadingView = childViews[bottomView + 1];
|
||||||
|
|
||||||
|
Em.run.schedule('afterRender', function() {
|
||||||
|
toUncloak.forEach(function (v) { v.uncloak(); });
|
||||||
|
toCloak.forEach(function (v) { v.cloak(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
for (var j=bottomView; j<childViews.length; j++) {
|
||||||
|
var checkView = childViews[j];
|
||||||
|
if (!checkView.get('containedView')) {
|
||||||
|
if (!checkView.get('loading')) {
|
||||||
|
checkView.$().html("<div class='spinner'>" + I18n.t('loading') + "</div>");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollTriggered: function() {
|
||||||
|
Em.run.scheduleOnce('afterRender', this, 'scrolled');
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement: function() {
|
||||||
|
var self = this,
|
||||||
|
onScrollMethod = function() {
|
||||||
|
Ember.run.debounce(self, 'scrollTriggered', 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
$(document).bind('touchmove.ember-cloak', onScrollMethod);
|
||||||
|
$(window).bind('scroll.ember-cloak', onScrollMethod);
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroyElement: function() {
|
||||||
|
$(document).bind('touchmove.ember-cloak');
|
||||||
|
$(window).bind('scroll.ember-cloak');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
A cloaked view is one that removes its content when scrolled off the screen
|
||||||
|
|
||||||
|
@class CloakedView
|
||||||
|
@extends Ember.View
|
||||||
|
@namespace Ember
|
||||||
|
**/
|
||||||
|
Ember.CloakedView = Ember.View.extend({
|
||||||
|
attributeBindings: ['style'],
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.uncloak();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Triggers the set up for rendering a view that is cloaked.
|
||||||
|
|
||||||
|
@method uncloak
|
||||||
|
*/
|
||||||
|
uncloak: function() {
|
||||||
|
var containedView = this.get('containedView');
|
||||||
|
if (!containedView) {
|
||||||
|
|
||||||
|
this.setProperties({
|
||||||
|
style: null,
|
||||||
|
loading: false,
|
||||||
|
containedView: this.createChildView(this.get('cloaks'), {content: this.get('content') })
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rerender();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Removes the view from the DOM and tears down all observers.
|
||||||
|
|
||||||
|
@method cloak
|
||||||
|
*/
|
||||||
|
cloak: function() {
|
||||||
|
var containedView = this.get('containedView'),
|
||||||
|
self = this;
|
||||||
|
|
||||||
|
if (containedView && this.get('state') === 'inDOM') {
|
||||||
|
var style = 'height: ' + this.$().height() + 'px;';
|
||||||
|
this.set('style', style);
|
||||||
|
this.$().prop('style', style);
|
||||||
|
|
||||||
|
// We need to remove the container after the height of the element has taken
|
||||||
|
// effect.
|
||||||
|
Ember.run.schedule('afterRender', function() {
|
||||||
|
self.set('containedView', null);
|
||||||
|
containedView.willDestroyElement();
|
||||||
|
containedView.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Render the cloaked view if applicable.
|
||||||
|
|
||||||
|
@method render
|
||||||
|
*/
|
||||||
|
render: function(buffer) {
|
||||||
|
var containedView = this.get('containedView');
|
||||||
|
if (containedView && containedView.get('state') !== 'inDOM') {
|
||||||
|
containedView.renderToBuffer(buffer);
|
||||||
|
containedView.transitionTo('inDOM');
|
||||||
|
Em.run.schedule('afterRender', function() {
|
||||||
|
containedView.didInsertElement();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Ember.Handlebars.registerHelper('cloaked-collection', function(options) {
|
||||||
|
var hash = options.hash,
|
||||||
|
types = options.hashTypes;
|
||||||
|
|
||||||
|
for (var prop in hash) {
|
||||||
|
if (types[prop] === 'ID') {
|
||||||
|
hash[prop + 'Binding'] = hash[prop];
|
||||||
|
delete hash[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ember.Handlebars.helpers.view.call(this, Ember.CloakedCollectionView, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
Loading…
Reference in a new issue