Version 3.18.1
Show:

File: scrollview/js/scrollbars-plugin.js

  1. /**
  2. * Provides a plugin, which adds support for a scroll indicator to ScrollView instances
  3. *
  4. * @module scrollview
  5. * @submodule scrollview-scrollbars
  6. */
  7. var getClassName = Y.ClassNameManager.getClassName,
  8. _classNames,
  9. Transition = Y.Transition,
  10. NATIVE_TRANSITIONS = Transition.useNative,
  11. SCROLLBAR = 'scrollbar',
  12. SCROLLVIEW = 'scrollview',
  13. VERTICAL_NODE = "verticalNode",
  14. HORIZONTAL_NODE = "horizontalNode",
  15. CHILD_CACHE = "childCache",
  16. TOP = "top",
  17. LEFT = "left",
  18. WIDTH = "width",
  19. HEIGHT = "height",
  20. HORIZ_CACHE = "_sbh",
  21. VERT_CACHE = "_sbv",
  22. TRANSITION_PROPERTY = Y.ScrollView._TRANSITION.PROPERTY,
  23. TRANSFORM = "transform",
  24. TRANSLATE_X = "translateX(",
  25. TRANSLATE_Y = "translateY(",
  26. SCALE_X = "scaleX(",
  27. SCALE_Y = "scaleY(",
  28. SCROLL_X = "scrollX",
  29. SCROLL_Y = "scrollY",
  30. PX = "px",
  31. CLOSE = ")",
  32. PX_CLOSE = PX + CLOSE;
  33. /**
  34. * ScrollView plugin that adds scroll indicators to ScrollView instances
  35. *
  36. * @class ScrollViewScrollbars
  37. * @namespace Plugin
  38. * @extends Plugin.Base
  39. * @constructor
  40. */
  41. function ScrollbarsPlugin() {
  42. ScrollbarsPlugin.superclass.constructor.apply(this, arguments);
  43. }
  44. ScrollbarsPlugin.CLASS_NAMES = {
  45. showing: getClassName(SCROLLVIEW, SCROLLBAR, 'showing'),
  46. scrollbar: getClassName(SCROLLVIEW, SCROLLBAR),
  47. scrollbarV: getClassName(SCROLLVIEW, SCROLLBAR, 'vert'),
  48. scrollbarH: getClassName(SCROLLVIEW, SCROLLBAR, 'horiz'),
  49. scrollbarVB: getClassName(SCROLLVIEW, SCROLLBAR, 'vert', 'basic'),
  50. scrollbarHB: getClassName(SCROLLVIEW, SCROLLBAR, 'horiz', 'basic'),
  51. child: getClassName(SCROLLVIEW, 'child'),
  52. first: getClassName(SCROLLVIEW, 'first'),
  53. middle: getClassName(SCROLLVIEW, 'middle'),
  54. last: getClassName(SCROLLVIEW, 'last')
  55. };
  56. _classNames = ScrollbarsPlugin.CLASS_NAMES;
  57. /**
  58. * The identity of the plugin
  59. *
  60. * @property NAME
  61. * @type String
  62. * @default 'pluginScrollViewScrollbars'
  63. * @static
  64. */
  65. ScrollbarsPlugin.NAME = 'pluginScrollViewScrollbars';
  66. /**
  67. * The namespace on which the plugin will reside.
  68. *
  69. * @property NS
  70. * @type String
  71. * @default 'scrollbars'
  72. * @static
  73. */
  74. ScrollbarsPlugin.NS = 'scrollbars';
  75. /**
  76. * HTML template for the scrollbar
  77. *
  78. * @property SCROLLBAR_TEMPLATE
  79. * @type Object
  80. * @static
  81. */
  82. ScrollbarsPlugin.SCROLLBAR_TEMPLATE = [
  83. '<div>',
  84. '<span class="' + _classNames.child + ' ' + _classNames.first + '"></span>',
  85. '<span class="' + _classNames.child + ' ' + _classNames.middle + '"></span>',
  86. '<span class="' + _classNames.child + ' ' + _classNames.last + '"></span>',
  87. '</div>'
  88. ].join('');
  89. /**
  90. * The default attribute configuration for the plugin
  91. *
  92. * @property ATTRS
  93. * @type Object
  94. * @static
  95. */
  96. ScrollbarsPlugin.ATTRS = {
  97. /**
  98. * Vertical scrollbar node
  99. *
  100. * @attribute verticalNode
  101. * @type Y.Node
  102. */
  103. verticalNode: {
  104. setter: '_setNode',
  105. valueFn: '_defaultNode'
  106. },
  107. /**
  108. * Horizontal scrollbar node
  109. *
  110. * @attribute horizontalNode
  111. * @type Y.Node
  112. */
  113. horizontalNode: {
  114. setter: '_setNode',
  115. valueFn: '_defaultNode'
  116. }
  117. };
  118. Y.namespace("Plugin").ScrollViewScrollbars = Y.extend(ScrollbarsPlugin, Y.Plugin.Base, {
  119. /**
  120. * Designated initializer
  121. *
  122. * @method initializer
  123. */
  124. initializer: function() {
  125. this._host = this.get("host");
  126. this.afterHostEvent('scrollEnd', this._hostScrollEnd);
  127. this.afterHostMethod('scrollTo', this._update);
  128. this.afterHostMethod('_uiDimensionsChange', this._hostDimensionsChange);
  129. },
  130. /**
  131. * Set up the DOM nodes for the scrollbars. This method is invoked whenever the
  132. * host's _uiDimensionsChange fires, giving us the opportunity to remove un-needed
  133. * scrollbars, as well as add one if necessary.
  134. *
  135. * @method _hostDimensionsChange
  136. * @protected
  137. */
  138. _hostDimensionsChange: function() {
  139. var host = this._host,
  140. axis = host._cAxis,
  141. scrollX = host.get(SCROLL_X),
  142. scrollY = host.get(SCROLL_Y);
  143. this._dims = host._getScrollDims();
  144. if (axis && axis.y) {
  145. this._renderBar(this.get(VERTICAL_NODE), true, 'vert');
  146. }
  147. if (axis && axis.x) {
  148. this._renderBar(this.get(HORIZONTAL_NODE), true, 'horiz');
  149. }
  150. this._update(scrollX, scrollY);
  151. Y.later(500, this, 'flash', true);
  152. },
  153. /**
  154. * Handler for the scrollEnd event fired by the host. Default implementation flashes the scrollbar
  155. *
  156. * @method _hostScrollEnd
  157. * @param {EventFacade} e The event facade.
  158. * @protected
  159. */
  160. _hostScrollEnd : function() {
  161. var host = this._host,
  162. scrollX = host.get(SCROLL_X),
  163. scrollY = host.get(SCROLL_Y);
  164. this.flash();
  165. this._update(scrollX, scrollY);
  166. },
  167. /**
  168. * Adds or removes a scrollbar node from the document.
  169. *
  170. * @method _renderBar
  171. * @private
  172. * @param {Node} bar The scrollbar node
  173. * @param {boolean} add true, to add the node, false to remove it
  174. */
  175. _renderBar: function(bar, add) {
  176. var inDoc = bar.inDoc(),
  177. bb = this._host._bb,
  178. className = bar.getData("isHoriz") ? _classNames.scrollbarHB : _classNames.scrollbarVB;
  179. if (add && !inDoc) {
  180. bb.append(bar);
  181. bar.toggleClass(className, this._basic);
  182. this._setChildCache(bar);
  183. } else if(!add && inDoc) {
  184. bar.remove();
  185. this._clearChildCache(bar);
  186. }
  187. },
  188. /**
  189. * Caches scrollbar child element information,
  190. * to optimize _update implementation
  191. *
  192. * @method _setChildCache
  193. * @private
  194. * @param {Node} node
  195. */
  196. _setChildCache : function(node) {
  197. var c = node.get("children"),
  198. fc = c.item(0),
  199. mc = c.item(1),
  200. lc = c.item(2),
  201. size = node.getData("isHoriz") ? "offsetWidth" : "offsetHeight";
  202. node.setStyle(TRANSITION_PROPERTY, TRANSFORM);
  203. mc.setStyle(TRANSITION_PROPERTY, TRANSFORM);
  204. lc.setStyle(TRANSITION_PROPERTY, TRANSFORM);
  205. node.setData(CHILD_CACHE, {
  206. fc : fc,
  207. lc : lc,
  208. mc : mc,
  209. fcSize : fc && fc.get(size),
  210. lcSize : lc && lc.get(size)
  211. });
  212. },
  213. /**
  214. * Clears child cache
  215. *
  216. * @method _clearChildCache
  217. * @private
  218. * @param {Node} node
  219. */
  220. _clearChildCache : function(node) {
  221. node.clearData(CHILD_CACHE);
  222. },
  223. /**
  224. * Utility method, to move/resize either vertical or horizontal scrollbars
  225. *
  226. * @method _updateBar
  227. * @private
  228. *
  229. * @param {Node} scrollbar The scrollbar node.
  230. * @param {Number} current The current scroll position.
  231. * @param {Number} duration The transition duration.
  232. * @param {boolean} horiz true if horizontal, false if vertical.
  233. */
  234. _updateBar : function(scrollbar, current, duration, horiz) {
  235. var host = this._host,
  236. basic = this._basic,
  237. scrollbarSize = 0,
  238. scrollbarPos = 1,
  239. childCache = scrollbar.getData(CHILD_CACHE),
  240. lastChild = childCache.lc,
  241. middleChild = childCache.mc,
  242. firstChildSize = childCache.fcSize,
  243. lastChildSize = childCache.lcSize,
  244. middleChildSize,
  245. lastChildPosition,
  246. transition,
  247. translate,
  248. scale,
  249. dim,
  250. dimOffset,
  251. dimCache,
  252. widgetSize,
  253. contentSize;
  254. if (horiz) {
  255. dim = WIDTH;
  256. dimOffset = LEFT;
  257. dimCache = HORIZ_CACHE;
  258. widgetSize = this._dims.offsetWidth;
  259. contentSize = this._dims.scrollWidth;
  260. translate = TRANSLATE_X;
  261. scale = SCALE_X;
  262. current = (current !== undefined) ? current : host.get(SCROLL_X);
  263. } else {
  264. dim = HEIGHT;
  265. dimOffset = TOP;
  266. dimCache = VERT_CACHE;
  267. widgetSize = this._dims.offsetHeight;
  268. contentSize = this._dims.scrollHeight;
  269. translate = TRANSLATE_Y;
  270. scale = SCALE_Y;
  271. current = (current !== undefined) ? current : host.get(SCROLL_Y);
  272. }
  273. scrollbarSize = Math.floor(widgetSize * (widgetSize/contentSize));
  274. scrollbarPos = Math.floor((current/(contentSize - widgetSize)) * (widgetSize - scrollbarSize));
  275. if (scrollbarSize > widgetSize) {
  276. scrollbarSize = 1;
  277. }
  278. if (scrollbarPos > (widgetSize - scrollbarSize)) {
  279. scrollbarSize = scrollbarSize - (scrollbarPos - (widgetSize - scrollbarSize));
  280. } else if (scrollbarPos < 0) {
  281. scrollbarSize = scrollbarPos + scrollbarSize;
  282. scrollbarPos = 0;
  283. } else if (isNaN(scrollbarPos)) {
  284. scrollbarPos = 0;
  285. }
  286. middleChildSize = (scrollbarSize - (firstChildSize + lastChildSize));
  287. if (middleChildSize < 0) {
  288. middleChildSize = 0;
  289. }
  290. if (middleChildSize === 0 && scrollbarPos !== 0) {
  291. scrollbarPos = widgetSize - (firstChildSize + lastChildSize) - 1;
  292. }
  293. if (duration !== 0) {
  294. // Position Scrollbar
  295. transition = {
  296. duration : duration
  297. };
  298. if (NATIVE_TRANSITIONS) {
  299. transition.transform = translate + scrollbarPos + PX_CLOSE;
  300. } else {
  301. transition[dimOffset] = scrollbarPos + PX;
  302. }
  303. scrollbar.transition(transition);
  304. } else {
  305. if (NATIVE_TRANSITIONS) {
  306. scrollbar.setStyle(TRANSFORM, translate + scrollbarPos + PX_CLOSE);
  307. } else {
  308. scrollbar.setStyle(dimOffset, scrollbarPos + PX);
  309. }
  310. }
  311. // Resize Scrollbar Middle Child
  312. if (this[dimCache] !== middleChildSize) {
  313. this[dimCache] = middleChildSize;
  314. if (middleChildSize > 0) {
  315. if (duration !== 0) {
  316. transition = {
  317. duration : duration
  318. };
  319. if(NATIVE_TRANSITIONS) {
  320. transition.transform = scale + middleChildSize + CLOSE;
  321. } else {
  322. transition[dim] = middleChildSize + PX;
  323. }
  324. middleChild.transition(transition);
  325. } else {
  326. if (NATIVE_TRANSITIONS) {
  327. middleChild.setStyle(TRANSFORM, scale + middleChildSize + CLOSE);
  328. } else {
  329. middleChild.setStyle(dim, middleChildSize + PX);
  330. }
  331. }
  332. // Position Last Child
  333. if (!horiz || !basic) {
  334. lastChildPosition = scrollbarSize - lastChildSize;
  335. if(duration !== 0) {
  336. transition = {
  337. duration : duration
  338. };
  339. if (NATIVE_TRANSITIONS) {
  340. transition.transform = translate + lastChildPosition + PX_CLOSE;
  341. } else {
  342. transition[dimOffset] = lastChildPosition;
  343. }
  344. lastChild.transition(transition);
  345. } else {
  346. if (NATIVE_TRANSITIONS) {
  347. lastChild.setStyle(TRANSFORM, translate + lastChildPosition + PX_CLOSE);
  348. } else {
  349. lastChild.setStyle(dimOffset, lastChildPosition + PX);
  350. }
  351. }
  352. }
  353. }
  354. }
  355. },
  356. /**
  357. * AOP method, invoked after the host's _uiScrollTo method,
  358. * to position and resize the scroll bars
  359. *
  360. * @method _update
  361. * @param x {Number} The current scrollX value
  362. * @param y {Number} The current scrollY value
  363. * @param duration {Number} Number of ms of animation (optional) - used when snapping to bounds
  364. * @param easing {String} Optional easing equation to use during the animation, if duration is set
  365. * @protected
  366. */
  367. _update: function(x, y, duration) {
  368. var vNode = this.get(VERTICAL_NODE),
  369. hNode = this.get(HORIZONTAL_NODE),
  370. host = this._host,
  371. axis = host._cAxis;
  372. duration = (duration || 0)/1000;
  373. if (!this._showing) {
  374. this.show();
  375. }
  376. if (axis && axis.y && vNode && y !== null) {
  377. this._updateBar(vNode, y, duration, false);
  378. }
  379. if (axis && axis.x && hNode && x !== null) {
  380. this._updateBar(hNode, x, duration, true);
  381. }
  382. },
  383. /**
  384. * Show the scroll bar indicators
  385. *
  386. * @method show
  387. * @param animated {Boolean} Whether or not to animate the showing
  388. */
  389. show: function(animated) {
  390. this._show(true, animated);
  391. },
  392. /**
  393. * Hide the scroll bar indicators
  394. *
  395. * @method hide
  396. * @param animated {Boolean} Whether or not to animate the hiding
  397. */
  398. hide: function(animated) {
  399. this._show(false, animated);
  400. },
  401. /**
  402. * Internal hide/show implementation utility method
  403. *
  404. * @method _show
  405. * @param {boolean} show Whether to show or hide the scrollbar
  406. * @param {bolean} animated Whether or not to animate while showing/hide
  407. * @protected
  408. */
  409. _show : function(show, animated) {
  410. var verticalNode = this.get(VERTICAL_NODE),
  411. horizontalNode = this.get(HORIZONTAL_NODE),
  412. duration = (animated) ? 0.6 : 0,
  413. opacity = (show) ? 1 : 0,
  414. transition;
  415. this._showing = show;
  416. if (this._flashTimer) {
  417. this._flashTimer.cancel();
  418. }
  419. transition = {
  420. duration : duration,
  421. opacity : opacity
  422. };
  423. if (verticalNode && verticalNode._node) {
  424. verticalNode.transition(transition);
  425. }
  426. if (horizontalNode && horizontalNode._node) {
  427. horizontalNode.transition(transition);
  428. }
  429. },
  430. /**
  431. * Momentarily flash the scroll bars to indicate current scroll position
  432. *
  433. * @method flash
  434. */
  435. flash: function() {
  436. this.show(true);
  437. this._flashTimer = Y.later(800, this, 'hide', true);
  438. },
  439. /**
  440. * Setter for the verticalNode and horizontalNode attributes
  441. *
  442. * @method _setNode
  443. * @param node {Node} The Y.Node instance for the scrollbar
  444. * @param name {String} The attribute name
  445. * @return {Node} The Y.Node instance for the scrollbar
  446. *
  447. * @protected
  448. */
  449. _setNode: function(node, name) {
  450. var horiz = (name === HORIZONTAL_NODE);
  451. node = Y.one(node);
  452. if (node) {
  453. node.addClass(_classNames.scrollbar);
  454. node.addClass( (horiz) ? _classNames.scrollbarH : _classNames.scrollbarV );
  455. node.setData("isHoriz", horiz);
  456. }
  457. return node;
  458. },
  459. /**
  460. * Creates default node instances for scrollbars
  461. *
  462. * @method _defaultNode
  463. * @return {Node} The Y.Node instance for the scrollbar
  464. *
  465. * @protected
  466. */
  467. _defaultNode: function() {
  468. return Y.Node.create(ScrollbarsPlugin.SCROLLBAR_TEMPLATE);
  469. },
  470. _basic: Y.UA.ie && Y.UA.ie <= 8
  471. });