Knockout + Gridster Memory leak

Trouble shooting integration between knockout and Gridster.

by PeterShafer

HTML

<script src="http://knockoutjs.com/downloads/knockout-3.3.0.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery.gridster/0.5.6/jquery.gridster.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.gridster/0.5.6/jquery.gridster.js"></script>
<button data-bind='click: add1'> Add 1 </button>
<button data-bind='click: add20'> Add 20 </button>
</button>

<div class="gridster">
<!-- The list.  Bound to the data model list. -->
  <ul data-bind="template: {foreach: board().widgets, afterAdd: board().addGridster, beforeRemove: board().removeGridster}"
      id="board-gridster">
    <li data-bind="attr: {'id': id, 'data-row': dataRow,'data-sizex': datasizex, 'data-sizey': datasizey}"
        class='gs-w'
        style='list-style-type: none; background:#99FF99;'>
      <div data-bind="click: removeSelected"
           style='float:right; cursor: pointer'>
        X
      </div>

      <div data-bind='if: state() === "Minimized"'>
        <span data-bind="style:{ 'backgroundColor': color">
          -
        </span>
      </div>

      <div data-bind='if: state() === "Maximized"'>
        <span data-bind="text: value">
        </span>
      </div>
    </li>
  </ul>
</div>

JavaScript

var vm;

$(function() {
  vm = new MainViewModel();
  ko.applyBindings(vm);
});

function MainViewModel() {
  var self = this;

  self.board = ko.observable(new BoardViewModel());
  
  self.add1= function() {
    self.board().addRandomWidget();
  };

  self.add20 = function() {
    for(var i = 0; i < 20; i++) {
      self.add1();
    }
  };
};

function BoardViewModel () {
  var self = this;

  // Used for binding to the ui.
  self.widgets = ko.observableArray([]);
  
  // Initialize the gridster plugin.
  self.gridster = $(".gridster").gridster({
    widget_margins : [8, 5],
    widget_base_dimensions : [100, 31],
    extra_rows: 2,
    resize : {
      enabled : false
    }
  }).data('gridster');

  self.cols = self.gridster.cols;
  self.rows = self.gridster.rows;

 
  /**
   * Used as a callback for knockout's afterAdd function.  This will be called 
   * after a node has been added to the dom from the foreach template.  Here,
   * we need to tell gridster that the node has been added and then set up 
   * the correct gridster parameters on the widget.
   */
  self.addGridster = function (node, index, obj) {
    var widget = $(node);
    var column = widget.attr("data-col");
    
    console.log('adding: ');
    console.log(node);
    
    // afterAdd is called one for each html tag.
    // We only want to process it for the main tag, which will have a data-col
    // attribute.
    if (column) {
      // width and height
      var sizeX = obj.datasizex;
      var sizeY = (obj.state() === "Minimized" || obj.state() === "Closed")? 1 : obj.datasizey;

      // add the widget to the next position
      self.gridster.add_widget(widget, sizeX, sizeY);
    }
  };

  /**
   * Used as a callback for knockout's beforeRemove.  Needs
   * to remove node parameter from the dom, or tell gridster
   * that the node should be removed if it is an li. 
   */
  var hackPrevWidget = null;
  self.removeGridster = function (node, index, widget) {
    // TODO this is never called...