From: Bill Erickson Date: Mon, 31 Mar 2014 14:57:53 +0000 (-0400) Subject: web staff : initial infini scroll grid support X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=aa64073b3acbdcff790be9d7c41f068d7e5d85ae;p=working%2FEvergreen.git web staff : initial infini scroll grid support Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/templates/staff/parts/t_autogrid.tt2 b/Open-ILS/src/templates/staff/parts/t_autogrid.tt2 index 6a0f57b73e..d3b732fcc1 100644 --- a/Open-ILS/src/templates/staff/parts/t_autogrid.tt2 +++ b/Open-ILS/src/templates/staff/parts/t_autogrid.tt2 @@ -79,7 +79,7 @@
{{column.label}} @@ -97,7 +97,7 @@
-
-
+ --> +
+ +
@@ -139,7 +145,7 @@
{{fieldValue(item, column.name) | egGridvalueFilter:column}}
diff --git a/Open-ILS/src/templates/staff/test/index.tt2 b/Open-ILS/src/templates/staff/test/index.tt2 index 0f360bdb1c..3ecd3f14c0 100644 --- a/Open-ILS/src/templates/staff/test/index.tt2 +++ b/Open-ILS/src/templates/staff/test/index.tt2 @@ -9,6 +9,8 @@ + + [% END %] diff --git a/Open-ILS/web/js/ui/default/staff/services/grid.js b/Open-ILS/web/js/ui/default/staff/services/grid.js index 118a0a6b16..587532df5f 100644 --- a/Open-ILS/web/js/ui/default/staff/services/grid.js +++ b/Open-ILS/web/js/ui/default/staff/services/grid.js @@ -1,7 +1,8 @@ -angular.module('egGridMod', ['egCoreMod', 'egListMod', 'egUiMod', 'ui.bootstrap']) +angular.module('egGridMod', + ['egCoreMod', 'egListMod', 'egUiMod', 'ui.bootstrap', 'ui.scroll.jqlite', 'ui.scroll']) -.directive('egGrid', function() { +.directive('egGrid', function($window) { return { restrict : 'AE', transclude : true, @@ -48,19 +49,20 @@ angular.module('egGridMod', ['egCoreMod', 'egListMod', 'egUiMod', 'ui.bootstrap' // link() is called after page compilation, which means our // eg-grid-field's have been parsed and loaded. Now it's // safe to perform our initial page load. - scope.fetchData(); + //scope.fetchData(); }, templateUrl : '/eg/staff/parts/t_autogrid', // TODO: avoid abs url controller : // TODO: reqs list - function($scope, $timeout, $modal, $document, $window, egIDL, egAuth, egNet, egList) { + function($scope, $timeout, $element, egIDL, egAuth, egNet, egList, egGridData) { var self = this; + self.egGridData = egGridData; // setup function. called at the end of the controller this.init = function() { - self.limit = 25; - self.ofset = 0; + self.limit = 10; + self.offset = 0; $scope.indexFlex = 1; $scope.selectorFlex = 1; @@ -82,6 +84,12 @@ angular.module('egGridMod', ['egCoreMod', 'egListMod', 'egUiMod', 'ui.bootstrap' self.compileAutoFields(); $scope.list.indexField = $scope.idField; + + self.egGridData.configure({ + idlClass : $scope.idlClass, + query : $scope.query, + list : $scope.list + }); } // column-header click quick sort @@ -94,7 +102,9 @@ angular.module('egGridMod', ['egCoreMod', 'egListMod', 'egUiMod', 'ui.bootstrap' } else { $scope.sort = [col_name]; } - $scope.fetchData(); + egGridData.sort = $scope.sort; + egGridData.reset(); + //$scope.fetchData(); } // maps numeric sort priority to flattener sort blob @@ -299,8 +309,7 @@ angular.module('egGridMod', ['egCoreMod', 'egListMod', 'egUiMod', 'ui.bootstrap' // handled externally. if ($scope.egList) return; - $scope.list.resetPageData(); - + //$scope.list.resetPageData(); var queryFields = {} angular.forEach($scope.list.allColumns, function(field) { @@ -393,6 +402,12 @@ angular.module('egGridMod', ['egCoreMod', 'egListMod', 'egUiMod', 'ui.bootstrap' $scope.$apply(); // needed } + $scope.fetchMoreData = function() { + console.log('fetchMoreData'); + self.offset += self.limit; + $scope.fetchData(); + } + this.init(); } }; @@ -421,6 +436,63 @@ angular.module('egGridMod', ['egCoreMod', 'egListMod', 'egUiMod', 'ui.bootstrap' }; }) +.factory('egGridData', ['egNet','egAuth', + + function(egNet, egAuth) { + var service = {}; + + service.configure = function(params) { + console.log('configure'); + service.idlClass = params.idlClass; + service.query = params.query; + service.list = params.list; + } + + service.reset = function() { + service.list.resetPageData(); + service._rev = Math.random(); + } + + service.revision = function() { + return service._rev; + } + + service.get = function(index, count, success) { + var queryFields = {} + console.log('service.get()'); + index -= 1; // we like zero-based + if (index < 0) return success([]); // hrm?? + + angular.forEach(service.list.allColumns, function(field) { + if (service.list.displayColumns[field.name]) + queryFields[field.name] = field.path || field.name; + }); + + egNet.request( + 'open-ils.fielder', + 'open-ils.fielder.flattened_search', + egAuth.token(), service.idlClass, queryFields, + service.query, + { sort : service.sort, + limit : count, + offset : index + } + ).then( + function() { // oncomplete + console.log(index + ' : ' + count); + success(service.list.items.slice(index, index + count)); + }, + null, // onerror + function(item) { // onmessage + service.list.items.push(item); + } + ); + } + + return service; + } +]) + /** Simplified dnd directives for grid column controls. * Extract these out if the can be made generic enough */ @@ -445,11 +517,11 @@ angular.module('egGridMod', ['egCoreMod', 'egListMod', 'egUiMod', 'ui.bootstrap' require : '^egGrid', link : function(scope, element, attrs, egGridCtrl) { element.bind('dragover', function(e) { - console.log('dragover'); e.stopPropagation(); e.preventDefault(); //e.dataTransfer.dropEffect = 'copy'; var col = angular.element(e.target).attr('column'); + console.log('dragover ' + col); egGridCtrl.onColumnDragOver(col); }); } diff --git a/Open-ILS/web/js/ui/default/staff/services/list.js b/Open-ILS/web/js/ui/default/staff/services/list.js index b3ce709b01..757dc2f99e 100644 --- a/Open-ILS/web/js/ui/default/staff/services/list.js +++ b/Open-ILS/web/js/ui/default/staff/services/list.js @@ -69,6 +69,14 @@ angular.module('egListMod', ['egCoreMod']) return item; } + // 0-based column position + this.columnPosition = function(name) { + for (var i = 0; i < this.allColumns.length; i++) { + if (this.allColumns[i].name == name) + return i; + } + } + // returns true if item1 appears in the list before item2; // false otherwise. this is slightly more efficient that // finding the position of each then comparing them. @@ -86,6 +94,7 @@ angular.module('egListMod', ['egCoreMod']) return false; } + // 0-based position of item in the current data set this.indexOf = function(item) { var idx = this.indexValue(item); for (var i = 0; i < this.items.length; i++) { diff --git a/Open-ILS/web/js/ui/default/staff/services/ui-scroll-jqlite.js b/Open-ILS/web/js/ui/default/staff/services/ui-scroll-jqlite.js new file mode 100644 index 0000000000..29bc512031 --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/services/ui-scroll-jqlite.js @@ -0,0 +1,221 @@ +'use strict'; + +angular.module('ui.scroll.jqlite', ['ui.scroll']).service('jqLiteExtras', [ + '$log', '$window', function(console, window) { + return { + registerFor: function(element) { + var convertToPx, css, getMeasurements, getStyle, getWidthHeight, isWindow, scrollTo; + css = angular.element.prototype.css; + element.prototype.css = function(name, value) { + var elem, self; + self = this; + elem = self[0]; + if (!(!elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style)) { + return css.call(self, name, value); + } + }; + isWindow = function(obj) { + return obj && obj.document && obj.location && obj.alert && obj.setInterval; + }; + scrollTo = function(self, direction, value) { + var elem, method, preserve, prop, _ref; + elem = self[0]; + _ref = { + top: ['scrollTop', 'pageYOffset', 'scrollLeft'], + left: ['scrollLeft', 'pageXOffset', 'scrollTop'] + }[direction], method = _ref[0], prop = _ref[1], preserve = _ref[2]; + if (isWindow(elem)) { + if (angular.isDefined(value)) { + return elem.scrollTo(self[preserve].call(self), value); + } else { + if (prop in elem) { + return elem[prop]; + } else { + return elem.document.documentElement[method]; + } + } + } else { + if (angular.isDefined(value)) { + return elem[method] = value; + } else { + return elem[method]; + } + } + }; + if (window.getComputedStyle) { + getStyle = function(elem) { + return window.getComputedStyle(elem, null); + }; + convertToPx = function(elem, value) { + return parseFloat(value); + }; + } else { + getStyle = function(elem) { + return elem.currentStyle; + }; + convertToPx = function(elem, value) { + var core_pnum, left, result, rnumnonpx, rs, rsLeft, style; + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source; + rnumnonpx = new RegExp('^(' + core_pnum + ')(?!px)[a-z%]+$', 'i'); + if (!rnumnonpx.test(value)) { + return parseFloat(value); + } else { + style = elem.style; + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + if (rs) { + rs.left = style.left; + } + style.left = value; + result = style.pixelLeft; + style.left = left; + if (rsLeft) { + rs.left = rsLeft; + } + return result; + } + }; + } + getMeasurements = function(elem, measure) { + var base, borderA, borderB, computedMarginA, computedMarginB, computedStyle, dirA, dirB, marginA, marginB, paddingA, paddingB, _ref; + if (isWindow(elem)) { + base = document.documentElement[{ + height: 'clientHeight', + width: 'clientWidth' + }[measure]]; + return { + base: base, + padding: 0, + border: 0, + margin: 0 + }; + } + _ref = { + width: [elem.offsetWidth, 'Left', 'Right'], + height: [elem.offsetHeight, 'Top', 'Bottom'] + }[measure], base = _ref[0], dirA = _ref[1], dirB = _ref[2]; + computedStyle = getStyle(elem); + paddingA = convertToPx(elem, computedStyle['padding' + dirA]) || 0; + paddingB = convertToPx(elem, computedStyle['padding' + dirB]) || 0; + borderA = convertToPx(elem, computedStyle['border' + dirA + 'Width']) || 0; + borderB = convertToPx(elem, computedStyle['border' + dirB + 'Width']) || 0; + computedMarginA = computedStyle['margin' + dirA]; + computedMarginB = computedStyle['margin' + dirB]; + marginA = convertToPx(elem, computedMarginA) || 0; + marginB = convertToPx(elem, computedMarginB) || 0; + return { + base: base, + padding: paddingA + paddingB, + border: borderA + borderB, + margin: marginA + marginB + }; + }; + getWidthHeight = function(elem, direction, measure) { + var computedStyle, measurements, result; + measurements = getMeasurements(elem, direction); + if (measurements.base > 0) { + return { + base: measurements.base - measurements.padding - measurements.border, + outer: measurements.base, + outerfull: measurements.base + measurements.margin + }[measure]; + } else { + computedStyle = getStyle(elem); + result = computedStyle[direction]; + if (result < 0 || result === null) { + result = elem.style[direction] || 0; + } + result = parseFloat(result) || 0; + return { + base: result - measurements.padding - measurements.border, + outer: result, + outerfull: result + measurements.padding + measurements.border + measurements.margin + }[measure]; + } + }; + return angular.forEach({ + before: function(newElem) { + var children, elem, i, parent, self, _i, _ref; + self = this; + elem = self[0]; + parent = self.parent(); + children = parent.contents(); + if (children[0] === elem) { + return parent.prepend(newElem); + } else { + for (i = _i = 1, _ref = children.length - 1; 1 <= _ref ? _i <= _ref : _i >= _ref; i = 1 <= _ref ? ++_i : --_i) { + if (children[i] === elem) { + angular.element(children[i - 1]).after(newElem); + return; + } + } + throw new Error('invalid DOM structure ' + elem.outerHTML); + } + }, + height: function(value) { + var self; + self = this; + if (angular.isDefined(value)) { + if (angular.isNumber(value)) { + value = value + 'px'; + } + return css.call(self, 'height', value); + } else { + return getWidthHeight(this[0], 'height', 'base'); + } + }, + outerHeight: function(option) { + return getWidthHeight(this[0], 'height', option ? 'outerfull' : 'outer'); + }, + offset: function(value) { + var box, doc, docElem, elem, self, win; + self = this; + if (arguments.length) { + if (value === void 0) { + return self; + } else { + return value; + + } + } + box = { + top: 0, + left: 0 + }; + elem = self[0]; + doc = elem && elem.ownerDocument; + if (!doc) { + return; + } + docElem = doc.documentElement; + if (elem.getBoundingClientRect) { + box = elem.getBoundingClientRect(); + } + win = doc.defaultView || doc.parentWindow; + return { + top: box.top + (win.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0), + left: box.left + (win.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0) + }; + }, + scrollTop: function(value) { + return scrollTo(this, 'top', value); + }, + scrollLeft: function(value) { + return scrollTo(this, 'left', value); + } + }, function(value, key) { + if (!element.prototype[key]) { + return element.prototype[key] = value; + } + }); + } + }; + } +]).run([ + '$log', '$window', 'jqLiteExtras', function(console, window, jqLiteExtras) { + if (!window.jQuery) { + return jqLiteExtras.registerFor(angular.element); + } + } +]); diff --git a/Open-ILS/web/js/ui/default/staff/services/ui-scroll.js b/Open-ILS/web/js/ui/default/staff/services/ui-scroll.js new file mode 100644 index 0000000000..a52430ffeb --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/services/ui-scroll.js @@ -0,0 +1,473 @@ +'use strict'; +/* + + List of used element methods available in JQuery but not in JQuery Lite + + element.before(elem) + element.height() + element.outerHeight(true) + element.height(value) = only for Top/Bottom padding elements + element.scrollTop() + element.scrollTop(value) + */ + +angular.module('ui.scroll', []).directive('ngScrollViewport', [ + '$log', function() { + return { + controller: [ + '$scope', '$element', function(scope, element) { + return element; + } + ] + }; + } + ]).directive('ngScroll', [ + '$log', '$injector', '$rootScope', '$timeout', function(console, $injector, $rootScope, $timeout) { + return { + require: ['?^ngScrollViewport'], + transclude: 'element', + priority: 1000, + terminal: true, + compile: function(elementTemplate, attr, linker) { + return function($scope, element, $attr, controllers) { + var adapter, adjustBuffer, adjustRowHeight, bof, bottomVisiblePos, buffer, bufferPadding, bufferSize, clipBottom, clipTop, datasource, datasourceName, enqueueFetch, eof, eventListener, fetch, finalize, first, insert, isDatasource, isLoading, itemName, loading, match, next, pending, reload, removeFromBuffer, resizeHandler, scrollHandler, scrollHeight, shouldLoadBottom, shouldLoadTop, tempScope, topVisiblePos, viewport; + match = $attr.ngScroll.match(/^\s*(\w+)\s+in\s+(\w+)\s*$/); + if (!match) { + throw new Error('Expected ngScroll in form of "item_ in _datasource_" but got "' + $attr.ngScroll + '"'); + } + itemName = match[1]; + datasourceName = match[2]; + isDatasource = function(datasource) { + return angular.isObject(datasource) && datasource.get && angular.isFunction(datasource.get); + }; + datasource = $scope[datasourceName]; + if (!isDatasource(datasource)) { + datasource = $injector.get(datasourceName); + if (!isDatasource(datasource)) { + throw new Error(datasourceName + ' is not a valid datasource'); + } + } + bufferSize = Math.max(3, +$attr.bufferSize || 10); + bufferPadding = function() { + return viewport.height() * Math.max(0.1, +$attr.padding || 0.1); + }; + scrollHeight = function(elem) { + console.log(elem[0].scrollHeight, elem[0].document); + if( !elem[0].scrollHeight && !elem[0].document ) { + throw new Error('Could not determine scrollHeight of your viewport; make sure it has a constrained height (not height:auto)'); + } + return elem[0].scrollHeight || elem[0].document.documentElement.scrollHeight; + }; + adapter = null; + linker(tempScope = $scope.$new(), function(template) { + var bottomPadding, createPadding, padding, repeaterType, topPadding, viewport; + repeaterType = template[0].localName; + if (repeaterType === 'dl') { + throw new Error('ng-scroll directive does not support <' + template[0].localName + '> as a repeating tag: ' + template[0].outerHTML); + } + if (repeaterType !== 'li' && repeaterType !== 'tr') { + repeaterType = 'div'; + } + viewport = controllers[0] || angular.element(window); + viewport.css({ + 'overflow-y': 'auto', + 'display': 'block' + }); + padding = function(repeaterType) { + var div, result, table; + switch (repeaterType) { + case 'tr': + table = angular.element('
'); + div = table.find('div'); + result = table.find('tr'); + result.paddingHeight = function() { + return div.height.apply(div, arguments); + }; + return result; + default: + result = angular.element('<' + repeaterType + '>'); + result.paddingHeight = result.height; + return result; + } + }; + createPadding = function(padding, element, direction) { + element[{ + top: 'before', + bottom: 'after' + }[direction]](padding); + return { + paddingHeight: function() { + return padding.paddingHeight.apply(padding, arguments); + }, + insert: function(element) { + return padding[{ + top: 'after', + bottom: 'before' + }[direction]](element); + } + }; + }; + topPadding = createPadding(padding(repeaterType), element, 'top'); + bottomPadding = createPadding(padding(repeaterType), element, 'bottom'); + tempScope.$destroy(); + return adapter = { + viewport: viewport, + topPadding: topPadding.paddingHeight, + bottomPadding: bottomPadding.paddingHeight, + append: bottomPadding.insert, + prepend: topPadding.insert, + bottomDataPos: function() { + return scrollHeight(viewport) - bottomPadding.paddingHeight(); + }, + topDataPos: function() { + return topPadding.paddingHeight(); + } + }; + }); + viewport = adapter.viewport; + first = 1; + next = 1; + buffer = []; + pending = []; + eof = false; + bof = false; + loading = datasource.loading || function() {}; + isLoading = false; + removeFromBuffer = function(start, stop) { + var i, _i; + for (i = _i = start; start <= stop ? _i < stop : _i > stop; i = start <= stop ? ++_i : --_i) { + buffer[i].scope.$destroy(); + buffer[i].element.remove(); + } + return buffer.splice(start, stop - start); + }; + reload = function() { + first = 1; + next = 1; + removeFromBuffer(0, buffer.length); + adapter.topPadding(0); + adapter.bottomPadding(0); + pending = []; + eof = false; + bof = false; + return adjustBuffer(false); + }; + bottomVisiblePos = function() { + return viewport.scrollTop() + viewport.height(); + }; + topVisiblePos = function() { + return viewport.scrollTop(); + }; + shouldLoadBottom = function() { + return !eof && adapter.bottomDataPos() < bottomVisiblePos() + bufferPadding(); + }; + clipBottom = function() { + var bottomHeight, i, itemHeight, overage, _i, _ref; + bottomHeight = 0; + overage = 0; + for (i = _i = _ref = buffer.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) { + itemHeight = buffer[i].element.outerHeight(true); + if (adapter.bottomDataPos() - bottomHeight - itemHeight > bottomVisiblePos() + bufferPadding()) { + bottomHeight += itemHeight; + overage++; + eof = false; + } else { + break; + } + } + if (overage > 0) { + adapter.bottomPadding(adapter.bottomPadding() + bottomHeight); + removeFromBuffer(buffer.length - overage, buffer.length); + next -= overage; + return console.log('clipped off bottom ' + overage + ' bottom padding ' + (adapter.bottomPadding())); + } + }; + shouldLoadTop = function() { + return !bof && (adapter.topDataPos() > topVisiblePos() - bufferPadding()); + }; + clipTop = function() { + var item, itemHeight, overage, topHeight, _i, _len; + topHeight = 0; + overage = 0; + for (_i = 0, _len = buffer.length; _i < _len; _i++) { + item = buffer[_i]; + itemHeight = item.element.outerHeight(true); + if (adapter.topDataPos() + topHeight + itemHeight < topVisiblePos() - bufferPadding()) { + topHeight += itemHeight; + overage++; + bof = false; + } else { + break; + } + } + if (overage > 0) { + adapter.topPadding(adapter.topPadding() + topHeight); + removeFromBuffer(0, overage); + first += overage; + return console.log('clipped off top ' + overage + ' top padding ' + (adapter.topPadding())); + } + }; + enqueueFetch = function(direction, scrolling) { + if (!isLoading) { + isLoading = true; + loading(true); + } + if (pending.push(direction) === 1) { + return fetch(scrolling); + } + }; + insert = function(index, item) { + var itemScope, toBeAppended, wrapper; + itemScope = $scope.$new(); + itemScope[itemName] = item; + toBeAppended = index > first; + itemScope.$index = index; + if (toBeAppended) { + itemScope.$index--; + } + wrapper = { + scope: itemScope + }; + linker(itemScope, function(clone) { + wrapper.element = clone; + if (toBeAppended) { + if (index === next) { + adapter.append(clone); + return buffer.push(wrapper); + } else { + buffer[index - first].element.after(clone); + return buffer.splice(index - first + 1, 0, wrapper); + } + } else { + adapter.prepend(clone); + return buffer.unshift(wrapper); + } + }); + return { + appended: toBeAppended, + wrapper: wrapper + }; + }; + adjustRowHeight = function(appended, wrapper) { + var newHeight; + if (appended) { + return adapter.bottomPadding(Math.max(0, adapter.bottomPadding() - wrapper.element.outerHeight(true))); + } else { + newHeight = adapter.topPadding() - wrapper.element.outerHeight(true); + if (newHeight >= 0) { + return adapter.topPadding(newHeight); + } else { + return viewport.scrollTop(viewport.scrollTop() + wrapper.element.outerHeight(true)); + } + } + }; + adjustBuffer = function(scrolling, newItems, finalize) { + var doAdjustment; + doAdjustment = function() { + console.log('top {actual=' + (adapter.topDataPos()) + ' visible from=' + (topVisiblePos()) + ' bottom {visible through=' + (bottomVisiblePos()) + ' actual=' + (adapter.bottomDataPos()) + '}'); + if (shouldLoadBottom()) { + enqueueFetch(true, scrolling); + } else { + if (shouldLoadTop()) { + enqueueFetch(false, scrolling); + } + } + if (finalize) { + return finalize(); + } + }; + if (newItems) { + return $timeout(function() { + var row, _i, _len; + for (_i = 0, _len = newItems.length; _i < _len; _i++) { + row = newItems[_i]; + adjustRowHeight(row.appended, row.wrapper); + } + return doAdjustment(); + }); + } else { + return doAdjustment(); + } + }; + finalize = function(scrolling, newItems) { + return adjustBuffer(scrolling, newItems, function() { + pending.shift(); + if (pending.length === 0) { + isLoading = false; + return loading(false); + } else { + return fetch(scrolling); + } + }); + }; + fetch = function(scrolling) { + var direction; + direction = pending[0]; + if (direction) { + if (buffer.length && !shouldLoadBottom()) { + return finalize(scrolling); + } else { + return datasource.get(next, bufferSize, function(result) { + var item, newItems, _i, _len; + newItems = []; + if (result.length === 0) { + eof = true; + adapter.bottomPadding(0); + console.log('appended: requested ' + bufferSize + ' records starting from ' + next + ' recieved: eof'); + } else { + clipTop(); + for (_i = 0, _len = result.length; _i < _len; _i++) { + item = result[_i]; + newItems.push(insert(++next, item)); + } + console.log('appended: requested ' + bufferSize + ' received ' + result.length + ' buffer size ' + buffer.length + ' first ' + first + ' next ' + next); + } + return finalize(scrolling, newItems); + }); + } + } else { + if (buffer.length && !shouldLoadTop()) { + return finalize(scrolling); + } else { + return datasource.get(first - bufferSize, bufferSize, function(result) { + var i, newItems, _i, _ref; + newItems = []; + if (result.length === 0) { + bof = true; + adapter.topPadding(0); + console.log('prepended: requested ' + bufferSize + ' records starting from ' + (first - bufferSize) + ' recieved: bof'); + } else { + clipBottom(); + for (i = _i = _ref = result.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) { + newItems.unshift(insert(--first, result[i])); + } + console.log('prepended: requested ' + bufferSize + ' received ' + result.length + ' buffer size ' + buffer.length + ' first ' + first + ' next ' + next); + } + return finalize(scrolling, newItems); + }); + } + } + }; + resizeHandler = function() { + if (!$rootScope.$$phase && !isLoading) { + adjustBuffer(false); + return $scope.$apply(); + } + }; + viewport.bind('resize', resizeHandler); + scrollHandler = function() { + if (!$rootScope.$$phase && !isLoading) { + adjustBuffer(true); + return $scope.$apply(); + } + }; + viewport.bind('scroll', scrollHandler); + $scope.$watch(datasource.revision, function() { + return reload(); + }); + if (datasource.scope) { + eventListener = datasource.scope.$new(); + } else { + eventListener = $scope.$new(); + } + $scope.$on('$destroy', function() { + eventListener.$destroy(); + viewport.unbind('resize', resizeHandler); + return viewport.unbind('scroll', scrollHandler); + }); + eventListener.$on('update.items', function(event, locator, newItem) { + var wrapper, _fn, _i, _len, _ref; + if (angular.isFunction(locator)) { + _fn = function(wrapper) { + return locator(wrapper.scope); + }; + for (_i = 0, _len = buffer.length; _i < _len; _i++) { + wrapper = buffer[_i]; + _fn(wrapper); + } + } else { + if ((0 <= (_ref = locator - first - 1) && _ref < buffer.length)) { + buffer[locator - first - 1].scope[itemName] = newItem; + } + } + return null; + }); + eventListener.$on('delete.items', function(event, locator) { + var i, item, temp, wrapper, _fn, _i, _j, _k, _len, _len1, _len2, _ref; + if (angular.isFunction(locator)) { + temp = []; + for (_i = 0, _len = buffer.length; _i < _len; _i++) { + item = buffer[_i]; + temp.unshift(item); + } + _fn = function(wrapper) { + if (locator(wrapper.scope)) { + removeFromBuffer(temp.length - 1 - i, temp.length - i); + return next--; + } + }; + for (i = _j = 0, _len1 = temp.length; _j < _len1; i = ++_j) { + wrapper = temp[i]; + _fn(wrapper); + } + } else { + if ((0 <= (_ref = locator - first - 1) && _ref < buffer.length)) { + removeFromBuffer(locator - first - 1, locator - first); + next--; + } + } + for (i = _k = 0, _len2 = buffer.length; _k < _len2; i = ++_k) { + item = buffer[i]; + item.scope.$index = first + i; + } + return adjustBuffer(false); + }); + return eventListener.$on('insert.item', function(event, locator, item) { + var i, inserted, temp, wrapper, _fn, _i, _j, _k, _len, _len1, _len2, _ref; + inserted = []; + if (angular.isFunction(locator)) { + temp = []; + for (_i = 0, _len = buffer.length; _i < _len; _i++) { + item = buffer[_i]; + temp.unshift(item); + } + _fn = function(wrapper) { + var j, newItems, _k, _len2, _results; + if (newItems = locator(wrapper.scope)) { + insert = function(index, newItem) { + insert(index, newItem); + return next++; + }; + if (angular.isArray(newItems)) { + _results = []; + for (j = _k = 0, _len2 = newItems.length; _k < _len2; j = ++_k) { + item = newItems[j]; + _results.push(inserted.push(insert(i + j, item))); + } + return _results; + } else { + return inserted.push(insert(i, newItems)); + } + } + }; + for (i = _j = 0, _len1 = temp.length; _j < _len1; i = ++_j) { + wrapper = temp[i]; + _fn(wrapper); + } + } else { + if ((0 <= (_ref = locator - first - 1) && _ref < buffer.length)) { + inserted.push(insert(locator, item)); + next++; + } + } + for (i = _k = 0, _len2 = buffer.length; _k < _len2; i = ++_k) { + item = buffer[i]; + item.scope.$index = first + i; + } + return adjustBuffer(false, inserted); + }); + }; + } + }; + } + ]);