Infinite loop for tag-it wrapped into a knockout component

102 views Asked by At

I created a knockout custom binding to wrap tag-it based on the proposal of Droyad. The final code (simplified) is:

ko.bindingHandlers.tagit = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        var bind = function () {
            valueAccessor().tagsList($(element).tagit("assignedTags"));
        };

        var options = $.extend({
            afterTagAdded: bind,
            afterTagRemoved: bind
        }, valueAccessor().options || {});

        // Create tags editor
        $(element).tagit(options);

        ////// Block 1
        var value = ko.utils.unwrapObservable(valueAccessor());
        var tags = value.tagsList();
        for(var x = 0; x < tags.length; x++) {
            $(element).tagit("createTag", tags[x]);
        }
        ////// End Block 1
    },
    ////// Block 2
    update: function (element, valueAccessor, allBindingsAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        var tags = value.tagsList();
        $(element).tagit("removeAll");
        for(var x = 0; x < tags.length; x++) {
            $(element).tagit("createTag", tags[x]);
        }
    }
    ////// End Block 2
};

The corresponding HTML is:

<div data-bind="with: currentDocument">
  <input data-bind="tagit: {tagsList: tags}"></input>
</div>

My problem: I have to use "Block 1" and remove "Block 2" otherwise the component enter an infinite loop. Without block 2, tag-it displays the tags every time I change document (currentDocument changes) and I can edit manually the tags without problems. Instead, if I change currentDocument.tags programmatically, the change is not visualized (obviously). If I remove block 1 and add block 2, the afterTagAdded callback fires for every added tag, that in turn change the tags array, that fires the update function and so forth.

Any idea on how solve the problem? I think if the afterTagAdded and afterTagRemoved callbacks fire only for manual modifications and not for $(element).tagit("createTag", tags[x]); the problem could be solved. Thanks for your help!

1

There are 1 answers

0
SiliconValley On

The solution is simple albeit not elegant. I defined a "loading" variable that is set to true when the change is caused by loading the tags editor. When true blocks the observable update. (In the previous code remove block 1 and add block 2).

Trying to explain the problem on SO, as usual, help me to find a solution. Thanks also for this!

ko.bindingHandlers.tagit = {
    loading: false,
    init: function (element, valueAccessor, allBindingsAccessor) {
        var bind = function () {
            if(ko.bindingHandlers.tagit.loading) return;
            valueAccessor().tagsList($(element).tagit("assignedTags"));
        };
        ...
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        // Load data
        ko.bindingHandlers.tagit.loading = true;
        var value = ko.utils.unwrapObservable(valueAccessor());
        var tags = value.tagsList();
        $(element).tagit("removeAll");
        for(var x = 0; x < tags.length; x++) {
            $(element).tagit("createTag", tags[x]);
        }
        ko.bindingHandlers.tagit.loading = false;
    }