a zip code crypto-currency system good for red ONLY

virtual-scroll.js 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  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. export class VirtualScroll {
  211. constructor(_iterableDiffers, _elementRef, _renderer, _zone, _cd, _content, _plt, _ctrl, _config, _dom) {
  212. this._iterableDiffers = _iterableDiffers;
  213. this._elementRef = _elementRef;
  214. this._renderer = _renderer;
  215. this._zone = _zone;
  216. this._cd = _cd;
  217. this._content = _content;
  218. this._plt = _plt;
  219. this._ctrl = _ctrl;
  220. this._config = _config;
  221. this._dom = _dom;
  222. this._init = false;
  223. this._lastEle = false;
  224. this._records = [];
  225. this._cells = [];
  226. this._nodes = [];
  227. this._vHeight = 0;
  228. this._lastCheck = 0;
  229. this._recordSize = 0;
  230. this._data = {
  231. scrollTop: 0,
  232. };
  233. this._queue = 1 /* NoChanges */;
  234. /**
  235. * @input {number} The buffer ratio is used to decide how many cells
  236. * should get created when initially rendered. The number is a
  237. * multiplier against the viewable area's height. For example, if it
  238. * takes `20` cells to fill up the height of the viewable area, then
  239. * with a buffer ratio of `3` it will create `60` cells that are
  240. * available for reuse while scrolling. For better performance, it's
  241. * better to have more cells than what are required to fill the
  242. * viewable area. Default is `3`.
  243. */
  244. this.bufferRatio = 3;
  245. /**
  246. * @input {string} The approximate width of each item template's cell.
  247. * This dimension is used to help determine how many cells should
  248. * be created when initialized, and to help calculate the height of
  249. * the scrollable area. This value can use either `px` or `%` units.
  250. * Note that the actual rendered size of each cell comes from the
  251. * app's CSS, whereas this approximation is used to help calculate
  252. * initial dimensions before the item has been rendered. Default is
  253. * `100%`.
  254. */
  255. this.approxItemWidth = '100%';
  256. /**
  257. * @input {string} The approximate width of each header template's cell.
  258. * This dimension is used to help determine how many cells should
  259. * be created when initialized, and to help calculate the height of
  260. * the scrollable area. This value can use either `px` or `%` units.
  261. * Note that the actual rendered size of each cell comes from the
  262. * app's CSS, whereas this approximation is used to help calculate
  263. * initial dimensions. Default is `100%`.
  264. */
  265. this.approxHeaderWidth = '100%';
  266. /**
  267. * @input {string} The approximate height of each header template's cell.
  268. * This dimension is used to help determine how many cells should
  269. * be created when initialized, and to help calculate the height of
  270. * the scrollable area. This height value can only use `px` units.
  271. * Note that the actual rendered size of each cell comes from the
  272. * app's CSS, whereas this approximation is used to help calculate
  273. * initial dimensions before the item has been rendered. Default is `40px`.
  274. */
  275. this.approxHeaderHeight = '40px';
  276. /**
  277. * @input {string} The approximate width of each footer template's cell.
  278. * This dimension is used to help determine how many cells should
  279. * be created when initialized, and to help calculate the height of
  280. * the scrollable area. This value can use either `px` or `%` units.
  281. * Note that the actual rendered size of each cell comes from the
  282. * app's CSS, whereas this approximation is used to help calculate
  283. * initial dimensions before the item has been rendered. Default is `100%`.
  284. */
  285. this.approxFooterWidth = '100%';
  286. /**
  287. * @input {string} The approximate height of each footer template's cell.
  288. * This dimension is used to help determine how many cells should
  289. * be created when initialized, and to help calculate the height of
  290. * the scrollable area. This height value can only use `px` units.
  291. * Note that the actual rendered size of each cell comes from the
  292. * app's CSS, whereas this approximation is used to help calculate
  293. * initial dimensions before the item has been rendered. Default is `40px`.
  294. */
  295. this.approxFooterHeight = '40px';
  296. // hide the virtual scroll element with opacity so we don't
  297. // see jank as it loads up, but we're still able to read
  298. // dimensions because it's still rendered and only opacity hidden
  299. this.setElementClass('virtual-loading', true);
  300. // wait for the content to be rendered and has readable dimensions
  301. const readSub = _ctrl.readReady.subscribe(() => {
  302. readSub.unsubscribe();
  303. this.readUpdate(true);
  304. });
  305. // wait for the content to be writable
  306. const writeSub = _ctrl.writeReady.subscribe(() => {
  307. writeSub.unsubscribe();
  308. this._init = true;
  309. this.writeUpdate(true);
  310. this._listeners();
  311. });
  312. }
  313. /**
  314. * @input {array} The data that builds the templates within the virtual scroll.
  315. * This is the same data that you'd pass to `*ngFor`. It's important to note
  316. * that when this data has changed, then the entire virtual scroll is reset,
  317. * which is an expensive operation and should be avoided if possible.
  318. */
  319. set virtualScroll(val) {
  320. this._records = val;
  321. }
  322. get virtualScroll() {
  323. return this._records;
  324. }
  325. /**
  326. * @input {function} Section headers and the data used within its given
  327. * template can be dynamically created by passing a function to `headerFn`.
  328. * For example, a large list of contacts usually has dividers between each
  329. * letter in the alphabet. App's can provide their own custom `headerFn`
  330. * which is called with each record within the dataset. The logic within
  331. * the header function can decide if the header template should be used,
  332. * and what data to give to the header template. The function must return
  333. * `null` if a header cell shouldn't be created.
  334. */
  335. set headerFn(val) {
  336. if (isFunction(val)) {
  337. this._hdrFn = val.bind((this._ctrl._cmp) || this);
  338. }
  339. }
  340. /**
  341. * @input {function} Section footers and the data used within its given
  342. * template can be dynamically created by passing a function to `footerFn`.
  343. * The logic within the footer function can decide if the footer template
  344. * should be used, and what data to give to the footer template. The function
  345. * must return `null` if a footer cell shouldn't be created.
  346. */
  347. set footerFn(val) {
  348. if (isFunction(val)) {
  349. this._ftrFn = val.bind((this._ctrl._cmp) || this);
  350. }
  351. }
  352. /**
  353. * @hidden
  354. */
  355. firstRecord() {
  356. const cells = this._cells;
  357. return (cells.length > 0) ? cells[0].record : 0;
  358. }
  359. /**
  360. * @hidden
  361. */
  362. lastRecord() {
  363. const cells = this._cells;
  364. return (cells.length > 0) ? cells[cells.length - 1].record : 0;
  365. }
  366. /**
  367. * @hidden
  368. */
  369. ngOnChanges(changes) {
  370. if ('virtualScroll' in changes) {
  371. // React on virtualScroll changes only once all inputs have been initialized
  372. const value = changes['virtualScroll'].currentValue;
  373. if (!isPresent(this._differ) && isPresent(value)) {
  374. try {
  375. this._differ = this._iterableDiffers.find(value).create(this.virtualTrackBy);
  376. }
  377. catch (e) {
  378. throw new Error(`Cannot find a differ supporting object '${value}'. VirtualScroll only supports binding to Iterables such as Arrays.`);
  379. }
  380. }
  381. }
  382. }
  383. /**
  384. * @hidden
  385. */
  386. ngDoCheck() {
  387. // only continue if we've already initialized
  388. if (!this._init) {
  389. return;
  390. }
  391. // and if there actually are changes
  392. const changes = isPresent(this._differ) ? this._differ.diff(this.virtualScroll) : null;
  393. if (!isPresent(changes)) {
  394. return;
  395. }
  396. let needClean = false;
  397. var lastRecord = this._recordSize;
  398. changes.forEachOperation((_, pindex, cindex) => {
  399. // add new record after current position
  400. if (pindex === null && (cindex < lastRecord)) {
  401. (void 0) /* console.debug */;
  402. needClean = true;
  403. return;
  404. }
  405. // remove record after current position
  406. if (pindex < lastRecord && cindex === null) {
  407. (void 0) /* console.debug */;
  408. needClean = true;
  409. return;
  410. }
  411. });
  412. this._recordSize = this._records ? this._records.length : 0;
  413. this.readUpdate(needClean);
  414. this.writeUpdate(needClean);
  415. }
  416. /**
  417. * @hidden
  418. */
  419. readUpdate(needClean) {
  420. if (needClean) {
  421. // reset everything
  422. (void 0) /* console.debug */;
  423. this._cells.length = 0;
  424. // this._nodes.length = 0;
  425. // this._itmTmp.viewContainer.clear();
  426. // ******** DOM READ ****************
  427. this.calcDimensions();
  428. }
  429. else {
  430. (void 0) /* console.debug */;
  431. }
  432. }
  433. /**
  434. * @hidden
  435. */
  436. writeUpdate(needClean) {
  437. (void 0) /* console.debug */;
  438. const data = this._data;
  439. const stopAtHeight = (data.scrollTop + data.renderHeight);
  440. data.scrollDiff = SCROLL_DIFFERENCE_MINIMUM + 1;
  441. processRecords(stopAtHeight, this._records, this._cells, this._hdrFn, this._ftrFn, this._data);
  442. // ******** DOM WRITE ****************
  443. this.renderVirtual(needClean);
  444. }
  445. /**
  446. * @hidden
  447. */
  448. calcDimensions() {
  449. calcDimensions(this._data, this._elementRef.nativeElement, this.approxItemWidth, this.approxItemHeight, this.approxHeaderWidth, this.approxHeaderHeight, this.approxFooterWidth, this.approxFooterHeight, this.bufferRatio);
  450. }
  451. /**
  452. * @hidden
  453. * DOM WRITE
  454. */
  455. renderVirtual(needClean) {
  456. this._plt.raf(() => {
  457. const nodes = this._nodes;
  458. const cells = this._cells;
  459. const data = this._data;
  460. const records = this._records;
  461. if (needClean) {
  462. // ******** DOM WRITE ****************
  463. updateDimensions(this._plt, nodes, cells, data, true);
  464. data.topCell = 0;
  465. data.bottomCell = (cells.length - 1);
  466. }
  467. adjustRendered(cells, data);
  468. this._zone.run(() => {
  469. 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);
  470. });
  471. if (needClean) {
  472. this._cd.detectChanges();
  473. }
  474. // at this point, this fn was called from within another
  475. // requestAnimationFrame, so the next dom reads/writes within the next frame
  476. // wait a frame before trying to read and calculate the dimensions
  477. // ******** DOM READ ****************
  478. this._dom.read(() => initReadNodes(this._plt, nodes, cells, data));
  479. this._dom.write(() => {
  480. // update the bound context for each node
  481. updateNodeContext(nodes, cells, data);
  482. // ******** DOM WRITE ****************
  483. this._stepChangeDetection();
  484. // ******** DOM WRITE ****************
  485. this._stepDOMWrite();
  486. // ******** DOM WRITE ****************
  487. this._content.imgsUpdate();
  488. // First time load
  489. if (!this._lastEle) {
  490. // add an element at the end so :last-child css doesn't get messed up
  491. // ******** DOM WRITE ****************
  492. var ele = this._elementRef.nativeElement;
  493. var lastEle = this._renderer.createElement(ele, 'div');
  494. lastEle.className = 'virtual-last';
  495. this._lastEle = true;
  496. // ******** DOM WRITE ****************
  497. this.setElementClass('virtual-scroll', true);
  498. // ******** DOM WRITE ****************
  499. this.setElementClass('virtual-loading', false);
  500. }
  501. (void 0) /* assert */;
  502. });
  503. });
  504. }
  505. /**
  506. * @hidden
  507. */
  508. resize() {
  509. // only continue if we've already initialized
  510. if (!this._init) {
  511. return;
  512. }
  513. // check if component is rendered in the dom currently
  514. if (this._elementRef.nativeElement.offsetParent === null) {
  515. return;
  516. }
  517. (void 0) /* console.debug */;
  518. this.calcDimensions();
  519. this.writeUpdate(false);
  520. }
  521. /**
  522. * @hidden
  523. */
  524. _stepDOMWrite() {
  525. const cells = this._cells;
  526. const nodes = this._nodes;
  527. // ******** DOM WRITE ****************
  528. writeToNodes(this._plt, nodes, cells, this._recordSize);
  529. // ******** DOM WRITE ****************
  530. this._setHeight(estimateHeight(this._recordSize, cells[cells.length - 1], this._vHeight, 0.25));
  531. // we're done here, good work
  532. this._queue = 1 /* NoChanges */;
  533. }
  534. /**
  535. * @hidden
  536. */
  537. _stepChangeDetection() {
  538. // we need to do some change detection in this frame
  539. // we've got work painting do, let's throw it in the
  540. // domWrite callback so everyone plays nice
  541. // ******** DOM WRITE ****************
  542. const nodes = this._nodes;
  543. for (var i = 0; i < nodes.length; i++) {
  544. if (nodes[i].hasChanges) {
  545. nodes[i].view.detectChanges();
  546. }
  547. }
  548. // on the next frame we need write to the dom nodes manually
  549. this._queue = 3 /* DomWrite */;
  550. }
  551. /**
  552. * @hidden
  553. */
  554. _stepNoChanges() {
  555. const data = this._data;
  556. // let's see if we've scroll far enough to require another check
  557. const diff = data.scrollDiff = (data.scrollTop - this._lastCheck);
  558. if (Math.abs(diff) < SCROLL_DIFFERENCE_MINIMUM) {
  559. return;
  560. }
  561. const cells = this._cells;
  562. const nodes = this._nodes;
  563. const records = this._records;
  564. // don't bother updating if the scrollTop hasn't changed much
  565. this._lastCheck = data.scrollTop;
  566. if (diff > 0) {
  567. // load data we may not have processed yet
  568. var stopAtHeight = (data.scrollTop + data.renderHeight);
  569. processRecords(stopAtHeight, records, cells, this._hdrFn, this._ftrFn, data);
  570. }
  571. // ******** DOM READ ****************
  572. updateDimensions(this._plt, nodes, cells, data, false);
  573. adjustRendered(cells, data);
  574. 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);
  575. if (hasChanges) {
  576. // queue making updates in the next frame
  577. this._queue = 2 /* ChangeDetection */;
  578. // update the bound context for each node
  579. updateNodeContext(nodes, cells, data);
  580. }
  581. }
  582. /**
  583. * @hidden
  584. */
  585. scrollUpdate(ev) {
  586. // set the scroll top from the scroll event
  587. this._data.scrollTop = ev.scrollTop;
  588. // there is a queue system so that we can
  589. // spread out the work over multiple frames
  590. const queue = this._queue;
  591. if (queue === 1 /* NoChanges */) {
  592. // no dom writes or change detection to take care of
  593. this._stepNoChanges();
  594. }
  595. else if (queue === 2 /* ChangeDetection */) {
  596. this._dom.write(() => this._stepChangeDetection());
  597. }
  598. else {
  599. (void 0) /* assert */;
  600. // there are DOM writes we need to take care of in this frame
  601. this._dom.write(() => this._stepDOMWrite());
  602. }
  603. }
  604. /**
  605. * @hidden
  606. * DOM WRITE
  607. */
  608. scrollEnd() {
  609. // ******** DOM READ ****************
  610. updateDimensions(this._plt, this._nodes, this._cells, this._data, false);
  611. adjustRendered(this._cells, this._data);
  612. 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);
  613. // ******** DOM WRITE ***************
  614. this._dom.write(() => {
  615. // update the bound context for each node
  616. updateNodeContext(this._nodes, this._cells, this._data);
  617. // ******** DOM WRITE ***************
  618. this._stepChangeDetection();
  619. // ******** DOM WRITE ****************
  620. this._stepDOMWrite();
  621. });
  622. }
  623. /**
  624. * @hidden
  625. * NO DOM
  626. */
  627. _listeners() {
  628. (void 0) /* assert */;
  629. if (!this._scrollSub) {
  630. if (this._config.getBoolean('virtualScrollEventAssist')) {
  631. // use JS scrolling for iOS UIWebView
  632. // goal is to completely remove this when iOS
  633. // fully supports scroll events
  634. // listen to JS scroll events
  635. this._content.enableJsScroll();
  636. }
  637. this._resizeSub = this._plt.resize.subscribe(this.resize.bind(this));
  638. this._scrollSub = this._content.ionScroll.subscribe(this.scrollUpdate.bind(this));
  639. this._scrollEndSub = this._content.ionScrollEnd.subscribe(this.scrollEnd.bind(this));
  640. }
  641. }
  642. /**
  643. * @hidden
  644. * DOM WRITE
  645. */
  646. _setHeight(newVirtualHeight) {
  647. if (newVirtualHeight !== this._vHeight) {
  648. // ******** DOM WRITE ****************
  649. this._renderer.setElementStyle(this._elementRef.nativeElement, 'height', newVirtualHeight > 0 ? newVirtualHeight + 'px' : '');
  650. this._vHeight = newVirtualHeight;
  651. (void 0) /* console.debug */;
  652. }
  653. }
  654. /**
  655. * @hidden
  656. */
  657. ngAfterContentInit() {
  658. (void 0) /* assert */;
  659. if (!this.approxItemHeight) {
  660. this.approxItemHeight = '40px';
  661. console.warn('Virtual Scroll: Please provide an "approxItemHeight" input to ensure proper virtual scroll rendering');
  662. }
  663. }
  664. /**
  665. * @hidden
  666. */
  667. setElementClass(className, add) {
  668. this._renderer.setElementClass(this._elementRef.nativeElement, className, add);
  669. }
  670. /**
  671. * @hidden
  672. */
  673. ngOnDestroy() {
  674. this._resizeSub && this._resizeSub.unsubscribe();
  675. this._scrollSub && this._scrollSub.unsubscribe();
  676. this._scrollEndSub && this._scrollEndSub.unsubscribe();
  677. this._resizeSub = this._scrollEndSub = this._scrollSub = null;
  678. this._hdrFn = this._ftrFn = this._records = this._cells = this._nodes = this._data = null;
  679. }
  680. }
  681. VirtualScroll.decorators = [
  682. { type: Directive, args: [{
  683. selector: '[virtualScroll]'
  684. },] },
  685. ];
  686. /** @nocollapse */
  687. VirtualScroll.ctorParameters = () => [
  688. { type: IterableDiffers, },
  689. { type: ElementRef, },
  690. { type: Renderer, },
  691. { type: NgZone, },
  692. { type: ChangeDetectorRef, },
  693. { type: Content, },
  694. { type: Platform, },
  695. { type: ViewController, },
  696. { type: Config, },
  697. { type: DomController, },
  698. ];
  699. VirtualScroll.propDecorators = {
  700. '_itmTmp': [{ type: ContentChild, args: [VirtualItem,] },],
  701. '_hdrTmp': [{ type: ContentChild, args: [VirtualHeader,] },],
  702. '_ftrTmp': [{ type: ContentChild, args: [VirtualFooter,] },],
  703. 'virtualScroll': [{ type: Input },],
  704. 'bufferRatio': [{ type: Input },],
  705. 'approxItemWidth': [{ type: Input },],
  706. 'approxItemHeight': [{ type: Input },],
  707. 'approxHeaderWidth': [{ type: Input },],
  708. 'approxHeaderHeight': [{ type: Input },],
  709. 'approxFooterWidth': [{ type: Input },],
  710. 'approxFooterHeight': [{ type: Input },],
  711. 'headerFn': [{ type: Input },],
  712. 'footerFn': [{ type: Input },],
  713. 'virtualTrackBy': [{ type: Input },],
  714. };
  715. const SCROLL_DIFFERENCE_MINIMUM = 40;
  716. //# sourceMappingURL=virtual-scroll.js.map