Search code examples
jqueryhtmlcsszurb-foundationgridstack

How to set widget data from local storage using gridstack.js


Scenario

I have a dashboard with some widgets grouped into tabs that the user can click through. The widgets have default starting positions including height and width, but if the user moves or resizes them (using gridstack.js) I save the new widget data to local storage.

If the user clicks between tabs the new widget positions are maintained.

Problem

When the user refreshes the page, the default widget positions are loaded instead of the data in local storage. I tried to set the new positions using getWidgetData() but no luck.

I have checked local storage for my grid-layout data and the new positions are present.

Code example

Here is Codepen since I think SO requires allow-same-origin, thus breaking the snippet.

// Initialize zurb foundation
$(document).foundation();

$(function() {
  var grid = $('.grid-stack').data('gridstack');

  var options = {
    cellHeight: 40,
    verticalMargin: 28,
    animate: false,
  };

  // Initialize grid-stack widgets
  widgetsInit = function(options) {
    $('.grid-stack').gridstack(options);
    getWidgetData(); // Find and set positions from local storage
  }

  updateVisibleWidgetHeights = function(options) {
    var targetPanel = '.tabs-panel.is-active';

    $(targetPanel + ' .grid-stack .grid-stack-item').each(function(i){
      $(targetPanel + ' .grid-stack').data('gridstack').resize(
        $(targetPanel + ' .grid-stack .grid-stack-item')[i],
        $($(targetPanel + ' .grid-stack .grid-stack-item')[i]).attr('data-gs-width'),
        Math.ceil(($(targetPanel + ' .grid-stack .grid-stack-item .grid-stack-item-content')[i].scrollHeight + $(targetPanel + ' .grid-stack').data('gridstack').opts.verticalMargin) / ($(targetPanel + ' .grid-stack').data('gridstack').cellHeight() + $(targetPanel + ' .grid-stack').data('gridstack').opts.verticalMargin))
      );
    });
  }

  updateAllWidgetHeights = function(options) {
    $('.grid-stack .grid-stack-item').each(function(i){
      $('.grid-stack').data('gridstack').resize(
        $('.grid-stack .grid-stack-item')[i],
        $($('.grid-stack .grid-stack-item')[i]).attr('data-gs-width'),
        Math.ceil(($('.grid-stack .grid-stack-item .grid-stack-item-content')[i].scrollHeight + $('.grid-stack').data('gridstack').opts.verticalMargin) / ($('.grid-stack').data('gridstack').cellHeight() + $('.grid-stack').data('gridstack').opts.verticalMargin))
      );
    });
  }

  getWidgetData = function() {
    // // Load from local storage
    var serialization = null;
    if (serialization = localStorage.getItem("grid-layout")) {
      _.each(JSON.parse(serialization), function (node) {
        // Update each widget's properties from values in storage
        $('.grid-stack-item[data-custom-id='+ node.id +']').attr({
          'data-gs-x': node.x,
          'data-gs-y': node.y,
          'data-gs-width': node.width,
          'data-gs-height': node.height,
        });
      });
    } else {
      console.log("There was no local storage data to read")
    }
  }

  widgetsInit(options);
  updateVisibleWidgetHeights();

  $('.grid-stack').on('change', function (event, items) {
    // Save data to local storage
    var res = _.map($('.grid-stack .grid-stack-item'), function (el) {
      el = $(el);
      node = el.data('_gridstack_node');
      return {
        // Grab widget properties
        id: el.attr('data-custom-id'),
        x: node.x,
        y: node.y,
        width: node.width,
        height: node.height
      };
    });
    localStorage.setItem("grid-layout", JSON.stringify(res))
  });

  // On tab change update widget heights to the height of the content they contain
  $(document).on('change.zf.tabs', function() {
    updateVisibleWidgetHeights();
  })
});
.grid-stack > .grid-stack-item > .grid-stack-item-content {
    background-color: #fff;
    cursor: move;
    border: 1px solid #e3e3e3;
    border-radius: 4px;
	padding: 1rem;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/0.4.0/gridstack.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.4.3/css/foundation.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/0.4.0/gridstack.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/0.4.0/gridstack.jQueryUI.min.js'></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.4.3/js/foundation.min.js"></script>


<ul class="tabs" data-tabs id="example-tabs" data-deep-link="true" data-update-history="true" data-match-height="false">
  <li class="tabs-title is-active"><a href="#panel1" aria-selected="true">Tab 1</a></li>
  <li class="tabs-title"><a href="#panel2">Tab 2</a></li>
</ul>

<div class="tabs-content unstyled" data-tabs-content="example-tabs">
  <div class="tabs-panel is-active" id="panel1">
    <div class="grid-stack">
      <div class="grid-stack-item" data-gs-x="0" data-gs-y="0" data-gs-width="4" data-gs-height="2">
        <div class="grid-stack-item-content">#1</div>
      </div>
      <div class="grid-stack-item" data-gs-x="4" data-gs-y="0" data-gs-width="4" data-gs-height="2">
        <div class="grid-stack-item-content">#2</div>
      </div>
      <div class="grid-stack-item" data-gs-x="8" data-gs-y="0"data-gs-width="4" data-gs-height="2">
        <div class="grid-stack-item-content">#3</div>
      </div>
    </div>
  </div>
  <div class="tabs-panel" id="panel2">
    <div class="grid-stack">
      <div class="grid-stack-item" data-gs-x="0" data-gs-y="0" data-gs-width="4" data-gs-height="2">
        <div class="grid-stack-item-content">#4</div>
      </div>
      <div class="grid-stack-item" data-gs-x="4" data-gs-y="0" data-gs-width="4" data-gs-height="2">
        <div class="grid-stack-item-content">#5</div>
      </div>
      <div class="grid-stack-item" data-gs-x="8" data-gs-y="0" data-gs-width="4" data-gs-height="2">
        <div class="grid-stack-item-content">#6</div>
      </div>
    </div>
  </div>
</div>


Solution

  • You need to look into few things.

    The default examples that are available are for a single instance whereas you have multiple.

    You are saving and loading the gridstack in one go which means you have no track of the element grid-stack-item and also you have no track which point belongs to which instance the first tab or the second,

    So even if you get it to work it will try to position all the 6 different widgets inside the very first tab rather than loading it in their respective tabs.

    So you need to make changes to the following

    • $('.grid-stack').on('change', function (event, items) {

    • getWidgetData()

    • assign id or custom-data-id attribute in you grid-stack-items so that they have a unique identifier

    • wrap the tabs inside a conatiner with id tabs-cotnainer.

    on the event change for the gridstack you should create the json object consisting the points in the sections dedicated to the tabs it will store like below.

    {
      "items": {
        "panel1": [
          {
            "id": "panel1-item1",
            "x": 0,
            "y": 0,
            "width": 4,
            "height": 2
          },
          {
            "id": "panel1-item2",
            "x": 4,
            "y": 0,
            "width": 4,
            "height": 2
          },
          {
            "id": "panel1-item3",
            "x": 8,
            "y": 0,
            "width": 4,
            "height": 2
          }
        ],
        "panel2": [
          {
            "id": "panel2-item1",
            "x": 0,
            "y": 0,
            "width": 4,
            "height": 2
          },
          {
            "id": "panel2-item2",
            "x": 4,
            "y": 0,
            "width": 4,
            "height": 2
          },
          {
            "id": "panel2-item3",
            "x": 8,
            "y": 0,
            "width": 4,
            "height": 2
          }
        ]
      }
    }
    

    replace with the following

    $('.grid-stack').on('change', function (event, items) {
        let tabs = $('#tabs-container >ul >li');
        let totalTabs = $('#tabs-container >ul >li').length;
    
        let storagePositions = {
            items: {}
        };
        $.each(tabs, function (index, elem) {
            let panel = $(elem).find('a').attr('href').replace("#", '');
            let stackItems = $('#' + panel + ' .grid-stack .grid-stack-item');
    
            storagePositions.items[panel] = _.map(stackItems, function (el) {
    
                el = $(el);
                node = el.data('_gridstack_node');
                //console.log(node);
    
                return { // Grab widget properties
                    id: el.attr('data-custom-id'),
                    x: node.x,
                    y: node.y,
                    width: node.width,
                    height: node.height
                };
            });
        });
    
        // Save data to local storage
        localStorage.setItem("grid-layout", JSON.stringify(storagePositions))
    });
    

    and then load the tabs in the similar manner

    getWidgetData = function () {
    
        // // Load from local storage
        var serialization = null;
        let tabs = $('#tabs-container >ul >li');
        let totalTabs = $('#tabs-container >ul >li').length;
    
        if (localStorage.getItem("grid-layout") !== null) {
    
            serialization = JSON.parse(localStorage.getItem("grid-layout"));
    
    
    
            $.each(tabs, function (index, elem) {
                let panel = $(elem).find('a').attr('href').replace('#', '');
                let myGrid = $('#' + panel + ' .grid-stack').data('gridstack')
    
                var items = GridStackUI.Utils.sort(serialization.items[panel]);
                _.each(items, function (node) {
    
                    myGrid.update($('div[data-custom-id="' + node.id + '"]'),
                        node.x, node.y, node.width, node.height);
                });
            });
        } else {
            console.log("There was no local storage data to read")
        }
    }
    

    Your updated HTML will look like following

    <div id="tabs-container">
        <ul class="tabs dashboard-tabs" data-tabs id="example-tabs" data-deep-link="true" data-update-history="true" data-match-height="false">
            <li class="tabs-title is-active">
                <a href="#panel1" aria-selected="true">Tab 1</a>
            </li>
            <li class="tabs-title">
                <a href="#panel2">Tab 2</a>
            </li>
        </ul>
    
        <div class="tabs-content unstyled" data-tabs-content="example-tabs">
            <div class="tabs-panel is-active" id="panel1">
                <div class="grid-stack">
                    <div class="grid-stack-item" data-custom-id="panel1-item1" data-gs-x="0" data-gs-y="0" data-gs-width="4" data-gs-height="2">
                        <div class="grid-stack-item-content">#1</div>
                    </div>
                    <div class="grid-stack-item" data-custom-id="panel1-item2" data-gs-x="4" data-gs-y="0" data-gs-width="4" data-gs-height="2">
                        <div class="grid-stack-item-content">#2</div>
                    </div>
                    <div class="grid-stack-item" data-custom-id="panel1-item3" data-gs-x="8" data-gs-y="0" data-gs-width="4" data-gs-height="2">
                        <div class="grid-stack-item-content">#3</div>
                    </div>
                </div>
            </div>
            <div class="tabs-panel" id="panel2">
                <div class="grid-stack">
                    <div class="grid-stack-item" data-custom-id="panel2-item1" data-gs-x="0" data-gs-y="0" data-gs-width="4" data-gs-height="2">
                        <div class="grid-stack-item-content">#4</div>
                    </div>
                    <div class="grid-stack-item" data-custom-id="panel2-item2" data-gs-x="4" data-gs-y="0" data-gs-width="4" data-gs-height="2">
                        <div class="grid-stack-item-content">#5</div>
                    </div>
                    <div class="grid-stack-item" data-custom-id="panel2-item3" data-gs-x="8" data-gs-y="0" data-gs-width="4" data-gs-height="2">
                        <div class="grid-stack-item-content">#6</div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    

    See here the full working demo on JSFIDDLE you can update positions in both of the tabs and then try refreshing the page it will show the updated positions.

    NOTE: just remove the previous values once by writing localStorage.removeItem("grid-layout") on the top of the script, and them remove this line after running for the first time.