gridstack
Version:
TypeScript/JS lib for dashboard layout and creation, no external dependencies, with many wrappers (React, Angular, Vue, Ember, knockout...)
1,060 lines (1,059 loc) • 72.3 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p);
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GridStack = void 0;
/*!
* GridStack 5.0
* https://gridstackjs.com/
*
* Copyright (c) 2021 Alain Dumesny
* see root license https://github.com/gridstack/gridstack.js/tree/master/LICENSE
*/
var gridstack_engine_1 = require("./gridstack-engine");
var utils_1 = require("./utils");
var gridstack_ddi_1 = require("./gridstack-ddi");
// export all dependent file as well to make it easier for users to just import the main file
__exportStar(require("./types"), exports);
__exportStar(require("./utils"), exports);
__exportStar(require("./gridstack-engine"), exports);
__exportStar(require("./gridstack-ddi"), exports);
// default values for grid options - used during init and when saving out
var GridDefaults = {
column: 12,
minRow: 0,
maxRow: 0,
itemClass: 'grid-stack-item',
placeholderClass: 'grid-stack-placeholder',
placeholderText: '',
handle: '.grid-stack-item-content',
handleClass: null,
styleInHead: false,
cellHeight: 'auto',
cellHeightThrottle: 100,
margin: 10,
auto: true,
minWidth: 768,
float: false,
staticGrid: false,
animate: true,
alwaysShowResizeHandle: false,
resizable: {
autoHide: true,
handles: 'se'
},
draggable: {
handle: '.grid-stack-item-content',
scroll: false,
appendTo: 'body'
},
disableDrag: false,
disableResize: false,
rtl: 'auto',
removable: false,
removableOptions: {
accept: '.grid-stack-item'
},
marginUnit: 'px',
cellHeightUnit: 'px',
disableOneColumnMode: false,
oneColumnModeDomSort: false
};
/**
* Main gridstack class - you will need to call `GridStack.init()` first to initialize your grid.
* Note: your grid elements MUST have the following classes for the CSS layout to work:
* @example
* <div class="grid-stack">
* <div class="grid-stack-item">
* <div class="grid-stack-item-content">Item 1</div>
* </div>
* </div>
*/
var GridStack = /** @class */ (function () {
/**
* Construct a grid item from the given element and options
* @param el
* @param opts
*/
function GridStack(el, opts) {
var _this = this;
if (opts === void 0) { opts = {}; }
/** @internal */
this._gsEventHandler = {};
/** @internal extra row added when dragging at the bottom of the grid */
this._extraDragRow = 0;
this.el = el; // exposed HTML element to the user
opts = opts || {}; // handles null/undefined/0
// if row property exists, replace minRow and maxRow instead
if (opts.row) {
opts.minRow = opts.maxRow = opts.row;
delete opts.row;
}
var rowAttr = utils_1.Utils.toNumber(el.getAttribute('gs-row'));
// flag only valid in sub-grids (handled by parent, not here)
if (opts.column === 'auto') {
delete opts.column;
}
// elements attributes override any passed options (like CSS style) - merge the two together
var defaults = __assign(__assign({}, utils_1.Utils.cloneDeep(GridDefaults)), { column: utils_1.Utils.toNumber(el.getAttribute('gs-column')) || 12, minRow: rowAttr ? rowAttr : utils_1.Utils.toNumber(el.getAttribute('gs-min-row')) || 0, maxRow: rowAttr ? rowAttr : utils_1.Utils.toNumber(el.getAttribute('gs-max-row')) || 0, staticGrid: utils_1.Utils.toBool(el.getAttribute('gs-static')) || false, _styleSheetClass: 'grid-stack-instance-' + (Math.random() * 10000).toFixed(0), alwaysShowResizeHandle: opts.alwaysShowResizeHandle || false, resizable: {
autoHide: !(opts.alwaysShowResizeHandle || false),
handles: 'se'
}, draggable: {
handle: (opts.handleClass ? '.' + opts.handleClass : (opts.handle ? opts.handle : '')) || '.grid-stack-item-content',
scroll: false,
appendTo: 'body'
}, removableOptions: {
accept: '.' + (opts.itemClass || 'grid-stack-item')
} });
if (el.getAttribute('gs-animate')) { // default to true, but if set to false use that instead
defaults.animate = utils_1.Utils.toBool(el.getAttribute('gs-animate'));
}
this.opts = utils_1.Utils.defaults(opts, defaults);
opts = null; // make sure we use this.opts instead
this.initMargin(); // part of settings defaults...
// Now check if we're loading into 1 column mode FIRST so we don't do un-necessary work (like cellHeight = width / 12 then go 1 column)
if (this.opts.column !== 1 && !this.opts.disableOneColumnMode && this._widthOrContainer() <= this.opts.minWidth) {
this._prevColumn = this.getColumn();
this.opts.column = 1;
}
if (this.opts.rtl === 'auto') {
this.opts.rtl = (el.style.direction === 'rtl');
}
if (this.opts.rtl) {
this.el.classList.add('grid-stack-rtl');
}
// check if we're been nested, and if so update our style and keep pointer around (used during save)
var parentGridItemEl = utils_1.Utils.closestByClass(this.el, GridDefaults.itemClass);
if (parentGridItemEl && parentGridItemEl.gridstackNode) {
this.opts._isNested = parentGridItemEl.gridstackNode;
this.opts._isNested.subGrid = this;
parentGridItemEl.classList.add('grid-stack-nested');
this.el.classList.add('grid-stack-nested');
}
this._isAutoCellHeight = (this.opts.cellHeight === 'auto');
if (this._isAutoCellHeight || this.opts.cellHeight === 'initial') {
// make the cell content square initially (will use resize/column event to keep it square)
this.cellHeight(undefined, false);
}
else {
// append unit if any are set
if (typeof this.opts.cellHeight == 'number' && this.opts.cellHeightUnit && this.opts.cellHeightUnit !== GridDefaults.cellHeightUnit) {
this.opts.cellHeight = this.opts.cellHeight + this.opts.cellHeightUnit;
delete this.opts.cellHeightUnit;
}
this.cellHeight(this.opts.cellHeight, false);
}
this.el.classList.add(this.opts._styleSheetClass);
this._setStaticClass();
this.engine = new gridstack_engine_1.GridStackEngine({
column: this.getColumn(),
float: this.opts.float,
maxRow: this.opts.maxRow,
onChange: function (cbNodes) {
var maxH = 0;
_this.engine.nodes.forEach(function (n) { maxH = Math.max(maxH, n.y + n.h); });
cbNodes.forEach(function (n) {
var el = n.el;
if (!el)
return;
if (n._removeDOM) {
if (el)
el.remove();
delete n._removeDOM;
}
else {
_this._writePosAttr(el, n);
}
});
_this._updateStyles(false, maxH); // false = don't recreate, just append if need be
}
});
if (this.opts.auto) {
this.batchUpdate(); // prevent in between re-layout #1535 TODO: this only set float=true, need to prevent collision check...
var elements_1 = [];
this.getGridItems().forEach(function (el) {
var x = parseInt(el.getAttribute('gs-x'));
var y = parseInt(el.getAttribute('gs-y'));
elements_1.push({
el: el,
// if x,y are missing (autoPosition) add them to end of list - but keep their respective DOM order
i: (Number.isNaN(x) ? 1000 : x) + (Number.isNaN(y) ? 1000 : y) * _this.getColumn()
});
});
elements_1.sort(function (a, b) { return a.i - b.i; }).forEach(function (e) { return _this._prepareElement(e.el); });
this.commit();
}
this.setAnimation(this.opts.animate);
this._updateStyles();
if (this.opts.column != 12) {
this.el.classList.add('grid-stack-' + this.opts.column);
}
// legacy support to appear 'per grid` options when really global.
if (this.opts.dragIn)
GridStack.setupDragIn(this.opts.dragIn, this.opts.dragInOptions);
delete this.opts.dragIn;
delete this.opts.dragInOptions;
this._setupRemoveDrop();
this._setupAcceptWidget();
this._updateWindowResizeEvent();
}
/**
* initializing the HTML element, or selector string, into a grid will return the grid. Calling it again will
* simply return the existing instance (ignore any passed options). There is also an initAll() version that support
* multiple grids initialization at once. Or you can use addGrid() to create the entire grid from JSON.
* @param options grid options (optional)
* @param elOrString element or CSS selector (first one used) to convert to a grid (default to '.grid-stack' class selector)
*
* @example
* let grid = GridStack.init();
*
* Note: the HTMLElement (of type GridHTMLElement) will store a `gridstack: GridStack` value that can be retrieve later
* let grid = document.querySelector('.grid-stack').gridstack;
*/
GridStack.init = function (options, elOrString) {
if (options === void 0) { options = {}; }
if (elOrString === void 0) { elOrString = '.grid-stack'; }
var el = GridStack.getGridElement(elOrString);
if (!el) {
if (typeof elOrString === 'string') {
console.error('GridStack.initAll() no grid was found with selector "' + elOrString + '" - element missing or wrong selector ?' +
'\nNote: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.');
}
else {
console.error('GridStack.init() no grid element was passed.');
}
return null;
}
if (!el.gridstack) {
el.gridstack = new GridStack(el, utils_1.Utils.cloneDeep(options));
}
return el.gridstack;
};
/**
* Will initialize a list of elements (given a selector) and return an array of grids.
* @param options grid options (optional)
* @param selector elements selector to convert to grids (default to '.grid-stack' class selector)
*
* @example
* let grids = GridStack.initAll();
* grids.forEach(...)
*/
GridStack.initAll = function (options, selector) {
if (options === void 0) { options = {}; }
if (selector === void 0) { selector = '.grid-stack'; }
var grids = [];
GridStack.getGridElements(selector).forEach(function (el) {
if (!el.gridstack) {
el.gridstack = new GridStack(el, utils_1.Utils.cloneDeep(options));
delete options.dragIn;
delete options.dragInOptions; // only need to be done once (really a static global thing, not per grid)
}
grids.push(el.gridstack);
});
if (grids.length === 0) {
console.error('GridStack.initAll() no grid was found with selector "' + selector + '" - element missing or wrong selector ?' +
'\nNote: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.');
}
return grids;
};
/**
* call to create a grid with the given options, including loading any children from JSON structure. This will call GridStack.init(), then
* grid.load() on any passed children (recursively). Great alternative to calling init() if you want entire grid to come from
* JSON serialized data, including options.
* @param parent HTML element parent to the grid
* @param opt grids options used to initialize the grid, and list of children
*/
GridStack.addGrid = function (parent, opt) {
if (opt === void 0) { opt = {}; }
if (!parent)
return null;
// create the grid element, but check if the passed 'parent' already has grid styling and should be used instead
var el = parent;
if (!parent.classList.contains('grid-stack')) {
var doc = document.implementation.createHTMLDocument(''); // IE needs a param
doc.body.innerHTML = "<div class=\"grid-stack " + (opt.class || '') + "\"></div>";
el = doc.body.children[0];
parent.appendChild(el);
}
// create grid class and load any children
var grid = GridStack.init(opt, el);
if (grid.opts.children) {
var children = grid.opts.children;
delete grid.opts.children;
grid.load(children);
}
return grid;
};
Object.defineProperty(GridStack.prototype, "placeholder", {
/** @internal create placeholder DIV as needed */
get: function () {
if (!this._placeholder) {
var placeholderChild = document.createElement('div'); // child so padding match item-content
placeholderChild.className = 'placeholder-content';
if (this.opts.placeholderText) {
placeholderChild.innerHTML = this.opts.placeholderText;
}
this._placeholder = document.createElement('div');
this._placeholder.classList.add(this.opts.placeholderClass, GridDefaults.itemClass, this.opts.itemClass);
this.placeholder.appendChild(placeholderChild);
}
return this._placeholder;
},
enumerable: false,
configurable: true
});
/**
* add a new widget and returns it.
*
* Widget will be always placed even if result height is more than actual grid height.
* You need to use `willItFit()` before calling addWidget for additional check.
* See also `makeWidget()`.
*
* @example
* let grid = GridStack.init();
* grid.addWidget({w: 3, content: 'hello'});
* grid.addWidget('<div class="grid-stack-item"><div class="grid-stack-item-content">hello</div></div>', {w: 3});
*
* @param el GridStackWidget (which can have content string as well), html element, or string definition to add
* @param options widget position/size options (optional, and ignore if first param is already option) - see GridStackWidget
*/
GridStack.prototype.addWidget = function (els, options) {
// support legacy call for now ?
if (arguments.length > 2) {
console.warn('gridstack.ts: `addWidget(el, x, y, width...)` is deprecated. Use `addWidget({x, y, w, content, ...})`. It will be removed soon');
// eslint-disable-next-line prefer-rest-params
var a = arguments, i = 1, opt = { x: a[i++], y: a[i++], w: a[i++], h: a[i++], autoPosition: a[i++],
minW: a[i++], maxW: a[i++], minH: a[i++], maxH: a[i++], id: a[i++] };
return this.addWidget(els, opt);
}
function isGridStackWidget(w) {
return w.x !== undefined || w.y !== undefined || w.w !== undefined || w.h !== undefined || w.content !== undefined ? true : false;
}
var el;
if (typeof els === 'string') {
var doc = document.implementation.createHTMLDocument(''); // IE needs a param
doc.body.innerHTML = els;
el = doc.body.children[0];
}
else if (arguments.length === 0 || arguments.length === 1 && isGridStackWidget(els)) {
var content = els ? els.content || '' : '';
options = els;
var doc = document.implementation.createHTMLDocument(''); // IE needs a param
doc.body.innerHTML = "<div class=\"grid-stack-item " + (this.opts.itemClass || '') + "\"><div class=\"grid-stack-item-content\">" + content + "</div></div>";
el = doc.body.children[0];
}
else {
el = els;
}
// Tempting to initialize the passed in opt with default and valid values, but this break knockout demos
// as the actual value are filled in when _prepareElement() calls el.getAttribute('gs-xyz) before adding the node.
// So make sure we load any DOM attributes that are not specified in passed in options (which override)
var domAttr = this._readAttr(el);
options = utils_1.Utils.cloneDeep(options) || {}; // make a copy before we modify in case caller re-uses it
utils_1.Utils.defaults(options, domAttr);
var node = this.engine.prepareNode(options);
this._writeAttr(el, options);
if (this._insertNotAppend) {
this.el.prepend(el);
}
else {
this.el.appendChild(el);
}
// similar to makeWidget() that doesn't read attr again and worse re-create a new node and loose any _id
this._prepareElement(el, true, options);
this._updateContainerHeight();
// check if nested grid definition is present
if (node.subGrid && !node.subGrid.el) { // see if there is a sub-grid to create too
// if column special case it set, remember that flag and set default
var autoColumn = void 0;
var ops = node.subGrid;
if (ops.column === 'auto') {
ops.column = node.w;
ops.disableOneColumnMode = true; // driven by parent
autoColumn = true;
}
var content = node.el.querySelector('.grid-stack-item-content');
node.subGrid = GridStack.addGrid(content, node.subGrid);
if (autoColumn) {
node.subGrid._autoColumn = true;
}
}
this._triggerAddEvent();
this._triggerChangeEvent();
return el;
};
/**
/**
* saves the current layout returning a list of widgets for serialization which might include any nested grids.
* @param saveContent if true (default) the latest html inside .grid-stack-content will be saved to GridStackWidget.content field, else it will
* be removed.
* @param saveGridOpt if true (default false), save the grid options itself, so you can call the new GridStack.addGrid()
* to recreate everything from scratch. GridStackOptions.children would then contain the widget list instead.
* @returns list of widgets or full grid option, including .children list of widgets
*/
GridStack.prototype.save = function (saveContent, saveGridOpt) {
if (saveContent === void 0) { saveContent = true; }
if (saveGridOpt === void 0) { saveGridOpt = false; }
// return copied nodes we can modify at will...
var list = this.engine.save(saveContent);
// check for HTML content and nested grids
list.forEach(function (n) {
if (saveContent && n.el && !n.subGrid) { // sub-grid are saved differently, not plain content
var sub = n.el.querySelector('.grid-stack-item-content');
n.content = sub ? sub.innerHTML : undefined;
if (!n.content)
delete n.content;
}
else {
if (!saveContent) {
delete n.content;
}
// check for nested grid
if (n.subGrid) {
n.subGrid = n.subGrid.save(saveContent, true);
}
}
delete n.el;
});
// check if save entire grid options (needed for recursive) + children...
if (saveGridOpt) {
var o = utils_1.Utils.cloneDeep(this.opts);
// delete default values that will be recreated on launch
if (o.marginBottom === o.marginTop && o.marginRight === o.marginLeft && o.marginTop === o.marginRight) {
o.margin = o.marginTop;
delete o.marginTop;
delete o.marginRight;
delete o.marginBottom;
delete o.marginLeft;
}
if (o.rtl === (this.el.style.direction === 'rtl')) {
o.rtl = 'auto';
}
if (this._isAutoCellHeight) {
o.cellHeight = 'auto';
}
if (this._autoColumn) {
o.column = 'auto';
delete o.disableOneColumnMode;
}
utils_1.Utils.removeInternalAndSame(o, GridDefaults);
o.children = list;
return o;
}
return list;
};
/**
* load the widgets from a list. This will call update() on each (matching by id) or add/remove widgets that are not there.
*
* @param layout list of widgets definition to update/create
* @param addAndRemove boolean (default true) or callback method can be passed to control if and how missing widgets can be added/removed, giving
* the user control of insertion.
*
* @example
* see http://gridstackjs.com/demo/serialization.html
**/
GridStack.prototype.load = function (layout, addAndRemove) {
var _this = this;
if (addAndRemove === void 0) { addAndRemove = true; }
var items = GridStack.Utils.sort(__spreadArrays(layout), -1, this._prevColumn || this.getColumn()); // make copy before we mod/sort
this._insertNotAppend = true; // since create in reverse order...
// if we're loading a layout into 1 column (_prevColumn is set only when going to 1) and items don't fit, make sure to save
// the original wanted layout so we can scale back up correctly #1471
if (this._prevColumn && this._prevColumn !== this.opts.column && items.some(function (n) { return (n.x + n.w) > _this.opts.column; })) {
this._ignoreLayoutsNodeChange = true; // skip layout update
this.engine.cacheLayout(items, this._prevColumn, true);
}
var removed = [];
this.batchUpdate();
// see if any items are missing from new layout and need to be removed first
if (addAndRemove) {
var copyNodes = __spreadArrays(this.engine.nodes); // don't loop through array you modify
copyNodes.forEach(function (n) {
var item = items.find(function (w) { return n.id === w.id; });
if (!item) {
if (typeof (addAndRemove) === 'function') {
addAndRemove(_this, n, false);
}
else {
removed.push(n); // batch keep track
_this.removeWidget(n.el, true, false);
}
}
});
}
// now add/update the widgets
items.forEach(function (w) {
var item = (w.id || w.id === 0) ? _this.engine.nodes.find(function (n) { return n.id === w.id; }) : undefined;
if (item) {
_this.update(item.el, w);
if (w.subGrid && w.subGrid.children) { // update any sub grid as well
var sub = item.el.querySelector('.grid-stack');
if (sub && sub.gridstack) {
sub.gridstack.load(w.subGrid.children); // TODO: support updating grid options ?
_this._insertNotAppend = true; // got reset by above call
}
}
}
else if (addAndRemove) {
if (typeof (addAndRemove) === 'function') {
w = addAndRemove(_this, w, true).gridstackNode;
}
else {
w = _this.addWidget(w).gridstackNode;
}
}
});
this.engine.removedNodes = removed;
this.commit();
// after commit, clear that flag
delete this._ignoreLayoutsNodeChange;
delete this._insertNotAppend;
return this;
};
/**
* Initializes batch updates. You will see no changes until `commit()` method is called.
*/
GridStack.prototype.batchUpdate = function () {
this.engine.batchUpdate();
return this;
};
/**
* Gets current cell height.
*/
GridStack.prototype.getCellHeight = function (forcePixel) {
if (forcePixel === void 0) { forcePixel = false; }
if (this.opts.cellHeight && this.opts.cellHeight !== 'auto' &&
(!forcePixel || !this.opts.cellHeightUnit || this.opts.cellHeightUnit === 'px')) {
return this.opts.cellHeight;
}
// else get first cell height
var el = this.el.querySelector('.' + this.opts.itemClass);
if (el) {
var height = utils_1.Utils.toNumber(el.getAttribute('gs-h'));
return Math.round(el.offsetHeight / height);
}
// else do entire grid and # of rows (but doesn't work if min-height is the actual constrain)
var rows = parseInt(this.el.getAttribute('gs-current-row'));
return rows ? Math.round(this.el.getBoundingClientRect().height / rows) : this.opts.cellHeight;
};
/**
* Update current cell height - see `GridStackOptions.cellHeight` for format.
* This method rebuilds an internal CSS style sheet.
* Note: You can expect performance issues if call this method too often.
*
* @param val the cell height. If not passed (undefined), cells content will be made square (match width minus margin),
* if pass 0 the CSS will be generated by the application instead.
* @param update (Optional) if false, styles will not be updated
*
* @example
* grid.cellHeight(100); // same as 100px
* grid.cellHeight('70px');
* grid.cellHeight(grid.cellWidth() * 1.2);
*/
GridStack.prototype.cellHeight = function (val, update) {
if (update === void 0) { update = true; }
// if not called internally, check if we're changing mode
if (update && val !== undefined) {
if (this._isAutoCellHeight !== (val === 'auto')) {
this._isAutoCellHeight = (val === 'auto');
this._updateWindowResizeEvent();
}
}
if (val === 'initial' || val === 'auto') {
val = undefined;
}
// make item content be square
if (val === undefined) {
var marginDiff = -this.opts.marginRight - this.opts.marginLeft
+ this.opts.marginTop + this.opts.marginBottom;
val = this.cellWidth() + marginDiff;
}
var data = utils_1.Utils.parseHeight(val);
if (this.opts.cellHeightUnit === data.unit && this.opts.cellHeight === data.h) {
return this;
}
this.opts.cellHeightUnit = data.unit;
this.opts.cellHeight = data.h;
if (update) {
this._updateStyles(true, this.getRow()); // true = force re-create, for that # of rows
}
return this;
};
/** Gets current cell width. */
GridStack.prototype.cellWidth = function () {
return this._widthOrContainer() / this.getColumn();
};
/** return our expected width (or parent) for 1 column check */
GridStack.prototype._widthOrContainer = function () {
// use `offsetWidth` or `clientWidth` (no scrollbar) ?
// https://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively
return (this.el.clientWidth || this.el.parentElement.clientWidth || window.innerWidth);
};
/**
* Finishes batch updates. Updates DOM nodes. You must call it after batchUpdate.
*/
GridStack.prototype.commit = function () {
this.engine.commit();
this._triggerRemoveEvent();
this._triggerAddEvent();
this._triggerChangeEvent();
return this;
};
/** re-layout grid items to reclaim any empty space */
GridStack.prototype.compact = function () {
this.engine.compact();
this._triggerChangeEvent();
return this;
};
/**
* set the number of columns in the grid. Will update existing widgets to conform to new number of columns,
* as well as cache the original layout so you can revert back to previous positions without loss.
* Requires `gridstack-extra.css` or `gridstack-extra.min.css` for [2-11],
* else you will need to generate correct CSS (see https://github.com/gridstack/gridstack.js#change-grid-columns)
* @param column - Integer > 0 (default 12).
* @param layout specify the type of re-layout that will happen (position, size, etc...).
* Note: items will never be outside of the current column boundaries. default (moveScale). Ignored for 1 column
*/
GridStack.prototype.column = function (column, layout) {
if (layout === void 0) { layout = 'moveScale'; }
if (column < 1 || this.opts.column === column)
return this;
var oldColumn = this.getColumn();
// if we go into 1 column mode (which happens if we're sized less than minW unless disableOneColumnMode is on)
// then remember the original columns so we can restore.
if (column === 1) {
this._prevColumn = oldColumn;
}
else {
delete this._prevColumn;
}
this.el.classList.remove('grid-stack-' + oldColumn);
this.el.classList.add('grid-stack-' + column);
this.opts.column = this.engine.column = column;
// update the items now - see if the dom order nodes should be passed instead (else default to current list)
var domNodes;
if (column === 1 && this.opts.oneColumnModeDomSort) {
domNodes = [];
this.getGridItems().forEach(function (el) {
if (el.gridstackNode) {
domNodes.push(el.gridstackNode);
}
});
if (!domNodes.length) {
domNodes = undefined;
}
}
this.engine.updateNodeWidths(oldColumn, column, domNodes, layout);
if (this._isAutoCellHeight)
this.cellHeight();
// and trigger our event last...
this._ignoreLayoutsNodeChange = true; // skip layout update
this._triggerChangeEvent();
delete this._ignoreLayoutsNodeChange;
return this;
};
/**
* get the number of columns in the grid (default 12)
*/
GridStack.prototype.getColumn = function () {
return this.opts.column;
};
/** returns an array of grid HTML elements (no placeholder) - used to iterate through our children in DOM order */
GridStack.prototype.getGridItems = function () {
var _this = this;
return Array.from(this.el.children)
.filter(function (el) { return el.matches('.' + _this.opts.itemClass) && !el.matches('.' + _this.opts.placeholderClass); });
};
/**
* Destroys a grid instance. DO NOT CALL any methods or access any vars after this as it will free up members.
* @param removeDOM if `false` grid and items HTML elements will not be removed from the DOM (Optional. Default `true`).
*/
GridStack.prototype.destroy = function (removeDOM) {
if (removeDOM === void 0) { removeDOM = true; }
if (!this.el)
return; // prevent multiple calls
this._updateWindowResizeEvent(true);
this.setStatic(true, false); // permanently removes DD but don't set CSS class (we're going away)
this.setAnimation(false);
if (!removeDOM) {
this.removeAll(removeDOM);
this.el.classList.remove(this.opts._styleSheetClass);
}
else {
this.el.parentNode.removeChild(this.el);
}
this._removeStylesheet();
this.el.removeAttribute('gs-current-row');
delete this.opts._isNested;
delete this.opts;
delete this._placeholder;
delete this.engine;
delete this.el.gridstack; // remove circular dependency that would prevent a freeing
delete this.el;
return this;
};
/**
* enable/disable floating widgets (default: `false`) See [example](http://gridstackjs.com/demo/float.html)
*/
GridStack.prototype.float = function (val) {
this.engine.float = val;
this._triggerChangeEvent();
return this;
};
/**
* get the current float mode
*/
GridStack.prototype.getFloat = function () {
return this.engine.float;
};
/**
* Get the position of the cell under a pixel on screen.
* @param position the position of the pixel to resolve in
* absolute coordinates, as an object with top and left properties
* @param useDocRelative if true, value will be based on document position vs parent position (Optional. Default false).
* Useful when grid is within `position: relative` element
*
* Returns an object with properties `x` and `y` i.e. the column and row in the grid.
*/
GridStack.prototype.getCellFromPixel = function (position, useDocRelative) {
if (useDocRelative === void 0) { useDocRelative = false; }
var box = this.el.getBoundingClientRect();
// console.log(`getBoundingClientRect left: ${box.left} top: ${box.top} w: ${box.w} h: ${box.h}`)
var containerPos;
if (useDocRelative) {
containerPos = { top: box.top + document.documentElement.scrollTop, left: box.left };
// console.log(`getCellFromPixel scrollTop: ${document.documentElement.scrollTop}`)
}
else {
containerPos = { top: this.el.offsetTop, left: this.el.offsetLeft };
// console.log(`getCellFromPixel offsetTop: ${containerPos.left} offsetLeft: ${containerPos.top}`)
}
var relativeLeft = position.left - containerPos.left;
var relativeTop = position.top - containerPos.top;
var columnWidth = (box.width / this.getColumn());
var rowHeight = (box.height / parseInt(this.el.getAttribute('gs-current-row')));
return { x: Math.floor(relativeLeft / columnWidth), y: Math.floor(relativeTop / rowHeight) };
};
/** returns the current number of rows, which will be at least `minRow` if set */
GridStack.prototype.getRow = function () {
return Math.max(this.engine.getRow(), this.opts.minRow);
};
/**
* Checks if specified area is empty.
* @param x the position x.
* @param y the position y.
* @param w the width of to check
* @param h the height of to check
*/
GridStack.prototype.isAreaEmpty = function (x, y, w, h) {
return this.engine.isAreaEmpty(x, y, w, h);
};
/**
* If you add elements to your grid by hand, you have to tell gridstack afterwards to make them widgets.
* If you want gridstack to add the elements for you, use `addWidget()` instead.
* Makes the given element a widget and returns it.
* @param els widget or single selector to convert.
*
* @example
* let grid = GridStack.init();
* grid.el.appendChild('<div id="gsi-1" gs-w="3"></div>');
* grid.makeWidget('#gsi-1');
*/
GridStack.prototype.makeWidget = function (els) {
var el = GridStack.getElement(els);
this._prepareElement(el, true);
this._updateContainerHeight();
this._triggerAddEvent();
this._triggerChangeEvent();
return el;
};
/**
* Event handler that extracts our CustomEvent data out automatically for receiving custom
* notifications (see doc for supported events)
* @param name of the event (see possible values) or list of names space separated
* @param callback function called with event and optional second/third param
* (see README documentation for each signature).
*
* @example
* grid.on('added', function(e, items) { log('added ', items)} );
* or
* grid.on('added removed change', function(e, items) { log(e.type, items)} );
*
* Note: in some cases it is the same as calling native handler and parsing the event.
* grid.el.addEventListener('added', function(event) { log('added ', event.detail)} );
*
*/
GridStack.prototype.on = function (name, callback) {
var _this = this;
// check for array of names being passed instead
if (name.indexOf(' ') !== -1) {
var names = name.split(' ');
names.forEach(function (name) { return _this.on(name, callback); });
return this;
}
if (name === 'change' || name === 'added' || name === 'removed' || name === 'enable' || name === 'disable') {
// native CustomEvent handlers - cash the generic handlers so we can easily remove
var noData = (name === 'enable' || name === 'disable');
if (noData) {
this._gsEventHandler[name] = function (event) { return callback(event); };
}
else {
this._gsEventHandler[name] = function (event) { return callback(event, event.detail); };
}
this.el.addEventListener(name, this._gsEventHandler[name]);
}
else if (name === 'drag' || name === 'dragstart' || name === 'dragstop' || name === 'resizestart' || name === 'resize' || name === 'resizestop' || name === 'dropped') {
// drag&drop stop events NEED to be call them AFTER we update node attributes so handle them ourself.
// do same for start event to make it easier...
this._gsEventHandler[name] = callback;
}
else {
console.log('GridStack.on(' + name + ') event not supported, but you can still use $(".grid-stack").on(...) while jquery-ui is still used internally.');
}
return this;
};
/**
* unsubscribe from the 'on' event below
* @param name of the event (see possible values)
*/
GridStack.prototype.off = function (name) {
var _this = this;
// check for array of names being passed instead
if (name.indexOf(' ') !== -1) {
var names = name.split(' ');
names.forEach(function (name) { return _this.off(name); });
return this;
}
if (name === 'change' || name === 'added' || name === 'removed' || name === 'enable' || name === 'disable') {
// remove native CustomEvent handlers
if (this._gsEventHandler[name]) {
this.el.removeEventListener(name, this._gsEventHandler[name]);
}
}
delete this._gsEventHandler[name];
return this;
};
/**
* Removes widget from the grid.
* @param el widget or selector to modify
* @param removeDOM if `false` DOM element won't be removed from the tree (Default? true).
* @param triggerEvent if `false` (quiet mode) element will not be added to removed list and no 'removed' callbacks will be called (Default? true).
*/
GridStack.prototype.removeWidget = function (els, removeDOM, triggerEvent) {
var _this = this;
if (removeDOM === void 0) { removeDOM = true; }
if (triggerEvent === void 0) { triggerEvent = true; }
GridStack.getElements(els).forEach(function (el) {
if (el.parentElement !== _this.el)
return; // not our child!
var node = el.gridstackNode;
// For Meteor support: https://github.com/gridstack/gridstack.js/pull/272
if (!node) {
node = _this.engine.nodes.find(function (n) { return el === n.el; });
}
if (!node)
return;
// remove our DOM data (circular link) and drag&drop permanently
delete el.gridstackNode;
gridstack_ddi_1.GridStackDDI.get().remove(el);
_this.engine.removeNode(node, removeDOM, triggerEvent);
if (removeDOM && el.parentElement) {
el.remove(); // in batch mode engine.removeNode doesn't call back to remove DOM
}
});
if (triggerEvent) {
this._triggerRemoveEvent();
this._triggerChangeEvent();
}
return this;
};
/**
* Removes all widgets from the grid.
* @param removeDOM if `false` DOM elements won't be removed from the tree (Default? `true`).
*/
GridStack.prototype.removeAll = function (removeDOM) {
if (removeDOM === void 0) { removeDOM = true; }
// always remove our DOM data (circular link) before list gets emptied and drag&drop permanently
this.engine.nodes.forEach(function (n) {
delete n.el.gridstackNode;
gridstack_ddi_1.GridStackDDI.get().remove(n.el);
});
this.engine.removeAll(removeDOM);
this._triggerRemoveEvent();
return this;
};
/**
* Toggle the grid animation state. Toggles the `grid-stack-animate` class.
* @param doAnimate if true the grid will animate.
*/
GridStack.prototype.setAnimation = function (doAnimate) {
if (doAnimate) {
this.el.classList.add('grid-stack-animate');
}
else {
this.el.classList.remove('grid-stack-animate');
}
return this;
};
/**
* Toggle the grid static state, which permanently removes/add Drag&Drop support, unlike disable()/enable() that just turns it off/on.
* Also toggle the grid-stack-static class.
* @param val if true the grid become static.
*/
GridStack.prototype.setStatic = function (val, updateClass) {
var _this = this;
if (updateClass === void 0) { updateClass = true; }
if (this.opts.staticGrid === val)
return this;
this.opts.staticGrid = val;
this._setupRemoveDrop();
this._setupAcceptWidget();
this.engine.nodes.forEach(function (n) { return _this._prepareDragDropByNode(n); }); // either delete or init Drag&drop
if (updateClass) {
this._setStaticClass();
}
return this;
};
/**
* Updates widget position/size and other info. Note: if you need to call this on all nodes, use load() instead which will update what changed.
* @param els widget or selector of objects to modify (note: setting the same x,y for multiple items will be indeterministic and likely unwanted)
* @param opt new widget options (x,y,w,h, etc..). Only those set will be updated.
*/
GridStack.prototype.update = function (els, opt) {
var _this = this;
// support legacy call for now ?
if (arguments.length > 2) {
console.warn('gridstack.ts: `update(el, x, y, w, h)` is deprecated. Use `update(el, {x, w, content, ...})`. It will be removed soon');
// eslint-disable-next-line prefer-rest-params
var a = arguments, i = 1;
opt = { x: a[i++], y: a[i++], w: a[i++], h: a[i++] };
return this.update(els, opt);
}
GridStack.getElements(els).forEach(function (el) {
if (!el || !el.gridstackNode)
return;
var n = el.gridstackNode;
var w = utils_1.Utils.cloneDeep(opt); // make a copy we can modify in case they re-use it or multiple items
delete w.autoPosition;
// move/resize widget if anything changed
var keys = ['x', 'y', 'w', 'h'];
var m;
if (keys.some(function (k) { return w[k] !== undefined && w[k] !== n[k]; })) {
m = {};
keys.forEach(function (k) {
m[k] = (w[k] !== undefined) ? w[k] : n[k];
delete w[k];
});
}
// for a move as well IFF there is any min/max fields set
if (!m && (w.minW || w.minH || w.maxW || w.maxH)) {
m = {}; // will use node position but validate values
}
// check for content changing
if (w.content) {
var sub = el.querySelector('.grid-stack-item-content');
if (sub && sub.innerHTML !== w.content) {
sub.innerHTML = w.content;
}
delete w.content;
}
// any remaining fields are assigned, but check for dragging changes, resize constrain
var changed = false;
var ddChanged = false;
for (var key in w) {
if (key[0] !== '_' && n[key] !== w[key]) {
n[key] = w[key];
changed = true;
ddChanged = ddChanged || (!_this.opts.staticGrid && (key === 'noResize' || key === 'noMove' || key === 'locked'));
}
}
// finally move the widget
if (m) {
_this.engine.cleanNodes()
.beginUpdate(n)
.moveNode(n, m);
_this._updateContainerHeight();
_this._triggerChangeEvent();
_this.engine.endUpdate();
}
if (changed) { // move will only update x,y,w,h so update the rest too
_this._writeAttr(el, n);
}
if (ddChanged) {
_this._prepareDragDropByNode(n);
}
});
return this;
};
/**
* Updates the margins which will set all 4 sides at once - see `GridStackOptions.margin` for format options (CSS string format of 1,2,4 values or single number).
* @param value margin value
*/
GridStack.prototype.margin = function (value) {
var isMultiValue = (typeof value === 'string' && value.split(' ').length > 1);
// check if we can skip re-creating our CSS file... won't check if multi values (too much hassle)
if (!isMultiValue) {
var data = utils_1.Utils.parseHeight(value);
if (this.opts.marginUnit === data.unit && this.opts.margin === data.h)
return;
}
// re-use existing margin handling
this.opts.margin = value;
this.opts.marginTop = this.opts.marginBottom = this.opts.marginLeft = this.opts.marginRight = undefined;
this.initMargin();
this._updateStyles(true); // true = force re-create
return this;
};
/** returns current margin number value (undefined if 4 sides don't match) */
GridStack.prototype.getMargin = function () { return this.opts.margin; };
/**
* Returns true if the height of the grid will be less than the vertical
* constraint. Always returns true if grid doesn't have height constraint.
* @param node contains x,y,w,h,auto-position options
*
* @example
* if (grid.willItFit(newWidget)) {
* grid.addWidget(newWidget);
* } else {
* alert('Not enough free space to place the widget');
* }
*/
GridStack.prototype.willItFit = function (node) {
// support legacy call for now
if (arguments.length > 1) {
console.warn('gridstack.ts: `willItFit(x,y,w,h,autoPosition)` is deprecated. Use `willItFit({x, y,...})`. It will be removed soon');
// eslint-disable-next-line prefer-rest-params
var a = arguments, i = 0, w = { x: a[i++], y: a[i++], w: a[i++], h: a[i++], autoPosition: a[i++] };
return this.willItFit(w);
}
return this.engine.willItFit(node);
};
/** @internal */
GridStack.prototype._triggerChangeEvent = function () {
if (this.engine.batchMode)