1WS.Namespace.Create('NovAxis.VirtualCollection', (function () { 2 var PIXEL = "px"; 3 var PERCENT = "%"; 4 5 // Constructor 6 var _class = function (htmlElement, options) { 7 if (!htmlElement) 8 throw _error('First argument type must be HTMLElement'); 9 if (typeof options !== 'object') 10 throw _error('Options argument is missing or invalid. Received ' + props); 11 if (typeof options.cellRenderer !== 'function') 12 throw _error('Option "cellRenderer" must be a function.'); 13 14 this._scrollUpdateDeltaTime = 10; 15 this._lastUpdateTime = 0; 16 this._renderedCellCache = []; 17 this._renderedEmptyCell = null; 18 this._layout = { 19 cellHeight: 0, 20 cellWidth: 0, 21 width: 0, 22 height: 0, 23 cellWidthPercent: 0 24 }; 25 26 this.container = $(htmlElement); 27 this.scrollable = $('<div class="scrollable"></div>'); 28 this.options = { 29 cellRenderer: null, 30 noCellRenderer: null, 31 cellCount: 0, 32 useCache: true, 33 scrollablePadding: 0, 34 rowOverload: 1 35 }; 36 37 Init.call(this, options); 38 }; 39 40 // Private methods 41 42 var Init = function (options) { 43 var me = this; 44 45 var container = this.container; 46 47 container.append(this.scrollable); 48 container.on('scroll', OnContainerScroll.bind(this)); 49 50 timeout = false; 51 delay = 300; 52 $(window).on("resize orientationchange", function () { 53 clearTimeout(timeout); 54 timeout = setTimeout(OnWindowResize.bind(me), delay); 55 }); 56 57 this.UpdateOptions(options); 58 }; 59 60 /** 61 * Renders a specified cell index using the current visible row and column index. 62 */ 63 var RenderCell = function (index, rowIndex, colIndex, noCache) { 64 var opt = this.options; 65 var style = GetCellPositionStyle.call(this, index, rowIndex, colIndex); 66 67 if (index < 0) { 68 cell = CreateCell(index, style, opt.noCellRenderer); 69 this._renderedEmptyCell = cell; 70 return cell; 71 } else { 72 var cell = noCache ? null : this._renderedCellCache[index]; 73 if (!cell) { 74 cell = CreateCell(index, style, opt.cellRenderer); 75 if (!noCache) { 76 this._renderedCellCache[index] = cell; 77 } 78 } 79 return cell; 80 } 81 }; 82 83 var GetCellPositionStyle = function (index, rowIndex, colIndex) { 84 var layout = this._layout; 85 var cellWidthPercent = layout.cellWidthPercent; 86 var cellHeight = layout.cellHeight; 87 return { 88 position: 'absolute', 89 top: (rowIndex * cellHeight) + PIXEL, 90 left: (colIndex * cellWidthPercent) + PERCENT 91 }; 92 }; 93 94 /** 95 * Creates a new cell element with required style. 96 */ 97 var CreateCell = function (index, style, cellRenderer) { 98 var cell = $('<div></div>'); 99 UpdateStyle(cell, style); 100 return cellRenderer(index, cell, style); 101 }; 102 103 /** 104 * Removes all "out-of-range" elements from DOM. 105 */ 106 var DetachElements = function (indexStart, indexEnd) { 107 var scrollable = this.scrollable; 108 var cells = this._renderedCellCache; 109 var length = cells.length; 110 for (var i = 0; i < length; i++) { 111 if (i < indexStart || i > indexEnd) { 112 var element = cells[i]; 113 if (element) { 114 element.detach(); 115 if (!this.options.useCache) { 116 this._renderedCellCache[i] = void 0; 117 } 118 } 119 } 120 } 121 }; 122 123 var GetScrollableHeight = function () { 124 var layout = this._layout; 125 return layout.cellHeight * Math.ceil(this.options.cellCount / Math.floor(layout.width / layout.cellWidth)); 126 } 127 128 var UpdateStyle = function (element, newStyle) { 129 element.css(newStyle); 130 }; 131 132 var OnContainerScroll = function (event) { 133 var time = event.timeStamp; 134 if ((time - this._lastUpdateTime) > this._scrollUpdateDeltaTime) { 135 this.Render(); 136 } 137 }; 138 139 var OnWindowResize = function (event) { 140 this.Layout(); 141 }; 142 143 var _error = function (message) { 144 return new Error('VirtualCollection: ' + message); 145 }; 146 147 var _assign = function (target, source) { 148 for (var key in source) { 149 target[key] = source[key]; 150 } 151 }; 152 153 // Public methods 154 155 (function (methods) { 156 157 methods.Layout = function () { 158 var container = this.container; 159 var ghostCell = this.options.cellCount > 0 ? RenderCell.call(this, 0, 0, 0, true) : $('<div></div>'); 160 var layout = this._layout; 161 162 ghostCell.css('visibility', 'hidden'); 163 164 this.ClearCache(); 165 this.scrollable.append(ghostCell); 166 167 var width = this.scrollable.innerWidth() 168 var cellWidth = ghostCell.innerWidth(); 169 170 _assign(layout, { 171 width: width,
172 height: container.innerHeight(), 173 cellWidth: cellWidth, 174 cellHeight: ghostCell.innerHeight(), 175 cellWidthPercent: cellWidth * 100 / width 176 }); 177 178 var scrollHeight = GetScrollableHeight.call(this); 179 180 UpdateStyle(this.scrollable, { 181 position: 'relative', 182 overflow: 'hidden', 183 width: '100%', 184 height: scrollHeight + PIXEL 185 }); 186 187 container.scrollTop(Math.min(container.scrollTop(), scrollHeight - layout.height)); 188 189 ghostCell.detach(); 190 191 this.Render(); 192 193 return this; 194 }; 195 196 methods.Render = function () { 197 var opt = this.options; 198 var layout = this._layout; 199 var scrollable = this.scrollable; 200 var rowIndexStart = Math.floor(this.GetScrollTop() / layout.cellHeight); 201 var rowIndexEnd = rowIndexStart + (layout.height / layout.cellHeight) + opt.rowOverload; 202 var columnCount = Math.floor(layout.width / layout.cellWidth);
203 var columnIndexStart = Math.floor(this.GetScrollLeft() / layout.cellWidth); 204 var columnIndexEnd = columnIndexStart + columnCount; 205 var index = rowIndexStart * columnCount; 206 207 var renderedCellCount = Math.min(opt.cellCount, (rowIndexEnd - rowIndexStart) * (columnIndexEnd - columnIndexStart)); 208 209 if (renderedCellCount < 1 && typeof opt.noCellRenderer === 'function') { 210 scrollable.empty(); 211 scrollable.append(RenderCell.call(this, -1, 0, 0)); 212 scrollable.css({ height: 'auto' }); 213 return; 214 } 215 216 DetachElements.call(this, index, index + renderedCellCount - 1); 217 scrollable.css({ height: GetScrollableHeight.call(this) + PIXEL }); 218 219 for (var i = rowIndexStart; i < rowIndexEnd; i++) { 220 for (var j = columnIndexStart; j < columnIndexEnd; j++) { 221 if (index < opt.cellCount) { 222 scrollable.append(RenderCell.call(this, index, i, j)); 223 } 224 index++; 225 } 226 } 227 return this; 228 }; 229 230 methods.UpdateOptions = function (options) { 231 if (options) { 232 _assign(this.options, options); 233 } 234 this.Layout(); 235 }; 236 237 methods.ClearCache = function () { 238 var length = this._renderedCellCache.length; 239 var cache = this._renderedCellCache; 240 var scrollable = this.scrollable; 241 for (var i = 0; i < length; i++) { 242 var element = cache[i]; 243 if (element) element.detach(); 244 } 245 if (this._renderedEmptyCell) this._renderedEmptyCell.detach(); 246 this._renderedCellCache = []; 247 }; 248 249 methods.ScrollTo = function (scrollTop) { 250 this.container.scrollTop(scrollTop); 251 }; 252 253 methods.GetScrollTop = function () { 254 return this.container.scrollTop(); 255 }; 256 257 methods.GetScrollLeft = function () { 258 return this.container.scrollLeft(); 259 }; 260 261 })(_class.prototype); 262 263 return _class; 264})());
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.