UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

543 lines (542 loc) • 19.8 kB
/** * DevExtreme (esm/ui/responsive_box.js) * Version: 21.1.4 * Build date: Mon Jun 21 2021 * * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import $ from "../core/renderer"; import eventsEngine from "../events/core/events_engine"; import { grep, noop } from "../core/utils/common"; import { isDefined, isPlainObject, isEmptyObject } from "../core/utils/type"; import errors from "./widget/ui.errors"; import { getWindow, defaultScreenFactorFunc, hasWindow } from "../core/utils/window"; var window = getWindow(); import { each, map } from "../core/utils/iterator"; import { extend } from "../core/utils/extend"; import registerComponent from "../core/component_registrator"; import Box from "./box"; import CollectionWidget from "./collection/ui.collection_widget.edit"; var RESPONSIVE_BOX_CLASS = "dx-responsivebox"; var SCREEN_SIZE_CLASS_PREFIX = RESPONSIVE_BOX_CLASS + "-screen-"; var BOX_ITEM_CLASS = "dx-box-item"; var BOX_ITEM_DATA_KEY = "dxBoxItemData"; var HD_SCREEN_WIDTH = 1920; var ResponsiveBox = CollectionWidget.inherit({ _getDefaultOptions: function() { return extend(this.callBase(), { rows: [], cols: [], screenByWidth: null, singleColumnScreen: "", height: "100%", width: "100%", activeStateEnabled: false, focusStateEnabled: false, onItemStateChanged: void 0, onLayoutChanged: null, currentScreenFactor: void 0, _layoutStrategy: void 0 }) }, _init: function() { if (!this.option("screenByWidth")) { this._options.silent("screenByWidth", defaultScreenFactorFunc) } this.callBase(); this._initLayoutChangedAction() }, _initLayoutChangedAction: function() { this._layoutChangedAction = this._createActionByOption("onLayoutChanged", { excludeValidators: ["disabled", "readonly"] }) }, _itemClass: function() { return BOX_ITEM_CLASS }, _itemDataKey: function() { return BOX_ITEM_DATA_KEY }, _initMarkup: function() { this.callBase(); this.$element().addClass(RESPONSIVE_BOX_CLASS); this._updateRootBox() }, _updateRootBox: function() { clearTimeout(this._updateTimer); this._updateTimer = setTimeout(function() { if (this._$root) { eventsEngine.triggerHandler(this._$root, "dxupdate") } }.bind(this)) }, _renderItems: function() { this._setScreenSize(); this._screenItems = this._itemsByScreen(); this._prepareGrid(); this._spreadItems(); this._layoutItems(); this._linkNodeToItem() }, _itemOptionChanged: function(item) { var $item = this._findItemElementByItem(item); if (!$item.length) { return } this._refreshItem($item, item); this._clearItemNodeTemplates(); this._update(true) }, _setScreenSize: function() { var currentScreen = this._getCurrentScreen(); this._removeScreenSizeClass(); this.$element().addClass(SCREEN_SIZE_CLASS_PREFIX + currentScreen); this.option("currentScreenFactor", currentScreen) }, _removeScreenSizeClass: function() { var currentScreenFactor = this.option("currentScreenFactor"); currentScreenFactor && this.$element().removeClass(SCREEN_SIZE_CLASS_PREFIX + currentScreenFactor) }, _prepareGrid: function() { var grid = this._grid = []; this._prepareRowsAndCols(); each(this._rows, function() { var row = []; grid.push(row); each(this._cols, function() { row.push(this._createEmptyCell()) }.bind(this)) }.bind(this)) }, getSingleColumnRows: function() { var rows = this.option("rows"); var screenItemsLength = this._screenItems.length; if (rows.length) { var filteredRows = this._filterByScreen(rows); var result = []; for (var i = 0; i < screenItemsLength; i++) { var sizeConfig = this._defaultSizeConfig(); if (i < filteredRows.length && isDefined(filteredRows[i].shrink)) { sizeConfig.shrink = filteredRows[i].shrink } result.push(sizeConfig) } return result } else { return this._defaultSizeConfig(screenItemsLength) } }, _prepareRowsAndCols: function() { if (this._isSingleColumnScreen()) { this._prepareSingleColumnScreenItems(); this._rows = this.getSingleColumnRows(); this._cols = this._defaultSizeConfig(1) } else { this._rows = this._sizesByScreen(this.option("rows")); this._cols = this._sizesByScreen(this.option("cols")) } }, _isSingleColumnScreen: function() { return this._screenRegExp().test(this.option("singleColumnScreen")) || !this.option("rows").length || !this.option("cols").length }, _prepareSingleColumnScreenItems: function() { this._screenItems.sort((function(item1, item2) { return item1.location.row - item2.location.row || item1.location.col - item2.location.col })); each(this._screenItems, (function(index, item) { extend(item.location, { row: index, col: 0, rowspan: 1, colspan: 1 }) })) }, _sizesByScreen: function(sizeConfigs) { return map(this._filterByScreen(sizeConfigs), function(sizeConfig) { return extend(this._defaultSizeConfig(), sizeConfig) }.bind(this)) }, _createDefaultSizeConfig: function() { return { ratio: 1, baseSize: 0, minSize: 0, maxSize: 0 } }, _defaultSizeConfig: function(size) { var defaultSizeConfig = this._createDefaultSizeConfig(); if (!arguments.length) { return defaultSizeConfig } var result = []; for (var i = 0; i < size; i++) { result.push(defaultSizeConfig) } return result }, _filterByScreen: function(items) { var screenRegExp = this._screenRegExp(); return grep(items, (function(item) { return !item.screen || screenRegExp.test(item.screen) })) }, _screenRegExp: function() { var screen = this._getCurrentScreen(); return new RegExp("(^|\\s)" + screen + "($|\\s)", "i") }, _getCurrentScreen: function() { var width = this._screenWidth(); return this.option("screenByWidth")(width) }, _screenWidth: function() { return hasWindow() ? $(window).width() : HD_SCREEN_WIDTH }, _createEmptyCell: function() { return { item: {}, location: { colspan: 1, rowspan: 1 } } }, _spreadItems: function() { each(this._screenItems, function(_, itemInfo) { var location = itemInfo.location || {}; var itemCol = location.col; var itemRow = location.row; var row = this._grid[itemRow]; var itemCell = row && row[itemCol]; this._occupyCells(itemCell, itemInfo) }.bind(this)) }, _itemsByScreen: function() { return this.option("items").reduce((result, item) => { var locations = item.location || {}; locations = isPlainObject(locations) ? [locations] : locations; this._filterByScreen(locations).forEach(location => { result.push({ item: item, location: extend({ rowspan: 1, colspan: 1 }, location) }) }); return result }, []) }, _occupyCells: function(itemCell, itemInfo) { if (!itemCell || this._isItemCellOccupied(itemCell, itemInfo)) { return } extend(itemCell, itemInfo); this._markSpanningCell(itemCell) }, _isItemCellOccupied: function(itemCell, itemInfo) { if (!isEmptyObject(itemCell.item)) { return true } var result = false; this._loopOverSpanning(itemInfo.location, (function(cell) { result = result || !isEmptyObject(cell.item) })); return result }, _loopOverSpanning: function(location, callback) { var rowEnd = location.row + location.rowspan - 1; var colEnd = location.col + location.colspan - 1; var boundRowEnd = Math.min(rowEnd, this._rows.length - 1); var boundColEnd = Math.min(colEnd, this._cols.length - 1); location.rowspan -= rowEnd - boundRowEnd; location.colspan -= colEnd - boundColEnd; for (var rowIndex = location.row; rowIndex <= boundRowEnd; rowIndex++) { for (var colIndex = location.col; colIndex <= boundColEnd; colIndex++) { if (rowIndex !== location.row || colIndex !== location.col) { callback(this._grid[rowIndex][colIndex]) } } } }, _markSpanningCell: function(itemCell) { this._loopOverSpanning(itemCell.location, (function(cell) { extend(cell, { item: itemCell.item, spanningCell: itemCell }) })) }, _linkNodeToItem: function() { each(this._itemElements(), (function(_, itemNode) { var $item = $(itemNode); var item = $item.data(BOX_ITEM_DATA_KEY); if (!item.box) { item.node = $item.children() } })) }, _layoutItems: function() { var rowsCount = this._grid.length; var colsCount = rowsCount && this._grid[0].length; if (!rowsCount && !colsCount) { return } var result = this._layoutBlock({ direction: "col", row: { start: 0, end: rowsCount - 1 }, col: { start: 0, end: colsCount - 1 } }); var rootBox = this._prepareBoxConfig(result.box || { direction: "row", items: [extend(result, { ratio: 1 })] }); extend(rootBox, this._rootBoxConfig(rootBox.items)); this._$root = $("<div>").appendTo(this._itemContainer()); this._createComponent(this._$root, Box, rootBox) }, _rootBoxConfig: function(items) { var rootItems = each(items, function(index, item) { this._needApplyAutoBaseSize(item) && extend(item, { baseSize: "auto" }) }.bind(this)); return extend({ width: "100%", height: "100%", items: rootItems, itemTemplate: this._getTemplateByOption("itemTemplate"), itemHoldTimeout: this.option("itemHoldTimeout"), onItemHold: this._createActionByOption("onItemHold"), onItemClick: this._createActionByOption("onItemClick"), onItemContextMenu: this._createActionByOption("onItemContextMenu"), onItemRendered: this._createActionByOption("onItemRendered") }, { _layoutStrategy: this.option("_layoutStrategy") }) }, _needApplyAutoBaseSize: function(item) { return !item.baseSize && (!item.minSize || "auto" === item.minSize) && (!item.maxSize || "auto" === item.maxSize) }, _prepareBoxConfig: function(config) { return extend(config || {}, { crossAlign: "stretch", onItemStateChanged: this.option("onItemStateChanged") }) }, _layoutBlock: function(options) { if (this._isSingleItem(options)) { return this._itemByCell(options.row.start, options.col.start) } return this._layoutDirection(options) }, _isSingleItem: function(options) { var firstCellLocation = this._grid[options.row.start][options.col.start].location; var isItemRowSpanned = options.row.end - options.row.start === firstCellLocation.rowspan - 1; var isItemColSpanned = options.col.end - options.col.start === firstCellLocation.colspan - 1; return isItemRowSpanned && isItemColSpanned }, _itemByCell: function(rowIndex, colIndex) { var itemCell = this._grid[rowIndex][colIndex]; return itemCell.spanningCell ? null : itemCell.item }, _layoutDirection: function(options) { var items = []; var direction = options.direction; var crossDirection = this._crossDirection(direction); var block; while (block = this._nextBlock(options)) { if (this._isBlockIndivisible(options.prevBlockOptions, block)) { throw errors.Error("E1025") } var item = this._layoutBlock({ direction: crossDirection, row: block.row, col: block.col, prevBlockOptions: options }); if (item) { extend(item, this._blockSize(block, crossDirection)); items.push(item) } options[crossDirection].start = block[crossDirection].end + 1 } return { box: this._prepareBoxConfig({ direction: direction, items: items }) } }, _isBlockIndivisible: function(options, block) { return options && options.col.start === block.col.start && options.col.end === block.col.end && options.row.start === block.row.start && options.row.end === block.row.end }, _crossDirection: function(direction) { return "col" === direction ? "row" : "col" }, _nextBlock: function(options) { var direction = options.direction; var crossDirection = this._crossDirection(direction); var startIndex = options[direction].start; var endIndex = options[direction].end; var crossStartIndex = options[crossDirection].start; if (crossStartIndex > options[crossDirection].end) { return null } var crossSpan = 1; for (var crossIndex = crossStartIndex; crossIndex < crossStartIndex + crossSpan; crossIndex++) { var lineCrossSpan = 1; for (var index = startIndex; index <= endIndex; index++) { var cell = this._cellByDirection(direction, index, crossIndex); lineCrossSpan = Math.max(lineCrossSpan, cell.location[crossDirection + "span"]) } var lineCrossEndIndex = crossIndex + lineCrossSpan; var crossEndIndex = crossStartIndex + crossSpan; if (lineCrossEndIndex > crossEndIndex) { crossSpan += lineCrossEndIndex - crossEndIndex } } var result = {}; result[direction] = { start: startIndex, end: endIndex }; result[crossDirection] = { start: crossStartIndex, end: crossStartIndex + crossSpan - 1 }; return result }, _cellByDirection: function(direction, index, crossIndex) { return "col" === direction ? this._grid[crossIndex][index] : this._grid[index][crossIndex] }, _blockSize: function(block, direction) { var defaultMinSize = "row" === direction ? "auto" : 0; var sizeConfigs = "row" === direction ? this._rows : this._cols; var result = extend(this._createDefaultSizeConfig(), { ratio: 0 }); for (var index = block[direction].start; index <= block[direction].end; index++) { var sizeConfig = sizeConfigs[index]; result.ratio += sizeConfig.ratio; result.baseSize += sizeConfig.baseSize; result.minSize += sizeConfig.minSize; result.maxSize += sizeConfig.maxSize; if (isDefined(sizeConfig.shrink)) { result.shrink = sizeConfig.shrink } } result.minSize = result.minSize ? result.minSize : defaultMinSize; result.maxSize = result.maxSize ? result.maxSize : "auto"; this._isSingleColumnScreen() && (result.baseSize = "auto"); return result }, _update: function(forceRemoveRoot) { var $existingRoot = this._$root; this._renderItems(); if ($existingRoot) { if (forceRemoveRoot) { $existingRoot.remove() } else { $existingRoot.detach(); this._saveAssistantRoot($existingRoot) } } this._layoutChangedAction(); this._updateRootBox() }, _saveAssistantRoot: function($root) { this._assistantRoots = this._assistantRoots || []; this._assistantRoots.push($root) }, _dispose: function() { clearTimeout(this._updateTimer); this._clearItemNodeTemplates(); this._cleanUnusedRoots(); this.callBase.apply(this, arguments) }, _cleanUnusedRoots: function() { if (!this._assistantRoots) { return } each(this._assistantRoots, (function(_, item) { $(item).remove() })) }, _clearItemNodeTemplates: function() { each(this.option("items"), (function() { delete this.node })) }, _toggleVisibility: function(visible) { this.callBase(visible); if (visible) { this._updateRootBox() } }, _attachClickEvent: noop, _optionChanged: function(args) { switch (args.name) { case "rows": case "cols": case "screenByWidth": case "_layoutStrategy": case "singleColumnScreen": this._clearItemNodeTemplates(); this._invalidate(); break; case "width": case "height": this.callBase(args); this._update(); break; case "onLayoutChanged": this._initLayoutChangedAction(); break; case "itemTemplate": this._clearItemNodeTemplates(); this.callBase(args); break; case "currentScreenFactor": break; default: this.callBase(args) } }, _dimensionChanged: function() { if (this._getCurrentScreen() !== this.option("currentScreenFactor")) { this._update() } }, repaint: function() { this._update() } }); registerComponent("dxResponsiveBox", ResponsiveBox); export default ResponsiveBox;