jQuery Sortable causes Knockout to lose track of/recreate DOM node?

731 views
Skip to first unread message

mathieu...@gmail.com

unread,
Jun 24, 2012, 9:50:10 PM6/24/12
to knock...@googlegroups.com
Hello all,

I've been loving working with Knockout.js.  It's an incredible tool.  Thank-you to everyone who has worked on it. :-)

However, I've been struggling with a problem when using jQuery UI's sortable, and I believe it might just be a bug in Knockout.  I've reproduced a minimal testcase on jsfiddle: http://jsfiddle.net/9EUym/.  In this test scenario, a model is used to populate a 5 element <ul>.  If you drag "Item #1" down and place it between "Item #3" and "Item #4", then magically a second, new, "Item #1" will appear.  The problem seems to be triggered by reordering items in an observableArray after jQuery UI has been used to sort them; I'm doing this in a collaborative application to handle other-clients performing hand sorting operations reflecting in real-time on every user's display.

I've tried a few things to address this issue.  I moved the modifications of the view model out of the sortable "stop" event handler, and/or used setTimeout to ensure that the sortable finishes working first.  That had no effect.  I used jQuery UI's "clone" helper option so that the original <li> item is not moved.  That didn't have any effect.  It seems that Knockout is losing track of the original DOM node and believes it has to create a new node.  I assume that something in jQuery UI is triggering this behavior, but I've had difficulty reading and understanding the relevant portions of knockout to figure out how it maps DOM nodes to their data elements.

I would appreciate any advice as to how to proceed.  I was thinking I'd disable parts of the jQuery UI sortable until I could maybe find what triggers this, but my gut feeling is that I'd just be hacking together a workaround where the problem lies elsewhere... any other approaches that might be a better idea?

Thanks,

Mathieu

mathieu...@gmail.com

unread,
Jun 24, 2012, 10:48:59 PM6/24/12
to knock...@googlegroups.com
Ah, I think I have found the root of this problem.  jQuery UI slightly modifies the nodes that are part of the sortable, but not in an immediately obvious way.  It will remove an element and insert it into another location relative to other elements, and it does not retain the ordering and presence of non-element nodes between elements (specifically, whitespace text nodes).  When Knockout refreshes an array and attempts to delete previously mapped elements, fixUpVirtualElements attempts to find an array of contiguous elements starting with the text whitespace before the LI, then the LI, then the text whitespace after the LI, but jQuery has moved the elements out of that order.

As a workaround, if I modify my test case from:

    <ul id="list" data-bind="foreach: array">
        <li data-bind="text: text">Text</li>
    </ul> 

To this:

    <ul id="list" data-bind="foreach: array"><li data-bind="text: text">Text</li></ul> 

It then works perfectly.

The issue is on the fault of jQuery UI, not Knockout.  I did have the thought though that Knockout might benefit a bit by ignoring whitespace text nodes... but I'm sure there could be downsides to that as well.

Mathieu

Michael Best

unread,
Jun 25, 2012, 3:05:30 AM6/25/12
to KnockoutJS
It's great that you figured it out yourself. I have written about this
in detail here:

https://github.com/rniemeyer/knockout-sortable/issues/9

-- Michael

mathieu...@gmail.com

unread,
Jun 25, 2012, 8:29:41 AM6/25/12
to knock...@googlegroups.com
Shoot.  I wish I would've found that in my Googlings. :-)  All I could find was example implementations that recommended the " ui.item.remove(); " approach, which failed quite subtly with corner cases in connected sortables.

Thanks,

Mathieu
Reply all
Reply to author
Forward
0 new messages