virtual-scroll.js 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. import { ChangeDetectorRef, ContentChild, Directive, ElementRef, Input, IterableDiffers, NgZone, Renderer } from '@angular/core';
  2. import { adjustRendered, calcDimensions, estimateHeight, initReadNodes, populateNodeData, processRecords, updateDimensions, updateNodeContext, writeToNodes } from './virtual-util';
  3. import { Config } from '../../config/config';
  4. import { Content } from '../content/content';
  5. import { DomController } from '../../platform/dom-controller';
  6. import { isFunction, isPresent } from '../../util/util';
  7. import { Platform } from '../../platform/platform';
  8. import { ViewController } from '../../navigation/view-controller';
  9. import { VirtualItem } from './virtual-item';
  10. import { VirtualFooter } from './virtual-footer';
  11. import { VirtualHeader } from './virtual-header';
  12. /**
  13. * @name VirtualScroll
  14. * @description
  15. * Virtual Scroll displays a virtual, "infinite" list. An array of records
  16. * is passed to the virtual scroll containing the data to create templates
  17. * for. The template created for each record, referred to as a cell, can
  18. * consist of items, headers, and footers.
  19. *
  20. * For performance reasons, not every record in the list is rendered at once;
  21. * instead a small subset of records (enough to fill the viewport) are rendered
  22. * and reused as the user scrolls.
  23. *
  24. * ### The Basics
  25. *
  26. * The array of records should be passed to the `virtualScroll` property.
  27. * The data given to the `virtualScroll` property must be an array. An item
  28. * template with the `*virtualItem` property is required in the `virtualScroll`.
  29. * The `virtualScroll` and `*virtualItem` properties can be added to any element.
  30. *
  31. * ```html
  32. * <ion-list [virtualScroll]="items">
  33. *
  34. * <ion-item *virtualItem="let item">
  35. * {% raw %}{{ item }}{% endraw %}
  36. * </ion-item>
  37. *
  38. * </ion-list>
  39. * ```
  40. *
  41. *
  42. * ### Section Headers and Footers
  43. *
  44. * Section headers and footers are optional. They can be dynamically created
  45. * from developer-defined functions. For example, a large list of contacts
  46. * usually has a divider for each letter in the alphabet. Developers provide
  47. * their own custom function to be called on each record. The logic in the
  48. * custom function should determine whether to create the section template
  49. * and what data to provide to the template. The custom function should
  50. * return `null` if a template shouldn't be created.
  51. *
  52. * ```html
  53. * <ion-list [virtualScroll]="items" [headerFn]="myHeaderFn">
  54. *
  55. * <ion-item-divider *virtualHeader="let header">
  56. * Header: {% raw %}{{ header }}{% endraw %}
  57. * </ion-item-divider>
  58. *
  59. * <ion-item *virtualItem="let item">
  60. * Item: {% raw %}{{ item }}{% endraw %}
  61. * </ion-item>
  62. *
  63. * </ion-list>
  64. * ```
  65. *
  66. * Below is an example of a custom function called on every record. It
  67. * gets passed the individual record, the record's index number,
  68. * and the entire array of records. In this example, after every 20
  69. * records a header will be inserted. So between the 19th and 20th records,
  70. * between the 39th and 40th, and so on, a `<ion-item-divider>` will
  71. * be created and the template's data will come from the function's
  72. * returned data.
  73. *
  74. * ```ts
  75. * myHeaderFn(record, recordIndex, records) {
  76. * if (recordIndex % 20 === 0) {
  77. * return 'Header ' + recordIndex;
  78. * }
  79. * return null;
  80. * }
  81. * ```
  82. *
  83. *
  84. * ### Approximate Widths and Heights
  85. *
  86. * If the height of items in the virtual scroll are not close to the
  87. * default size of 40px, it is extremely important to provide a value for
  88. * approxItemHeight height. An exact pixel-perfect size is not necessary,
  89. * but without an estimate the virtual scroll will not render correctly.
  90. *
  91. * The approximate width and height of each template is used to help
  92. * determine how many cells should be created, and to help calculate
  93. * the height of the scrollable area. Note that the actual rendered size
  94. * of each cell comes from the app's CSS, whereas this approximation
  95. * is only used to help calculate initial dimensions.
  96. *
  97. * It's also important to know that Ionic's default item sizes have
  98. * slightly different heights between platforms, which is perfectly fine.
  99. *
  100. *
  101. * ### Images Within Virtual Scroll
  102. *
  103. * HTTP requests, image decoding, and image rendering can cause jank while
  104. * scrolling. In order to better control images, Ionic provides `<ion-img>`
  105. * to manage HTTP requests and image rendering. While scrolling through items
  106. * quickly, `<ion-img>` knows when and when not to make requests, when and
  107. * when not to render images, and only loads the images that are viewable
  108. * after scrolling. [Read more about `ion-img`.](../../img/Img/)
  109. *
  110. * It's also important for app developers to ensure image sizes are locked in,
  111. * and after images have fully loaded they do not change size and affect any
  112. * other element sizes. Simply put, to ensure rendering bugs are not introduced,
  113. * it's vital that elements within a virtual item does not dynamically change.
  114. *
  115. * For virtual scrolling, the natural effects of the `<img>` are not desirable
  116. * features. We recommend using the `<ion-img>` component over the native
  117. * `<img>` element because when an `<img>` element is added to the DOM, it
  118. * immediately makes a HTTP request for the image file. Additionally, `<img>`
  119. * renders whenever it wants which could be while the user is scrolling. However,
  120. * `<ion-img>` is governed by the containing `ion-content` and does not render
  121. * images while scrolling quickly.
  122. *
  123. * ```html
  124. * <ion-list [virtualScroll]="items">
  125. *
  126. * <ion-item *virtualItem="let item">
  127. * <ion-avatar item-start>
  128. * <ion-img [src]="item.avatarUrl"></ion-img>
  129. * </ion-avatar>
  130. * {% raw %} {{ item.firstName }} {{ item.lastName }}{% endraw %}
  131. * </ion-item>
  132. *
  133. * </ion-list>
  134. * ```
  135. *
  136. *
  137. * ### Custom Components
  138. *
  139. * If a custom component is going to be used within Virtual Scroll, it's best
  140. * to wrap it with a good old `<div>` to ensure the component is rendered
  141. * correctly. Since each custom component's implementation and internals can be
  142. * quite different, wrapping within a `<div>` is a safe way to make sure
  143. * dimensions are measured correctly.
  144. *
  145. * ```html
  146. * <ion-list [virtualScroll]="items">
  147. *
  148. * <div *virtualItem="let item">
  149. * <my-custom-item [item]="item">
  150. * {% raw %} {{ item }}{% endraw %}
  151. * </my-custom-item>
  152. * </div>
  153. *
  154. * </ion-list>
  155. * ```
  156. *
  157. *
  158. * ## Virtual Scroll Performance Tips
  159. *
  160. * #### iOS Cordova WKWebView
  161. *
  162. * When deploying to iOS with Cordova, it's highly recommended to use the
  163. * [WKWebView plugin](http://blog.ionic.io/cordova-ios-performance-improvements-drop-in-speed-with-wkwebview/)
  164. * in order to take advantage of iOS's higher performimg webview. Additionally,
  165. * WKWebView is superior at scrolling efficiently in comparision to the older
  166. * UIWebView.
  167. *
  168. * #### Lock in element dimensions and locations
  169. *
  170. * In order for virtual scroll to efficiently size and locate every item, it's
  171. * very important every element within each virtual item does not dynamically
  172. * change its dimensions or location. The best way to ensure size and location
  173. * does not change, it's recommended each virtual item has locked in its size
  174. * via CSS.
  175. *
  176. * #### Use `ion-img` for images
  177. *
  178. * When including images within Virtual Scroll, be sure to use
  179. * [`ion-img`](../img/Img/) rather than the standard `<img>` HTML element.
  180. * With `ion-img`, images are lazy loaded so only the viewable ones are
  181. * rendered, and HTTP requests are efficiently controlled while scrolling.
  182. *
  183. * #### Set Approximate Widths and Heights
  184. *
  185. * As mentioned above, all elements should lock in their dimensions. However,
  186. * virtual scroll isn't aware of the dimensions until after they have been
  187. * rendered. For the initial render, virtual scroll still needs to set
  188. * how many items should be built. With "approx" property inputs, such as
  189. * `approxItemHeight`, we're able to give virtual scroll an approximate size,
  190. * therefore allowing virtual scroll to decide how many items should be
  191. * created.
  192. *
  193. * #### Changing dataset should use `virtualTrackBy`
  194. *
  195. * It is possible for the identities of elements in the iterator to change
  196. * while the data does not. This can happen, for example, if the iterator
  197. * produced from an RPC to the server, and that RPC is re-run. Even if the
  198. * "data" hasn't changed, the second response will produce objects with
  199. * different identities, and Ionic will tear down the entire DOM and rebuild
  200. * it. This is an expensive operation and should be avoided if possible.
  201. *
  202. * #### Efficient headers and footer functions
  203. *
  204. * Each virtual item must stay extremely efficient, but one way to really
  205. * kill its performance is to perform any DOM operations within section header
  206. * and footer functions. These functions are called for every record in the
  207. * dataset, so please make sure they're performant.
  208. *
  209. */
  210. var VirtualScroll = (function () {
  211. function VirtualScroll(_iterableDiffers, _elementRef, _renderer, _zone, _cd, _content, _plt, _ctrl, _config, _dom) {
  212. var _this = this;
  213. this._iterableDiffers = _iterableDiffers;
  214. this._elementRef = _elementRef;
  215. this._renderer = _renderer;
  216. this._zone = _zone;
  217. this._cd = _cd;
  218. this._content = _content;
  219. this._plt = _plt;
  220. this._ctrl = _ctrl;
  221. this._config = _config;
  222. this._dom = _dom;
  223. this._init = false;
  224. this._lastEle = false;
  225. this._records = [];
  226. this._cells = [];
  227. this._nodes = [];
  228. this._vHeight = 0;
  229. this._lastCheck = 0;
  230. this._recordSize = 0;
  231. this._data = {
  232. scrollTop: 0,
  233. };
  234. this._queue = 1 /* NoChanges */;
  235. /**
  236. * @input {number} The buffer ratio is used to decide how many cells
  237. * should get created when initially rendered. The number is a
  238. * multiplier against the viewable area's height. For example, if it
  239. * takes `20` cells to fill up the height of the viewable area, then
  240. * with a buffer ratio of `3` it will create `60` cells that are
  241. * available for reuse while scrolling. For better performance, it's
  242. * better to have more cells than what are required to fill the
  243. * viewable area. Default is `3`.
  244. */
  245. this.bufferRatio = 3;
  246. /**
  247. * @input {string} The approximate width of each item template's cell.
  248. * This dimension is used to help determine how many cells should
  249. * be created when initialized, and to help calculate the height of
  250. * the scrollable area. This value can use either `px` or `%` units.
  251. * Note that the actual rendered size of each cell comes from the
  252. * app's CSS, whereas this approximation is used to help calculate
  253. * initial dimensions before the item has been rendered. Default is
  254. * `100%`.
  255. */
  256. this.approxItemWidth = '100%';
  257. /**
  258. * @input {string} The approximate width of each header template's cell.
  259. * This dimension is used to help determine how many cells should
  260. * be created when initialized, and to help calculate the height of
  261. * the scrollable area. This value can use either `px` or `%` units.
  262. * Note that the actual rendered size of each cell comes from the
  263. * app's CSS, whereas this approximation is used to help calculate
  264. * initial dimensions. Default is `100%`.
  265. */
  266. this.approxHeaderWidth = '100%';
  267. /**
  268. * @input {string} The approximate height of each header template's cell.
  269. * This dimension is used to help determine how many cells should
  270. * be created when initialized, and to help calculate the height of
  271. * the scrollable area. This height value can only use `px` units.
  272. * Note that the actual rendered size of each cell comes from the
  273. * app's CSS, whereas this approximation is used to help calculate
  274. * initial dimensions before the item has been rendered. Default is `40px`.
  275. */
  276. this.approxHeaderHeight = '40px';
  277. /**
  278. * @input {string} The approximate width of each footer template's cell.
  279. * This dimension is used to help determine how many cells should
  280. * be created when initialized, and to help calculate the height of
  281. * the scrollable area. This value can use either `px` or `%` units.
  282. * Note that the actual rendered size of each cell comes from the
  283. * app's CSS, whereas this approximation is used to help calculate
  284. * initial dimensions before the item has been rendered. Default is `100%`.
  285. */
  286. this.approxFooterWidth = '100%';
  287. /**
  288. * @input {string} The approximate height of each footer template's cell.
  289. * This dimension is used to help determine how many cells should
  290. * be created when initialized, and to help calculate the height of
  291. * the scrollable area. This height value can only use `px` units.
  292. * Note that the actual rendered size of each cell comes from the
  293. * app's CSS, whereas this approximation is used to help calculate
  294. * initial dimensions before the item has been rendered. Default is `40px`.
  295. */
  296. this.approxFooterHeight = '40px';
  297. // hide the virtual scroll element with opacity so we don't
  298. // see jank as it loads up, but we're still able to read
  299. // dimensions because it's still rendered and only opacity hidden
  300. this.setElementClass('virtual-loading', true);
  301. // wait for the content to be rendered and has readable dimensions
  302. var readSub = _ctrl.readReady.subscribe(function () {
  303. readSub.unsubscribe();
  304. _this.readUpdate(true);
  305. });
  306. // wait for the content to be writable
  307. var writeSub = _ctrl.writeReady.subscribe(function () {
  308. writeSub.unsubscribe();
  309. _this._init = true;
  310. _this.writeUpdate(true);
  311. _this._listeners();
  312. });
  313. }
  314. Object.defineProperty(VirtualScroll.prototype, "virtualScroll", {
  315. get: function () {
  316. return this._records;
  317. },
  318. /**
  319. * @input {array} The data that builds the templates within the virtual scroll.
  320. * This is the same data that you'd pass to `*ngFor`. It's important to note
  321. * that when this data has changed, then the entire virtual scroll is reset,
  322. * which is an expensive operation and should be avoided if possible.
  323. */
  324. set: function (val) {
  325. this._records = val;
  326. },
  327. enumerable: true,
  328. configurable: true
  329. });
  330. Object.defineProperty(VirtualScroll.prototype, "headerFn", {
  331. /**
  332. * @input {function} Section headers and the data used within its given
  333. * template can be dynamically created by passing a function to `headerFn`.
  334. * For example, a large list of contacts usually has dividers between each
  335. * letter in the alphabet. App's can provide their own custom `headerFn`
  336. * which is called with each record within the dataset. The logic within
  337. * the header function can decide if the header template should be used,
  338. * and what data to give to the header template. The function must return
  339. * `null` if a header cell shouldn't be created.
  340. */
  341. set: function (val) {
  342. if (isFunction(val)) {
  343. this._hdrFn = val.bind((this._ctrl._cmp) || this);
  344. }
  345. },
  346. enumerable: true,
  347. configurable: true
  348. });
  349. Object.defineProperty(VirtualScroll.prototype, "footerFn", {
  350. /**
  351. * @input {function} Section footers and the data used within its given
  352. * template can be dynamically created by passing a function to `footerFn`.
  353. * The logic within the footer function can decide if the footer template
  354. * should be used, and what data to give to the footer template. The function
  355. * must return `null` if a footer cell shouldn't be created.
  356. */
  357. set: function (val) {
  358. if (isFunction(val)) {
  359. this._ftrFn = val.bind((this._ctrl._cmp) || this);
  360. }
  361. },
  362. enumerable: true,
  363. configurable: true
  364. });
  365. /**
  366. * @hidden
  367. */
  368. VirtualScroll.prototype.firstRecord = function () {
  369. var cells = this._cells;
  370. return (cells.length > 0) ? cells[0].record : 0;
  371. };
  372. /**
  373. * @hidden
  374. */
  375. VirtualScroll.prototype.lastRecord = function () {
  376. var cells = this._cells;
  377. return (cells.length > 0) ? cells[cells.length - 1].record : 0;
  378. };
  379. /**
  380. * @hidden
  381. */
  382. VirtualScroll.prototype.ngOnChanges = function (changes) {
  383. if ('virtualScroll' in changes) {
  384. // React on virtualScroll changes only once all inputs have been initialized
  385. var value = changes['virtualScroll'].currentValue;
  386. if (!isPresent(this._differ) && isPresent(value)) {
  387. try {
  388. this._differ = this._iterableDiffers.find(value).create(this.virtualTrackBy);
  389. }
  390. catch (e) {
  391. throw new Error("Cannot find a differ supporting object '" + value + "'. VirtualScroll only supports binding to Iterables such as Arrays.");
  392. }
  393. }
  394. }
  395. };
  396. /**
  397. * @hidden
  398. */
  399. VirtualScroll.prototype.ngDoCheck = function () {
  400. // only continue if we've already initialized
  401. if (!this._init) {
  402. return;
  403. }
  404. // and if there actually are changes
  405. var changes = isPresent(this._differ) ? this._differ.diff(this.virtualScroll) : null;
  406. if (!isPresent(changes)) {
  407. return;
  408. }
  409. var needClean = false;
  410. var lastRecord = this._recordSize;
  411. changes.forEachOperation(function (_, pindex, cindex) {
  412. // add new record after current position
  413. if (pindex === null && (cindex < lastRecord)) {
  414. (void 0) /* console.debug */;
  415. needClean = true;
  416. return;
  417. }
  418. // remove record after current position
  419. if (pindex < lastRecord && cindex === null) {
  420. (void 0) /* console.debug */;
  421. needClean = true;
  422. return;
  423. }
  424. });
  425. this._recordSize = this._records ? this._records.length : 0;
  426. this.readUpdate(needClean);
  427. this.writeUpdate(needClean);
  428. };
  429. /**
  430. * @hidden
  431. */
  432. VirtualScroll.prototype.readUpdate = function (needClean) {
  433. if (needClean) {
  434. // reset everything
  435. (void 0) /* console.debug */;
  436. this._cells.length = 0;
  437. // this._nodes.length = 0;
  438. // this._itmTmp.viewContainer.clear();
  439. // ******** DOM READ ****************
  440. this.calcDimensions();
  441. }
  442. else {
  443. (void 0) /* console.debug */;
  444. }
  445. };
  446. /**
  447. * @hidden
  448. */
  449. VirtualScroll.prototype.writeUpdate = function (needClean) {
  450. (void 0) /* console.debug */;
  451. var data = this._data;
  452. var stopAtHeight = (data.scrollTop + data.renderHeight);
  453. data.scrollDiff = SCROLL_DIFFERENCE_MINIMUM + 1;
  454. processRecords(stopAtHeight, this._records, this._cells, this._hdrFn, this._ftrFn, this._data);
  455. // ******** DOM WRITE ****************
  456. this.renderVirtual(needClean);
  457. };
  458. /**
  459. * @hidden
  460. */
  461. VirtualScroll.prototype.calcDimensions = function () {
  462. calcDimensions(this._data, this._elementRef.nativeElement, this.approxItemWidth, this.approxItemHeight, this.approxHeaderWidth, this.approxHeaderHeight, this.approxFooterWidth, this.approxFooterHeight, this.bufferRatio);
  463. };
  464. /**
  465. * @hidden
  466. * DOM WRITE
  467. */
  468. VirtualScroll.prototype.renderVirtual = function (needClean) {
  469. var _this = this;
  470. this._plt.raf(function () {
  471. var nodes = _this._nodes;
  472. var cells = _this._cells;
  473. var data = _this._data;
  474. var records = _this._records;
  475. if (needClean) {
  476. // ******** DOM WRITE ****************
  477. updateDimensions(_this._plt, nodes, cells, data, true);
  478. data.topCell = 0;
  479. data.bottomCell = (cells.length - 1);
  480. }
  481. adjustRendered(cells, data);
  482. _this._zone.run(function () {
  483. populateNodeData(data.topCell, data.bottomCell, true, cells, records, nodes, _this._itmTmp.viewContainer, _this._itmTmp.templateRef, _this._hdrTmp && _this._hdrTmp.templateRef, _this._ftrTmp && _this._ftrTmp.templateRef);
  484. });
  485. if (needClean) {
  486. _this._cd.detectChanges();
  487. }
  488. // at this point, this fn was called from within another
  489. // requestAnimationFrame, so the next dom reads/writes within the next frame
  490. // wait a frame before trying to read and calculate the dimensions
  491. // ******** DOM READ ****************
  492. _this._dom.read(function () { return initReadNodes(_this._plt, nodes, cells, data); });
  493. _this._dom.write(function () {
  494. // update the bound context for each node
  495. updateNodeContext(nodes, cells, data);
  496. // ******** DOM WRITE ****************
  497. _this._stepChangeDetection();
  498. // ******** DOM WRITE ****************
  499. _this._stepDOMWrite();
  500. // ******** DOM WRITE ****************
  501. _this._content.imgsUpdate();
  502. // First time load
  503. if (!_this._lastEle) {
  504. // add an element at the end so :last-child css doesn't get messed up
  505. // ******** DOM WRITE ****************
  506. var ele = _this._elementRef.nativeElement;
  507. var lastEle = _this._renderer.createElement(ele, 'div');
  508. lastEle.className = 'virtual-last';
  509. _this._lastEle = true;
  510. // ******** DOM WRITE ****************
  511. _this.setElementClass('virtual-scroll', true);
  512. // ******** DOM WRITE ****************
  513. _this.setElementClass('virtual-loading', false);
  514. }
  515. (void 0) /* assert */;
  516. });
  517. });
  518. };
  519. /**
  520. * @hidden
  521. */
  522. VirtualScroll.prototype.resize = function () {
  523. // only continue if we've already initialized
  524. if (!this._init) {
  525. return;
  526. }
  527. // check if component is rendered in the dom currently
  528. if (this._elementRef.nativeElement.offsetParent === null) {
  529. return;
  530. }
  531. (void 0) /* console.debug */;
  532. this.calcDimensions();
  533. this.writeUpdate(false);
  534. };
  535. /**
  536. * @hidden
  537. */
  538. VirtualScroll.prototype._stepDOMWrite = function () {
  539. var cells = this._cells;
  540. var nodes = this._nodes;
  541. // ******** DOM WRITE ****************
  542. writeToNodes(this._plt, nodes, cells, this._recordSize);
  543. // ******** DOM WRITE ****************
  544. this._setHeight(estimateHeight(this._recordSize, cells[cells.length - 1], this._vHeight, 0.25));
  545. // we're done here, good work
  546. this._queue = 1 /* NoChanges */;
  547. };
  548. /**
  549. * @hidden
  550. */
  551. VirtualScroll.prototype._stepChangeDetection = function () {
  552. // we need to do some change detection in this frame
  553. // we've got work painting do, let's throw it in the
  554. // domWrite callback so everyone plays nice
  555. // ******** DOM WRITE ****************
  556. var nodes = this._nodes;
  557. for (var i = 0; i < nodes.length; i++) {
  558. if (nodes[i].hasChanges) {
  559. nodes[i].view.detectChanges();
  560. }
  561. }
  562. // on the next frame we need write to the dom nodes manually
  563. this._queue = 3 /* DomWrite */;
  564. };
  565. /**
  566. * @hidden
  567. */
  568. VirtualScroll.prototype._stepNoChanges = function () {
  569. var data = this._data;
  570. // let's see if we've scroll far enough to require another check
  571. var diff = data.scrollDiff = (data.scrollTop - this._lastCheck);
  572. if (Math.abs(diff) < SCROLL_DIFFERENCE_MINIMUM) {
  573. return;
  574. }
  575. var cells = this._cells;
  576. var nodes = this._nodes;
  577. var records = this._records;
  578. // don't bother updating if the scrollTop hasn't changed much
  579. this._lastCheck = data.scrollTop;
  580. if (diff > 0) {
  581. // load data we may not have processed yet
  582. var stopAtHeight = (data.scrollTop + data.renderHeight);
  583. processRecords(stopAtHeight, records, cells, this._hdrFn, this._ftrFn, data);
  584. }
  585. // ******** DOM READ ****************
  586. updateDimensions(this._plt, nodes, cells, data, false);
  587. adjustRendered(cells, data);
  588. var hasChanges = populateNodeData(data.topCell, data.bottomCell, diff > 0, cells, records, nodes, this._itmTmp.viewContainer, this._itmTmp.templateRef, this._hdrTmp && this._hdrTmp.templateRef, this._ftrTmp && this._ftrTmp.templateRef);
  589. if (hasChanges) {
  590. // queue making updates in the next frame
  591. this._queue = 2 /* ChangeDetection */;
  592. // update the bound context for each node
  593. updateNodeContext(nodes, cells, data);
  594. }
  595. };
  596. /**
  597. * @hidden
  598. */
  599. VirtualScroll.prototype.scrollUpdate = function (ev) {
  600. var _this = this;
  601. // set the scroll top from the scroll event
  602. this._data.scrollTop = ev.scrollTop;
  603. // there is a queue system so that we can
  604. // spread out the work over multiple frames
  605. var queue = this._queue;
  606. if (queue === 1 /* NoChanges */) {
  607. // no dom writes or change detection to take care of
  608. this._stepNoChanges();
  609. }
  610. else if (queue === 2 /* ChangeDetection */) {
  611. this._dom.write(function () { return _this._stepChangeDetection(); });
  612. }
  613. else {
  614. (void 0) /* assert */;
  615. // there are DOM writes we need to take care of in this frame
  616. this._dom.write(function () { return _this._stepDOMWrite(); });
  617. }
  618. };
  619. /**
  620. * @hidden
  621. * DOM WRITE
  622. */
  623. VirtualScroll.prototype.scrollEnd = function () {
  624. var _this = this;
  625. // ******** DOM READ ****************
  626. updateDimensions(this._plt, this._nodes, this._cells, this._data, false);
  627. adjustRendered(this._cells, this._data);
  628. populateNodeData(this._data.topCell, this._data.bottomCell, true, this._cells, this._records, this._nodes, this._itmTmp.viewContainer, this._itmTmp.templateRef, this._hdrTmp && this._hdrTmp.templateRef, this._ftrTmp && this._ftrTmp.templateRef);
  629. // ******** DOM WRITE ***************
  630. this._dom.write(function () {
  631. // update the bound context for each node
  632. updateNodeContext(_this._nodes, _this._cells, _this._data);
  633. // ******** DOM WRITE ***************
  634. _this._stepChangeDetection();
  635. // ******** DOM WRITE ****************
  636. _this._stepDOMWrite();
  637. });
  638. };
  639. /**
  640. * @hidden
  641. * NO DOM
  642. */
  643. VirtualScroll.prototype._listeners = function () {
  644. (void 0) /* assert */;
  645. if (!this._scrollSub) {
  646. if (this._config.getBoolean('virtualScrollEventAssist')) {
  647. // use JS scrolling for iOS UIWebView
  648. // goal is to completely remove this when iOS
  649. // fully supports scroll events
  650. // listen to JS scroll events
  651. this._content.enableJsScroll();
  652. }
  653. this._resizeSub = this._plt.resize.subscribe(this.resize.bind(this));
  654. this._scrollSub = this._content.ionScroll.subscribe(this.scrollUpdate.bind(this));
  655. this._scrollEndSub = this._content.ionScrollEnd.subscribe(this.scrollEnd.bind(this));
  656. }
  657. };
  658. /**
  659. * @hidden
  660. * DOM WRITE
  661. */
  662. VirtualScroll.prototype._setHeight = function (newVirtualHeight) {
  663. if (newVirtualHeight !== this._vHeight) {
  664. // ******** DOM WRITE ****************
  665. this._renderer.setElementStyle(this._elementRef.nativeElement, 'height', newVirtualHeight > 0 ? newVirtualHeight + 'px' : '');
  666. this._vHeight = newVirtualHeight;
  667. (void 0) /* console.debug */;
  668. }
  669. };
  670. /**
  671. * @hidden
  672. */
  673. VirtualScroll.prototype.ngAfterContentInit = function () {
  674. (void 0) /* assert */;
  675. if (!this.approxItemHeight) {
  676. this.approxItemHeight = '40px';
  677. console.warn('Virtual Scroll: Please provide an "approxItemHeight" input to ensure proper virtual scroll rendering');
  678. }
  679. };
  680. /**
  681. * @hidden
  682. */
  683. VirtualScroll.prototype.setElementClass = function (className, add) {
  684. this._renderer.setElementClass(this._elementRef.nativeElement, className, add);
  685. };
  686. /**
  687. * @hidden
  688. */
  689. VirtualScroll.prototype.ngOnDestroy = function () {
  690. this._resizeSub && this._resizeSub.unsubscribe();
  691. this._scrollSub && this._scrollSub.unsubscribe();
  692. this._scrollEndSub && this._scrollEndSub.unsubscribe();
  693. this._resizeSub = this._scrollEndSub = this._scrollSub = null;
  694. this._hdrFn = this._ftrFn = this._records = this._cells = this._nodes = this._data = null;
  695. };
  696. VirtualScroll.decorators = [
  697. { type: Directive, args: [{
  698. selector: '[virtualScroll]'
  699. },] },
  700. ];
  701. /** @nocollapse */
  702. VirtualScroll.ctorParameters = function () { return [
  703. { type: IterableDiffers, },
  704. { type: ElementRef, },
  705. { type: Renderer, },
  706. { type: NgZone, },
  707. { type: ChangeDetectorRef, },
  708. { type: Content, },
  709. { type: Platform, },
  710. { type: ViewController, },
  711. { type: Config, },
  712. { type: DomController, },
  713. ]; };
  714. VirtualScroll.propDecorators = {
  715. '_itmTmp': [{ type: ContentChild, args: [VirtualItem,] },],
  716. '_hdrTmp': [{ type: ContentChild, args: [VirtualHeader,] },],
  717. '_ftrTmp': [{ type: ContentChild, args: [VirtualFooter,] },],
  718. 'virtualScroll': [{ type: Input },],
  719. 'bufferRatio': [{ type: Input },],
  720. 'approxItemWidth': [{ type: Input },],
  721. 'approxItemHeight': [{ type: Input },],
  722. 'approxHeaderWidth': [{ type: Input },],
  723. 'approxHeaderHeight': [{ type: Input },],
  724. 'approxFooterWidth': [{ type: Input },],
  725. 'approxFooterHeight': [{ type: Input },],
  726. 'headerFn': [{ type: Input },],
  727. 'footerFn': [{ type: Input },],
  728. 'virtualTrackBy': [{ type: Input },],
  729. };
  730. return VirtualScroll;
  731. }());
  732. export { VirtualScroll };
  733. var SCROLL_DIFFERENCE_MINIMUM = 40;
  734. //# sourceMappingURL=virtual-scroll.js.map