(function () { /** Display a list of cloaked items @class CloakedCollectionView @extends Ember.CollectionView @namespace Ember **/ Ember.CloakedCollectionView = Ember.CollectionView.extend({ topVisible: null, bottomVisible: null, offsetFixedTopElement: null, offsetFixedBottomElement: null, init: function() { var cloakView = this.get('cloakView'), idProperty = this.get('idProperty'), uncloakDefault = !!this.get('uncloakDefault'); // 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, preservesContext: this.get('preservesContext') === "true", cloaksController: this.get('itemController'), defaultHeight: this.get('defaultHeight'), init: function() { this._super(); if (idProperty) { this.set('elementId', cloakView + '-cloak-' + this.get('content.' + idProperty)); } if (uncloakDefault) { this.uncloak(); } else { this.cloak(); } } })); 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), // in case of not full-window scrolling scrollOffset = this.get('wrapperTop') >> 0, $view = childViews[mid].$(), viewBottom = $view.position().top + scrollOffset + $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() { if (!this.get('scrollingEnabled')) { return; } var childViews = this.get('childViews'); if ((!childViews) || (childViews.length === 0)) { return; } var toUncloak = [], onscreen = [], // calculating viewport edges $w = $(window), windowHeight = this.get('wrapperHeight') || ( window.innerHeight ? window.innerHeight : $w.height() ), windowTop = this.get('wrapperTop') || $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 = this.get('wrapperHeight') ? this.$().height() : $('body').height(), bottomView = topView, offsetFixedTopElement = this.get('offsetFixedTopElement'), offsetFixedBottomElement = this.get('offsetFixedBottomElement'); if (windowBottom > bodyHeight) { windowBottom = bodyHeight; } if (viewportBottom > bodyHeight) { viewportBottom = bodyHeight; } if (offsetFixedTopElement) { windowTop += (offsetFixedTopElement.outerHeight(true) || 0); } if (offsetFixedBottomElement) { windowBottom -= (offsetFixedBottomElement.outerHeight(true) || 0); } // Find the bottom view and what's onscreen while (bottomView < childViews.length) { var view = childViews[bottomView], $view = view.$(), // in case of not full-window scrolling scrollOffset = this.get('wrapperTop') || 0, viewTop = $view.offset().top + scrollOffset, 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)); Em.run.schedule('afterRender', function() { toUncloak.forEach(function (v) { v.uncloak(); }); toCloak.forEach(function (v) { v.cloak(); }); }); for (var j=bottomView; j