Gridster jQuery plugin on AngularJS ng-repeat content going bad

All we need is an easy explanation of the problem, so here it is.

I’m trying to integrate Gridster with AngularJS, but without too much success yet.

Reading the documentation on Angular UI’s ui-jq directive, I get the impression that this (check fiddle) should work. But when I look a bit further with Chrome’s debugger, it turns out that on this line, it doesn’t find any children at all.

I suspect that somewhere in the ng-repeat directive, AngularJS decides to rip out the part that will be repeated, and I see why, but that doesn’t solve my problem. I’d welcome any clue that would help me to get a little further.

Update 1

I started turning it into a directive, hoping that would improve things. However, the nested ng-repeat is also getting in the way in case of a homegrown directive. I tried postponing hooking up the jQuery plugin as long as I could ($evalAsync) and alike, and eventually ended up using a $timeout. That’s the only way in which I could get it working.

Update 2

I think the original approach would have never given me what I needed. So implemented a custom directive. See my answer below.

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I eventually ended up writing my own directives for it. I needed to be sure that every change to the underlying data would be seen by gridster, but at the same time, I didn’t want to write my own monitoring on the data model and replace everything you normally do within gridster with a directive that hides all of that. (It would involve implementing most of ng-repeat within the directive itself.)

This is what I have (and assume “foo” to be the name of my module):

foo.directive "gridster", () -> {
  restrict: "E"
  template: '<div class="gridster"><div ng-transclude/></div>'
  transclude: true
  replace: true
  controller: () ->
    gr = null
    return {
      init: (elem) ->
        ul = elem.find("ul")
        gr = ul.gridster().data('gridster')
        return
      addItem: (elm) ->
        gr.add_widget elm
        return
      removeItem: (elm) ->
        gr.remove_widget elm
        return
    }
  link: (scope, elem, attrs, controller) ->
    controller.init elem
}

foo.directive "gridsterItem", () -> {
  restrict: "A"
  require: "^gridster"
  link: (scope, elm, attrs, controller) ->
    controller.addItem elm
    elm.bind "$destroy", () ->
      controller.removeItem elm
      return
} 

With this, I can have a gridster generated view, by adding this:

<gridster>
  <ul>
    <li ... ng-repeat="item in ..." gridster-item>
      <!-- Have something here for displaying that item. -->
      <!-- In my case, I'm switching here based on some item properties. -->
    </li>
  </ul>
</gridster>

Whenever items are added to or removed from the collection observed by the ng-repeat directive, they will be automatically added and removed from gridster controlled view.

EDIT

Here is a plunk demonstrating a slightly modified version of this directive:

angular.module('ngGridster', []);

angular.module('ngGridster').directive('gridster', [
    function () {
        return {
            restrict: 'E',
            template: '<div class="gridster"><div ng-transclude/></div>',
            transclude: true,
            replace: true,
            controller: function () {
                gr = null;
                return {
                    init: function (elem, options) {
                        ul = $(elem);
                        gr = ul.gridster(angular.extend({
                          widget_selector: 'gridster-item'
                        }, options)).data('gridster');
                    },
                    addItem: function (elm)  {
                        gr.add_widget(elm);
                    },
                    removeItem: function (elm) {
                        gr.remove_widget(elm);
                    }
                };
            },
            link: function (scope, elem, attrs, controller) {
              var options = scope.$eval(attrs.options);
              controller.init(elem, options);
            }
        };
    }
]);

angular.module('ngGridster').directive('gridsterItem', [
    function () {
        return {
            restrict: 'EA',
            require: '^gridster',
            link: function (scope, elm, attrs, controller) {
                controller.addItem(elm);
                elm.bind('$destroy', function () {
                    controller.removeItem(elm);
                });
            }
        };
    }
]);

Method 2

If you want you can try and roll your own wrapper for gridster. I spent most of the night last night and it was a little glitchy. The way gridster handles serialization isn’t straightforward, etc. Anyway I stumbled on this project and it works really well.

https://github.com/ManifestWebDesign/angular-gridster

I couldn’t find an online demo so I made a plunk of it:

http://plnkr.co/edit/r5cSY1USjtr2bSs7rvlC?p=preview

Method 3

This will be fixed in the next release of

https://github.com/angular-ui/angular-ui/pull/347

The new deferred attribute will take care of the problem as soon as I can figure out why the stupid unit tests won’t pass.

Method 4

There’s also Angular Gridster.

Came across it and this question while researching Gridster implementation in Angular.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply