gallery.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  1. /**
  2. * DevExtreme (ui/gallery.js)
  3. * Version: 19.1.16
  4. * Build date: Tue Oct 18 2022
  5. *
  6. * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
  7. * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
  8. */
  9. "use strict";
  10. var $ = require("../core/renderer");
  11. var eventsEngine = require("../events/core/events_engine");
  12. var registerComponent = require("../core/component_registrator");
  13. var commonUtils = require("../core/utils/common");
  14. var typeUtils = require("../core/utils/type");
  15. var windowUtils = require("../core/utils/window");
  16. var extend = require("../core/utils/extend").extend;
  17. var getPublicElement = require("../core/utils/dom").getPublicElement;
  18. var fx = require("../animation/fx");
  19. var clickEvent = require("../events/click");
  20. var translator = require("../animation/translator");
  21. var devices = require("../core/devices");
  22. var Widget = require("./widget/ui.widget");
  23. var eventUtils = require("../events/utils");
  24. var CollectionWidget = require("./collection/ui.collection_widget.edit");
  25. var Swipeable = require("../events/gesture/swipeable");
  26. var BindableTemplate = require("./widget/bindable_template");
  27. var Deferred = require("../core/utils/deferred").Deferred;
  28. var GALLERY_CLASS = "dx-gallery";
  29. var GALLERY_WRAPPER_CLASS = GALLERY_CLASS + "-wrapper";
  30. var GALLERY_LOOP_CLASS = "dx-gallery-loop";
  31. var GALLERY_ITEM_CONTAINER_CLASS = GALLERY_CLASS + "-container";
  32. var GALLERY_ACTIVE_CLASS = GALLERY_CLASS + "-active";
  33. var GALLERY_ITEM_CLASS = GALLERY_CLASS + "-item";
  34. var GALLERY_INVISIBLE_ITEM_CLASS = GALLERY_CLASS + "-item-invisible";
  35. var GALLERY_LOOP_ITEM_CLASS = GALLERY_ITEM_CLASS + "-loop";
  36. var GALLERY_ITEM_SELECTOR = "." + GALLERY_ITEM_CLASS;
  37. var GALLERY_ITEM_SELECTED_CLASS = GALLERY_ITEM_CLASS + "-selected";
  38. var GALLERY_INDICATOR_CLASS = GALLERY_CLASS + "-indicator";
  39. var GALLERY_INDICATOR_ITEM_CLASS = GALLERY_INDICATOR_CLASS + "-item";
  40. var GALLERY_INDICATOR_ITEM_SELECTOR = "." + GALLERY_INDICATOR_ITEM_CLASS;
  41. var GALLERY_INDICATOR_ITEM_SELECTED_CLASS = GALLERY_INDICATOR_ITEM_CLASS + "-selected";
  42. var GALLERY_IMAGE_CLASS = "dx-gallery-item-image";
  43. var GALLERY_ITEM_DATA_KEY = "dxGalleryItemData";
  44. var MAX_CALC_ERROR = 1;
  45. var GalleryNavButton = Widget.inherit({
  46. _supportedKeys: function() {
  47. return extend(this.callBase(), {
  48. pageUp: commonUtils.noop,
  49. pageDown: commonUtils.noop
  50. })
  51. },
  52. _getDefaultOptions: function() {
  53. return extend(this.callBase(), {
  54. direction: "next",
  55. onClick: null,
  56. hoverStateEnabled: true,
  57. activeStateEnabled: true
  58. })
  59. },
  60. _render: function() {
  61. this.callBase();
  62. var that = this;
  63. var $element = this.$element();
  64. var eventName = eventUtils.addNamespace(clickEvent.name, this.NAME);
  65. $element.addClass(GALLERY_CLASS + "-nav-button-" + this.option("direction"));
  66. eventsEngine.off($element, eventName);
  67. eventsEngine.on($element, eventName, function(e) {
  68. that._createActionByOption("onClick")({
  69. event: e
  70. })
  71. })
  72. },
  73. _optionChanged: function(args) {
  74. switch (args.name) {
  75. case "onClick":
  76. case "direction":
  77. this._invalidate();
  78. break;
  79. default:
  80. this.callBase(args)
  81. }
  82. }
  83. });
  84. var Gallery = CollectionWidget.inherit({
  85. _activeStateUnit: GALLERY_ITEM_SELECTOR,
  86. _getDefaultOptions: function() {
  87. return extend(this.callBase(), {
  88. activeStateEnabled: false,
  89. animationDuration: 400,
  90. animationEnabled: true,
  91. loop: false,
  92. swipeEnabled: true,
  93. indicatorEnabled: true,
  94. showIndicator: true,
  95. selectedIndex: 0,
  96. slideshowDelay: 0,
  97. showNavButtons: false,
  98. wrapAround: false,
  99. initialItemWidth: void 0,
  100. stretchImages: false,
  101. _itemAttributes: {
  102. role: "option"
  103. },
  104. loopItemFocus: false,
  105. selectOnFocus: true,
  106. selectionMode: "single",
  107. selectionRequired: true,
  108. selectionByClick: false
  109. })
  110. },
  111. _defaultOptionsRules: function() {
  112. return this.callBase().concat([{
  113. device: function() {
  114. return "desktop" === devices.real().deviceType && !devices.isSimulator()
  115. },
  116. options: {
  117. focusStateEnabled: true
  118. }
  119. }])
  120. },
  121. _init: function() {
  122. this.callBase();
  123. this.option("loopItemFocus", this.option("loop"))
  124. },
  125. _initTemplates: function() {
  126. this.callBase();
  127. this._defaultTemplates.item = new BindableTemplate(function($container, data) {
  128. var $img = $("<img>").addClass(GALLERY_IMAGE_CLASS);
  129. if (typeUtils.isPlainObject(data)) {
  130. this._prepareDefaultItemTemplate(data, $container);
  131. $img.attr({
  132. src: data.imageSrc,
  133. alt: data.imageAlt
  134. }).appendTo($container)
  135. } else {
  136. $img.attr("src", String(data)).appendTo($container)
  137. }
  138. }.bind(this), ["imageSrc", "imageAlt", "text", "html"], this.option("integrationOptions.watchMethod"))
  139. },
  140. _dataSourceOptions: function() {
  141. return {
  142. paginate: false
  143. }
  144. },
  145. _itemContainer: function() {
  146. return this._$container
  147. },
  148. _itemClass: function() {
  149. return GALLERY_ITEM_CLASS
  150. },
  151. _itemDataKey: function() {
  152. return GALLERY_ITEM_DATA_KEY
  153. },
  154. _actualItemWidth: function() {
  155. var isWrapAround = this.option("wrapAround");
  156. if (this.option("stretchImages")) {
  157. var itemPerPage = isWrapAround ? this._itemsPerPage() + 1 : this._itemsPerPage();
  158. return 1 / itemPerPage
  159. }
  160. if (isWrapAround) {
  161. return this._itemPercentWidth() * this._itemsPerPage() / (this._itemsPerPage() + 1)
  162. }
  163. return this._itemPercentWidth()
  164. },
  165. _itemPercentWidth: function() {
  166. var percentWidth;
  167. var elementWidth = this.$element().outerWidth();
  168. var initialItemWidth = this.option("initialItemWidth");
  169. if (initialItemWidth && initialItemWidth <= elementWidth) {
  170. percentWidth = initialItemWidth / elementWidth
  171. } else {
  172. percentWidth = 1
  173. }
  174. return percentWidth
  175. },
  176. _itemsPerPage: function() {
  177. var itemsPerPage = windowUtils.hasWindow() ? Math.floor(1 / this._itemPercentWidth()) : 1;
  178. return Math.min(itemsPerPage, this._itemsCount())
  179. },
  180. _pagesCount: function() {
  181. return Math.ceil(this._itemsCount() / this._itemsPerPage())
  182. },
  183. _itemsCount: function() {
  184. return (this.option("items") || []).length
  185. },
  186. _offsetDirection: function() {
  187. return this.option("rtlEnabled") ? -1 : 1
  188. },
  189. _initMarkup: function() {
  190. this._renderWrapper();
  191. this._renderItemsContainer();
  192. this.$element().addClass(GALLERY_CLASS);
  193. this.$element().toggleClass(GALLERY_LOOP_CLASS, this.option("loop"));
  194. this.callBase();
  195. this.setAria({
  196. role: "listbox",
  197. label: "gallery"
  198. })
  199. },
  200. _render: function() {
  201. this._renderDragHandler();
  202. this._renderContainerPosition();
  203. this._renderItemSizes();
  204. this._renderItemPositions();
  205. this._renderNavButtons();
  206. this._renderIndicator();
  207. this._renderSelectedItem();
  208. this._renderItemVisibility();
  209. this._renderUserInteraction();
  210. this._setupSlideShow();
  211. this._reviseDimensions();
  212. this.callBase()
  213. },
  214. _dimensionChanged: function() {
  215. var selectedIndex = this.option("selectedIndex") || 0;
  216. this._stopItemAnimations();
  217. this._clearCacheWidth();
  218. this._cloneDuplicateItems();
  219. this._renderItemSizes();
  220. this._renderItemPositions();
  221. this._renderIndicator();
  222. this._renderContainerPosition(this._calculateIndexOffset(selectedIndex), true);
  223. this._renderItemVisibility()
  224. },
  225. _renderDragHandler: function() {
  226. var eventName = eventUtils.addNamespace("dragstart", this.NAME);
  227. eventsEngine.off(this.$element(), eventName);
  228. eventsEngine.on(this.$element(), eventName, "img", function() {
  229. return false
  230. })
  231. },
  232. _renderWrapper: function() {
  233. if (this._$wrapper) {
  234. return
  235. }
  236. this._$wrapper = $("<div>").addClass(GALLERY_WRAPPER_CLASS).appendTo(this.$element())
  237. },
  238. _renderItems: function(items) {
  239. if (!windowUtils.hasWindow()) {
  240. var selectedIndex = this.option("selectedIndex");
  241. items = items.length > selectedIndex ? items.slice(selectedIndex, selectedIndex + 1) : items.slice(0, 1)
  242. }
  243. this.callBase(items);
  244. this._loadNextPageIfNeeded()
  245. },
  246. _renderItemsContainer: function() {
  247. if (this._$container) {
  248. return
  249. }
  250. this._$container = $("<div>").addClass(GALLERY_ITEM_CONTAINER_CLASS).appendTo(this._$wrapper)
  251. },
  252. _cloneDuplicateItems: function() {
  253. if (!this.option("loop")) {
  254. return
  255. }
  256. var items = this.option("items") || [];
  257. var itemsCount = items.length;
  258. var lastItemIndex = itemsCount - 1;
  259. var i;
  260. if (!itemsCount) {
  261. return
  262. }
  263. this._getLoopedItems().remove();
  264. var duplicateCount = Math.min(this._itemsPerPage(), itemsCount);
  265. var $items = this._getRealItems();
  266. var $container = this._itemContainer();
  267. for (i = 0; i < duplicateCount; i++) {
  268. this._cloneItemForDuplicate($items[i], $container)
  269. }
  270. for (i = 0; i < duplicateCount; i++) {
  271. this._cloneItemForDuplicate($items[lastItemIndex - i], $container)
  272. }
  273. },
  274. _cloneItemForDuplicate: function(item, $container) {
  275. if (item) {
  276. $(item).clone(true).addClass(GALLERY_LOOP_ITEM_CLASS).css("margin", 0).appendTo($container)
  277. }
  278. },
  279. _getRealItems: function() {
  280. var selector = "." + GALLERY_ITEM_CLASS + ":not(." + GALLERY_LOOP_ITEM_CLASS + ")";
  281. return this.$element().find(selector)
  282. },
  283. _getLoopedItems: function() {
  284. return this.$element().find("." + GALLERY_LOOP_ITEM_CLASS)
  285. },
  286. _emptyMessageContainer: function() {
  287. return this._$wrapper
  288. },
  289. _renderItemSizes: function(startIndex) {
  290. var $items = this._itemElements();
  291. var itemWidth = this._actualItemWidth();
  292. if (void 0 !== startIndex) {
  293. $items = $items.slice(startIndex)
  294. }
  295. $items.each(function(index) {
  296. $($items[index]).outerWidth(100 * itemWidth + "%")
  297. })
  298. },
  299. _renderItemPositions: function() {
  300. var itemWidth = this._actualItemWidth();
  301. var itemsCount = this._itemsCount();
  302. var itemsPerPage = this._itemsPerPage();
  303. var loopItemsCount = this.$element().find("." + GALLERY_LOOP_ITEM_CLASS).length;
  304. var lastItemDuplicateIndex = itemsCount + loopItemsCount - 1;
  305. var offsetRatio = this.option("wrapAround") ? .5 : 0;
  306. var freeSpace = this._itemFreeSpace();
  307. var isGapBetweenImages = !!freeSpace;
  308. var rtlEnabled = this.option("rtlEnabled");
  309. var selectedIndex = this.option("selectedIndex");
  310. var side = rtlEnabled ? "Right" : "Left";
  311. this._itemElements().each(function(index) {
  312. var realIndex = index;
  313. var isLoopItem = $(this).hasClass(GALLERY_LOOP_ITEM_CLASS);
  314. if (index > itemsCount + itemsPerPage - 1) {
  315. realIndex = lastItemDuplicateIndex - realIndex - itemsPerPage
  316. }
  317. if (!isLoopItem && 0 !== realIndex) {
  318. if (isGapBetweenImages) {
  319. $(this).css("margin" + side, 100 * freeSpace + "%")
  320. }
  321. return
  322. }
  323. var itemPosition = itemWidth * (realIndex + offsetRatio) + freeSpace * (realIndex + 1 - offsetRatio);
  324. var property = isLoopItem ? side.toLowerCase() : "margin" + side;
  325. $(this).css(property, 100 * itemPosition + "%")
  326. });
  327. this._relocateItems(selectedIndex, selectedIndex, true)
  328. },
  329. _itemFreeSpace: function() {
  330. var itemsPerPage = this._itemsPerPage();
  331. if (this.option("wrapAround")) {
  332. itemsPerPage += 1
  333. }
  334. return (1 - this._actualItemWidth() * itemsPerPage) / (itemsPerPage + 1)
  335. },
  336. _renderContainerPosition: function(offset, hideItems, animate) {
  337. this._releaseInvisibleItems();
  338. offset = offset || 0;
  339. var that = this;
  340. var itemWidth = this._actualItemWidth();
  341. var targetIndex = offset;
  342. var targetPosition = this._offsetDirection() * targetIndex * (itemWidth + this._itemFreeSpace());
  343. var positionReady;
  344. if (typeUtils.isDefined(this._animationOverride)) {
  345. animate = this._animationOverride;
  346. delete this._animationOverride
  347. }
  348. if (animate) {
  349. that._startSwipe();
  350. positionReady = that._animate(targetPosition).done(that._endSwipe.bind(that))
  351. } else {
  352. translator.move(this._$container, {
  353. left: targetPosition * this._elementWidth(),
  354. top: 0
  355. });
  356. positionReady = (new Deferred).resolveWith(that)
  357. }
  358. positionReady.done(function() {
  359. this._deferredAnimate && that._deferredAnimate.resolveWith(that);
  360. hideItems && this._renderItemVisibility()
  361. });
  362. return positionReady.promise()
  363. },
  364. _startSwipe: function() {
  365. this.$element().addClass(GALLERY_ACTIVE_CLASS)
  366. },
  367. _endSwipe: function() {
  368. this.$element().removeClass(GALLERY_ACTIVE_CLASS)
  369. },
  370. _animate: function(targetPosition, extraConfig) {
  371. var that = this;
  372. var $container = this._$container;
  373. var animationComplete = new Deferred;
  374. fx.animate(this._$container, extend({
  375. type: "slide",
  376. to: {
  377. left: targetPosition * this._elementWidth()
  378. },
  379. duration: that.option("animationDuration"),
  380. complete: function() {
  381. if (that._needMoveContainerForward()) {
  382. translator.move($container, {
  383. left: 0,
  384. top: 0
  385. })
  386. }
  387. if (that._needMoveContainerBack()) {
  388. translator.move($container, {
  389. left: that._maxContainerOffset() * that._elementWidth(),
  390. top: 0
  391. })
  392. }
  393. animationComplete.resolveWith(that)
  394. }
  395. }, extraConfig || {}));
  396. return animationComplete
  397. },
  398. _needMoveContainerForward: function() {
  399. var expectedPosition = this._$container.position().left * this._offsetDirection();
  400. var actualPosition = -this._maxItemWidth() * this._elementWidth() * this._itemsCount();
  401. return expectedPosition <= actualPosition + MAX_CALC_ERROR
  402. },
  403. _needMoveContainerBack: function() {
  404. var expectedPosition = this._$container.position().left * this._offsetDirection();
  405. var actualPosition = this._actualItemWidth() * this._elementWidth();
  406. return expectedPosition >= actualPosition - MAX_CALC_ERROR
  407. },
  408. _maxContainerOffset: function() {
  409. return -this._maxItemWidth() * (this._itemsCount() - this._itemsPerPage()) * this._offsetDirection()
  410. },
  411. _maxItemWidth: function() {
  412. return this._actualItemWidth() + this._itemFreeSpace()
  413. },
  414. _reviseDimensions: function() {
  415. var that = this;
  416. var $firstItem = that._itemElements().first().find(".dx-item-content");
  417. if (!$firstItem || $firstItem.is(":hidden")) {
  418. return
  419. }
  420. if (!that.option("height")) {
  421. that.option("height", $firstItem.outerHeight())
  422. }
  423. if (!that.option("width")) {
  424. that.option("width", $firstItem.outerWidth())
  425. }
  426. this._dimensionChanged()
  427. },
  428. _renderIndicator: function() {
  429. this._cleanIndicators();
  430. if (!this.option("showIndicator")) {
  431. return
  432. }
  433. var indicator = this._$indicator = $("<div>").addClass(GALLERY_INDICATOR_CLASS).appendTo(this._$wrapper);
  434. for (var i = 0; i < this._pagesCount(); i++) {
  435. $("<div>").addClass(GALLERY_INDICATOR_ITEM_CLASS).appendTo(indicator)
  436. }
  437. this._renderSelectedPageIndicator()
  438. },
  439. _cleanIndicators: function() {
  440. if (this._$indicator) {
  441. this._$indicator.remove()
  442. }
  443. },
  444. _renderSelectedItem: function() {
  445. var selectedIndex = this.option("selectedIndex");
  446. this._itemElements().removeClass(GALLERY_ITEM_SELECTED_CLASS).eq(selectedIndex).addClass(GALLERY_ITEM_SELECTED_CLASS)
  447. },
  448. _renderItemVisibility: function() {
  449. if (this.option("initialItemWidth") || this.option("wrapAround")) {
  450. this._releaseInvisibleItems();
  451. return
  452. }
  453. this._itemElements().each(function(index, item) {
  454. if (this.option("selectedIndex") === index) {
  455. $(item).removeClass(GALLERY_INVISIBLE_ITEM_CLASS)
  456. } else {
  457. $(item).addClass(GALLERY_INVISIBLE_ITEM_CLASS)
  458. }
  459. }.bind(this));
  460. this._getLoopedItems().addClass(GALLERY_INVISIBLE_ITEM_CLASS)
  461. },
  462. _releaseInvisibleItems: function() {
  463. this._itemElements().removeClass(GALLERY_INVISIBLE_ITEM_CLASS);
  464. this._getLoopedItems().removeClass(GALLERY_INVISIBLE_ITEM_CLASS)
  465. },
  466. _renderSelectedPageIndicator: function() {
  467. if (!this._$indicator) {
  468. return
  469. }
  470. var itemIndex = this.option("selectedIndex");
  471. var lastIndex = this._pagesCount() - 1;
  472. var pageIndex = Math.ceil(itemIndex / this._itemsPerPage());
  473. pageIndex = Math.min(lastIndex, pageIndex);
  474. this._$indicator.find(GALLERY_INDICATOR_ITEM_SELECTOR).removeClass(GALLERY_INDICATOR_ITEM_SELECTED_CLASS).eq(pageIndex).addClass(GALLERY_INDICATOR_ITEM_SELECTED_CLASS)
  475. },
  476. _renderUserInteraction: function() {
  477. var rootElement = this.$element();
  478. var swipeEnabled = this.option("swipeEnabled") && this._itemsCount() > 1;
  479. this._createComponent(rootElement, Swipeable, {
  480. disabled: this.option("disabled") || !swipeEnabled,
  481. onStart: this._swipeStartHandler.bind(this),
  482. onUpdated: this._swipeUpdateHandler.bind(this),
  483. onEnd: this._swipeEndHandler.bind(this),
  484. itemSizeFunc: this._elementWidth.bind(this)
  485. });
  486. var indicatorSelectAction = this._createAction(this._indicatorSelectHandler);
  487. eventsEngine.off(rootElement, eventUtils.addNamespace(clickEvent.name, this.NAME), GALLERY_INDICATOR_ITEM_SELECTOR);
  488. eventsEngine.on(rootElement, eventUtils.addNamespace(clickEvent.name, this.NAME), GALLERY_INDICATOR_ITEM_SELECTOR, function(e) {
  489. indicatorSelectAction({
  490. event: e
  491. })
  492. })
  493. },
  494. _indicatorSelectHandler: function(args) {
  495. var e = args.event;
  496. var instance = args.component;
  497. if (!instance.option("indicatorEnabled")) {
  498. return
  499. }
  500. var indicatorIndex = $(e.target).index();
  501. var itemIndex = instance._fitPaginatedIndex(indicatorIndex * instance._itemsPerPage());
  502. instance._needLongMove = true;
  503. instance.option("selectedIndex", itemIndex);
  504. instance._loadNextPageIfNeeded(itemIndex)
  505. },
  506. _renderNavButtons: function() {
  507. var that = this;
  508. if (!that.option("showNavButtons")) {
  509. that._cleanNavButtons();
  510. return
  511. }
  512. that._prevNavButton = $("<div>").appendTo(this._$wrapper);
  513. that._createComponent(that._prevNavButton, GalleryNavButton, {
  514. direction: "prev",
  515. onClick: function() {
  516. that._prevPage()
  517. }
  518. });
  519. that._nextNavButton = $("<div>").appendTo(this._$wrapper);
  520. that._createComponent(that._nextNavButton, GalleryNavButton, {
  521. direction: "next",
  522. onClick: function() {
  523. that._nextPage()
  524. }
  525. });
  526. this._renderNavButtonsVisibility()
  527. },
  528. _prevPage: function() {
  529. var visiblePageSize = this._itemsPerPage();
  530. var newSelectedIndex = this.option("selectedIndex") - visiblePageSize;
  531. if (newSelectedIndex === -visiblePageSize && visiblePageSize === this._itemsCount()) {
  532. return this._relocateItems(newSelectedIndex, 0)
  533. } else {
  534. return this.goToItem(this._fitPaginatedIndex(newSelectedIndex))
  535. }
  536. },
  537. _nextPage: function() {
  538. var visiblePageSize = this._itemsPerPage();
  539. var newSelectedIndex = this.option("selectedIndex") + visiblePageSize;
  540. if (newSelectedIndex === visiblePageSize && visiblePageSize === this._itemsCount()) {
  541. return this._relocateItems(newSelectedIndex, 0)
  542. } else {
  543. return this.goToItem(this._fitPaginatedIndex(newSelectedIndex)).done(this._loadNextPageIfNeeded)
  544. }
  545. },
  546. _loadNextPageIfNeeded: function(selectedIndex) {
  547. selectedIndex = void 0 === selectedIndex ? this.option("selectedIndex") : selectedIndex;
  548. if (this._dataSource && this._dataSource.paginate() && this._shouldLoadNextPage(selectedIndex) && !this._isDataSourceLoading() && !this._isLastPage()) {
  549. this._loadNextPage().done(function() {
  550. this._renderIndicator();
  551. this._renderItemPositions();
  552. this._renderNavButtonsVisibility();
  553. this._renderItemSizes(selectedIndex)
  554. }.bind(this))
  555. }
  556. },
  557. _shouldLoadNextPage: function(selectedIndex) {
  558. var visiblePageSize = this._itemsPerPage();
  559. return selectedIndex + 2 * visiblePageSize > this.option("items").length
  560. },
  561. _allowDynamicItemsAppend: function() {
  562. return true
  563. },
  564. _fitPaginatedIndex: function(itemIndex) {
  565. var itemsPerPage = this._itemsPerPage();
  566. var restItemsCount = itemIndex < 0 ? itemsPerPage + itemIndex : this._itemsCount() - itemIndex;
  567. if (itemIndex > this._itemsCount() - 1) {
  568. itemIndex = 0;
  569. this._goToGhostItem = true
  570. } else {
  571. if (restItemsCount < itemsPerPage && restItemsCount > 0) {
  572. if (itemIndex > 0) {
  573. itemIndex -= itemsPerPage - restItemsCount
  574. } else {
  575. itemIndex += itemsPerPage - restItemsCount
  576. }
  577. }
  578. }
  579. return itemIndex
  580. },
  581. _cleanNavButtons: function() {
  582. if (this._prevNavButton) {
  583. this._prevNavButton.remove();
  584. delete this._prevNavButton
  585. }
  586. if (this._nextNavButton) {
  587. this._nextNavButton.remove();
  588. delete this._nextNavButton
  589. }
  590. },
  591. _renderNavButtonsVisibility: function() {
  592. if (!this.option("showNavButtons") || !this._prevNavButton || !this._nextNavButton) {
  593. return
  594. }
  595. var selectedIndex = this.option("selectedIndex");
  596. var loop = this.option("loop");
  597. var itemsCount = this._itemsCount();
  598. this._prevNavButton.show();
  599. this._nextNavButton.show();
  600. if (0 === itemsCount) {
  601. this._prevNavButton.hide();
  602. this._nextNavButton.hide()
  603. }
  604. if (loop) {
  605. return
  606. }
  607. var nextHidden = selectedIndex === itemsCount - this._itemsPerPage();
  608. var prevHidden = itemsCount < 2 || 0 === selectedIndex;
  609. if (this._dataSource && this._dataSource.paginate()) {
  610. nextHidden = nextHidden && this._isLastPage()
  611. } else {
  612. nextHidden = nextHidden || itemsCount < 2
  613. }
  614. if (prevHidden) {
  615. this._prevNavButton.hide()
  616. }
  617. if (nextHidden) {
  618. this._nextNavButton.hide()
  619. }
  620. },
  621. _setupSlideShow: function() {
  622. var that = this;
  623. var slideshowDelay = that.option("slideshowDelay");
  624. clearTimeout(that._slideshowTimer);
  625. if (!slideshowDelay) {
  626. return
  627. }
  628. that._slideshowTimer = setTimeout(function() {
  629. if (that._userInteraction) {
  630. that._setupSlideShow();
  631. return
  632. }
  633. that.nextItem(true).done(that._setupSlideShow)
  634. }, slideshowDelay)
  635. },
  636. _elementWidth: function() {
  637. if (!this._cacheElementWidth) {
  638. this._cacheElementWidth = this.$element().width()
  639. }
  640. return this._cacheElementWidth
  641. },
  642. _clearCacheWidth: function() {
  643. delete this._cacheElementWidth
  644. },
  645. _swipeStartHandler: function(e) {
  646. this._releaseInvisibleItems();
  647. this._clearCacheWidth();
  648. this._elementWidth();
  649. var itemsCount = this._itemsCount();
  650. if (!itemsCount) {
  651. e.event.cancel = true;
  652. return
  653. }
  654. this._stopItemAnimations();
  655. this._startSwipe();
  656. this._userInteraction = true;
  657. if (!this.option("loop")) {
  658. var selectedIndex = this.option("selectedIndex");
  659. var startOffset = itemsCount - selectedIndex - this._itemsPerPage();
  660. var endOffset = selectedIndex;
  661. var rtlEnabled = this.option("rtlEnabled");
  662. e.event.maxLeftOffset = rtlEnabled ? endOffset : startOffset;
  663. e.event.maxRightOffset = rtlEnabled ? startOffset : endOffset
  664. }
  665. },
  666. _stopItemAnimations: function() {
  667. fx.stop(this._$container, true)
  668. },
  669. _swipeUpdateHandler: function(e) {
  670. var wrapAroundRatio = this.option("wrapAround") ? 1 : 0;
  671. var offset = this._offsetDirection() * e.event.offset * (this._itemsPerPage() + wrapAroundRatio) - this.option("selectedIndex");
  672. if (offset < 0) {
  673. this._loadNextPageIfNeeded(Math.ceil(Math.abs(offset)))
  674. }
  675. this._renderContainerPosition(offset)
  676. },
  677. _swipeEndHandler: function(e) {
  678. var targetOffset = e.event.targetOffset * this._offsetDirection() * this._itemsPerPage();
  679. var selectedIndex = this.option("selectedIndex");
  680. var newIndex = this._fitIndex(selectedIndex - targetOffset);
  681. var paginatedIndex = this._fitPaginatedIndex(newIndex);
  682. if (Math.abs(targetOffset) < this._itemsPerPage()) {
  683. this._relocateItems(selectedIndex);
  684. return
  685. }
  686. if (this._itemsPerPage() === this._itemsCount()) {
  687. if (targetOffset > 0) {
  688. this._relocateItems(-targetOffset)
  689. } else {
  690. this._relocateItems(0)
  691. }
  692. return
  693. }
  694. this.option("selectedIndex", paginatedIndex)
  695. },
  696. _setFocusOnSelect: function() {
  697. this._userInteraction = true;
  698. var selectedItem = this.itemElements().filter("." + GALLERY_ITEM_SELECTED_CLASS);
  699. this.option("focusedElement", getPublicElement(selectedItem));
  700. this._userInteraction = false
  701. },
  702. _flipIndex: function(index) {
  703. var itemsCount = this._itemsCount();
  704. index %= itemsCount;
  705. if (index > (itemsCount + 1) / 2) {
  706. index -= itemsCount
  707. }
  708. if (index < -(itemsCount - 1) / 2) {
  709. index += itemsCount
  710. }
  711. return index
  712. },
  713. _fitIndex: function(index) {
  714. if (!this.option("loop")) {
  715. return index
  716. }
  717. var itemsCount = this._itemsCount();
  718. if (index >= itemsCount || index < 0) {
  719. this._goToGhostItem = true
  720. }
  721. if (index >= itemsCount) {
  722. index = itemsCount - index
  723. }
  724. index %= itemsCount;
  725. if (index < 0) {
  726. index += itemsCount
  727. }
  728. return index
  729. },
  730. _clean: function() {
  731. this.callBase();
  732. this._cleanIndicators();
  733. this._cleanNavButtons()
  734. },
  735. _dispose: function() {
  736. clearTimeout(this._slideshowTimer);
  737. this.callBase()
  738. },
  739. _updateSelection: function(addedSelection, removedSelection) {
  740. this._stopItemAnimations();
  741. this._renderNavButtonsVisibility();
  742. this._renderSelectedItem();
  743. this._relocateItems(addedSelection[0], removedSelection[0]);
  744. this._renderSelectedPageIndicator()
  745. },
  746. _relocateItems: function(newIndex, prevIndex, withoutAnimation) {
  747. if (void 0 === prevIndex) {
  748. prevIndex = newIndex
  749. }
  750. var indexOffset = this._calculateIndexOffset(newIndex, prevIndex);
  751. this._renderContainerPosition(indexOffset, true, this.option("animationEnabled") && !withoutAnimation).done(function() {
  752. this._setFocusOnSelect();
  753. this._userInteraction = false;
  754. this._setupSlideShow()
  755. })
  756. },
  757. _focusInHandler: function() {
  758. if (fx.isAnimating(this._$container) || this._userInteraction) {
  759. return
  760. }
  761. this.callBase.apply(this, arguments)
  762. },
  763. _focusOutHandler: function() {
  764. if (fx.isAnimating(this._$container) || this._userInteraction) {
  765. return
  766. }
  767. this.callBase.apply(this, arguments)
  768. },
  769. _selectFocusedItem: commonUtils.noop,
  770. _moveFocus: function() {
  771. this._stopItemAnimations();
  772. this.callBase.apply(this, arguments);
  773. var index = this.itemElements().index($(this.option("focusedElement")));
  774. this.goToItem(index, this.option("animationEnabled"))
  775. },
  776. _visibilityChanged: function(visible) {
  777. if (visible) {
  778. this._reviseDimensions()
  779. }
  780. },
  781. _calculateIndexOffset: function(newIndex, lastIndex) {
  782. if (void 0 === lastIndex) {
  783. lastIndex = newIndex
  784. }
  785. var indexOffset = lastIndex - newIndex;
  786. if (this.option("loop") && !this._needLongMove && this._goToGhostItem) {
  787. if (this._isItemOnFirstPage(newIndex) && this._isItemOnLastPage(lastIndex)) {
  788. indexOffset = -this._itemsPerPage()
  789. } else {
  790. if (this._isItemOnLastPage(newIndex) && this._isItemOnFirstPage(lastIndex)) {
  791. indexOffset = this._itemsPerPage()
  792. }
  793. }
  794. this._goToGhostItem = false
  795. }
  796. this._needLongMove = false;
  797. indexOffset -= lastIndex;
  798. return indexOffset
  799. },
  800. _isItemOnLastPage: function(itemIndex) {
  801. return itemIndex >= this._itemsCount() - this._itemsPerPage()
  802. },
  803. _isItemOnFirstPage: function(itemIndex) {
  804. return itemIndex <= this._itemsPerPage()
  805. },
  806. _optionChanged: function(args) {
  807. switch (args.name) {
  808. case "width":
  809. case "initialItemWidth":
  810. this.callBase.apply(this, arguments);
  811. this._dimensionChanged();
  812. break;
  813. case "animationDuration":
  814. this._renderNavButtonsVisibility();
  815. break;
  816. case "animationEnabled":
  817. break;
  818. case "loop":
  819. this.$element().toggleClass(GALLERY_LOOP_CLASS, args.value);
  820. this.option("loopItemFocus", args.value);
  821. if (windowUtils.hasWindow()) {
  822. this._cloneDuplicateItems();
  823. this._renderItemPositions();
  824. this._renderNavButtonsVisibility()
  825. }
  826. break;
  827. case "showIndicator":
  828. this._renderIndicator();
  829. break;
  830. case "showNavButtons":
  831. this._renderNavButtons();
  832. break;
  833. case "slideshowDelay":
  834. this._setupSlideShow();
  835. break;
  836. case "wrapAround":
  837. case "stretchImages":
  838. if (windowUtils.hasWindow()) {
  839. this._renderItemSizes();
  840. this._renderItemPositions();
  841. this._renderItemVisibility()
  842. }
  843. break;
  844. case "swipeEnabled":
  845. case "indicatorEnabled":
  846. this._renderUserInteraction();
  847. break;
  848. default:
  849. this.callBase(args)
  850. }
  851. },
  852. goToItem: function(itemIndex, animation) {
  853. var selectedIndex = this.option("selectedIndex");
  854. var itemsCount = this._itemsCount();
  855. if (void 0 !== animation) {
  856. this._animationOverride = animation
  857. }
  858. itemIndex = this._fitIndex(itemIndex);
  859. this._deferredAnimate = new Deferred;
  860. if (itemIndex > itemsCount - 1 || itemIndex < 0 || selectedIndex === itemIndex) {
  861. return this._deferredAnimate.resolveWith(this).promise()
  862. }
  863. this.option("selectedIndex", itemIndex);
  864. return this._deferredAnimate.promise()
  865. },
  866. prevItem: function(animation) {
  867. return this.goToItem(this.option("selectedIndex") - 1, animation)
  868. },
  869. nextItem: function(animation) {
  870. return this.goToItem(this.option("selectedIndex") + 1, animation)
  871. }
  872. });
  873. registerComponent("dxGallery", Gallery);
  874. module.exports = Gallery;
  875. module.exports.default = module.exports;