devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
351 lines (350 loc) • 13.1 kB
JavaScript
/**
* DevExtreme (ui/tabs.js)
* Version: 18.1.3
* Build date: Tue May 15 2018
*
* Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var $ = require("../core/renderer"),
eventsEngine = require("../events/core/events_engine"),
devices = require("../core/devices"),
registerComponent = require("../core/component_registrator"),
Button = require("./button"),
inkRipple = require("./widget/utils.ink_ripple"),
eventUtils = require("../events/utils"),
extend = require("../core/utils/extend").extend,
isPlainObject = require("../core/utils/type").isPlainObject,
pointerEvents = require("../events/pointer"),
TabsItem = require("./tabs/item"),
themes = require("./themes"),
holdEvent = require("../events/hold"),
Scrollable = require("./scroll_view/ui.scrollable"),
CollectionWidget = require("./collection/ui.collection_widget.edit"),
iconUtils = require("../core/utils/icon"),
BindableTemplate = require("./widget/bindable_template");
var TABS_CLASS = "dx-tabs",
TABS_WRAPPER_CLASS = "dx-tabs-wrapper",
TABS_EXPANDED_CLASS = "dx-tabs-expanded",
TABS_SCROLLABLE_CLASS = "dx-tabs-scrollable",
TABS_NAV_BUTTONS_CLASS = "dx-tabs-nav-buttons",
OVERFLOW_HIDDEN_CLASS = "dx-overflow-hidden",
TABS_ITEM_CLASS = "dx-tab",
TABS_ITEM_SELECTED_CLASS = "dx-tab-selected",
TABS_NAV_BUTTON_CLASS = "dx-tabs-nav-button",
TABS_LEFT_NAV_BUTTON_CLASS = "dx-tabs-nav-button-left",
TABS_RIGHT_NAV_BUTTON_CLASS = "dx-tabs-nav-button-right",
TABS_ITEM_TEXT_CLASS = "dx-tab-text",
TABS_ITEM_DATA_KEY = "dxTabData",
FEEDBACK_HIDE_TIMEOUT = 100,
FEEDBACK_DURATION_INTERVAL = 5,
FEEDBACK_SCROLL_TIMEOUT = 300,
TAB_OFFSET = 30;
var Tabs = CollectionWidget.inherit({
_activeStateUnit: "." + TABS_ITEM_CLASS,
_getDefaultOptions: function() {
return extend(this.callBase(), {
hoverStateEnabled: true,
showNavButtons: true,
scrollByContent: true,
scrollingEnabled: true,
selectionMode: "single",
activeStateEnabled: true,
selectionRequired: false,
selectOnFocus: true,
loopItemFocus: false,
useInkRipple: false,
badgeExpr: function(data) {
return data ? data.badge : void 0
}
})
},
_defaultOptionsRules: function() {
return this.callBase().concat([{
device: function() {
return "generic" !== devices.real().platform
},
options: {
showNavButtons: false
}
}, {
device: {
platform: "generic"
},
options: {
scrollByContent: false
}
}, {
device: function() {
return "desktop" === devices.real().deviceType && !devices.isSimulator()
},
options: {
focusStateEnabled: true
}
}, {
device: function() {
return /android5/.test(themes.current())
},
options: {
useInkRipple: true
}
}, {
device: function() {
return themes.isMaterial()
},
options: {
useInkRipple: true,
selectOnFocus: false
}
}])
},
_init: function() {
this.callBase();
this.setAria("role", "tablist");
this.$element().addClass(TABS_CLASS);
this._renderMultiple();
this._feedbackHideTimeout = FEEDBACK_HIDE_TIMEOUT
},
_initTemplates: function() {
this.callBase();
this._defaultTemplates.item = new BindableTemplate(function($container, data) {
if (isPlainObject(data)) {
this._prepareDefaultItemTemplate(data, $container)
} else {
$container.text(String(data))
}
var $iconElement = iconUtils.getImageContainer(data.icon);
$container.wrapInner($("<span>").addClass(TABS_ITEM_TEXT_CLASS));
$iconElement && $iconElement.prependTo($container)
}.bind(this), ["text", "html", "icon"], this.option("integrationOptions.watchMethod"))
},
_itemClass: function() {
return TABS_ITEM_CLASS
},
_selectedItemClass: function() {
return TABS_ITEM_SELECTED_CLASS
},
_itemDataKey: function() {
return TABS_ITEM_DATA_KEY
},
_initMarkup: function() {
this.callBase();
this._renderWrapper();
this.setAria("role", "tab", this.itemElements());
this.option("useInkRipple") && this._renderInkRipple();
this.$element().addClass(OVERFLOW_HIDDEN_CLASS)
},
_render: function() {
this.callBase();
this._renderScrolling()
},
_renderScrolling: function() {
this.$element().removeClass(TABS_EXPANDED_CLASS);
this.$element().removeClass(OVERFLOW_HIDDEN_CLASS);
if (this._allowScrolling()) {
if (!this._scrollable) {
this._renderScrollable();
this._renderNavButtons()
}
this._scrollable.update();
this._updateNavButtonsVisibility();
if (this.option("rtlEnabled")) {
this._scrollable.scrollTo({
left: this._scrollable.scrollWidth() - this._scrollable.clientWidth()
})
}
this._scrollToItem(this.option("selectedItem"))
}
if (!this._allowScrolling()) {
this._cleanScrolling();
this.$element().removeClass(TABS_NAV_BUTTONS_CLASS).addClass(TABS_EXPANDED_CLASS)
}
},
_cleanNavButtons: function() {
if (!this._leftButton || !this._rightButton) {
return
}
this._leftButton.$element().remove();
this._rightButton.$element().remove();
this._leftButton = null;
this._rightButton = null
},
_cleanScrolling: function() {
if (!this._scrollable) {
return
}
this._scrollable.$content().children("." + TABS_WRAPPER_CLASS).appendTo(this._itemContainer());
this._scrollable.$element().remove();
this._scrollable = null;
this._cleanNavButtons()
},
_renderInkRipple: function() {
this._inkRipple = inkRipple.render()
},
_toggleActiveState: function($element, value, e) {
this.callBase.apply(this, arguments);
if (!this._inkRipple) {
return
}
var config = {
element: $element,
event: e
};
if (value) {
this._inkRipple.showWave(config)
} else {
this._inkRipple.hideWave(config)
}
},
_renderMultiple: function() {
if ("multiple" === this.option("selectionMode")) {
this.option("selectOnFocus", false)
}
},
_renderWrapper: function() {
this.$element().wrapInner($("<div>").addClass(TABS_WRAPPER_CLASS))
},
_renderScrollable: function() {
var $itemContainer = this.$element().wrapInner($("<div>").addClass(TABS_SCROLLABLE_CLASS)).children();
this._scrollable = this._createComponent($itemContainer, Scrollable, {
direction: "horizontal",
showScrollbar: false,
useKeyboard: false,
useNative: false,
scrollByContent: this.option("scrollByContent"),
onScroll: this._updateNavButtonsVisibility.bind(this)
});
this.$element().append(this._scrollable.$element())
},
_scrollToItem: function(itemData) {
if (!this._scrollable) {
return
}
var $item = this._editStrategy.getItemElement(itemData);
this._scrollable.scrollToElement($item)
},
_allowScrolling: function() {
if (!this.option("scrollingEnabled")) {
return false
}
var tabItemsWidth = 0;
this._getAvailableItems().each(function(_, tabItem) {
tabItemsWidth += $(tabItem).outerWidth(true)
});
return tabItemsWidth - 1 > this.$element().width()
},
_renderNavButtons: function() {
this.$element().toggleClass(TABS_NAV_BUTTONS_CLASS, this.option("showNavButtons"));
if (!this.option("showNavButtons")) {
return
}
this._leftButton = this._createNavButton(-TAB_OFFSET, "chevronprev");
var $leftButton = this._leftButton.$element();
$leftButton.addClass(TABS_LEFT_NAV_BUTTON_CLASS);
this.$element().prepend($leftButton);
this._rightButton = this._createNavButton(TAB_OFFSET, "chevronnext");
var $rightButton = this._rightButton.$element();
$rightButton.addClass(TABS_RIGHT_NAV_BUTTON_CLASS);
this.$element().append($rightButton)
},
_updateNavButtonsVisibility: function() {
this._leftButton && this._leftButton.option("disabled", this._scrollable.scrollLeft() <= 0);
this._rightButton && this._rightButton.option("disabled", this._scrollable.scrollLeft() >= Math.round(this._scrollable.scrollWidth() - this._scrollable.clientWidth()))
},
_updateScrollPosition: function(offset, duration) {
this._scrollable.update();
this._scrollable.scrollBy(offset / duration)
},
_createNavButton: function(offset, icon) {
var that = this;
var holdAction = that._createAction(function() {
that._holdInterval = setInterval(function() {
that._updateScrollPosition(offset, FEEDBACK_DURATION_INTERVAL)
}, FEEDBACK_DURATION_INTERVAL)
}),
holdEventName = eventUtils.addNamespace(holdEvent.name, "dxNavButton"),
pointerUpEventName = eventUtils.addNamespace(pointerEvents.up, "dxNavButton"),
pointerOutEventName = eventUtils.addNamespace(pointerEvents.out, "dxNavButton");
var navButton = this._createComponent($("<div>").addClass(TABS_NAV_BUTTON_CLASS), Button, {
focusStateEnabled: false,
icon: icon,
onClick: function() {
that._updateScrollPosition(offset, 1)
},
integrationOptions: {}
});
var $navButton = navButton.$element();
eventsEngine.on($navButton, holdEventName, {
timeout: FEEDBACK_SCROLL_TIMEOUT
}, function(e) {
holdAction({
event: e
})
}.bind(this));
eventsEngine.on($navButton, pointerUpEventName, function() {
that._clearInterval()
});
eventsEngine.on($navButton, pointerOutEventName, function() {
that._clearInterval()
});
return navButton
},
_clearInterval: function() {
if (this._holdInterval) {
clearInterval(this._holdInterval)
}
},
_renderSelection: function(addedSelection) {
this._scrollable && this._scrollable.scrollToElement(this.itemElements().eq(addedSelection[0]), {
left: 1,
right: 1
})
},
_visibilityChanged: function(visible) {
if (visible) {
this._dimensionChanged()
}
},
_dimensionChanged: function() {
if (this.option("scrollingEnabled")) {
this._renderScrolling()
}
},
_itemSelectHandler: function(e) {
if ("single" === this.option("selectionMode") && this.isItemSelected(e.currentTarget)) {
return
}
this.callBase(e)
},
_clean: function() {
this._scrollable = null;
this.callBase()
},
_optionChanged: function(args) {
switch (args.name) {
case "useInkRipple":
case "scrollingEnabled":
case "showNavButtons":
this._invalidate();
break;
case "scrollByContent":
this._scrollable && this._scrollable.option(args.name, args.value);
break;
case "selectionMode":
this._renderMultiple();
this.callBase(args);
break;
case "badgeExpr":
this._invalidate();
break;
default:
this.callBase(args)
}
}
});
Tabs.ItemClass = TabsItem;
registerComponent("dxTabs", Tabs);
module.exports = Tabs;
module.exports.getTabsExpandedClass = TABS_EXPANDED_CLASS;
module.exports.default = module.exports;