From 3a2ae97668e80c5513e4b0872fff5c73f68f5fab Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 4 Dec 2013 13:23:00 -0500 Subject: [PATCH] Vendor the CloakedView stuff --- .../views/cloaked_collection_view.js | 164 ----------- .../discourse/views/cloaked_view.js | 77 ----- app/assets/javascripts/main_include.js | 1 + vendor/assets/javascripts/ember-cloaking.js | 273 ++++++++++++++++++ 4 files changed, 274 insertions(+), 241 deletions(-) delete mode 100644 app/assets/javascripts/discourse/views/cloaked_collection_view.js delete mode 100644 app/assets/javascripts/discourse/views/cloaked_view.js create mode 100644 vendor/assets/javascripts/ember-cloaking.js diff --git a/app/assets/javascripts/discourse/views/cloaked_collection_view.js b/app/assets/javascripts/discourse/views/cloaked_collection_view.js deleted file mode 100644 index 04eb6d39a..000000000 --- a/app/assets/javascripts/discourse/views/cloaked_collection_view.js +++ /dev/null @@ -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" + I18n.t('loading') + ""); - } - return; - } - } - - }, - - didInsertElement: function() { - this.bindScrolling({debounce: 10}); - }, - - willDestroyElement: function() { - this.unbindScrolling(); - } - -}); - - -Discourse.View.registerHelper('cloaked-collection', Discourse.CloakedCollectionView); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/views/cloaked_view.js b/app/assets/javascripts/discourse/views/cloaked_view.js deleted file mode 100644 index fd1f343f7..000000000 --- a/app/assets/javascripts/discourse/views/cloaked_view.js +++ /dev/null @@ -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(); - }); - } - } - -}); diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 537e1a6f0..e933c64c5 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -32,6 +32,7 @@ //= require rsvp.js //= require show-html.js //= require lock-on.js +//= require ember-cloaking //= require ./discourse/helpers/i18n_helpers //= require ./discourse/mixins/ajax diff --git a/vendor/assets/javascripts/ember-cloaking.js b/vendor/assets/javascripts/ember-cloaking.js new file mode 100644 index 000000000..5ac0f29d1 --- /dev/null +++ b/vendor/assets/javascripts/ember-cloaking.js @@ -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" + I18n.t('loading') + ""); + } + 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); + }); + +})();