1

I'm loading pages via ajax, on the start of each ajax request I clean the page and then load applybindings on the new page that is added. I keep a persistent model throughout, it's the view that updates.

addEventListeners(){
    this.subscriptions.push(PubSub.subscribe('route.start',  this.removeShopView));
    this.subscriptions.push(PubSub.subscribe('route.success',  this.addShopView));
}

removeShopView(){
    ko.cleanNode(this.currentPage);
}   

addShopView(){
    this.currentPage = document.querySelector('.page-context');
    ko.applyBindings(this.model, this.currentPage);
}

My problem is that in cleaning the node, I seem to be erasing the event listener attached to my anchor link on the page. This anchor link contains an element displays a counter of products in my model

<ul>
    <li><a class='nav-bag' href="/bag">Bag<span data-bind='text: counter'></span></a></li>
</ul>

This event I have setup elsewhere no longer gets triggered, my page refreshes instead of running the ajax call.

setupLinks(){
    this.pageContextWrap.addEventListener("click", (e) => {
        this.linkClickType = undefined;
        if (e.target && e.target.nodeName.toLowerCase() == 'a') {

            if(href.indexOf(document.domain) === -1) {
                console.log("outside link");
                this.href = false;
                return;
            }

            // anchor link is local page, ajax load the href
            this.doAjaxCall();
            e.preventDefault();
            e.stopPropagation();
        }
    });
}

Is there a better way to go about this? Does cleaning the node remove all associated event listeners?

Jai Sandhu
  • 224
  • 1
  • 4
  • 19
  • 1
    You should not be using `cleanNode`. You can probably put your whole app into Knockout, turning the Ajax HTML into `template` nodes, and using them in [the template binding](http://knockoutjs.com/documentation/template-binding.html). – Roy J Apr 19 '17 at 02:08
  • I understand cleanNode is actually used by Knockout internally and it’s better to template sections to re-render the code. cleanNode discussions on Stackoverflow actually led me astray, until I found http://stackoverflow.com/questions/15063794/can-cleannode-be-used-to-clean-binding/15069509#15069509. In my case an ajax call loads a new page, that replaces the old page. Would you suggest I bind my unchanged data model to the new view using ko.applyBindings after every ajax call and template each section of the page, like the shopping basket for example? – Jai Sandhu Apr 19 '17 at 08:00
  • Your use of `applyBindings` is appropriate for what you need to do. I'm not sure what the cleanest/most Knockout way of doing it is, but I'm thinking about it. – Roy J Apr 19 '17 at 11:50
  • Have a look at this: http://knockoutjs.com/documentation/custom-bindings-controlling-descendant-bindings.html – Roy J Apr 19 '17 at 12:08

1 Answers1

3

First, as you found, you should not use cleanNode.

Second, you should prefer to have Knockout manage the entire DOM rather than a chunk of it. In your case, you're handling the app the old-fashioned manipulate-the-DOM-yourself way, and managing the changing content with Knockout.

All you really need is a version of the html binding that applies bindings to its content. A simple custom binding handler using applyBindingsToDescendants can make that work. Example:

ko.bindingHandlers.boundHtml = {
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        const contentHtml = ko.unwrap(valueAccessor());
        
        element.innerHTML = contentHtml;
        ko.applyBindingsToDescendants(bindingContext, element)
    }
};

const someContents = [
'First content <span data-bind="text:someValue"></span>',
'Second content <span data-bind="text:anotherValue"></span>',
'Third content <span data-bind="text:someValue() + anotherValue()"></span>'
]

ko.applyBindings({
  pageContent: ko.observable('hi there'),
  someValue: ko.observable(3),
  anotherValue: ko.observable('foo'),
  changePage() {
    if (someContents.length) {
      this.pageContent(someContents.shift());
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="boundHtml:pageContent"></div>
<button data-bind="click:changePage">Change</button>
Community
  • 1
  • 1
Roy J
  • 42,522
  • 10
  • 78
  • 102
  • Thank you Roy, I've put together a fiddle simulating an ajax page load, I feel like it's almost there. To keep this simple I'm actually trying to maintain the shopping bag counter each time a new page is loaded. It sets it to 1 on the first load, but I haven't managed to get my head around getting this to update the view for the second or third page load http://jsfiddle.net/j2is/zcnxnL85/7/ – Jai Sandhu Apr 19 '17 at 16:44
  • You are still doing the some-jQuery-some-Knockout thing. Model everything. Update the model as needed. See http://jsfiddle.net/ezyLn828/1/ – Roy J Apr 19 '17 at 17:11
  • Ah this makes total sense now, sorry about the jQuery pollution – Jai Sandhu Apr 19 '17 at 17:23