PlTaylor
PlTaylor

Reputation: 7525

Knockout and JSTree not rendering properly

I am using KnockoutJS on my page and trying to populate the DOM for a JsTree, but the JsTree is not identifying the leaves correctly and applying any changes to the code of the leaves(or folders) that KnockoutJs populates. Is there a way to tell JsTree that there is new data and should re-evaluate everything? Script at beginning of page

$(document).ready(function() {

        function treeNode(data) {
            var self = this;
            self.id = ko.observable(data.ID);
            self.name = ko.observable(data.Name);
        }

        function partsViewModel() {
            var self = this;
            self.partTypes = ko.observableArray([]);
            self.selectedPartType = ko.observable();
            self.selectedPartType = ko.observable();

            $.getJSON("/PartKO/PartTypes", function(allData) {
                var mappedPartTypes = $.map(allData, function(item) { return new treeNode(item); });
                self.partTypes(mappedPartTypes);
            });

            $("#navigation").jstree({
                "themes": { "theme": "classic" },
                "plugins": ["themes", "html_data"]
            });
        }

        ko.applyBindings(new partsViewModel());

    });

HTML

ul id="navigation" class="treeview" data-bind="foreach: partTypes">
        <li>
            <a href="#" data-bind="text:name"></a>
        </li>
    </ul>

Upvotes: 2

Views: 2635

Answers (1)

kubetz
kubetz

Reputation: 8556

You need to call .jstree after the list is populated, so move the call to the end of the $.getJSON callback.

Also the navigation should be a container containing the <ul> element and not the list itself.

Change partsViewModel function to:

function partsViewModel() {
    var self = this;
    self.partTypes = ko.observableArray([]);
    self.selectedPartType = ko.observable();
    self.selectedPartType = ko.observable();

    $.getJSON("/PartKO/PartTypes", function(allData) {
      var mappedPartTypes = $.map(allData, function(item) { return new treeNode(item); });
      self.partTypes(mappedPartTypes);

      $("#navigation").jstree({
        "themes": { "theme": "classic" },
        "plugins": ["themes", "html_data"]
      });
    });
}

And your HTML to:

<div id="navigation">
  <ul class="treeview" data-bind="foreach: partTypes">
    <li>
        <a href="#" data-bind="text:name"></a>
    </li>
  </ul>
</div>

HERE is a simplified example.

Update:

If you want to update the tree everytime the <ul> is changed you can use Knockout's .afterRender callback. This will be called after the DOM is updated. You cannot use .subscribe() because that is called before the DOM changes.

Insert this into partsViewModel and remove the .jstree part from .getJSON callback:

    self.redrawTree = function() {
        $("#navigation").jstree({
            "themes": {
                "theme": "classic"
            },
            "plugins": ["themes", "html_data"]
        });
    };

And change HTML to:

<div id="navigation">
  <ul class="treeview" data-bind="foreach: { data: partTypes, afterRender: redrawTree }">
    <li>
      <a href="#" data-bind="text:name"></a>
    </li>
  </ul>
</div>

Upvotes: 5

Related Questions