UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

793 lines (660 loc) • 23.6 kB
"use strict"; var $ = require("../core/renderer"), eventsEngine = require("../events/core/events_engine"), Class = require("../core/class"), registerComponent = require("../core/component_registrator"), extend = require("../core/utils/extend").extend, noop = require("../core/utils/common").noop, windowUtils = require("../core/utils/window"), inflector = require("../core/utils/inflector"), isDefined = require("../core/utils/type").isDefined, styleUtils = require("../core/utils/style"), each = require("../core/utils/iterator").each, browser = require("../core/utils/browser"), CollectionWidgetItem = require("./collection/item"), devices = require("../core/devices"), CollectionWidget = require("./collection/ui.collection_widget.edit"); var BOX_CLASS = "dx-box", BOX_SELECTOR = ".dx-box", BOX_ITEM_CLASS = "dx-box-item", BOX_ITEM_DATA_KEY = "dxBoxItemData"; var MINSIZE_MAP = { "row": "minWidth", "col": "minHeight" }; var MAXSIZE_MAP = { "row": "maxWidth", "col": "maxHeight" }; var SHRINK = 1; // NEW FLEXBOX STRATEGY var FLEX_JUSTIFY_CONTENT_MAP = { "start": "flex-start", "end": "flex-end", "center": "center", "space-between": "space-between", "space-around": "space-around" }; var FLEX_ALIGN_ITEMS_MAP = { "start": "flex-start", "end": "flex-end", "center": "center", "stretch": "stretch" }; var FLEX_DIRECTION_MAP = { "row": "row", "col": "column" }; var BoxItem = CollectionWidgetItem.inherit({ _renderVisible: function _renderVisible(value, oldValue) { this.callBase(value); if (isDefined(oldValue)) { this._options.fireItemStateChangedAction({ name: "visible", state: value, oldState: oldValue }); } } }); var setFlexProp = function setFlexProp(element, prop, value) { // NOTE: workaround for jQuery version < 1.11.1 (T181692) value = styleUtils.normalizeStyleProp(prop, value); element.style[styleUtils.styleProp(prop)] = value; // NOTE: workaround for Domino issue https://github.com/fgnass/domino/issues/119 if (!windowUtils.hasWindow()) { if (value === "" || !isDefined(value)) { return; } var cssName = inflector.dasherize(prop); var styleExpr = cssName + ": " + value + ";"; if (!element.attributes.style) { element.setAttribute("style", styleExpr); } else if (element.attributes.style.value.indexOf(styleExpr) < 0) { element.attributes.style.value += " " + styleExpr; } } }; var FlexLayoutStrategy = Class.inherit({ ctor: function ctor($element, option) { this._$element = $element; this._option = option; }, renderBox: function renderBox() { this._$element.css({ display: styleUtils.stylePropPrefix("flexDirection") + "flex" }); setFlexProp(this._$element.get(0), "flexDirection", FLEX_DIRECTION_MAP[this._option("direction")]); }, renderAlign: function renderAlign() { this._$element.css({ justifyContent: this._normalizedAlign() }); }, _normalizedAlign: function _normalizedAlign() { var align = this._option("align"); return align in FLEX_JUSTIFY_CONTENT_MAP ? FLEX_JUSTIFY_CONTENT_MAP[align] : align; }, renderCrossAlign: function renderCrossAlign() { this._$element.css({ alignItems: this._normalizedCrossAlign() }); }, _normalizedCrossAlign: function _normalizedCrossAlign() { var crossAlign = this._option("crossAlign"); return crossAlign in FLEX_ALIGN_ITEMS_MAP ? FLEX_ALIGN_ITEMS_MAP[crossAlign] : crossAlign; }, renderItems: function renderItems($items) { var flexPropPrefix = styleUtils.stylePropPrefix("flexDirection"); var direction = this._option("direction"); each($items, function () { var $item = $(this); var item = $item.data(BOX_ITEM_DATA_KEY); $item.css({ display: flexPropPrefix + "flex" }).css(MAXSIZE_MAP[direction], item.maxSize || "none").css(MINSIZE_MAP[direction], item.minSize || "0"); setFlexProp($item.get(0), "flexBasis", item.baseSize || 0); setFlexProp($item.get(0), "flexGrow", item.ratio); setFlexProp($item.get(0), "flexShrink", isDefined(item.shrink) ? item.shrink : SHRINK); $item.children().each(function (_, itemContent) { $(itemContent).css({ width: "auto", height: "auto", display: styleUtils.stylePropPrefix("flexDirection") + "flex", flexBasis: 0 }); setFlexProp(itemContent, "flexGrow", 1); setFlexProp(itemContent, "flexDirection", $(itemContent)[0].style.flexDirection || "column"); }); }); }, initSize: noop, update: noop }); // FALLBACK STRATEGY FOR IE var BOX_EVENTNAMESPACE = "dxBox", UPDATE_EVENT = "dxupdate." + BOX_EVENTNAMESPACE, FALLBACK_BOX_ITEM = "dx-box-fallback-item"; var FALLBACK_WRAP_MAP = { "row": "nowrap", "col": "normal" }; var FALLBACK_MAIN_SIZE_MAP = { "row": "width", "col": "height" }; var FALLBACK_CROSS_SIZE_MAP = { "row": "height", "col": "width" }; var FALLBACK_PRE_MARGIN_MAP = { "row": "marginLeft", "col": "marginTop" }; var FALLBACK_POST_MARGIN_MAP = { "row": "marginRight", "col": "marginBottom" }; var FALLBACK_CROSS_PRE_MARGIN_MAP = { "row": "marginTop", "col": "marginLeft" }; var FALLBACK_CROSS_POST_MARGIN_MAP = { "row": "marginBottom", "col": "marginRight" }; var MARGINS_RTL_FLIP_MAP = { "marginLeft": "marginRight", "marginRight": "marginLeft" }; var FallbackLayoutStrategy = Class.inherit({ ctor: function ctor($element, option) { this._$element = $element; this._option = option; }, renderBox: function renderBox() { this._$element.css({ fontSize: 0, whiteSpace: FALLBACK_WRAP_MAP[this._option("direction")], verticalAlign: "top" }); eventsEngine.off(this._$element, UPDATE_EVENT); eventsEngine.on(this._$element, UPDATE_EVENT, this.update.bind(this)); }, renderAlign: function renderAlign() { var $items = this._$items; if (!$items) { return; } var align = this._option("align"), shift = 0, totalItemSize = this.totalItemSize, direction = this._option("direction"), boxSize = this._$element[FALLBACK_MAIN_SIZE_MAP[direction]](), freeSpace = boxSize - totalItemSize; // NOTE: clear margins this._setItemsMargins($items, direction, 0); switch (align) { case "start": break; case "end": shift = freeSpace; $items.first().css(this._chooseMarginSide(FALLBACK_PRE_MARGIN_MAP[direction]), shift); break; case "center": shift = 0.5 * freeSpace; $items.first().css(this._chooseMarginSide(FALLBACK_PRE_MARGIN_MAP[direction]), shift); $items.last().css(this._chooseMarginSide(FALLBACK_POST_MARGIN_MAP[direction]), shift); break; case "space-between": shift = 0.5 * freeSpace / ($items.length - 1); this._setItemsMargins($items, direction, shift); $items.first().css(this._chooseMarginSide(FALLBACK_PRE_MARGIN_MAP[direction]), 0); $items.last().css(this._chooseMarginSide(FALLBACK_POST_MARGIN_MAP[direction]), 0); break; case "space-around": shift = 0.5 * freeSpace / $items.length; this._setItemsMargins($items, direction, shift); break; } }, _setItemsMargins: function _setItemsMargins($items, direction, shift) { $items.css(this._chooseMarginSide(FALLBACK_PRE_MARGIN_MAP[direction]), shift).css(this._chooseMarginSide(FALLBACK_POST_MARGIN_MAP[direction]), shift); }, renderCrossAlign: function renderCrossAlign() { var $items = this._$items; if (!$items) { return; } var crossAlign = this._option("crossAlign"), direction = this._option("direction"), size = this._$element[FALLBACK_CROSS_SIZE_MAP[direction]](); var that = this; switch (crossAlign) { case "start": break; case "end": each($items, function () { var $item = $(this), itemSize = $item[FALLBACK_CROSS_SIZE_MAP[direction]](), shift = size - itemSize; $item.css(that._chooseMarginSide(FALLBACK_CROSS_PRE_MARGIN_MAP[direction]), shift); }); break; case "center": each($items, function () { var $item = $(this), itemSize = $item[FALLBACK_CROSS_SIZE_MAP[direction]](), shift = 0.5 * (size - itemSize); $item.css(that._chooseMarginSide(FALLBACK_CROSS_PRE_MARGIN_MAP[direction]), shift).css(that._chooseMarginSide(FALLBACK_CROSS_POST_MARGIN_MAP[direction]), shift); }); break; case "stretch": $items.css(that._chooseMarginSide(FALLBACK_CROSS_PRE_MARGIN_MAP[direction]), 0).css(that._chooseMarginSide(FALLBACK_CROSS_POST_MARGIN_MAP[direction]), 0).css(FALLBACK_CROSS_SIZE_MAP[direction], "100%"); break; } }, _chooseMarginSide: function _chooseMarginSide(value) { if (!this._option("rtlEnabled")) { return value; } return MARGINS_RTL_FLIP_MAP[value] || value; }, renderItems: function renderItems($items) { this._$items = $items; var direction = this._option("direction"), totalRatio = 0, totalWeightedShrink = 0, totalBaseSize = 0; each($items, function (_, item) { var $item = $(item); $item.css({ display: "inline-block", verticalAlign: "top" }); $item[FALLBACK_MAIN_SIZE_MAP[direction]]("auto"); $item.removeClass(FALLBACK_BOX_ITEM); var itemData = $item.data(BOX_ITEM_DATA_KEY), ratio = itemData.ratio || 0, size = this._baseSize($item), shrink = isDefined(itemData.shrink) ? itemData.shrink : SHRINK; totalRatio += ratio; totalWeightedShrink += shrink * size; totalBaseSize += size; }.bind(this)); var freeSpaceSize = this._boxSize() - totalBaseSize; var itemSize = function ($item) { var itemData = $item.data(BOX_ITEM_DATA_KEY), size = this._baseSize($item), factor = freeSpaceSize >= 0 ? itemData.ratio || 0 : (isDefined(itemData.shrink) ? itemData.shrink : SHRINK) * size, totalFactor = freeSpaceSize >= 0 ? totalRatio : totalWeightedShrink, shift = totalFactor ? Math.round(freeSpaceSize * factor / totalFactor) : 0; return size + shift; }.bind(this); var totalItemSize = 0; each($items, function (_, item) { var $item = $(item), itemData = $(item).data(BOX_ITEM_DATA_KEY), size = itemSize($item); totalItemSize += size; $item.css(MAXSIZE_MAP[direction], itemData.maxSize || "none").css(MINSIZE_MAP[direction], itemData.minSize || "0").css(FALLBACK_MAIN_SIZE_MAP[direction], size); $item.addClass(FALLBACK_BOX_ITEM); }); this.totalItemSize = totalItemSize; }, _baseSize: function _baseSize(item) { var itemData = $(item).data(BOX_ITEM_DATA_KEY); return itemData.baseSize == null ? 0 : itemData.baseSize === "auto" ? this._contentSize(item) : this._parseSize(itemData.baseSize); }, _contentSize: function _contentSize(item) { return $(item)[FALLBACK_MAIN_SIZE_MAP[this._option("direction")]](); }, _parseSize: function _parseSize(size) { return String(size).match(/.+%$/) ? 0.01 * parseFloat(size) * this._boxSizeValue : size; }, _boxSize: function _boxSize(value) { if (!arguments.length) { this._boxSizeValue = this._boxSizeValue || this._totalBaseSize(); return this._boxSizeValue; } this._boxSizeValue = value; }, _totalBaseSize: function _totalBaseSize() { var result = 0; each(this._$items, function (_, item) { result += this._baseSize(item); }.bind(this)); return result; }, initSize: function initSize() { this._boxSize(this._$element[FALLBACK_MAIN_SIZE_MAP[this._option("direction")]]()); }, update: function update() { if (!this._$items || this._$element.is(":hidden")) { return; } this._$items.detach(); this.initSize(); this._$element.append(this._$items); this.renderItems(this._$items); this.renderAlign(); this.renderCrossAlign(); var element = this._$element.get(0); this._$items.find(BOX_SELECTOR).each(function () { if (element === $(this).parent().closest(BOX_SELECTOR).get(0)) { eventsEngine.triggerHandler(this, UPDATE_EVENT); } }); } }); /** * @name dxBox * @publicName dxBox * @inherits CollectionWidget * @module ui/box * @export default */ var Box = CollectionWidget.inherit({ _getDefaultOptions: function _getDefaultOptions() { return extend(this.callBase(), { /** * @name dxBoxOptions.direction * @publicName direction * @type Enums.BoxDirection * @default 'row' */ direction: "row", /** * @name dxBoxOptions.align * @publicName align * @type Enums.BoxAlign * @default 'start' */ align: "start", /** * @name dxBoxOptions.crossAlign * @publicName crossAlign * @type Enums.BoxCrossAlign * @default 'start' */ crossAlign: "stretch", /** * @name dxBoxOptions.activeStateEnabled * @publicName activeStateEnabled * @hidden * @inheritdoc */ activeStateEnabled: false, /** * @name dxBoxOptions.focusStateEnabled * @publicName focusStateEnabled * @hidden * @inheritdoc */ focusStateEnabled: false, onItemStateChanged: undefined, _layoutStrategy: "flex", _queue: undefined /** * @name dxBoxOptions.hint * @publicName hint * @hidden * @inheritdoc */ /** * @name dxBoxOptions.noDataText * @publicName noDataText * @hidden * @inheritdoc */ /** * @name dxBoxOptions.onSelectionChanged * @publicName onSelectionChanged * @hidden * @inheritdoc */ /** * @name dxBoxOptions.selectedIndex * @publicName selectedIndex * @hidden * @inheritdoc */ /** * @name dxBoxOptions.selectedItem * @publicName selectedItem * @hidden * @inheritdoc */ /** * @name dxBoxOptions.selectedItems * @publicName selectedItems * @hidden * @inheritdoc */ /** * @name dxBoxOptions.selectedItemKeys * @publicName selectedItemKeys * @hidden * @inheritdoc */ /** * @name dxBoxOptions.keyExpr * @publicName keyExpr * @hidden * @inheritdoc */ /** * @name dxBoxOptions.tabIndex * @publicName tabIndex * @hidden * @inheritdoc */ /** * @name dxBoxOptions.accessKey * @publicName accessKey * @hidden * @inheritdoc */ }); }, _defaultOptionsRules: function _defaultOptionsRules() { return this.callBase().concat([{ device: function device() { var device = devices.real(); var isOldAndroid = device.platform === "android" && (device.version[0] < 4 || device.version[0] === 4 && device.version[1] < 4), isOldIos = device.platform === "ios" && device.version[0] < 7; return device.platform === "win" || browser["msie"] || isOldAndroid || isOldIos; }, options: { _layoutStrategy: "fallback" } }]); }, _itemClass: function _itemClass() { return BOX_ITEM_CLASS; }, _itemDataKey: function _itemDataKey() { return BOX_ITEM_DATA_KEY; }, _itemElements: function _itemElements() { return this._itemContainer().children(this._itemSelector()); }, _init: function _init() { this.callBase(); this.$element().addClass(BOX_CLASS + "-" + this.option("_layoutStrategy")); this._initLayout(); this._initBoxQueue(); }, _initLayout: function _initLayout() { this._layout = this.option("_layoutStrategy") === "fallback" ? new FallbackLayoutStrategy(this.$element(), this.option.bind(this)) : new FlexLayoutStrategy(this.$element(), this.option.bind(this)); }, _initBoxQueue: function _initBoxQueue() { this._queue = this.option("_queue") || []; }, _queueIsNotEmpty: function _queueIsNotEmpty() { return this.option("_queue") ? false : !!this._queue.length; }, _pushItemToQueue: function _pushItemToQueue($item, config) { this._queue.push({ $item: $item, config: config }); }, _shiftItemFromQueue: function _shiftItemFromQueue() { return this._queue.shift(); }, _initMarkup: function _initMarkup() { this.$element().addClass(BOX_CLASS); this._layout.renderBox(); this.callBase(); this._renderAlign(); this._renderActions(); }, _renderActions: function _renderActions() { this._onItemStateChanged = this._createActionByOption("onItemStateChanged"); }, _renderAlign: function _renderAlign() { this._layout.renderAlign(); this._layout.renderCrossAlign(); }, _renderItems: function _renderItems(items) { this._layout.initSize(); this.callBase(items); while (this._queueIsNotEmpty()) { var item = this._shiftItemFromQueue(); this._createComponent(item.$item, Box, extend({ _layoutStrategy: this.option("_layoutStrategy"), itemTemplate: this.option("itemTemplate"), itemHoldTimeout: this.option("itemHoldTimeout"), onItemHold: this.option("onItemHold"), onItemClick: this.option("onItemClick"), onItemContextMenu: this.option("onItemContextMenu"), onItemRendered: this.option("onItemRendered"), _queue: this._queue }, item.config)); } this._layout.renderItems(this._itemElements()); clearTimeout(this._updateTimer); this._updateTimer = setTimeout(function () { if (!this._isUpdated) { this._layout.update(); } this._isUpdated = false; this._updateTimer = null; }.bind(this)); }, _renderItemContent: function _renderItemContent(args) { var $itemNode = args.itemData && args.itemData.node; if ($itemNode) { return this._renderItemContentByNode(args, $itemNode); } return this.callBase(args); }, _postprocessRenderItem: function _postprocessRenderItem(args) { var boxConfig = args.itemData.box; if (!boxConfig) { return; } this._pushItemToQueue(args.itemContent, boxConfig); }, _createItemByTemplate: function _createItemByTemplate(itemTemplate, args) { if (args.itemData.box) { return itemTemplate.source ? itemTemplate.source() : $(); } return this.callBase(itemTemplate, args); }, _visibilityChanged: function _visibilityChanged(visible) { if (visible) { this._dimensionChanged(); } }, _dimensionChanged: function _dimensionChanged() { if (this._updateTimer) { return; } this._isUpdated = true; this._layout.update(); }, _dispose: function _dispose() { clearTimeout(this._updateTimer); this.callBase.apply(this, arguments); }, _itemOptionChanged: function _itemOptionChanged(item, property, value, oldValue) { if (property === "visible") { this._onItemStateChanged({ name: property, state: value, oldState: oldValue !== false }); } this.callBase(item, property, value); }, _optionChanged: function _optionChanged(args) { switch (args.name) { case "_layoutStrategy": case "_queue": case "direction": this._invalidate(); break; case "align": this._layout.renderAlign(); break; case "crossAlign": this._layout.renderCrossAlign(); break; default: this.callBase(args); } }, _itemOptions: function _itemOptions() { var that = this, options = this.callBase(); options.fireItemStateChangedAction = function (e) { that._onItemStateChanged(e); }; return options; }, repaint: function repaint() { this._dimensionChanged(); } /** * @name dxBoxMethods.registerKeyHandler * @publicName registerKeyHandler(key, handler) * @hidden * @inheritdoc */ /** * @name dxBoxMethods.focus * @publicName focus() * @hidden * @inheritdoc */ }); /** * @name dxBoxItemTemplate * @publicName dxBoxItemTemplate * @inherits CollectionWidgetItemTemplate * @type object */ /** * @name dxBoxItemTemplate.ratio * @publicName ratio * @type number * @default 0 */ /** * @name dxBoxItemTemplate.baseSize * @publicName baseSize * @type number | Enums.Mode * @default 0 */ /** * @name dxBoxItemTemplate.shrink * @publicName shrink * @type number * @default 1 */ /** * @name dxBoxItemTemplate.box * @publicName box * @type dxBoxOptions * @default undefined */ Box.ItemClass = BoxItem; registerComponent("dxBox", Box); module.exports = Box;