Skip to content

Commit

Permalink
cellHeight:auto fix
Browse files Browse the repository at this point in the history
- big re-write on how `cellHeight()` works. you can now call it at any time (not just grid init options) including switching to 'auto' or other modes on the fly.
- fix `cellHeight:auto` now keeps cell square as window resizes (regressing from 2.x TS conversion). `Utils.throttle()` works better too (guaranteed to be called last event)
- new `cellHeight:initial` which makes the cell squares initially, but doesn't change as windows resizes (better performance)
- new grid option `cellHeightThrottle` (100ms) to control throttle of auto sizing triggers
- fix gridstack#1600
- height too small with `cellHeight:auto` loading in 1 column. Now detect we load at 1 column and size accordingly (default 'auto' could make big 700x700 cells, so explicit px might still be wanted)
  • Loading branch information
adumesny committed Jan 31, 2021
1 parent fd117ba commit 765fe3b
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 54 deletions.
56 changes: 56 additions & 0 deletions demo/cell-height.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>cell height demo</title>

<link rel="stylesheet" href="demo.css"/>
<script src="../dist/gridstack-h5.js"></script>
<style type="text/css">
.container {
background-color: lightblue;
width: 100%;
height: 800px;
}
</style>

</head>
<body>
<div class="container">
<h1>Cell Height grid options demo</h1>
<p>sample showing the different cellHeight options and what happens when you resize the window</p>
<div>
<a class="btn btn-primary" onClick="setOpt('auto')" href="#">auto</a>
<a class="btn btn-primary" onClick="setOpt('initial')" href="#">initial</a>
<a class="btn btn-primary" onClick="setOpt(100)" href="#">100px</a>
<a class="btn btn-primary" onClick="setOpt('10vh')" href="#">'10vh'</a>
<a class="btn btn-primary" onClick="setOpt('10%')" href="#">'10%' of blue (broken)</a>
<span>settings:</span><span id="info"></span>
</div>
<br>
<div class="grid-stack"></div>
</div>
<script type="text/javascript">
let opts = {
cellHeight: 'auto', // see other possible values (best to do in here)
cellHeightThrottle: 100,
}
let grid = GridStack.init(opts);
let items = [
{x:0, y:0, content:'0'},
{x:1, y:0, w:2, content:'1'},
{x:3, y:0, h:2, content:'2'},
];
grid.load(items);

let info = document.querySelector('#info');
info.innerHTML = opts.cellHeight;
setOpt = function(val) {
grid.cellHeight(val);
info.innerHTML = val;
}
</script>
</body>
</html>
1 change: 1 addition & 0 deletions demo/responsive.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ <h1>Responsive grid demo</h1>

<script type="text/javascript">
let grid = GridStack.init({
cellHeight: 70,
disableOneColumnMode: true, // will manually do 1 column
float: true });
let text = document.querySelector('#column-text');
Expand Down
6 changes: 5 additions & 1 deletion doc/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ Change log
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## 3.2.0-dev

- TBD
- big re-write on how `cellHeight()` works. you can now call it at any time (not just grid init options) including switching to 'auto' or other modes on the fly.
- fix `cellHeight:auto` now keeps cell square as window resizes (regressing from 2.x TS conversion). `Utils.throttle()` works better too (guaranteed to be called last event)
- new `cellHeight:initial` which makes the cell squares initially, but doesn't change as windows resizes (better performance)
- new grid option `cellHeightThrottle` (100ms) to control throttle of auto sizing triggers
- fix [1600](https://github.com/gridstack/gridstack.js/issues/1600) height too small with `cellHeight:auto` loading in 1 column. Now detect we load at 1 column and size accordingly (default 'auto' could make big 700x700 cells, so explicit px might still be wanted)

## 3.2.0 (2021-1-25)

Expand Down
16 changes: 9 additions & 7 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,14 @@ gridstack.js API
See [example](http://gridstack.github.io/gridstack.js/demo/mobile.html)
- `animate` - turns animation on to smooth transitions (default: `true`)
- `auto` - if `false` gridstack will not initialize existing items (default: `true`)
- `cellHeight` - one cell height (default: `auto`). Can be:
* an integer (px)
* a string (ex: '100px', '10em', '10rem', '10%', `10vh')
* 0 or null, in which case the library will not generate styles for rows. Everything must be defined in CSS files.
* `'auto'` - height will be square cells initially.
- `cellHeight`- one cell height (default?: 'auto'). Can be:
* an integer (px)
* a string (ex: '100px', '10em', '10rem'). Note: % doesn't right - see demo/cell-height.html
* 0, in which case the library will not generate styles for rows. Everything must be defined in your own CSS files.
* `auto` - height will be calculated for square cells (width / column) and updated live as you resize the window - also see `cellHeightThrottle`
* `initial` - similar to 'auto' (start at square cells) but stay that size during window resizing.
- `cellHeightThrottle`?: number - throttle time delay (in ms) used when cellHeight='auto' to improve performance vs usability (default?: 100).
* A value of 0 will make it instant at a cost of re-creating the CSS file at ever window resize event!
- `children`?: GridStackWidget[] - list of children item to create when calling load() or addGrid()
- `column` - number of columns (default: `12`) which can change on the fly with `column(N)` as well. See [example](http://gridstackjs.com/demo/column.html)
- `class`?: string - additional class on top of '.grid-stack' (which is required for our CSS) to differentiate this instance
Expand Down Expand Up @@ -284,8 +287,7 @@ re-layout grid items to reclaim any empty space.

### cellHeight(val: number, update = true)

Update current cell height. This method rebuilds an internal CSS stylesheet (unless optional update=false). Note: You can expect performance issues if
call this method too often.
Update current cell height (see - `cellHeight` options format). This method rebuilds an internal CSS stylesheet (unless optional update=false). Note: You can expect performance issues if call this method too often.

```js
grid.cellHeight(grid.cellWidth() * 1.2);
Expand Down
106 changes: 69 additions & 37 deletions src/gridstack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const GridDefaults: GridStackOptions = {
handleClass: null,
styleInHead: false,
cellHeight: 'auto',
cellHeightThrottle: 100,
margin: 10,
auto: true,
minWidth: 768,
Expand Down Expand Up @@ -223,7 +224,9 @@ export class GridStack {
/** @internal flag to keep cells square during resize */
private _isAutoCellHeight: boolean;
/** @internal track event binding to window resize so we can remove */
private _windowResizeBind: () => GridStack;
private _windowResizeBind: () => void;
/** @internal limit auto cell resizing method */
private _cellHeightThrottle: () => void;
/** @internal true when loading items to insert first rather than append */
private _insertNotAppend: boolean;

Expand Down Expand Up @@ -274,10 +277,16 @@ export class GridStack {

this.opts = Utils.defaults(opts, defaults);
opts = null; // make sure we use this.opts instead
this.initMargin();
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.opts.column;
this.opts.column = 1;
}

if (this.opts.rtl === 'auto') {
this.opts.rtl = el.style.direction === 'rtl';
this.opts.rtl = (el.style.direction === 'rtl');
}

if (this.opts.rtl) {
Expand All @@ -293,11 +302,9 @@ export class GridStack {
}

this._isAutoCellHeight = (this.opts.cellHeight === 'auto');
if (this._isAutoCellHeight) {
if (this._isAutoCellHeight || this.opts.cellHeight === 'initial') {
// make the cell content square initially (will use resize event to keep it square)
let marginDiff = - (this.opts.marginRight as number) - (this.opts.marginLeft as number)
+ (this.opts.marginTop as number) + (this.opts.marginBottom as number);
this.cellHeight(this.cellWidth() + marginDiff, false);
this.cellHeight(undefined, false);
} else {
this.cellHeight(this.opts.cellHeight, false);
}
Expand Down Expand Up @@ -352,7 +359,7 @@ export class GridStack {
this._setupDragIn();
this._setupRemoveDrop();
this._setupAcceptWidget();
this._updateWindowResizeEvent(); // finally this may size us down to 1 column
this._updateWindowResizeEvent();
}

/**
Expand Down Expand Up @@ -574,13 +581,33 @@ export class GridStack {
* 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
* @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);
*/
public cellHeight(val: numberOrString, update = true): GridStack {
public cellHeight(val?: numberOrString, update = true): GridStack {

// 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) {
let marginDiff = - (this.opts.marginRight as number) - (this.opts.marginLeft as number)
+ (this.opts.marginTop as number) + (this.opts.marginBottom as number);
val = this.cellWidth() + marginDiff;
}

let data = Utils.parseHeight(val);
if (this.opts.cellHeightUnit === data.unit && this.opts.cellHeight === data.h) {
return this;
Expand All @@ -595,12 +622,15 @@ export class GridStack {
return this;
}

/**
* Gets current cell width.
*/
/** Gets current cell width. */
public cellWidth(): number {
// use parent width if we're 0 (no size yet)
return (this.el.offsetWidth || this.el.parentElement.offsetWidth || window.innerWidth) / this.opts.column;
return this._widthOrContainer() / this.opts.column;
}
/** return our expected width (or parent) for 1 column check */
private _widthOrContainer(): number {
// 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);
}

/**
Expand Down Expand Up @@ -1307,40 +1337,42 @@ export class GridStack {
*/
public onParentResize(): GridStack {
if (!this.el || !this.el.clientWidth) return; // return if we're gone or no size yet (will get called again)

// make the cells content (minus margin) square again
if (this._isAutoCellHeight) {
Utils.throttle(() => {
let marginDiff = - (this.opts.marginRight as number) - (this.opts.marginLeft as number)
+ (this.opts.marginTop as number) + (this.opts.marginBottom as number);
this.cellHeight(this.cellWidth() + marginDiff);
}, 100);
let changedOneColumn = false;
let oneColumn = !this.opts.disableOneColumnMode && this.el.clientWidth <= this.opts.minWidth;

if (!this._oneColumnMode !== !oneColumn) { // use ! (negate) so we can check undefined == false vs true
this._oneColumnMode = oneColumn;
changedOneColumn = true;
if (this.opts.animate) { this.setAnimation(false); } // 1 <-> 12 is too radical, turn off animation
this.column(oneColumn ? 1 : this._prevColumn);
this._resizeNestedGrids(this.el);
if (this.opts.animate) { this.setAnimation(true); }
}

if (!this.opts.disableOneColumnMode && this.el.clientWidth <= this.opts.minWidth) {
if (this._oneColumnMode) return this;
this._oneColumnMode = true;
this.column(1);
this._resizeNestedGrids(this.el);
} else {
if (!this._oneColumnMode) return this;
delete this._oneColumnMode;
this.column(this._prevColumn);
this._resizeNestedGrids(this.el);
// make the cells content square again
if (this._isAutoCellHeight) {
if (!changedOneColumn && this.opts.cellHeightThrottle) {
if (!this._cellHeightThrottle) {
this._cellHeightThrottle = Utils.throttle(() => this.cellHeight(), this.opts.cellHeightThrottle);
}
this._cellHeightThrottle();
} else {
// immediate update if we've changed to/from oneColumn or have no threshold
this.cellHeight();
}
}

return this;
}

/** add or remove the window size event handler */
private _updateWindowResizeEvent(forceRemove = false): GridStack {
const workTodo = (this._isAutoCellHeight || !this.opts.disableOneColumnMode);

// only add event if we're not nested (parent will call us) and we're auto sizing cells or supporting oneColumn (i.e. doing work)
if (workTodo && !forceRemove && !this.opts._isNested && !this._windowResizeBind) {
const workTodo = (this._isAutoCellHeight || !this.opts.disableOneColumnMode) && !this.opts._isNested;

if (!forceRemove && workTodo && !this._windowResizeBind) {
this._windowResizeBind = this.onParentResize.bind(this); // so we can properly remove later
window.addEventListener('resize', this._windowResizeBind);
this.onParentResize(); // initially call it once...
} else if ((forceRemove || !workTodo) && this._windowResizeBind) {
window.removeEventListener('resize', this._windowResizeBind);
delete this._windowResizeBind; // remove link to us so we can free
Expand Down
14 changes: 10 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,20 @@ export interface GridStackOptions {
auto?: boolean;

/**
* one cell height (default?: 60). Can be:
* one cell height (default?: 'auto'). Can be:
* an integer (px)
* a string (ex: '100px', '10em', '10rem', '10%')
* 0 or null, in which case the library will not generate styles for rows. Everything must be defined in CSS files.
* 'auto' - height will be calculated to match cell width (initial square grid).
* a string (ex: '100px', '10em', '10rem'). Note: % doesn't right - see demo/cell-height.html
* 0, in which case the library will not generate styles for rows. Everything must be defined in your own CSS files.
* 'auto' - height will be calculated for square cells (width / column) and updated live as you resize the window - also see `cellHeightThrottle`
* 'initial' - similar to 'auto' (start at square cells) but stay that size during window resizing.
*/
cellHeight?: numberOrString;

/** throttle time delay (in ms) used when cellHeight='auto' to improve performance vs usability (default?: 100).
* A value of 0 will make it instant at a cost of re-creating the CSS file at ever window resize event!
* */
cellHeightThrottle?: number;

/** (internal) unit for cellHeight (default? 'px') which is set when a string cellHeight with a unit is passed (ex: '10rem') */
cellHeightUnit?: string;

Expand Down
8 changes: 3 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,15 +245,13 @@ export class Utils {
return null;
}

/** delay calling the given function by certain amount of time */
static throttle(callback: () => void, delay: number): () => void {
/** delay calling the given function for given delay, preventing new calls from happening while waiting */
static throttle(func, delay: number): () => void {
let isWaiting = false;

return (...args) => {
if (!isWaiting) {
callback.apply(this, args);
isWaiting = true;
setTimeout(() => isWaiting = false, delay);
setTimeout(() => { func(...args); isWaiting = false; }, delay);
}
}
}
Expand Down

0 comments on commit 765fe3b

Please sign in to comment.