ducksboard / gridster.js

gridster.js is a jQuery plugin that makes building intuitive draggable layouts from elements spanning multiple columns
http://gridster.net/
MIT License
6.04k stars 1.2k forks source link

Widget binding with Gridster and Knockout #251

Open rainmakerpl opened 10 years ago

rainmakerpl commented 10 years ago

Hello, Could you please help with the issue of gridster knockout integration? There is an error when adding a widget using knockout databinding. Details described here: http://stackoverflow.com/questions/19549122/widget-binding-with-gridster-and-knockout/19677459#19677459

I have just noticed that my post was deleted from stackoverflow, I will post my message here:

"Same problem here but in addition to what I have seen in your jsfiddle, I got unhandled exception when I try to move the added grid tile: Unhandled exception at line 1587, column 9 in /scripts/gridster/jquery.gridster.with-extras.js

0x800a138f - JavaScript runtime error: Unable to get property 'size_y' of undefined or null reference

From Immediate Window:

this.player_grid_data.size_y Unable to get property 'size_y' of undefined or null reference

el_grid_data.size_x Unable to get property 'size_x' of undefined or null reference"

I have a working workaround but I will not post it know since it is not an elegant solution. I hope someone will help with proper solution.

Regards

IntStarFoo commented 10 years ago

Did you see this JSFiddle? http://jsfiddle.net/Y5swe/3/

I figured out how to make it work but I'm still unsure about why I have to create a new instance of the object to add a widget to an existing object. And I'm unsure what .data('gridster') is doing below as well. I'm obviously new to javascript and JQuery.

$.each($(".layouts_grid2 li"), function (index, obj) {
        if (undefined !== obj.attributes['class'] && -1 === obj.attributes['class'].nodeValue.search('gs_w')) {
            //What is going on here????
            var g = $(".gridster ul").gridster({
                    widget_margins: [5, 5],
                    widget_base_dimensions: [140, 140]
            }).data('gridster');
            g.add_widget(obj);
        }
        });
kurash commented 10 years ago

IntStarFoo-

gridster stores a reference to its object in a data attribute of the DOM element it's attached to, so what the .data('gridster') does is get that object from the element. You shouldn't recreate the object here (assuming you've created it elsewhere). Instead just do this:

var g = $(".gridster ul").data('gridster');
if (g)
  g.add_widget(obj);

If you have just one gridster instance on your page, you might consider caching a reference to the gridster object, or caching a reference to the doc element so you don't have to select it each time.

I have no idea what the rest of your code is trying to do.

IntStarFoo commented 10 years ago

Ah! I see. So .data allows one to hang some state off of an element. Because I use $(“.gridster ul”) to create the object, it has the .data method built in.

(from jQuery Docs..) “jQuery() — which can also be written as $() — searches through the DOM for any elements that match the provided selector and creates a new jQuery object that references these elements:”

This is all starting to make sense now. ☺ Thanks for the help!

I have two more questions:

  1.  Why do I have to call add_object manually when adding to a the observableArray that the “.gridster ul” is bound to?  It works initially but not subsequently.
  2.  When I manually add a widget, I check the class of the LI for ‘gs_w’.  If it exists, I assume that the Gridster Object picked it up.  If not, I assume I have to add it using add_widget.  Is there a better way to do this?

My updated example is at: http://jsfiddle.net/Y5swe/8/

Thanks Again!

IntStarFoo

From: kurash [mailto:notifications@github.com] Sent: Thursday, November 7, 2013 1:53 PM To: ducksboard/gridster.js Cc: John A. Berry Subject: Re: [gridster.js] Widget binding with Gridster and Knockout (#251)

IntStarFoo-

gridster stores a reference to its object in a data attribute of the DOM element it's attached to, so what the .data('gridster') does is get that object from the element. You shouldn't recreate the object here (assuming you've created it elsewhere). Instead just do this:

var g = $(".gridster ul").data('gridster');

if (g)

g.add_widget(obj);

If you have just one gridster instance on your page, you might consider caching a reference to the gridster object, or caching a reference to the doc element so you don't have to select it each time.

I have no idea what the rest of your code is trying to do.

— Reply to this email directly or view it on GitHubhttps://github.com/ducksboard/gridster.js/issues/251#issuecomment-28010221.

kurash commented 10 years ago

Ah! I see. So .data allows one to hang some state off of an element. Because I use $(“.gridster ul”) to create the object, it has the .data method built in.

More accurately, you can always hang something of an element. JQuery just makes it easier to do that by using the .data() method.

I have two more questions:

  1. Why do I have to call add_object manually when adding to a the observableArray that the “.gridster ul” is bound to? It works initially but not subsequently.

I have no idea. I don't use Knockout.

  1. When I manually add a widget, I check the class of the LI for ‘gs_w’. If it exists, I assume that the Gridster Object picked it up. If not, I assume I have to add it using add_widget. Is there a better way to do this?

To make things easier, only add li elements to the list by going through add_widget. If Knockout requires that you add to the DOM before doing the add_widget, then you could use JQuery's hasClass method to simplify things. Instead of this (which isn't quite the logic you want anyway):

        if (undefined !== obj.attributes['class'] && -1 === obj.attributes['class'].nodeValue.search('gs_w')) {

do this:

        if (!$(obj).hasClass('gs_w')) {

That said, I really don't think things are working as you want them to in your code. You'll need to fix up the fiddle a bit to demonstrate what you want to happen that isn't.

IntStarFoo commented 10 years ago

That makes things much more concise. I fixed that and updated the widget with some comments. The issue I am concerned with is the code below. I feel like this should just happen, without having to manually do it. If I comment this block out of the jsfiddle, the new li ends up behind everything and the gridster doesn't know anything about it. I know it has some sense of applying style to new elements because it does it for the first elements on load. It just doesn't 'hear' the message that the UL has been updated.
http://jsfiddle.net/Y5swe/9/

    $.each($(".layouts_grid2 li"), function (index, obj) {
        if (!$(obj).hasClass('gs_w')) {
           g.add_widget(obj);
        }
    });
kurash commented 10 years ago

I feel like this should just happen, without having to manually do it.

It doesn't work that way. gridster has no way of knowing that you've added an item unless you tell it, which is what you are doing by calling add_widget(). So the code you have is necessary, whether you do it "manually", or some magic is added to gridster to do it for you.

You could simplify the code a bit by having knockout (or whatever) add a class to the li, like "not-in-grid". Then your $.each iteration can be something like:

$('.not-in-grid').each(function() {
  $(this).removeClass('not-in-grid');
  g.add_widget(this);
});

I used the jq selector-specifc form of .each() just cuz.

IntStarFoo commented 10 years ago

I do understand the point, but here's the thing... It does know to do it when I first create the gridster object at the bottom of the javascript file. Somehow, gridster knows to walk through all of the LIs that exist within the UL and adds them to itself. At first, I thought it was because I had class="new" in the HTML. Then I thought it is because it sees the data- elements in the attributes. Now I suspect it just grabs any LI elements it sees at the time of creation and tries to add them to itself. So... Like you have explained to me several times - Gridster knows absolutely nothing about the Knockout binding. It doesn't actively watch the UL it is bound to for changes. It just does that the first time it's created.

In this jsfiddle, I demonstrate this by changing a few parameters within the gridData elements. Gridster is indeed looking at each of the elements, seeing data-row, data-col, data-sizex, data-sizey and doing something with them. But only upon object creation.

http://jsfiddle.net/Y5swe/13/

kurash commented 10 years ago

Now I suspect it just grabs any LI elements it sees at the time of creation and tries to add them to itself

Yup, as I stated above, gridster will only incorporate items when you tell it to. There are currently two ways of doing that: 1) when you create the gridster, it will add all of the LI's in the container UL, and 2) when you call add_widget.

If you wanted to get fancy (and work on fewer browsers ;-) you could extend gridster to use DOM Manipulation events to get notified when a new LI is added to the list, and call add_widget when that happens. I think that's the functionality you're really looking for, but it depends on a DOM feature that is unofficial (and sparsely supported, and marked for replacement).

rainmakerpl commented 10 years ago

I observe similar behaviour - initially the knockout binding works, but after adding a widget through data binding (pushing item on a collection), the gridster stops working. I have a workaround but I cannot replicate it in jsfiddle. The idea is that after adding or removing widget, I remove the apropriate html, create new one, apply knockout bindings and inject html. I tried in jsfiddle but somehow it does not work - http://jsfiddle.net/Y5swe/26/ Locally I have durandal spa and this workaround works.

jbblanchet commented 10 years ago

I posted a working solution in http://stackoverflow.com/questions/19549122/widget-binding-with-gridster-and-knockout/20080711#20080711.