API Docs for: 3.18.1
Show:

File: scrollview/js/paginator-plugin.js

  1. /**
  2. * Provides a plugin that adds pagination support to ScrollView instances
  3. *
  4. * @module scrollview-paginator
  5. */
  6. var getClassName = Y.ClassNameManager.getClassName,
  7. SCROLLVIEW = 'scrollview',
  8. CLASS_HIDDEN = getClassName(SCROLLVIEW, 'hidden'),
  9. CLASS_PAGED = getClassName(SCROLLVIEW, 'paged'),
  10. UI = (Y.ScrollView) ? Y.ScrollView.UI_SRC : 'ui',
  11. INDEX = 'index',
  12. SCROLL_X = 'scrollX',
  13. SCROLL_Y = 'scrollY',
  14. TOTAL = 'total',
  15. DISABLED = 'disabled',
  16. HOST = 'host',
  17. SELECTOR = 'selector',
  18. AXIS = 'axis',
  19. DIM_X = 'x',
  20. DIM_Y = 'y';
  21.  
  22. /**
  23. * Scrollview plugin that adds support for paging
  24. *
  25. * @class ScrollViewPaginator
  26. * @namespace Plugin
  27. * @extends Plugin.Base
  28. * @constructor
  29. */
  30. function PaginatorPlugin() {
  31. PaginatorPlugin.superclass.constructor.apply(this, arguments);
  32. }
  33.  
  34. Y.extend(PaginatorPlugin, Y.Plugin.Base, {
  35.  
  36. /**
  37. * Designated initializer
  38. *
  39. * @method initializer
  40. * @param {Object} Configuration object for the plugin
  41. */
  42. initializer: function (config) {
  43. var paginator = this,
  44. host = paginator.get(HOST);
  45.  
  46. // Initialize & default
  47. paginator._pageDims = [];
  48. paginator._pageBuffer = 1;
  49. paginator._optimizeMemory = false;
  50.  
  51. // Cache some values
  52. paginator._host = host;
  53. paginator._bb = host._bb;
  54. paginator._cb = host._cb;
  55. paginator._cIndex = paginator.get(INDEX);
  56. paginator._cAxis = paginator.get(AXIS);
  57.  
  58. // Apply configs
  59. if (config._optimizeMemory) {
  60. paginator._optimizeMemory = config._optimizeMemory;
  61. }
  62.  
  63. if (config._pageBuffer) {
  64. paginator._pageBuffer = config._pageBuffer;
  65. }
  66.  
  67. // Attach event bindings
  68. paginator._bindAttrs();
  69. },
  70.  
  71. /**
  72. *
  73. *
  74. * @method _bindAttrs
  75. * @private
  76. */
  77. _bindAttrs: function () {
  78. var paginator = this;
  79.  
  80. // Event listeners
  81. paginator.after({
  82. 'indexChange': paginator._afterIndexChange,
  83. 'axisChange': paginator._afterAxisChange
  84. });
  85.  
  86. // Host method listeners
  87. paginator.beforeHostMethod('scrollTo', paginator._beforeHostScrollTo);
  88. paginator.beforeHostMethod('_mousewheel', paginator._beforeHostMousewheel);
  89. paginator.beforeHostMethod('_flick', paginator._beforeHostFlick);
  90. paginator.afterHostMethod('_onGestureMoveEnd', paginator._afterHostGestureMoveEnd);
  91. paginator.afterHostMethod('_uiDimensionsChange', paginator._afterHostUIDimensionsChange);
  92. paginator.afterHostMethod('syncUI', paginator._afterHostSyncUI);
  93.  
  94. // Host event listeners
  95. paginator.afterHostEvent('render', paginator._afterHostRender);
  96. paginator.afterHostEvent('scrollEnd', paginator._afterHostScrollEnded);
  97. },
  98.  
  99. /**
  100. * After host render
  101. *
  102. * @method _afterHostRender
  103. * @param e {EventFacade} The event facade
  104. * @protected
  105. */
  106. _afterHostRender: function () {
  107. var paginator = this,
  108. bb = paginator._bb,
  109. host = paginator._host,
  110. index = paginator._cIndex,
  111. paginatorAxis = paginator._cAxis,
  112. pageNodes = paginator._getPageNodes(),
  113. size = pageNodes.size(),
  114. pageDim = paginator._pageDims[index];
  115.  
  116. if (paginatorAxis[DIM_Y]) {
  117. host._maxScrollX = pageDim.maxScrollX;
  118. }
  119. else if (paginatorAxis[DIM_X]) {
  120. host._maxScrollY = pageDim.maxScrollY;
  121. }
  122.  
  123. // Set the page count
  124. paginator.set(TOTAL, size);
  125.  
  126. // Jump to the index
  127. if (index !== 0) {
  128. paginator.scrollToIndex(index, 0);
  129. }
  130.  
  131. // Add the paginator class
  132. bb.addClass(CLASS_PAGED);
  133.  
  134. // Trigger the optimization process
  135. paginator._optimize();
  136. },
  137.  
  138. /**
  139. * After host syncUI
  140. *
  141. * @method _afterHostSyncUI
  142. * @param e {EventFacade} The event facade
  143. * @protected
  144. */
  145. _afterHostSyncUI: function () {
  146. var paginator = this,
  147. host = paginator._host,
  148. pageNodes = paginator._getPageNodes(),
  149. size = pageNodes.size();
  150.  
  151. // Set the page count
  152. paginator.set(TOTAL, size);
  153.  
  154. // If paginator's 'axis' property is to be automatically determined, inherit host's property
  155. if (paginator._cAxis === undefined) {
  156. paginator._set(AXIS, host.get(AXIS));
  157. }
  158. },
  159.  
  160. /**
  161. * After host _uiDimensionsChange
  162. *
  163. * @method _afterHostUIDimensionsChange
  164. * @param e {EventFacade} The event facade
  165. * @protected
  166. */
  167. _afterHostUIDimensionsChange: function () {
  168.  
  169. var paginator = this,
  170. host = paginator._host,
  171. dims = host._getScrollDims(),
  172. widgetWidth = dims.offsetWidth,
  173. widgetHeight = dims.offsetHeight,
  174. pageNodes = paginator._getPageNodes();
  175.  
  176. // Inefficient. Should not reinitialize every page every syncUI
  177. pageNodes.each(function (node, i) {
  178. var scrollWidth = node.get('scrollWidth'),
  179. scrollHeight = node.get('scrollHeight'),
  180. maxScrollX = Math.max(0, scrollWidth - widgetWidth), // Math.max to ensure we don't set it to a negative value
  181. maxScrollY = Math.max(0, scrollHeight - widgetHeight);
  182.  
  183. // Don't initialize any page _pageDims that already have been.
  184. if (!paginator._pageDims[i]) {
  185.  
  186. paginator._pageDims[i] = {
  187.  
  188. // Current scrollX & scrollY positions (default to 0)
  189. scrollX: 0,
  190. scrollY: 0,
  191.  
  192. // Maximum scrollable values
  193. maxScrollX: maxScrollX,
  194. maxScrollY: maxScrollY,
  195.  
  196. // Height & width of the page
  197. width: scrollWidth,
  198. height: scrollHeight
  199. };
  200.  
  201. } else {
  202. paginator._pageDims[i].maxScrollX = maxScrollX;
  203. paginator._pageDims[i].maxScrollY = maxScrollY;
  204. }
  205.  
  206. });
  207. },
  208.  
  209. /**
  210. * Executed before host.scrollTo
  211. *
  212. * @method _beforeHostScrollTo
  213. * @param x {Number} The x-position to scroll to. (null for no movement)
  214. * @param y {Number} The y-position to scroll to. (null for no movement)
  215. * @param {Number} [duration] Duration, in ms, of the scroll animation (default is 0)
  216. * @param {String} [easing] An easing equation if duration is set
  217. * @param {String} [node] The node to move
  218. * @protected
  219. */
  220. _beforeHostScrollTo: function (x, y, duration, easing, node) {
  221. var paginator = this,
  222. host = paginator._host,
  223. gesture = host._gesture,
  224. index = paginator._cIndex,
  225. paginatorAxis = paginator._cAxis,
  226. pageNodes = paginator._getPageNodes(),
  227. gestureAxis;
  228.  
  229. if (gesture) {
  230. gestureAxis = gesture.axis;
  231.  
  232. // Null the opposite axis so it won't be modified by host.scrollTo
  233. if (gestureAxis === DIM_Y) {
  234. x = null;
  235. } else {
  236. y = null;
  237. }
  238.  
  239. // If they are scrolling against the specified axis, pull out the page's node to have its own offset
  240. if (paginatorAxis[gestureAxis] === false) {
  241. node = pageNodes.item(index);
  242. }
  243.  
  244. }
  245.  
  246. // Return the modified argument list
  247. return new Y.Do.AlterArgs("new args", [x, y, duration, easing, node]);
  248. },
  249.  
  250. /**
  251. * Executed after host._gestureMoveEnd
  252. * Determines if the gesture should page prev or next (if at all)
  253. *
  254. * @method _afterHostGestureMoveEnd
  255. * @param e {EventFacade} The event facade
  256. * @protected
  257. */
  258. _afterHostGestureMoveEnd: function () {
  259.  
  260. // This was a flick, so we don't need to do anything here
  261. if (this._host._gesture.flick) {
  262. return;
  263. }
  264.  
  265. var paginator = this,
  266. host = paginator._host,
  267. gesture = host._gesture,
  268. index = paginator._cIndex,
  269. paginatorAxis = paginator._cAxis,
  270. gestureAxis = gesture.axis,
  271. isHorizontal = (gestureAxis === DIM_X),
  272. delta = gesture[(isHorizontal ? 'deltaX' : 'deltaY')],
  273. isForward = (delta > 0),
  274. pageDims = paginator._pageDims[index],
  275. halfway = pageDims[(isHorizontal ? 'width' : 'height')] / 2,
  276. isHalfway = (Math.abs(delta) >= halfway),
  277. canScroll = paginatorAxis[gestureAxis],
  278. rtl = host.rtl;
  279.  
  280. if (canScroll) {
  281. if (isHalfway) { // TODO: This condition should probably be configurable
  282. // Fire next()/prev()
  283. paginator[(rtl === isForward ? 'prev' : 'next')]();
  284. }
  285. // Scrollback
  286. else {
  287. paginator.scrollToIndex(paginator.get(INDEX));
  288. }
  289. }
  290. },
  291.  
  292. /**
  293. * Executed before host._mousewheel
  294. * Prevents mousewheel events in some conditions
  295. *
  296. * @method _beforeHostMousewheel
  297. * @param e {EventFacade} The event facade
  298. * @protected
  299. */
  300. _beforeHostMousewheel: function (e) {
  301. var paginator = this,
  302. host = paginator._host,
  303. bb = host._bb,
  304. isForward = (e.wheelDelta < 0),
  305. paginatorAxis = paginator._cAxis;
  306.  
  307. // Only if the mousewheel event occurred on a DOM node inside the BB
  308. if (bb.contains(e.target) && paginatorAxis[DIM_Y]) {
  309.  
  310. // Fire next()/prev()
  311. paginator[(isForward ? 'next' : 'prev')]();
  312.  
  313. // prevent browser default behavior on mousewheel
  314. e.preventDefault();
  315.  
  316. // Block host._mousewheel from running
  317. return new Y.Do.Prevent();
  318. }
  319. },
  320.  
  321. /**
  322. * Executed before host._flick
  323. * Prevents flick events in some conditions
  324. *
  325. * @method _beforeHostFlick
  326. * @param e {EventFacade} The event facade
  327. * @protected
  328. */
  329. _beforeHostFlick: function (e) {
  330.  
  331. // If the widget is disabled
  332. if (this._host.get(DISABLED)) {
  333. return false;
  334. }
  335.  
  336. // The drag was out of bounds, so do nothing (which will cause a snapback)
  337. if (this._host._isOutOfBounds()){
  338. return new Y.Do.Prevent();
  339. }
  340.  
  341. var paginator = this,
  342. host = paginator._host,
  343. gesture = host._gesture,
  344. paginatorAxis = paginator.get(AXIS),
  345. flick = e.flick,
  346. velocity = flick.velocity,
  347. flickAxis = flick.axis || false,
  348. isForward = (velocity < 0),
  349. canScroll = paginatorAxis[flickAxis],
  350. rtl = host.rtl;
  351.  
  352. // Store the flick data in the this._host._gesture object so it knows this was a flick
  353. if (gesture) {
  354. gesture.flick = flick;
  355. }
  356.  
  357. // Can we scroll along this axis?
  358. if (canScroll) {
  359.  
  360. // Fire next()/prev()
  361. paginator[(rtl === isForward ? 'prev' : 'next')]();
  362.  
  363. // Prevent flicks on the paginated axis
  364. if (paginatorAxis[flickAxis]) {
  365. return new Y.Do.Prevent();
  366. }
  367. }
  368. },
  369.  
  370. /**
  371. * Executes after host's 'scrollEnd' event
  372. * Runs cleanup operations
  373. *
  374. * @method _afterHostScrollEnded
  375. * @param e {EventFacade} The event facade
  376. * @protected
  377. */
  378. _afterHostScrollEnded: function () {
  379. var paginator = this,
  380. host = paginator._host,
  381. index = paginator._cIndex,
  382. scrollX = host.get(SCROLL_X),
  383. scrollY = host.get(SCROLL_Y),
  384. paginatorAxis = paginator._cAxis;
  385.  
  386. if (paginatorAxis[DIM_Y]) {
  387. paginator._pageDims[index].scrollX = scrollX;
  388. } else {
  389. paginator._pageDims[index].scrollY = scrollY;
  390. }
  391.  
  392. paginator._optimize();
  393. },
  394.  
  395. /**
  396. * index attr change handler
  397. *
  398. * @method _afterIndexChange
  399. * @param e {EventFacade} The event facade
  400. * @protected
  401. */
  402. _afterIndexChange: function (e) {
  403. var paginator = this,
  404. host = paginator._host,
  405. index = e.newVal,
  406. pageDims = paginator._pageDims[index],
  407. hostAxis = host._cAxis,
  408. paginatorAxis = paginator._cAxis;
  409.  
  410. // Cache the new index value
  411. paginator._cIndex = index;
  412.  
  413. // For dual-axis instances, we need to hack some host properties to the
  414. // current page's max height/width and current stored offset
  415. if (hostAxis[DIM_X] && hostAxis[DIM_Y]) {
  416. if (paginatorAxis[DIM_Y]) {
  417. host._maxScrollX = pageDims.maxScrollX;
  418. host.set(SCROLL_X, pageDims.scrollX, { src: UI });
  419. }
  420. else if (paginatorAxis[DIM_X]) {
  421. host._maxScrollY = pageDims.maxScrollY;
  422. host.set(SCROLL_Y, pageDims.scrollY, { src: UI });
  423. }
  424. }
  425.  
  426. if (e.src !== UI) {
  427. paginator.scrollToIndex(index);
  428. }
  429. },
  430.  
  431. /**
  432. * Optimization: Hides the pages not near the viewport
  433. *
  434. * @method _optimize
  435. * @protected
  436. */
  437. _optimize: function () {
  438.  
  439. if (!this._optimizeMemory) {
  440. return false;
  441. }
  442.  
  443. var paginator = this,
  444. currentIndex = paginator._cIndex,
  445. pageNodes = paginator._getStage(currentIndex);
  446.  
  447. // Show the pages in/near the viewport & hide the rest
  448. paginator._showNodes(pageNodes.visible);
  449. paginator._hideNodes(pageNodes.hidden);
  450. },
  451.  
  452. /**
  453. * Optimization: Determines which nodes should be visible, and which should be hidden.
  454. *
  455. * @method _getStage
  456. * @param index {Number} The page index # intended to be in focus.
  457. * @return {object}
  458. * @protected
  459. */
  460. _getStage: function (index) {
  461. var paginator = this,
  462. pageBuffer = paginator._pageBuffer,
  463. pageCount = paginator.get(TOTAL),
  464. pageNodes = paginator._getPageNodes(),
  465. start = Math.max(0, index - pageBuffer),
  466. end = Math.min(pageCount, index + 1 + pageBuffer); // noninclusive
  467.  
  468. return {
  469. visible: pageNodes.splice(start, end - start),
  470. hidden: pageNodes
  471. };
  472. },
  473.  
  474. /**
  475. * A utility method to show node(s)
  476. *
  477. * @method _showNodes
  478. * @param nodeList {Object} The list of nodes to show
  479. * @protected
  480. */
  481. _showNodes: function (nodeList) {
  482. if (nodeList) {
  483. nodeList.removeClass(CLASS_HIDDEN).setStyle('visibility', '');
  484. }
  485. },
  486.  
  487. /**
  488. * A utility method to hide node(s)
  489. *
  490. * @method _hideNodes
  491. * @param nodeList {Object} The list of nodes to hide
  492. * @protected
  493. */
  494. _hideNodes: function (nodeList) {
  495. if (nodeList) {
  496. nodeList.addClass(CLASS_HIDDEN).setStyle('visibility', 'hidden');
  497. }
  498. },
  499.  
  500. /**
  501. * Gets a nodeList for the "pages"
  502. *
  503. * @method _getPageNodes
  504. * @protected
  505. * @return {nodeList}
  506. */
  507. _getPageNodes: function () {
  508. var paginator = this,
  509. host = paginator._host,
  510. cb = host._cb,
  511. pageSelector = paginator.get(SELECTOR),
  512. pageNodes = (pageSelector ? cb.all(pageSelector) : cb.get('children'));
  513.  
  514. return pageNodes;
  515. },
  516.  
  517. /**
  518. * Scroll to the next page, with animation
  519. *
  520. * @method next
  521. */
  522. next: function () {
  523. var paginator = this,
  524. scrollview = paginator._host,
  525. index = paginator._cIndex,
  526. target = index + 1,
  527. total = paginator.get(TOTAL);
  528.  
  529. // If the widget is disabled, ignore
  530. if (scrollview.get(DISABLED)) {
  531. return;
  532. }
  533.  
  534. // If the target index is greater than the page count, ignore
  535. if (target >= total) {
  536. return;
  537. }
  538.  
  539. // Update the index
  540. paginator.set(INDEX, target);
  541. },
  542.  
  543. /**
  544. * Scroll to the previous page, with animation
  545. *
  546. * @method prev
  547. */
  548. prev: function () {
  549. var paginator = this,
  550. scrollview = paginator._host,
  551. index = paginator._cIndex,
  552. target = index - 1;
  553.  
  554. // If the widget is disabled, ignore
  555. if (scrollview.get(DISABLED)) {
  556. return;
  557. }
  558.  
  559. // If the target index is before the first page, ignore
  560. if (target < 0) {
  561. return;
  562. }
  563.  
  564. // Update the index
  565. paginator.set(INDEX, target);
  566. },
  567.  
  568. /**
  569. * Deprecated for 3.7.0.
  570. * @method scrollTo
  571. * @deprecated
  572. */
  573. scrollTo: function () {
  574. return this.scrollToIndex.apply(this, arguments);
  575. },
  576.  
  577. /**
  578. * Scroll to a given page in the scrollview
  579. *
  580. * @method scrollToIndex
  581. * @since 3.7.0
  582. * @param index {Number} The index of the page to scroll to
  583. * @param {Number} [duration] The number of ms the animation should last
  584. * @param {String} [easing] The timing function to use in the animation
  585. */
  586. scrollToIndex: function (index, duration, easing) {
  587. var paginator = this,
  588. host = paginator._host,
  589. pageNode = paginator._getPageNodes().item(index),
  590. scrollAxis = (paginator._cAxis[DIM_X] ? SCROLL_X : SCROLL_Y),
  591. scrollOffset = pageNode.get(scrollAxis === SCROLL_X ? 'offsetLeft' : 'offsetTop');
  592.  
  593. duration = (duration !== undefined) ? duration : PaginatorPlugin.TRANSITION.duration;
  594. easing = (easing !== undefined) ? easing : PaginatorPlugin.TRANSITION.easing;
  595.  
  596. // Set the index ATTR to the specified index value
  597. paginator.set(INDEX, index, { src: UI });
  598.  
  599. // Makes sure the viewport nodes are visible
  600. paginator._showNodes(pageNode);
  601.  
  602. // Scroll to the offset
  603. host.set(scrollAxis, scrollOffset, {
  604. duration: duration,
  605. easing: easing
  606. });
  607. },
  608.  
  609. /**
  610. * Setter for 'axis' attribute
  611. *
  612. * @method _axisSetter
  613. * @param val {Mixed} A string ('x', 'y', 'xy') to specify which axis/axes to allow scrolling on
  614. * @param name {String} The attribute name
  615. * @return {Object} An object to specify scrollability on the x & y axes
  616. *
  617. * @protected
  618. */
  619. _axisSetter: function (val) {
  620.  
  621. // Turn a string into an axis object
  622. if (Y.Lang.isString(val)) {
  623. return {
  624. x: (val.match(/x/i) ? true : false),
  625. y: (val.match(/y/i) ? true : false)
  626. };
  627. }
  628. },
  629.  
  630.  
  631. /**
  632. * After listener for the axis attribute
  633. *
  634. * @method _afterAxisChange
  635. * @param e {EventFacade} The event facade
  636. * @protected
  637. */
  638. _afterAxisChange: function (e) {
  639. this._cAxis = e.newVal;
  640. }
  641.  
  642. // End prototype properties
  643.  
  644. }, {
  645.  
  646. // Static properties
  647.  
  648. /**
  649. * The identity of the plugin
  650. *
  651. * @property NAME
  652. * @type String
  653. * @default 'pluginScrollViewPaginator'
  654. * @readOnly
  655. * @protected
  656. * @static
  657. */
  658. NAME: 'pluginScrollViewPaginator',
  659.  
  660. /**
  661. * The namespace on which the plugin will reside
  662. *
  663. * @property NS
  664. * @type String
  665. * @default 'pages'
  666. * @static
  667. */
  668. NS: 'pages',
  669.  
  670. /**
  671. * The default attribute configuration for the plugin
  672. *
  673. * @property ATTRS
  674. * @type {Object}
  675. * @static
  676. */
  677. ATTRS: {
  678.  
  679. /**
  680. * Specifies ability to scroll on x, y, or x and y axis/axes.
  681. * If unspecified, it inherits from the host instance.
  682. *
  683. * @attribute axis
  684. * @type String
  685. */
  686. axis: {
  687. setter: '_axisSetter',
  688. writeOnce: 'initOnly'
  689. },
  690.  
  691. /**
  692. * CSS selector for a page inside the scrollview. The scrollview
  693. * will snap to the closest page.
  694. *
  695. * @attribute selector
  696. * @type {String}
  697. * @default null
  698. */
  699. selector: {
  700. value: null
  701. },
  702.  
  703. /**
  704. * The active page number for a paged scrollview
  705. *
  706. * @attribute index
  707. * @type {Number}
  708. * @default 0
  709. */
  710. index: {
  711. value: 0
  712. },
  713.  
  714. /**
  715. * The total number of pages
  716. *
  717. * @attribute total
  718. * @type {Number}
  719. * @default 0
  720. */
  721. total: {
  722. value: 0
  723. }
  724. },
  725.  
  726. /**
  727. * The default snap to current duration and easing values used on scroll end.
  728. *
  729. * @property SNAP_TO_CURRENT
  730. * @static
  731. */
  732. TRANSITION: {
  733. duration: 300,
  734. easing: 'ease-out'
  735. }
  736.  
  737. // End static properties
  738.  
  739. });
  740.  
  741. Y.namespace('Plugin').ScrollViewPaginator = PaginatorPlugin;
  742.