diff --git a/demo/index.html b/demo/index.html index 3545e4ee9..9cd9ed3e7 100644 --- a/demo/index.html +++ b/demo/index.html @@ -15,6 +15,7 @@

Demos

  • Float grid
  • Knockout.js
  • +
  • Lazy Load
  • Mobile touch
  • Nested grids
  • Nested Advanced grids
  • diff --git a/demo/lazy_load.html b/demo/lazy_load.html new file mode 100644 index 000000000..bddbe96a3 --- /dev/null +++ b/demo/lazy_load.html @@ -0,0 +1,36 @@ + + + + + + + Lazy loading demo + + + + + + +
    +

    Lazy loading demo

    +

    New V11 GridStackWidget.lazyLoad feature. open console and see widget content (or angular components) created as they become visible.

    +
    +
    +
    +
    + + + diff --git a/doc/CHANGES.md b/doc/CHANGES.md index 2c81b5ce5..bb22e30f5 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -119,6 +119,7 @@ Change log * fix: [#2231](https://github.com/gridstack/gridstack.js/bug/2231),[#1840](https://github.com/gridstack/gridstack.js/bug/1840),[#2354](https://github.com/gridstack/gridstack.js/bug/2354) big overall to how we do sidepanel drag&drop helper. see release notes. * feat: [#2818](https://github.com/gridstack/gridstack.js/pull/2818) support for Angular Component hosting true sub-grids (that size according to parent) without requring them to be only child of grid-item-content. +* feat: Lazy loading of widget content until visible (`GridStackOptions.lazyLoad` and `GridStackWidget.lazyLoad`) ## 10.3.1 (2024-07-21) * fix: [#2734](https://github.com/gridstack/gridstack.js/bug/2734) rotate() JS error diff --git a/doc/README.md b/doc/README.md index 478b7537c..a85747497 100644 --- a/doc/README.md +++ b/doc/README.md @@ -106,6 +106,7 @@ gridstack.js API - `handle` - draggable handle selector (default: `'.grid-stack-item-content'`) - `handleClass` - draggable handle class (e.g. `'grid-stack-item-content'`). If set `handle` is ignored (default: `null`) - `itemClass` - widget class (default: `'grid-stack-item'`) +- `lazyLoad?`: boolean - true when widgets are only created when they scroll into view (visible). also overridable per widget in `GridStackWidget` - `margin` - gap size around grid item and content (default: `10`). Can be: * an integer (px) * a string (ex: '2em', '20px', '2rem') @@ -155,7 +156,7 @@ most of the above options are also available as HTML attributes using the `gs-` Extras: - `gs-current-row` - (internal) current rows amount. Set by the library only. Can be used by the CSS rules. -## Item Options +## Item Options - GridStackWidget options you can pass when calling `addWidget()`, `update()`, `load()` and many others @@ -174,6 +175,7 @@ You need to add `noResize` and `noMove` attributes to completely lock the widget Note: This also allow you to set a maximum h value (but user changeable during normal resizing) to prevent unlimited content from taking too much space (get scrollbar) - `subGrid`?: GridStackOptions - optional nested grid options and list of children - `subGridDynamic`?: boolean - enable/disable the creation of sub-grids on the fly by dragging items completely over others (nest) vs partially (push). Forces `DDDragOpt.pause=true` to accomplish that. +- `lazyLoad?`: boolean - true when widgets are only created when they scroll into view (visible). also optin on entire grid. ## Item attributes diff --git a/src/gridstack.ts b/src/gridstack.ts index aa906763c..43c885edd 100644 --- a/src/gridstack.ts +++ b/src/gridstack.ts @@ -461,12 +461,13 @@ export class GridStack { let el: GridItemHTMLElement; let node: GridStackNode = w; + node.grid = this; if (node?.el) { el = node.el; // re-use element stored in the node } else if (GridStack.addRemoveCB) { el = GridStack.addRemoveCB(this.el, w, true, false); } else { - el = Utils.createWidgetDivs(this.opts.itemClass, w); + el = Utils.createWidgetDivs(this.opts.itemClass, node); } if (!el) return; diff --git a/src/types.ts b/src/types.ts index 2e1092ca2..c4d8079d4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -191,6 +191,9 @@ export interface GridStackOptions { /** additional widget class (default?: 'grid-stack-item') */ itemClass?: string; + /** true when widgets are only created when they scroll into view (visible) */ + lazyLoad?: boolean; + /** * gap between grid item and content (default?: 10). This will set all 4 sides and support the CSS formats below * an integer (px) @@ -331,6 +334,8 @@ export interface GridStackWidget extends GridStackPosition { id?: string; /** html to append inside as content */ content?: string; + /** true when widgets are only created when they scroll into view (visible) */ + lazyLoad?: boolean; /** local (vs grid) override - see GridStackOptions. * Note: This also allow you to set a maximum h value (but user changeable during normal resizing) to prevent unlimited content from taking too much space (get scrollbar) */ sizeToContent?: boolean | number; @@ -447,4 +452,6 @@ export interface GridStackNode extends GridStackWidget { _removeDOM?: boolean; /** @internal had drag&drop been initialized */ _initDD?: boolean; + /** @internal allow delay creation when visible */ + _visibleObservable?: IntersectionObserver; } diff --git a/src/utils.ts b/src/utils.ts index 095e4323a..2bf633324 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -110,19 +110,29 @@ export class Utils { return els; } - /** create the default grid item divs */ - static createWidgetDivs(itemClass?: string, w?: GridStackWidget): HTMLElement { + /** create the default grid item divs, and content possibly lazy loaded calling GridStack.renderCB */ + static createWidgetDivs(itemClass: string, n: GridStackNode): HTMLElement { const el = Utils.createDiv(['grid-stack-item', itemClass]); - Utils.createDiv(['grid-stack-item-content'], el, w); + const cont = Utils.createDiv(['grid-stack-item-content'], el); + + const lazyLoad = n.lazyLoad || n.grid?.opts?.lazyLoad && n.lazyLoad !== false; + if (lazyLoad) { + n._visibleObservable = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { + n._visibleObservable.disconnect(); + delete n._visibleObservable; + GridStack.renderCB(cont, n) + }}); + window.setTimeout(() => n._visibleObservable.observe(el)); // wait until callee sets position attributes + } else GridStack.renderCB(cont, n); + return el; } - /** create a div (possibly 2) with the given classes */ - static createDiv(classes: string[], parent?: HTMLElement, w?: GridStackWidget): HTMLElement { + /** create a div with the given classes */ + static createDiv(classes: string[], parent?: HTMLElement): HTMLElement { const el = document.createElement('div'); classes.forEach(c => {if (c) el.classList.add(c)}); parent?.appendChild(el); - if (w) GridStack.renderCB(el, w); return el; }