Alan
Alan

Reputation: 3745

Knockout bindings are not working with jsTree

I'm trying to get jsTree (1.0-rc3) working with Knockout.js (2.2.1).

See example jsFiddle: http://jsfiddle.net/adeconsulting/qfr6A/

Note: I've included several JS resources in the Fiddle to match as close as possible my Visual Studio project, in case there's a conflict between libraries which might be causing this problem.

Run the Fiddle and navigate through the jsTree, it's a list of servers by their physical location and type. It helps to have Firebug's console open so you can see the ajax calls and responses. When you click a leaf node, an ajax call is made to retrieve the server details and display a form whose values use Knockout bindings. I hide the form when a non-leaf node is selected.

It works the first time you click a leaf node. After that, Knockout does not update the form for leaf node clicks. However, if you happen to click the Edit button, then all of a sudden the most recent server details ARE displayed.

I'm thinking that there's a conflict between jsTree and Knockout bindings, but don't know where to start troubleshooting what that might be.

Since stackoverflow apparently requires at least one code block, here's the JavaScript portion of the Fiddle:

// Global vars:
var prevJsTreeNodeId = null;
var serverModelBindingsApplied = false;
var serverLoadInProgress = false; 

/*
 * The knockout.js view model
 */
var ServerViewModel = function () {
    // Data
    var self = this;
    self.IsReadOnly = ko.observable(true);       // the form's input mode
    self.btnEditSave = ko.observable("Edit");    // the Edit/Save button text
    self.Server = ko.observable({});             // the Server object

    // Operations
    self.setEditable = function () {
        self.IsReadOnly(false);
        self.btnEditSave("Save");
    };

    self.setReadOnly = function () {
        self.IsReadOnly(true);
        self.btnEditSave("Edit");
    };

    self.doEditSave = function () {
        var flag = self.IsReadOnly();
        if (flag) {
            // switch to Edit mode
            self.setEditable();
        }
        else {
            // switch back to readOnly
            self.setReadOnly();
        }
    };

    // use ajax to update the knockout.js view model's Server object for the specified server name
    self.load = function (serverName) {
        if (!serverLoadInProgress) {
            serverLoadInProgress = true;
            // use ajax to retrieve the server's details
            var data = {
              json: JSON.stringify({
                   ServerName: serverName,
                   PrimaryIP: "1.2.3.4",
                   BrandDesc: "Dell",
                   OSDesc: "Windows 2003 Server",
                   Location: "xyz"
              }),
              delay: 1
            };
            $.ajax({
                url:"/echo/json/",
                data:data,
                type:"POST",
                success:function(response)
                {
                   console.log(response);
                   window.ServerViewModelInstance.Server = ko.mapping.fromJS(response);
                   // apply bindings the first time we retrieve a Server object
                   if (!serverModelBindingsApplied) {
                      ko.applyBindings(window.ServerViewModelInstance,
                                       document.getElementById('servercontent'));
                      serverModelBindingsApplied = true;
                   }  
                   else {
                     // hmmm... updating the view model's .Server property doesn't trigger the 
                     //  form to be updated, yet if we click the Edit button, the new values
                     //  suddenly appear, so try emulating that here...
                     self.setReadOnly();
                   }
                }
            });

           serverLoadInProgress = false;
        }
    };
};    // ServerViewModel

/*
 * document load 
 */
$(function () {
    // configure the jsTree
    $("#divtree")
      .jstree({
          "themes": { "theme": "default", "dots": true, "icons": true },
          "plugins": ["themes", "html_data", "ui", "types"],
          "types": {
              "type_attr": "tag",   // the attribute which contains the type name
              "max_depth": -2,      // disable depth check
              "max_children": -2,   // disable max children check
              "valid_children": ["root"],
              "types": {
                  "root": {
                      "valid_children": ["level1"]
                  },
                  "level1": {
                      "valid_children": ["level2"],
                      "start_drag": false,
                      "move_node": false,
                      "delete_node": false,
                      "remove": false
                  },
                  "level2": {
                      "valid_children": ["leaf"],
                      // use the theme icon for the level2 nodes
                      "start_drag": false,
                      "move_node": false,
                      "delete_node": false,
                      "remove": false
                  },
                  "leaf": {
                      "valid_children": "none"
                  }
              }
          }
      });

    // register to receive notifications from #divtree when a jsTree node is selected
    $("#divtree").bind("select_node.jstree", function (event, data) {
          // data.rslt.obj is the jquery extended node that was clicked
          var key = data.rslt.obj.attr("key");
          var id = data.rslt.obj.attr("id");
          if (id == prevJsTreeNodeId) {
              // user clicked the same node, nothing to do
              return;
          }
          prevJsTreeNodeId = id;

          // when a jsTree node is selected, reset the knockout.js view model to read only
          window.ServerViewModelInstance.setReadOnly();

          var idx = key.indexOf("Server");
          if (idx === 0) {  // "Server|servername"
              // show the "servercontent" div
              $("#servercontent").show();
              // display the server details
              var serverName = key.substr(idx + 7, key.length);
              window.ServerViewModelInstance.load(serverName);
          }
          else {
              // hide the "servercontent" div
              $("#servercontent").hide();
          }
      });


    // hide the "servercontent" div
    $("#servercontent").hide();

    // instantiate the knockout.js view model
    window.ServerViewModelInstance = new ServerViewModel();
});  // document ready

// initialization timer routine to select the main jsTree node
setTimeout(function () {
    // open the root node
    $.jstree._reference("#divtree").open_node("#root");
}, 500);

Upvotes: 0

Views: 2135

Answers (1)

Rick
Rick

Reputation: 56

Sorry for my bad formatting below - this editor is not my friend... :-/
If I understand you right, the detail panel for a clicked tree node isn't updated with the correct data - right?

Try to do the following:
change:
window.ServerViewModelInstance.Server = ko.mapping.fromJS(response);
to:
window.ServerViewModelInstance.Server(response);

(e.g. not overwriting the initial ko.observable which you only binds once, instead updating its values)

and in view where you bind to the observables.. for instance instead of:
... "value: Server.ServerName, ...
change it to:
... "value: Server().ServerName, ...
(e.g executing the function before accessing the property)
It works and updates the form when clicking on a new server name node in the tree (tried in firefox)
A copy of your example with the modified code can be found at: http://jsfiddle.net/RZ92g/2/

Upvotes: 2

Related Questions