ui.list.base.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. /**
  2. * DevExtreme (ui/list/ui.list.base.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 commonUtils = require("../../core/utils/common");
  13. var typeUtils = require("../../core/utils/type");
  14. var iconUtils = require("../../core/utils/icon");
  15. var getPublicElement = require("../../core/utils/dom").getPublicElement;
  16. var each = require("../../core/utils/iterator").each;
  17. var compileGetter = require("../../core/utils/data").compileGetter;
  18. var extend = require("../../core/utils/extend").extend;
  19. var fx = require("../../animation/fx");
  20. var clickEvent = require("../../events/click");
  21. var swipeEvents = require("../../events/swipe");
  22. var support = require("../../core/utils/support");
  23. var messageLocalization = require("../../localization/message");
  24. var inkRipple = require("../widget/utils.ink_ripple");
  25. var devices = require("../../core/devices");
  26. var ListItem = require("./item");
  27. var Button = require("../button");
  28. var eventUtils = require("../../events/utils");
  29. var themes = require("../themes");
  30. var windowUtils = require("../../core/utils/window");
  31. var ScrollView = require("../scroll_view");
  32. var deviceDependentOptions = require("../scroll_view/ui.scrollable").deviceDependentOptions;
  33. var CollectionWidget = require("../collection/ui.collection_widget.live_update").default;
  34. var BindableTemplate = require("../widget/bindable_template");
  35. var Deferred = require("../../core/utils/deferred").Deferred;
  36. var DataConverterMixin = require("../shared/grouped_data_converter_mixin").default;
  37. var LIST_CLASS = "dx-list";
  38. var LIST_ITEM_CLASS = "dx-list-item";
  39. var LIST_ITEM_SELECTOR = "." + LIST_ITEM_CLASS;
  40. var LIST_ITEM_ICON_CONTAINER_CLASS = "dx-list-item-icon-container";
  41. var LIST_ITEM_ICON_CLASS = "dx-list-item-icon";
  42. var LIST_GROUP_CLASS = "dx-list-group";
  43. var LIST_GROUP_HEADER_CLASS = "dx-list-group-header";
  44. var LIST_GROUP_BODY_CLASS = "dx-list-group-body";
  45. var LIST_COLLAPSIBLE_GROUPS_CLASS = "dx-list-collapsible-groups";
  46. var LIST_GROUP_COLLAPSED_CLASS = "dx-list-group-collapsed";
  47. var LIST_GROUP_HEADER_INDICATOR_CLASS = "dx-list-group-header-indicator";
  48. var LIST_HAS_NEXT_CLASS = "dx-has-next";
  49. var LIST_NEXT_BUTTON_CLASS = "dx-list-next-button";
  50. var SELECT_ALL_ITEM_SELECTOR = ".dx-list-select-all";
  51. var LIST_ITEM_DATA_KEY = "dxListItemData";
  52. var LIST_FEEDBACK_SHOW_TIMEOUT = 70;
  53. var groupItemsGetter = compileGetter("items");
  54. var ListBase = CollectionWidget.inherit({
  55. _activeStateUnit: [LIST_ITEM_SELECTOR, SELECT_ALL_ITEM_SELECTOR].join(","),
  56. _supportedKeys: function() {
  57. var that = this;
  58. var moveFocusPerPage = function(direction) {
  59. var $item = getEdgeVisibleItem(direction);
  60. var isFocusedItem = $item.is(that.option("focusedElement"));
  61. if (isFocusedItem) {
  62. scrollListTo($item, direction);
  63. $item = getEdgeVisibleItem(direction)
  64. }
  65. that.option("focusedElement", getPublicElement($item));
  66. that.scrollToItem($item)
  67. };
  68. var getEdgeVisibleItem = function(direction) {
  69. var scrollTop = that.scrollTop();
  70. var containerHeight = that.$element().height();
  71. var $item = $(that.option("focusedElement"));
  72. var isItemVisible = true;
  73. if (!$item.length) {
  74. return $()
  75. }
  76. while (isItemVisible) {
  77. var $nextItem = $item[direction]();
  78. if (!$nextItem.length) {
  79. break
  80. }
  81. var nextItemLocation = $nextItem.position().top + $nextItem.outerHeight() / 2;
  82. isItemVisible = nextItemLocation < containerHeight + scrollTop && nextItemLocation > scrollTop;
  83. if (isItemVisible) {
  84. $item = $nextItem
  85. }
  86. }
  87. return $item
  88. };
  89. var scrollListTo = function($item, direction) {
  90. var resultPosition = $item.position().top;
  91. if ("prev" === direction) {
  92. resultPosition = $item.position().top - that.$element().height() + $item.outerHeight()
  93. }
  94. that.scrollTo(resultPosition)
  95. };
  96. return extend(this.callBase(), {
  97. leftArrow: commonUtils.noop,
  98. rightArrow: commonUtils.noop,
  99. pageUp: function() {
  100. moveFocusPerPage("prev");
  101. return false
  102. },
  103. pageDown: function() {
  104. moveFocusPerPage("next");
  105. return false
  106. }
  107. })
  108. },
  109. _getDefaultOptions: function() {
  110. return extend(this.callBase(), {
  111. hoverStateEnabled: true,
  112. pullRefreshEnabled: false,
  113. scrollingEnabled: true,
  114. showScrollbar: "onScroll",
  115. useNativeScrolling: true,
  116. bounceEnabled: true,
  117. scrollByContent: true,
  118. scrollByThumb: false,
  119. pullingDownText: messageLocalization.format("dxList-pullingDownText"),
  120. pulledDownText: messageLocalization.format("dxList-pulledDownText"),
  121. refreshingText: messageLocalization.format("dxList-refreshingText"),
  122. pageLoadingText: messageLocalization.format("dxList-pageLoadingText"),
  123. onScroll: null,
  124. onPullRefresh: null,
  125. onPageLoading: null,
  126. pageLoadMode: "scrollBottom",
  127. nextButtonText: messageLocalization.format("dxList-nextButtonText"),
  128. onItemSwipe: null,
  129. grouped: false,
  130. onGroupRendered: null,
  131. collapsibleGroups: false,
  132. groupTemplate: "group",
  133. indicateLoading: true,
  134. activeStateEnabled: true,
  135. _itemAttributes: {
  136. role: "option"
  137. },
  138. _listAttributes: {
  139. role: "listbox"
  140. },
  141. useInkRipple: false,
  142. showChevronExpr: function(data) {
  143. return data ? data.showChevron : void 0
  144. },
  145. badgeExpr: function(data) {
  146. return data ? data.badge : void 0
  147. }
  148. })
  149. },
  150. _defaultOptionsRules: function() {
  151. var themeName = themes.current();
  152. return this.callBase().concat(deviceDependentOptions(), [{
  153. device: function() {
  154. return !support.nativeScrolling
  155. },
  156. options: {
  157. useNativeScrolling: false
  158. }
  159. }, {
  160. device: function(_device) {
  161. return !support.nativeScrolling && !devices.isSimulator() && "generic" === devices.real().platform && "generic" === _device.platform
  162. },
  163. options: {
  164. showScrollbar: "onHover",
  165. pageLoadMode: "nextButton"
  166. }
  167. }, {
  168. device: function() {
  169. return "desktop" === devices.real().deviceType && !devices.isSimulator()
  170. },
  171. options: {
  172. focusStateEnabled: true
  173. }
  174. }, {
  175. device: function() {
  176. return "win" === devices.current().platform && devices.isSimulator()
  177. },
  178. options: {
  179. bounceEnabled: false
  180. }
  181. }, {
  182. device: function() {
  183. return themes.isMaterial(themeName)
  184. },
  185. options: {
  186. pullingDownText: "",
  187. pulledDownText: "",
  188. refreshingText: "",
  189. pageLoadingText: "",
  190. useInkRipple: true
  191. }
  192. }])
  193. },
  194. _visibilityChanged: function(visible) {
  195. if (visible) {
  196. this._updateLoadingState(true)
  197. }
  198. },
  199. _itemClass: function() {
  200. return LIST_ITEM_CLASS
  201. },
  202. _itemDataKey: function() {
  203. return LIST_ITEM_DATA_KEY
  204. },
  205. _itemContainer: function() {
  206. return this._$container
  207. },
  208. _refreshItemElements: function() {
  209. if (!this.option("grouped")) {
  210. this._itemElementsCache = this._itemContainer().children(this._itemSelector())
  211. } else {
  212. this._itemElementsCache = this._itemContainer().children("." + LIST_GROUP_CLASS).children("." + LIST_GROUP_BODY_CLASS).children(this._itemSelector())
  213. }
  214. },
  215. _modifyByChanges: function() {
  216. this.callBase.apply(this, arguments);
  217. this._refreshItemElements()
  218. },
  219. reorderItem: function(itemElement, toItemElement) {
  220. var promise = this.callBase(itemElement, toItemElement);
  221. return promise.done(function() {
  222. this._refreshItemElements()
  223. })
  224. },
  225. deleteItem: function(itemElement) {
  226. var promise = this.callBase(itemElement);
  227. return promise.done(function() {
  228. this._refreshItemElements()
  229. })
  230. },
  231. _itemElements: function() {
  232. return this._itemElementsCache
  233. },
  234. _itemSelectHandler: function(e) {
  235. if ("single" === this.option("selectionMode") && this.isItemSelected(e.currentTarget)) {
  236. return
  237. }
  238. this.callBase(e)
  239. },
  240. _allowDynamicItemsAppend: function() {
  241. return true
  242. },
  243. _init: function() {
  244. this.callBase();
  245. this._$container = this.$element();
  246. this._initScrollView();
  247. this._feedbackShowTimeout = LIST_FEEDBACK_SHOW_TIMEOUT;
  248. this._createGroupRenderAction()
  249. },
  250. _scrollBottomMode: function() {
  251. return "scrollBottom" === this.option("pageLoadMode")
  252. },
  253. _nextButtonMode: function() {
  254. return "nextButton" === this.option("pageLoadMode")
  255. },
  256. _dataSourceOptions: function() {
  257. var scrollBottom = this._scrollBottomMode();
  258. var nextButton = this._nextButtonMode();
  259. return extend(this.callBase(), {
  260. paginate: commonUtils.ensureDefined(scrollBottom || nextButton, true)
  261. })
  262. },
  263. _getGroupedOption: function() {
  264. return this.option("grouped")
  265. },
  266. _dataSourceFromUrlLoadMode: function() {
  267. return "raw"
  268. },
  269. _initScrollView: function() {
  270. var scrollingEnabled = this.option("scrollingEnabled");
  271. var pullRefreshEnabled = scrollingEnabled && this.option("pullRefreshEnabled");
  272. var autoPagingEnabled = scrollingEnabled && this._scrollBottomMode() && !!this._dataSource;
  273. this._scrollView = this._createComponent(this.$element(), ScrollView, {
  274. disabled: this.option("disabled") || !scrollingEnabled,
  275. onScroll: this._scrollHandler.bind(this),
  276. onPullDown: pullRefreshEnabled ? this._pullDownHandler.bind(this) : null,
  277. onReachBottom: autoPagingEnabled ? this._scrollBottomHandler.bind(this) : null,
  278. showScrollbar: this.option("showScrollbar"),
  279. useNative: this.option("useNativeScrolling"),
  280. bounceEnabled: this.option("bounceEnabled"),
  281. scrollByContent: this.option("scrollByContent"),
  282. scrollByThumb: this.option("scrollByThumb"),
  283. pullingDownText: this.option("pullingDownText"),
  284. pulledDownText: this.option("pulledDownText"),
  285. refreshingText: this.option("refreshingText"),
  286. reachBottomText: this.option("pageLoadingText"),
  287. useKeyboard: false
  288. });
  289. this._$container = $(this._scrollView.content());
  290. this._createScrollViewActions()
  291. },
  292. _createScrollViewActions: function() {
  293. this._scrollAction = this._createActionByOption("onScroll");
  294. this._pullRefreshAction = this._createActionByOption("onPullRefresh");
  295. this._pageLoadingAction = this._createActionByOption("onPageLoading")
  296. },
  297. _scrollHandler: function(e) {
  298. this._scrollAction && this._scrollAction(e)
  299. },
  300. _initTemplates: function() {
  301. this.callBase();
  302. this._defaultTemplates.group = new BindableTemplate(function($container, data) {
  303. if (typeUtils.isPlainObject(data)) {
  304. if (data.key) {
  305. $container.text(data.key)
  306. }
  307. } else {
  308. $container.text(String(data))
  309. }
  310. }, ["key"], this.option("integrationOptions.watchMethod"))
  311. },
  312. _prepareDefaultItemTemplate: function(data, $container) {
  313. this.callBase(data, $container);
  314. if (data.icon) {
  315. var $icon = iconUtils.getImageContainer(data.icon).addClass(LIST_ITEM_ICON_CLASS);
  316. var $iconContainer = $("<div>").addClass(LIST_ITEM_ICON_CONTAINER_CLASS);
  317. $iconContainer.append($icon);
  318. $container.prepend($iconContainer)
  319. }
  320. },
  321. _getBindableFields: function() {
  322. return ["text", "html", "icon"]
  323. },
  324. _updateLoadingState: function(tryLoadMore) {
  325. var isDataLoaded = !tryLoadMore || this._isLastPage();
  326. var scrollBottomMode = this._scrollBottomMode();
  327. var stopLoading = isDataLoaded || !scrollBottomMode;
  328. var hideLoadIndicator = stopLoading && !this._isDataSourceLoading();
  329. if (stopLoading || this._scrollViewIsFull()) {
  330. this._scrollView.release(hideLoadIndicator);
  331. this._toggleNextButton(this._shouldRenderNextButton() && !this._isLastPage());
  332. this._loadIndicationSuppressed(false)
  333. } else {
  334. this._infiniteDataLoading()
  335. }
  336. },
  337. _shouldRenderNextButton: function() {
  338. return this._nextButtonMode() && this._dataSource && this._dataSource.isLoaded()
  339. },
  340. _dataSourceLoadingChangedHandler: function(isLoading) {
  341. if (this._loadIndicationSuppressed()) {
  342. return
  343. }
  344. if (isLoading && this.option("indicateLoading")) {
  345. this._showLoadingIndicatorTimer = setTimeout(function() {
  346. var isEmpty = !this._itemElements().length;
  347. if (this._scrollView && !isEmpty) {
  348. this._scrollView.startLoading()
  349. }
  350. }.bind(this))
  351. } else {
  352. clearTimeout(this._showLoadingIndicatorTimer);
  353. this._scrollView && this._scrollView.finishLoading()
  354. }
  355. },
  356. _dataSourceChangedHandler: function(newItems) {
  357. if (!this._shouldAppendItems() && windowUtils.hasWindow()) {
  358. this._scrollView && this._scrollView.scrollTo(0)
  359. }
  360. this.callBase.apply(this, arguments)
  361. },
  362. _refreshContent: function() {
  363. this._prepareContent();
  364. this._fireContentReadyAction()
  365. },
  366. _hideLoadingIfLoadIndicationOff: function() {
  367. if (!this.option("indicateLoading")) {
  368. this._dataSourceLoadingChangedHandler(false)
  369. }
  370. },
  371. _loadIndicationSuppressed: function(value) {
  372. if (!arguments.length) {
  373. return this._isLoadIndicationSuppressed
  374. }
  375. this._isLoadIndicationSuppressed = value
  376. },
  377. _scrollViewIsFull: function() {
  378. return !this._scrollView || this._scrollView.isFull()
  379. },
  380. _pullDownHandler: function(e) {
  381. this._pullRefreshAction(e);
  382. if (this._dataSource && !this._isDataSourceLoading()) {
  383. this._clearSelectedItems();
  384. this._dataSource.pageIndex(0);
  385. this._dataSource.reload()
  386. } else {
  387. this._updateLoadingState()
  388. }
  389. },
  390. _infiniteDataLoading: function() {
  391. var isElementVisible = this.$element().is(":visible");
  392. if (isElementVisible && !this._scrollViewIsFull() && !this._isDataSourceLoading() && !this._isLastPage()) {
  393. clearTimeout(this._loadNextPageTimer);
  394. this._loadNextPageTimer = setTimeout(this._loadNextPage.bind(this))
  395. }
  396. },
  397. _scrollBottomHandler: function(e) {
  398. this._pageLoadingAction(e);
  399. if (!this._isDataSourceLoading() && !this._isLastPage()) {
  400. this._loadNextPage()
  401. } else {
  402. this._updateLoadingState()
  403. }
  404. },
  405. _renderItems: function(items) {
  406. if (this.option("grouped")) {
  407. each(items, this._renderGroup.bind(this));
  408. this._attachGroupCollapseEvent();
  409. this._renderEmptyMessage();
  410. if (themes.isMaterial()) {
  411. this.attachGroupHeaderInkRippleEvents()
  412. }
  413. } else {
  414. this.callBase.apply(this, arguments)
  415. }
  416. this._refreshItemElements();
  417. this._updateLoadingState(true)
  418. },
  419. _attachGroupCollapseEvent: function() {
  420. var eventName = eventUtils.addNamespace(clickEvent.name, this.NAME);
  421. var selector = "." + LIST_GROUP_HEADER_CLASS;
  422. var $element = this.$element();
  423. var collapsibleGroups = this.option("collapsibleGroups");
  424. $element.toggleClass(LIST_COLLAPSIBLE_GROUPS_CLASS, collapsibleGroups);
  425. eventsEngine.off($element, eventName, selector);
  426. if (collapsibleGroups) {
  427. eventsEngine.on($element, eventName, selector, function(e) {
  428. this._createAction(function(e) {
  429. var $group = $(e.event.currentTarget).parent();
  430. this._collapseGroupHandler($group);
  431. if (this.option("focusStateEnabled")) {
  432. this.option("focusedElement", getPublicElement($group.find("." + LIST_ITEM_CLASS).eq(0)))
  433. }
  434. }.bind(this), {
  435. validatingTargetName: "element"
  436. })({
  437. event: e
  438. })
  439. }.bind(this))
  440. }
  441. },
  442. _collapseGroupHandler: function($group, toggle) {
  443. var deferred = new Deferred;
  444. if ($group.hasClass(LIST_GROUP_COLLAPSED_CLASS) === toggle) {
  445. return deferred.resolve()
  446. }
  447. var $groupBody = $group.children("." + LIST_GROUP_BODY_CLASS);
  448. var startHeight = $groupBody.outerHeight();
  449. var endHeight = 0 === startHeight ? $groupBody.height("auto").outerHeight() : 0;
  450. $group.toggleClass(LIST_GROUP_COLLAPSED_CLASS, toggle);
  451. fx.animate($groupBody, {
  452. type: "custom",
  453. from: {
  454. height: startHeight
  455. },
  456. to: {
  457. height: endHeight
  458. },
  459. duration: 200,
  460. complete: function() {
  461. this.updateDimensions();
  462. this._updateLoadingState();
  463. deferred.resolve()
  464. }.bind(this)
  465. });
  466. return deferred.promise()
  467. },
  468. _dataSourceLoadErrorHandler: function() {
  469. this._forgetNextPageLoading();
  470. if (this._initialized) {
  471. this._renderEmptyMessage();
  472. this._updateLoadingState()
  473. }
  474. },
  475. _initMarkup: function() {
  476. this._itemElementsCache = $();
  477. this.$element().addClass(LIST_CLASS);
  478. this.callBase();
  479. this.option("useInkRipple") && this._renderInkRipple();
  480. this.setAria("role", this.option("_listAttributes").role)
  481. },
  482. _renderInkRipple: function() {
  483. this._inkRipple = inkRipple.render()
  484. },
  485. _toggleActiveState: function($element, value, e) {
  486. this.callBase.apply(this, arguments);
  487. var that = this;
  488. if (!this._inkRipple) {
  489. return
  490. }
  491. var config = {
  492. element: $element,
  493. event: e
  494. };
  495. if (value) {
  496. if (themes.isMaterial()) {
  497. this._inkRippleTimer = setTimeout(function() {
  498. that._inkRipple.showWave(config)
  499. }, LIST_FEEDBACK_SHOW_TIMEOUT / 2)
  500. } else {
  501. that._inkRipple.showWave(config)
  502. }
  503. } else {
  504. clearTimeout(this._inkRippleTimer);
  505. this._inkRipple.hideWave(config)
  506. }
  507. },
  508. _postprocessRenderItem: function(args) {
  509. this._refreshItemElements();
  510. this.callBase.apply(this, arguments);
  511. if (this.option("onItemSwipe")) {
  512. this._attachSwipeEvent($(args.itemElement))
  513. }
  514. },
  515. _attachSwipeEvent: function($itemElement) {
  516. var endEventName = eventUtils.addNamespace(swipeEvents.end, this.NAME);
  517. eventsEngine.on($itemElement, endEventName, this._itemSwipeEndHandler.bind(this))
  518. },
  519. _itemSwipeEndHandler: function(e) {
  520. this._itemDXEventHandler(e, "onItemSwipe", {
  521. direction: e.offset < 0 ? "left" : "right"
  522. })
  523. },
  524. _nextButtonHandler: function() {
  525. var source = this._dataSource;
  526. if (source && !source.isLoading()) {
  527. this._scrollView.toggleLoading(true);
  528. this._$nextButton.detach();
  529. this._loadIndicationSuppressed(true);
  530. this._loadNextPage()
  531. }
  532. },
  533. _renderGroup: function(index, group) {
  534. var $groupElement = $("<div>").addClass(LIST_GROUP_CLASS).appendTo(this._itemContainer());
  535. var $groupHeaderElement = $("<div>").addClass(LIST_GROUP_HEADER_CLASS).appendTo($groupElement);
  536. var groupTemplateName = this.option("groupTemplate");
  537. var groupTemplate = this._getTemplate(group.template || groupTemplateName, group, index, $groupHeaderElement);
  538. var renderArgs = {
  539. index: index,
  540. itemData: group,
  541. container: getPublicElement($groupHeaderElement)
  542. };
  543. this._createItemByTemplate(groupTemplate, renderArgs);
  544. if (themes.isMaterial()) {
  545. $("<div>").addClass(LIST_GROUP_HEADER_INDICATOR_CLASS).prependTo($groupHeaderElement)
  546. }
  547. this._renderingGroupIndex = index;
  548. var $groupBody = $("<div>").addClass(LIST_GROUP_BODY_CLASS).appendTo($groupElement);
  549. each(groupItemsGetter(group) || [], function(index, item) {
  550. this._renderItem(index, item, $groupBody)
  551. }.bind(this));
  552. this._groupRenderAction({
  553. groupElement: getPublicElement($groupElement),
  554. groupIndex: index,
  555. groupData: group
  556. })
  557. },
  558. downInkRippleHandler: function(e) {
  559. this._toggleActiveState($(e.currentTarget), true, e)
  560. },
  561. upInkRippleHandler: function(e) {
  562. this._toggleActiveState($(e.currentTarget), false)
  563. },
  564. attachGroupHeaderInkRippleEvents: function() {
  565. var selector = "." + LIST_GROUP_HEADER_CLASS;
  566. var $element = this.$element();
  567. this._downInkRippleHandler = this._downInkRippleHandler || this.downInkRippleHandler.bind(this);
  568. this._upInkRippleHandler = this._upInkRippleHandler || this.upInkRippleHandler.bind(this);
  569. var downArguments = [$element, "dxpointerdown", selector, this._downInkRippleHandler];
  570. var upArguments = [$element, "dxpointerup dxpointerout", selector, this._upInkRippleHandler];
  571. eventsEngine.off.apply(eventsEngine, downArguments);
  572. eventsEngine.on.apply(eventsEngine, downArguments);
  573. eventsEngine.off.apply(eventsEngine, upArguments);
  574. eventsEngine.on.apply(eventsEngine, upArguments)
  575. },
  576. _createGroupRenderAction: function() {
  577. this._groupRenderAction = this._createActionByOption("onGroupRendered")
  578. },
  579. _clean: function() {
  580. clearTimeout(this._inkRippleTimer);
  581. if (this._$nextButton) {
  582. this._$nextButton.remove();
  583. this._$nextButton = null
  584. }
  585. this.callBase.apply(this, arguments)
  586. },
  587. _dispose: function() {
  588. clearTimeout(this._holdTimer);
  589. clearTimeout(this._loadNextPageTimer);
  590. clearTimeout(this._showLoadingIndicatorTimer);
  591. this.callBase()
  592. },
  593. _toggleDisabledState: function(value) {
  594. this.callBase(value);
  595. this._scrollView.option("disabled", value || !this.option("scrollingEnabled"))
  596. },
  597. _toggleNextButton: function(value) {
  598. var dataSource = this._dataSource;
  599. var $nextButton = this._getNextButton();
  600. this.$element().toggleClass(LIST_HAS_NEXT_CLASS, value);
  601. if (value && dataSource && dataSource.isLoaded()) {
  602. $nextButton.appendTo(this._itemContainer())
  603. }
  604. if (!value) {
  605. $nextButton.detach()
  606. }
  607. },
  608. _getNextButton: function() {
  609. if (!this._$nextButton) {
  610. this._$nextButton = this._createNextButton()
  611. }
  612. return this._$nextButton
  613. },
  614. _createNextButton: function() {
  615. var $result = $("<div>").addClass(LIST_NEXT_BUTTON_CLASS);
  616. var $button = $("<div>").appendTo($result);
  617. this._createComponent($button, Button, {
  618. text: this.option("nextButtonText"),
  619. onClick: this._nextButtonHandler.bind(this),
  620. type: themes.isMaterial() ? "default" : void 0,
  621. integrationOptions: {}
  622. });
  623. return $result
  624. },
  625. _moveFocus: function() {
  626. this.callBase.apply(this, arguments);
  627. this.scrollToItem(this.option("focusedElement"))
  628. },
  629. _refresh: function() {
  630. if (!windowUtils.hasWindow()) {
  631. this.callBase()
  632. } else {
  633. var scrollTop = this._scrollView.scrollTop();
  634. this.callBase();
  635. scrollTop && this._scrollView.scrollTo(scrollTop)
  636. }
  637. },
  638. _optionChanged: function(args) {
  639. switch (args.name) {
  640. case "pageLoadMode":
  641. this._toggleNextButton(args.value);
  642. this._initScrollView();
  643. break;
  644. case "dataSource":
  645. this.callBase(args);
  646. this._initScrollView();
  647. break;
  648. case "pullingDownText":
  649. case "pulledDownText":
  650. case "refreshingText":
  651. case "pageLoadingText":
  652. case "useNative":
  653. case "showScrollbar":
  654. case "bounceEnabled":
  655. case "scrollByContent":
  656. case "scrollByThumb":
  657. case "scrollingEnabled":
  658. case "pullRefreshEnabled":
  659. this._initScrollView();
  660. this._updateLoadingState();
  661. break;
  662. case "nextButtonText":
  663. case "onItemSwipe":
  664. case "useInkRipple":
  665. this._invalidate();
  666. break;
  667. case "onScroll":
  668. case "onPullRefresh":
  669. case "onPageLoading":
  670. this._createScrollViewActions();
  671. this._invalidate();
  672. break;
  673. case "grouped":
  674. case "collapsibleGroups":
  675. case "groupTemplate":
  676. this._invalidate();
  677. break;
  678. case "onGroupRendered":
  679. this._createGroupRenderAction();
  680. break;
  681. case "width":
  682. case "height":
  683. this.callBase(args);
  684. this._scrollView.update();
  685. break;
  686. case "indicateLoading":
  687. this._hideLoadingIfLoadIndicationOff();
  688. break;
  689. case "visible":
  690. this.callBase(args);
  691. this._scrollView.update();
  692. break;
  693. case "rtlEnabled":
  694. this._initScrollView();
  695. this.callBase(args);
  696. break;
  697. case "showChevronExpr":
  698. case "badgeExpr":
  699. this._invalidate();
  700. break;
  701. case "_listAttributes":
  702. break;
  703. default:
  704. this.callBase(args)
  705. }
  706. },
  707. _extendActionArgs: function($itemElement) {
  708. if (!this.option("grouped")) {
  709. return this.callBase($itemElement)
  710. }
  711. var $group = $itemElement.closest("." + LIST_GROUP_CLASS);
  712. var $item = $group.find("." + LIST_ITEM_CLASS);
  713. return extend(this.callBase($itemElement), {
  714. itemIndex: {
  715. group: $group.index(),
  716. item: $item.index($itemElement)
  717. }
  718. })
  719. },
  720. expandGroup: function(groupIndex) {
  721. var deferred = new Deferred;
  722. var $group = this._itemContainer().find("." + LIST_GROUP_CLASS).eq(groupIndex);
  723. this._collapseGroupHandler($group, false).done(function() {
  724. deferred.resolveWith(this)
  725. }.bind(this));
  726. return deferred.promise()
  727. },
  728. collapseGroup: function(groupIndex) {
  729. var deferred = new Deferred;
  730. var $group = this._itemContainer().find("." + LIST_GROUP_CLASS).eq(groupIndex);
  731. this._collapseGroupHandler($group, true).done(function() {
  732. deferred.resolveWith(this)
  733. }.bind(this));
  734. return deferred
  735. },
  736. updateDimensions: function() {
  737. var that = this;
  738. var deferred = new Deferred;
  739. if (that._scrollView) {
  740. that._scrollView.update().done(function() {
  741. !that._scrollViewIsFull() && that._updateLoadingState(true);
  742. deferred.resolveWith(that)
  743. })
  744. } else {
  745. deferred.resolveWith(that)
  746. }
  747. return deferred.promise()
  748. },
  749. reload: function() {
  750. this.callBase();
  751. this.scrollTo(0);
  752. this._pullDownHandler()
  753. },
  754. repaint: function() {
  755. this.scrollTo(0);
  756. this.callBase()
  757. },
  758. scrollTop: function() {
  759. return this._scrollView.scrollOffset().top
  760. },
  761. clientHeight: function() {
  762. return this._scrollView.clientHeight()
  763. },
  764. scrollHeight: function() {
  765. return this._scrollView.scrollHeight()
  766. },
  767. scrollBy: function(distance) {
  768. this._scrollView.scrollBy(distance)
  769. },
  770. scrollTo: function(location) {
  771. this._scrollView.scrollTo(location)
  772. },
  773. scrollToItem: function(itemElement) {
  774. var $item = this._editStrategy.getItemElement(itemElement);
  775. this._scrollView.scrollToElement($item)
  776. }
  777. }).include(DataConverterMixin);
  778. ListBase.ItemClass = ListItem;
  779. module.exports = ListBase;