javascriptjqueryjquery-mobileknockout.jscustom-binding

custom binding is not firing update


I am working with system that is using Knockout.js and Jquery Mobile UI combination. One of the issues I ran into was getting Jquery Mobile being applied on elements that have knockout :if bindings. Initial solution we were using was in page cycle fire events when ko.applyBindings and ko.valueHasMutated are being called and then in event handlers calling the following

$(element).find('div[data-onthemovecollapsible="true"]:not(div[data-role="collapsible"])').attr("data-role", "collapsible");
$(element).collapsibleset().collapsibleset("refresh");

Problem was this sometimes would get called to many times or sometimes someone would forget to trigger required events and would produce same bugs over and over again so business has decided move this mechanism to custom binding with following logic: If element is bound (becomes visible or some other way ends up on a page) then apply JQuery mobile UI. init of custom binding would be equivalent of ko.applyBindings and update equivalent of ko.valueHasMutated. It worked fine for when list 'appears' on the page however when pagination or filtering is being used model is not being 're-bound' only viewModel array is being changed and ko.valueHasMutated is being called. I have just found out that update is not getting called when value has mutated is being fired. I have done some reading around and this seems to be a known issue however none of the solutions seem to work in my case

ko.bindingHandlers.expandableListJQM = {
    init: function (element, valueAccessor) {
        $(element).find('div[data-onthemovecollapsible="true"]:not(div[data-role="collapsible"])').attr("data-role", "collapsible");
        $(element).collapsibleset().collapsibleset("refresh");
        var val = ko.utils.unwrapObservable(valueAccessor());
        if (val!= undefined) {
             valueAccessor().subscribe(function (element, valueAccessor) {
                $(element).find('div[data-onthemovecollapsible="true"]:not(div[data-role="collapsible"])').attr("data-role", "collapsible");
                $(element).collapsibleset().collapsibleset("refresh");
            });
        }
    },

    update: function (element, valueAccessor) {
        $(element).find('div[data-onthemovecollapsible="true"]:not(div[data-role="collapsible"])').attr("data-role", "collapsible");
        $(element).collapsibleset().collapsibleset("refresh");
    }
};

even if I am subscribing to valueAccessor() like in example function (element, valueAccessor) {... it's not of much use to me as variable that is being passed as element is empty array and not something usefull.
I could start applying .collapsibleset().collapsibleset by using JQuery selector as I know what ID list has, but it does not feel like a proper solution, we might as well leave that hack we had previously.

How can I on ko.valueHasMutated make knockout.js custom binding update fire with proper arguments being passed?


Solution

  • For the time being I am using this solution as I know someone is going to propose it. But this is only a hack and I would prefer a 'proper' solution from someone.

    ko.bindingHandlers.expandableListJQM = {
        init: function (element, valueAccessor) {
            $(element).find('div[data-onthemovecollapsible="true"]:not(div[data-role="collapsible"])').attr("data-role", "collapsible");
            $(element).collapsibleset().collapsibleset("refresh");
            var element =  $(element);
            var val = ko.utils.unwrapObservable(valueAccessor());
            if (val!= undefined) {
                 valueAccessor().subscribe(function() {
                    $(element).find('div[data-onthemovecollapsible="true"]:not(div[data-role="collapsible"])').attr("data-role", "collapsible");
                    $(element).collapsibleset().collapsibleset("refresh");
                });
            }
        },
    
        update: function (element, valueAccessor) {
            $(element).find('div[data-onthemovecollapsible="true"]:not(div[data-role="collapsible"])').attr("data-role", "collapsible");
            $(element).collapsibleset().collapsibleset("refresh");
        }
    };