devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
516 lines (435 loc) • 17 kB
JavaScript
"use strict";
var $ = require("../core/renderer"),
eventsEngine = require("../events/core/events_engine"),
fx = require("../animation/fx"),
clickEvent = require("../events/click"),
devices = require("../core/devices"),
extend = require("../core/utils/extend").extend,
getPublicElement = require("../core/utils/dom").getPublicElement,
iteratorUtils = require("../core/utils/iterator"),
isPlainObject = require("../core/utils/type").isPlainObject,
registerComponent = require("../core/component_registrator"),
eventUtils = require("../events/utils"),
CollectionWidget = require("./collection/ui.collection_widget.edit"),
deferredUtils = require("../core/utils/deferred"),
when = deferredUtils.when,
Deferred = deferredUtils.Deferred,
BindableTemplate = require("./widget/bindable_template"),
iconUtils = require("../core/utils/icon"),
isDefined = require("../core/utils/type").isDefined,
themes = require("./themes");
var ACCORDION_CLASS = "dx-accordion",
ACCORDION_WRAPPER_CLASS = "dx-accordion-wrapper",
ACCORDION_ITEM_CLASS = "dx-accordion-item",
ACCORDION_ITEM_OPENED_CLASS = "dx-accordion-item-opened",
ACCORDION_ITEM_CLOSED_CLASS = "dx-accordion-item-closed",
ACCORDION_ITEM_TITLE_CLASS = "dx-accordion-item-title",
ACCORDION_ITEM_BODY_CLASS = "dx-accordion-item-body",
ACCORDION_ITEM_TITLE_CAPTION_CLASS = "dx-accordion-item-title-caption",
ACCORDION_ITEM_DATA_KEY = "dxAccordionItemData";
/**
* @name dxAccordion
* @publicName dxAccordion
* @inherits CollectionWidget
* @module ui/accordion
* @export default
*/
var Accordion = CollectionWidget.inherit({
_activeStateUnit: "." + ACCORDION_ITEM_CLASS,
_getDefaultOptions: function _getDefaultOptions() {
return extend(this.callBase(), {
/**
* @name dxAccordionOptions.hoverStateEnabled
* @publicName hoverStateEnabled
* @type boolean
* @default true
* @inheritdoc
*/
hoverStateEnabled: true,
/**
* @name dxAccordionOptions.height
* @publicName height
* @type number|string|function
* @default undefined
* @type_function_return number|string
*/
height: undefined,
/**
* @name dxAccordionOptions.itemTitleTemplate
* @publicName itemTitleTemplate
* @type template|function
* @default "title"
* @type_function_param1 itemData:object
* @type_function_param2 itemIndex:number
* @type_function_param3 itemElement:dxElement
* @type_function_return string|Node|jQuery
*/
itemTitleTemplate: "title",
/**
* @name dxAccordionOptions.onItemTitleClick
* @publicName onItemTitleClick
* @type function(e)|string
* @extends Action
* @type_function_param1 e:object
* @type_function_param1_field4 itemData:object
* @type_function_param1_field5 itemElement:dxElement
* @type_function_param1_field6 itemIndex:number
* @action
*/
onItemTitleClick: null,
/**
* @name dxAccordionOptions.selectedIndex
* @publicName selectedIndex
* @type number
* @default 0
*/
selectedIndex: 0,
/**
* @name dxAccordionOptions.collapsible
* @publicName collapsible
* @type boolean
* @default false
*/
collapsible: false,
/**
* @name dxAccordionOptions.multiple
* @publicName multiple
* @type boolean
* @default false
*/
multiple: false,
/**
* @name dxAccordionOptions.animationDuration
* @publicName animationDuration
* @type number
* @default 300
*/
animationDuration: 300,
/**
* @name dxAccordionOptions.deferRendering
* @publicName deferRendering
* @type boolean
* @default true
*/
deferRendering: true,
/**
* @name dxAccordionOptions.itemTemplate
* @publicName itemTemplate
* @type template|function
* @default "item"
* @type_function_param1 itemData:object
* @type_function_param2 itemIndex:number
* @type_function_param3 itemElement:dxElement
* @type_function_return string|Node|jQuery
*/
selectionByClick: true,
activeStateEnabled: true,
_itemAttributes: { role: "tab" },
_animationEasing: "ease"
});
},
_defaultOptionsRules: function _defaultOptionsRules() {
return this.callBase().concat([{
device: function device() {
return devices.real().deviceType === "desktop" && !devices.isSimulator();
},
options: {
/**
* @name dxAccordionOptions.focusStateEnabled
* @publicName focusStateEnabled
* @type boolean
* @default true @for desktop
* @inheritdoc
*/
focusStateEnabled: true
}
}, {
device: function device() {
return themes.isMaterial();
},
options: {
itemTitleTemplate: function itemTitleTemplate(data) {
return $("<div>").text(data.title).addClass(ACCORDION_ITEM_TITLE_CAPTION_CLASS);
},
/**
* @name dxAccordionOptions.animationDuration
* @publicName animationDuration
* @type number
* @default 200 @for Material
*/
animationDuration: 200,
_animationEasing: "cubic-bezier(0.4, 0, 0.2, 1)"
}
}]);
},
_itemElements: function _itemElements() {
return this._itemContainer().children(this._itemSelector());
},
_init: function _init() {
this.callBase();
this.option("selectionRequired", !this.option("collapsible"));
this.option("selectionMode", this.option("multiple") ? "multiple" : "single");
var $element = this.$element();
$element.addClass(ACCORDION_CLASS);
this._$container = $("<div>").addClass(ACCORDION_WRAPPER_CLASS);
$element.append(this._$container);
},
_initTemplates: function _initTemplates() {
this.callBase();
/**
* @name dxAccordionItemTemplate
* @publicName dxAccordionItemTemplate
* @inherits CollectionWidgetItemTemplate
* @type object
*/
/**
* @name dxAccordionItemTemplate.title
* @publicName title
* @type String
*/
/**
* @name dxAccordionItemTemplate.icon
* @publicName icon
* @type String
*/
this._defaultTemplates["title"] = new BindableTemplate(function ($container, data) {
if (isPlainObject(data)) {
if (data.title) {
$container.text(data.title);
}
$container.append(iconUtils.getImageContainer(data.icon));
} else {
$container.text(String(data));
}
}, ["title", "icon"], this.option("integrationOptions.watchMethod"));
},
_initMarkup: function _initMarkup() {
this._deferredItems = [];
this.callBase();
this.setAria({
"role": "tablist",
"multiselectable": this.option("multiple")
});
},
_render: function _render() {
this.callBase();
this._updateItemHeightsWrapper(true);
this._attachItemTitleClickAction();
},
_itemDataKey: function _itemDataKey() {
return ACCORDION_ITEM_DATA_KEY;
},
_itemClass: function _itemClass() {
return ACCORDION_ITEM_CLASS;
},
_itemContainer: function _itemContainer() {
return this._$container;
},
_itemTitles: function _itemTitles() {
return this._itemElements().find("." + ACCORDION_ITEM_TITLE_CLASS);
},
_itemContents: function _itemContents() {
return this._itemElements().find("." + ACCORDION_ITEM_BODY_CLASS);
},
_getItemData: function _getItemData(target) {
return $(target).parent().data(this._itemDataKey()) || this.callBase.apply(this, arguments);
},
_executeItemRenderAction: function _executeItemRenderAction(itemData) {
if (itemData.type) {
return;
}
this.callBase.apply(this, arguments);
},
_itemSelectHandler: function _itemSelectHandler(e) {
if ($(e.target).closest(this._itemContents()).length) {
return;
}
this.callBase.apply(this, arguments);
},
_renderItemContent: function _renderItemContent(args) {
var itemTitle = this.callBase(extend({}, args, {
contentClass: ACCORDION_ITEM_TITLE_CLASS,
templateProperty: "titleTemplate",
defaultTemplateName: this.option("itemTitleTemplate")
}));
var deferred = new Deferred();
if (isDefined(this._deferredItems[args.index])) {
this._deferredItems[args.index] = deferred;
} else {
this._deferredItems.push(deferred);
}
if (!this.option("deferRendering") || this._getSelectedItemIndices().indexOf(args.index) >= 0) {
deferred.resolve();
}
deferred.done(this.callBase.bind(this, extend({}, args, {
contentClass: ACCORDION_ITEM_BODY_CLASS,
container: getPublicElement($("<div>").appendTo($(itemTitle).parent()))
})));
},
_attachItemTitleClickAction: function _attachItemTitleClickAction() {
var itemSelector = "." + ACCORDION_ITEM_TITLE_CLASS,
eventName = eventUtils.addNamespace(clickEvent.name, this.NAME);
eventsEngine.off(this._itemContainer(), eventName, itemSelector);
eventsEngine.on(this._itemContainer(), eventName, itemSelector, this._itemTitleClickHandler.bind(this));
},
_itemTitleClickHandler: function _itemTitleClickHandler(e) {
this._itemDXEventHandler(e, "onItemTitleClick");
},
_renderSelection: function _renderSelection(addedSelection, removedSelection) {
this._itemElements().addClass(ACCORDION_ITEM_CLOSED_CLASS);
this.setAria("hidden", true, this._itemContents());
this._updateItems(addedSelection, removedSelection);
},
_updateSelection: function _updateSelection(addedSelection, removedSelection) {
this._updateItems(addedSelection, removedSelection);
this._updateItemHeightsWrapper(false);
},
_updateItems: function _updateItems(addedSelection, removedSelection) {
var $items = this._itemElements(),
that = this;
iteratorUtils.each(addedSelection, function (_, index) {
that._deferredItems[index].resolve();
var $item = $items.eq(index).addClass(ACCORDION_ITEM_OPENED_CLASS).removeClass(ACCORDION_ITEM_CLOSED_CLASS);
that.setAria("hidden", false, $item.find("." + ACCORDION_ITEM_BODY_CLASS));
});
iteratorUtils.each(removedSelection, function (_, index) {
var $item = $items.eq(index).removeClass(ACCORDION_ITEM_OPENED_CLASS);
that.setAria("hidden", true, $item.find("." + ACCORDION_ITEM_BODY_CLASS));
});
},
_updateItemHeightsWrapper: function _updateItemHeightsWrapper(skipAnimation) {
if (this.option("templatesRenderAsynchronously")) {
this._animationTimer = setTimeout(function () {
this._updateItemHeights(skipAnimation);
}.bind(this));
} else {
this._updateItemHeights(skipAnimation);
}
},
_updateItemHeights: function _updateItemHeights(skipAnimation) {
var that = this,
deferredAnimate = that._deferredAnimate,
itemHeight = this._splitFreeSpace(this._calculateFreeSpace());
clearTimeout(this._animationTimer);
return when.apply($, iteratorUtils.map(this._itemElements(), function (item) {
return that._updateItemHeight($(item), itemHeight, skipAnimation);
})).done(function () {
if (deferredAnimate) {
deferredAnimate.resolveWith(that);
}
});
},
_updateItemHeight: function _updateItemHeight($item, itemHeight, skipAnimation) {
var $title = $item.children("." + ACCORDION_ITEM_TITLE_CLASS);
if (fx.isAnimating($item)) {
fx.stop($item);
}
var startItemHeight = $item.outerHeight(),
finalItemHeight = $item.hasClass(ACCORDION_ITEM_OPENED_CLASS) ? itemHeight + $title.outerHeight() || $item.height("auto").outerHeight() : $title.outerHeight();
return this._animateItem($item, startItemHeight, finalItemHeight, skipAnimation, !!itemHeight);
},
_animateItem: function _animateItem($element, startHeight, endHeight, skipAnimation, fixedHeight) {
var d;
if (skipAnimation || startHeight === endHeight) {
$element.css("height", endHeight);
d = new Deferred().resolve();
} else {
d = fx.animate($element, {
type: "custom",
from: { height: startHeight },
to: { height: endHeight },
duration: this.option("animationDuration"),
easing: this.option("_animationEasing")
});
}
return d.done(function () {
if ($element.hasClass(ACCORDION_ITEM_OPENED_CLASS) && !fixedHeight) {
$element.css("height", "");
}
$element.not("." + ACCORDION_ITEM_OPENED_CLASS).addClass(ACCORDION_ITEM_CLOSED_CLASS);
});
},
_splitFreeSpace: function _splitFreeSpace(freeSpace) {
if (!freeSpace) {
return freeSpace;
}
return freeSpace / this.option("selectedItems").length;
},
_calculateFreeSpace: function _calculateFreeSpace() {
var height = this.option("height");
if (height === undefined || height === "auto") {
return;
}
var $titles = this._itemTitles(),
itemsHeight = 0;
iteratorUtils.each($titles, function (_, title) {
itemsHeight += $(title).outerHeight();
});
return this.$element().height() - itemsHeight;
},
_visibilityChanged: function _visibilityChanged(visible) {
if (visible) {
this._dimensionChanged();
}
},
_dimensionChanged: function _dimensionChanged() {
this._updateItemHeights(true);
},
_clean: function _clean() {
clearTimeout(this._animationTimer);
this.callBase();
},
_optionChanged: function _optionChanged(args) {
switch (args.name) {
case "animationDuration":
case "onItemTitleClick":
case "_animationEasing":
break;
case "collapsible":
this.option("selectionRequired", !this.option("collapsible"));
break;
case "itemTitleTemplate":
case "height":
case "deferRendering":
this._invalidate();
break;
case "multiple":
this.option("selectionMode", args.value ? "multiple" : "single");
break;
default:
this.callBase(args);
}
},
/**
* @name dxAccordionMethods.expandItem
* @publicName expandItem(index)
* @param1 index:numeric
* @return Promise<void>
*/
expandItem: function expandItem(index) {
this._deferredAnimate = new Deferred();
this.selectItem(index);
return this._deferredAnimate.promise();
},
/**
* @name dxAccordionMethods.collapseItem
* @publicName collapseItem(index)
* @param1 index:numeric
* @return Promise<void>
*/
collapseItem: function collapseItem(index) {
this._deferredAnimate = new Deferred();
this.unselectItem(index);
return this._deferredAnimate.promise();
},
/**
* @name dxAccordionMethods.updateDimensions
* @publicName updateDimensions()
* @return Promise<void>
*/
updateDimensions: function updateDimensions() {
return this._updateItemHeights(false);
}
});
registerComponent("dxAccordion", Accordion);
module.exports = Accordion;