UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

766 lines (654 loc) • 23.9 kB
"use strict"; var $ = require("../core/renderer"), eventsEngine = require("../events/core/events_engine"), commonUtils = require("../core/utils/common"), typeUtils = require("../core/utils/type"), errors = require("./widget/ui.errors"), windowUtils = require("../core/utils/window"), window = windowUtils.getWindow(), iteratorUtils = require("../core/utils/iterator"), extend = require("../core/utils/extend").extend, registerComponent = require("../core/component_registrator"), Box = require("./box"), CollectionWidget = require("./collection/ui.collection_widget.edit"); var RESPONSIVE_BOX_CLASS = "dx-responsivebox", SCREEN_SIZE_CLASS_PREFIX = RESPONSIVE_BOX_CLASS + "-screen-", BOX_ITEM_CLASS = "dx-box-item", BOX_ITEM_DATA_KEY = "dxBoxItemData", HD_SCREEN_WIDTH = 1920; /** * @name dxResponsiveBox * @publicName dxResponsiveBox * @type object * @inherits CollectionWidget * @module ui/responsive_box * @export default */ var ResponsiveBox = CollectionWidget.inherit({ _getDefaultOptions: function _getDefaultOptions() { return extend(this.callBase(), { /** * @name dxResponsiveBoxOptions.rows * @publicName rows * @type Array<Object> */ /** * @name dxResponsiveBoxOptions.rows.baseSize * @publicName baseSize * @type number | Enums.Mode * @default 0 */ /** * @name dxResponsiveBoxOptions.rows.shrink * @publicName shrink * @type number * @default 1 */ /** * @name dxResponsiveBoxOptions.rows.ratio * @publicName ratio * @type number * @default 1 */ /** * @name dxResponsiveBoxOptions.rows.screen * @publicName screen * @type string * @default undefined */ rows: [], /** * @name dxResponsiveBoxOptions.cols * @publicName cols * @type Array<Object> */ /** * @name dxResponsiveBoxOptions.cols.baseSize * @publicName baseSize * @type number | Enums.Mode * @default 0 */ /** * @name dxResponsiveBoxOptions.cols.shrink * @publicName shrink * @type number * @default 1 */ /** * @name dxResponsiveBoxOptions.cols.ratio * @publicName ratio * @type number * @default 1 */ /** * @name dxResponsiveBoxOptions.cols.screen * @publicName screen * @type string * @default undefined */ cols: [], /** * @name dxResponsiveBoxOptions.screenByWidth * @publicName screenByWidth * @type function * @default null */ screenByWidth: null, /** * @name dxResponsiveBoxOptions.singleColumnScreen * @publicName singleColumnScreen * @type string * @default "" */ singleColumnScreen: "", /** * @name dxResponsiveBoxOptions.height * @publicName height * @default '100%' * @inheritdoc */ height: "100%", /** * @name dxResponsiveBoxOptions.width * @publicName width * @default '100%' * @inheritdoc */ width: "100%", /** * @name dxResponsiveBoxOptions.activeStateEnabled * @publicName activeStateEnabled * @hidden * @inheritdoc */ activeStateEnabled: false, /** * @name dxResponsiveBoxOptions.focusStateEnabled * @publicName focusStateEnabled * @hidden * @inheritdoc */ focusStateEnabled: false, onItemStateChanged: undefined, /** * @name dxResponsiveBoxOptions.accessKey * @publicName accessKey * @hidden * @inheritdoc */ /** * @name dxResponsiveBoxOptions.hint * @publicName hint * @hidden * @inheritdoc */ /** * @name dxResponsiveBoxOptions.noDataText * @publicName noDataText * @hidden * @inheritdoc */ /** * @name dxResponsiveBoxOptions.onSelectionChanged * @publicName onSelectionChanged * @hidden * @inheritdoc */ /** * @name dxResponsiveBoxOptions.selectedIndex * @publicName selectedIndex * @hidden * @inheritdoc */ /** * @name dxResponsiveBoxOptions.selectedItem * @publicName selectedItem * @hidden * @inheritdoc */ /** * @name dxResponsiveBoxOptions.selectedItems * @publicName selectedItems * @hidden * @inheritdoc */ /** * @name dxResponsiveBoxOptions.selectedItemKeys * @publicName selectedItemKeys * @hidden * @inheritdoc */ /** * @name dxResponsiveBoxOptions.keyExpr * @publicName keyExpr * @hidden * @inheritdoc */ /** * @name dxResponsiveBoxOptions.tabIndex * @publicName tabIndex * @hidden * @inheritdoc */ onLayoutChanged: null, currentScreenFactor: undefined, _layoutStrategy: undefined }); }, _init: function _init() { if (!this.option("screenByWidth")) { this._options.screenByWidth = windowUtils.defaultScreenFactorFunc; } this.callBase(); this._initLayoutChangedAction(); }, _initLayoutChangedAction: function _initLayoutChangedAction() { this._layoutChangedAction = this._createActionByOption("onLayoutChanged", { excludeValidators: ["disabled", "readonly"] }); }, _itemClass: function _itemClass() { return BOX_ITEM_CLASS; }, _itemDataKey: function _itemDataKey() { return BOX_ITEM_DATA_KEY; }, _initMarkup: function _initMarkup() { this.callBase(); this.$element().addClass(RESPONSIVE_BOX_CLASS); // NOTE: Fallback box strategy this._updateRootBox(); }, _updateRootBox: function _updateRootBox() { clearTimeout(this._updateTimer); this._updateTimer = setTimeout(function () { if (this._$root) { eventsEngine.triggerHandler(this._$root, "dxupdate"); } }.bind(this)); }, _renderItems: function _renderItems() { this._setScreenSize(); this._screenItems = this._itemsByScreen(); this._prepareGrid(); this._spreadItems(); this._layoutItems(); this._linkNodeToItem(); }, _setScreenSize: function _setScreenSize() { var currentScreen = this._getCurrentScreen(); this._removeScreenSizeClass(); this.$element().addClass(SCREEN_SIZE_CLASS_PREFIX + currentScreen); this.option("currentScreenFactor", currentScreen); }, _removeScreenSizeClass: function _removeScreenSizeClass() { var currentScreenFactor = this.option("currentScreenFactor"); currentScreenFactor && this.$element().removeClass(SCREEN_SIZE_CLASS_PREFIX + currentScreenFactor); }, _prepareGrid: function _prepareGrid() { var grid = this._grid = []; this._prepareRowsAndCols(); iteratorUtils.each(this._rows, function () { var row = []; grid.push(row); iteratorUtils.each(this._cols, function () { row.push(this._createEmptyCell()); }.bind(this)); }.bind(this)); }, _prepareRowsAndCols: function _prepareRowsAndCols() { if (this._isSingleColumnScreen()) { this._prepareSingleColumnScreenItems(); this._rows = this._defaultSizeConfig(this._screenItems.length); this._cols = this._defaultSizeConfig(1); } else { this._rows = this._sizesByScreen(this.option("rows")); this._cols = this._sizesByScreen(this.option("cols")); } }, _isSingleColumnScreen: function _isSingleColumnScreen() { return this._screenRegExp().test(this.option("singleColumnScreen")) || !this.option("rows").length || !this.option("cols").length; }, _prepareSingleColumnScreenItems: function _prepareSingleColumnScreenItems() { this._screenItems.sort(function (item1, item2) { return item1.location.row - item2.location.row || item1.location.col - item2.location.col; }); iteratorUtils.each(this._screenItems, function (index, item) { extend(item.location, { row: index, col: 0, rowspan: 1, colspan: 1 }); }); }, _sizesByScreen: function _sizesByScreen(sizeConfigs) { return iteratorUtils.map(this._filterByScreen(sizeConfigs), function (sizeConfig) { return extend(this._defaultSizeConfig(), sizeConfig); }.bind(this)); }, _defaultSizeConfig: function _defaultSizeConfig(size) { var defaultSizeConfig = { ratio: 1, baseSize: 0, minSize: 0, maxSize: 0 }; if (!arguments.length) { return defaultSizeConfig; } var result = []; for (var i = 0; i < size; i++) { result.push(defaultSizeConfig); } return result; }, _filterByScreen: function _filterByScreen(items) { var screenRegExp = this._screenRegExp(); return commonUtils.grep(items, function (item) { return !item.screen || screenRegExp.test(item.screen); }); }, _screenRegExp: function _screenRegExp() { var screen = this._getCurrentScreen(); return new RegExp("(^|\\s)" + screen + "($|\\s)", "i"); }, _getCurrentScreen: function _getCurrentScreen() { var width = this._screenWidth(); return this.option("screenByWidth")(width); }, _screenWidth: function _screenWidth() { return windowUtils.hasWindow() ? $(window).width() : HD_SCREEN_WIDTH; }, _createEmptyCell: function _createEmptyCell() { return { item: {}, location: { colspan: 1, rowspan: 1 } }; }, _spreadItems: function _spreadItems() { iteratorUtils.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 _itemsByScreen() { return iteratorUtils.map(this.option("items"), function (item) { var locations = item.location || {}; locations = typeUtils.isPlainObject(locations) ? [locations] : locations; return iteratorUtils.map(this._filterByScreen(locations), function (location) { return { item: item, location: extend({ rowspan: 1, colspan: 1 }, location) }; }); }.bind(this)); }, _occupyCells: function _occupyCells(itemCell, itemInfo) { if (!itemCell || this._isItemCellOccupied(itemCell, itemInfo)) { return; } extend(itemCell, itemInfo); this._markSpanningCell(itemCell); }, _isItemCellOccupied: function _isItemCellOccupied(itemCell, itemInfo) { if (!typeUtils.isEmptyObject(itemCell.item)) { return true; } var result = false; this._loopOverSpanning(itemInfo.location, function (cell) { result = result || !typeUtils.isEmptyObject(cell.item); }); return result; }, _loopOverSpanning: function _loopOverSpanning(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 _markSpanningCell(itemCell) { this._loopOverSpanning(itemCell.location, function (cell) { extend(cell, { item: itemCell.item, spanningCell: itemCell }); }); }, _linkNodeToItem: function _linkNodeToItem() { iteratorUtils.each(this._itemElements(), function (_, itemNode) { var $item = $(itemNode), item = $item.data(BOX_ITEM_DATA_KEY); if (!item.box) { item.node = $item.children(); } }); }, _layoutItems: function _layoutItems() { 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 _rootBoxConfig(items) { var rootItems = iteratorUtils.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 _needApplyAutoBaseSize(item) { return !item.baseSize && (!item.minSize || item.minSize === "auto") && (!item.maxSize || item.maxSize === "auto"); }, _prepareBoxConfig: function _prepareBoxConfig(config) { return extend(config || {}, { crossAlign: "stretch", onItemStateChanged: this.option("onItemStateChanged") }); }, _layoutBlock: function _layoutBlock(options) { if (this._isSingleItem(options)) { return this._itemByCell(options.row.start, options.col.start); } return this._layoutDirection(options); }, _isSingleItem: function _isSingleItem(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 _itemByCell(rowIndex, colIndex) { var itemCell = this._grid[rowIndex][colIndex]; return itemCell.spanningCell ? null : itemCell.item; }, _layoutDirection: function _layoutDirection(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 _isBlockIndivisible(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 _crossDirection(direction) { return direction === "col" ? "row" : "col"; }, _nextBlock: function _nextBlock(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 _cellByDirection(direction, index, crossIndex) { return direction === "col" ? this._grid[crossIndex][index] : this._grid[index][crossIndex]; }, _blockSize: function _blockSize(block, direction) { var sizeConfigs = direction === "row" ? this._rows : this._cols; var result = { ratio: 0, baseSize: 0, minSize: 0, maxSize: 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; } result.minSize = result.minSize ? result.minSize : "auto"; result.maxSize = result.maxSize ? result.maxSize : "auto"; this._isSingleColumnScreen() && (result.baseSize = 'auto'); return result; }, _update: function _update() { var $existingRoot = this._$root; this._renderItems(); $existingRoot && $existingRoot.detach(); this._saveAssistantRoot($existingRoot); this._layoutChangedAction(); this._updateRootBox(); }, _saveAssistantRoot: function _saveAssistantRoot($root) { this._assistantRoots = this._assistantRoots || []; this._assistantRoots.push($root); }, _dispose: function _dispose() { clearTimeout(this._updateTimer); this._cleanUnusedRoots(); this.callBase.apply(this, arguments); }, _cleanUnusedRoots: function _cleanUnusedRoots() { if (!this._assistantRoots) { return; } iteratorUtils.each(this._assistantRoots, function (_, item) { $(item).remove(); }); }, _clearItemNodeTemplates: function _clearItemNodeTemplates() { iteratorUtils.each(this.option("items"), function () { delete this.node; }); }, _toggleVisibility: function _toggleVisibility(visible) { this.callBase(visible); if (visible) { this._updateRootBox(); } }, _attachClickEvent: commonUtils.noop, _optionChanged: function _optionChanged(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 _dimensionChanged() { if (this._getCurrentScreen() !== this.option("currentScreenFactor")) { this._update(); } }, repaint: function repaint() { this._update(); } /** * @name dxResponsiveBoxMethods.registerKeyHandler * @publicName registerKeyHandler(key, handler) * @hidden * @inheritdoc */ /** * @name dxResponsiveBoxMethods.focus * @publicName focus() * @hidden * @inheritdoc */ }); /** * @name dxResponsiveBoxItemTemplate * @publicName dxResponsiveBoxItemTemplate * @inherits CollectionWidgetItemTemplate * @type object */ /** * @name dxResponsiveBoxItemTemplate.location * @publicName location * @type Object|Array<Object> */ /** * @name dxResponsiveBoxItemTemplate.location.row * @publicName row * @type number */ /** * @name dxResponsiveBoxItemTemplate.location.col * @publicName col * @type number */ /** * @name dxResponsiveBoxItemTemplate.location.rowspan * @publicName rowspan * @type number * @default undefined */ /** * @name dxResponsiveBoxItemTemplate.location.colspan * @publicName colspan * @type number * @default undefined */ /** * @name dxResponsiveBoxItemTemplate.location.screen * @publicName screen * @type string * @default undefined */ registerComponent("dxResponsiveBox", ResponsiveBox); module.exports = ResponsiveBox;