Български

  jsTree v.0.8.1

October 9, 2008

jsTree v.0.8.1

Filed under: Tech — Tags: , , — vakata @ 11:56 am

This is a late post, actually. Version 0.8.1 has been up for some time now - the reason for this post is the download that was added, so you don’t need to check out.

The big news is that a new examples page is almost ready. As I have seen it is better to give working examples so that people can build on them, rather than read the documentation.

The list of changes in this version includes:

  • fixed renaming bug - a ‘ in the name brakes the rename (by anotherhero)
  • fixed #marker bug - plus sign not showing while dragging (by anotherhero)
  • onload callback (fired when: the tree is loaded for the first time, the tree or a node is refreshed)
  • * in dragrules (for example: “file * folder” means that a file-node can be before,after or inside a folder, “* inside folder” means any node can be inside a folder)
  • refreshing a single node is possible (in async mode)
  • passed json can now be an object, not an array of objects (by Marius Bratu)
  • onchange called in deselect_branch (by jstreeuser)
  • id is urlencoded when making calls to data.url (by Arjan Haverkamp)

Will post again as soon as the new examples page is done!

31 Comments »

  1. Happy to see more progress being made on this. Can you provide an example for how to pull JSON from a web service call?

    Comment by David — October 12, 2008 @ 8:51 pm

  2. I started to get the hang of jsTree using a JSON web service. But, I have a few general questions before I proceed too headlong into a production solution:

    1. If “async” is set to true, should (can?) nodes auto-expand upon page refresh if one also makes use of the cookie extension? This is primarily the reason why I am most attracted to jsTree so hope very much that this is the case. I suppose these means firing off a number of requests to expand the necessary nodes. If this is already implemented, then see #3 below.
    2. Can one have more that one root node? As near as I can tell, and as is true in all your examples, this appears not to be possible.
    3. In “async” : true mode, what is the proper way to populate the children nodes in JSON? Evidently, children : [] does not work, but this would be easiest.
    4. Finally, I am trying to integrate your work into Drupal because it has jQuery built into core and it is relatively trivial to construct JSON outputs. Are you OK with this?

    Thanks for great work,

    Dave

    Comment by David — October 15, 2008 @ 4:09 am

  3. @David

    1. YES, the tree will reopen opened nodes on refresh if you use the cookie feature.
    2. YES you can have a few root nodes, just make sure your logic is solid. (for example: prevent the user from deleting the root nodes)
    3. Just pass an array (without the “children : ” part) or if it is a single node - the object of that node.
    4. YES, go ahead. The more people and situations jsTree is used in - the better it will become. Just share your problems and ideas.

    Comment by vakata — October 15, 2008 @ 10:48 am

  4. About to restart using this in a new project, and something that would be useful, would be if the releases were tagged in SVN - so that I can set up an external without tracking trunk. Alternatively mentioning what revision a particular release is would work to.

    Updates since last used look great - looking forward to getting the dynamic loading running.

    Comment by Colin — October 15, 2008 @ 4:28 pm

  5. Hi,

    Again while using your wonderful component in our website I found out a bug, well it is not a bug since I believe it’s related to JQuery and no tree_component.

    On IE if you drag a node, and the div is not on top of the document, when you dragged it the new element created would be positioned far away from the dragged element.

    I believe the problem lies in line 437:

    _this.po = $(_this.drag).offsetParent().offset({scroll : false});

    The $(_this.drag).offsetParent(), returns the correct element on Firefox, since it returns body, no matter where the container div is placed.

    And since the cloned LI has position: absolute, eeything works ok.

    The problem happens on IE because $(_this.drag).offsetParent() also returns the body element in IE, and since the top and left attributes for elements positioned absoluted are relative to their containers, we have a problem. offsetParent() on IE should return the container div, and not the body element.

    I have made a quick fix by replacing line 437 by:

    _this.po = $.browser.msie ? _this.container.offset({scroll : false}) : $(_this.drag).offsetParent().offset({scroll : false});

    This seems to fix the problems so far on IE while offSetParent is not fixed in JQuery.

    Comment by Nuno Mota — October 16, 2008 @ 8:39 pm

  6. What do I have to add to the tree init options so that the navigation works when a user clicks on a node?

    Each node contains an <a> node. So when I click on a node with a url in the anchor tag, I would expect normal browser navigation. I would think that if you didn’t want navigation you’d put “#” as the navigation target.

    Thanks in advance.

    Comment by Michael — October 16, 2008 @ 8:42 pm

  7. @Nuno Mota
    Thanks for the report and the fix. I’ll update and test!
    EDIT: After some testing it turns out that offsetParent() returns the HTML element in MSIE, and the subsequent “offset()” then returns the scrollTop property, which leads to miscalculations.

    @Michael
    Just use the callbacks:

    onchange : function(NODE) {
      document.location.href = $(NODE).attr("href");
    }

    You could also use a more complex logic in the callback, or just modify the tree_component.js file by deleting line №332 & 334 (the “event.preventDefault()” and “return false”)
    The tree breaks normal browser behavior because in 99.9% of the time this is what you want (integration in CMS, more complex logic attached to clicking & dragging, etc)

    Comment by vakata — October 17, 2008 @ 9:14 am

  8. Hi,

    first of all, thank you very much for this nice component. It´s working very well.

    I am populating the tree via a JSON. One thing i am missing (maybe i have overseen it :) ) is, that you cant configure a node as selected via the JSON Response from the server.

    An Example:
    1) a node is selected in the tree
    2) some processing goes on and
    3) the tree is rerendered ( from a totaly new JSON Object).
    After this rendering i want a special node somwere in the “new” tree marked as selected and also the tree opened to that node.

    I tried to use “select_branch” in a onload-callback, but without success…

    Kind regards,
    Thorsten.

    Comment by Thorsten — October 21, 2008 @ 5:20 pm

  9. First of all, thank you for this awesome tree control. It’s brilliant.

    I have a page with two jsTree controls set in multitree mode.

    I have two issues I can’t figure:

    1. It seems that when I drag one node from tree1 into tree2, neither of the dragrules from any of the trees apply. What I would like to do is allow nodes to be dragged only inside the root node of tree2. Whatever rules I put in the tree lets me drag the node anywhere I want (including before or after the root node). I can easily see a more generic scenario where a page contains a main tree with categories and several secondary trees with items that can be dragged into certain categories from the main tree.

    2. I’m wondering what is the right way to alter the drag behavior so instead of moving the node from tree1 into tree2 it copies it.

    Thanks!

    Comment by bogdan — October 23, 2008 @ 10:33 am

  10. @Thorsten:
    if you know the id of the new node (which you obviously do :)) you could use the onload (or onopen) callback:

    your_tree_name.select_branch($("#THE_ID").find("a:eq(0)"));

    @Bogdan
    Multitree is just experimental and needs a lot of work - i know :(. Actually the originating tree rules are followed. As for the copy part - I will include it as soon as possible - obviously a lot of people need it.

    Comment by vakata — October 23, 2008 @ 10:49 am

  11. Maybe I’m doing something wrong or it is now working anymore.

    Here is my setup.

    Tree one uses:
    draggable : ["tab"],
    dragrules : ["tab inside profile_root"],

    And tree two:
    draggable : ["tab"],
    dragrules : ["tab * tab", "tab inside profile_root"]

    where profile_root is the type of the root node in tree2. Both tree have tab nodes. I shouldn’t be able to move a tab node from tree1 before profile_root node in tree2, right? As a matter of fact, anything I put in the rules of tree1 behaves the same. Even if I put in tree1
    dragrules : ["asdasdasd inside profile_root"]
    it works with no errors.

    Another issue that I came across, is it possible to determine which tree has focus?

    Comment by bogdan — October 23, 2008 @ 11:12 am

  12. I was able to make a dirty quick tweak to allow copying between trees. Basically, I’ve modified the built-in copy and paste functions. Here are the changes in case someone needs this functionality until you get to implement it.
    One part that may be worth keeping is setting the ids of copies. Each new copy will get an incremental number appended.

    copy : function (targetTree, node) {
    if (!targetTree) targetTree = this;
    if(targetTree.locked) return targetTree.error(”LOCKED”);
    if(!node && !this.selected) return this.error(”COPY: NO NODE SELECTED”);
    targetTree.copy_nodes = node ? $(node) : this.container.find(”a.clicked”).filter(”:first-child”).parent();
    targetTree.cut_nodes = false;
    },
    paste : function (targetNode, type) {
    if (!type) type =”inside”;
    targetNode = targetNode ? $(targetNode) : this.selected;
    if(this.locked) return this.error(”LOCKED”);
    if(!targetNode) return this.error(”PASTE: NO NODE SELECTED”);
    if(!this.copy_nodes && !this.cut_nodes) return this.error(”PASTE: NOTHING TO DO”);
    if(this.copy_nodes && this.copy_nodes.size()) {
    if(!this.checkMove(this.copy_nodes, targetNode.children(”a:eq(0)”), type)) return false;
    _this = this;
    tmp = this.copy_nodes.clone();
    tmp.each(function (i) {
    this.id = this.id + “_copy_” + _this.container.find(”[id^='"+this.id+"']“).length;
    $(this).find(”li”).each(function () {
    this.id = this.id + “_copy” + _this.container.find(”[id^='"+this.id+"']“).length;
    })
    });
    this.moved(tmp, targetNode.children(”a:eq(0)”), type, false, true);
    this.copy_nodes = false;
    }
    if(this.cut_nodes && this.cut_nodes.size()) {
    if(!this.checkMove(this.cut_nodes, targetNode.children(”a:eq(0)”), type)) return false;
    this.moved(this.cut_nodes, targetNode.children(”a:eq(0)”), type);
    this.cut_nodes = false;
    }
    }

    To use it, I’ve put the following callback into the original tree:

    callback : {
    beforemove : function(NODE,REF_NODE,TYPE,TREE_OBJ) {
    treeTabs.copy(treeProfileItems, NODE);
    treeProfileItems.paste(REF_NODE, TYPE);
    return false;
    }
    Regards

    Comment by bogdan — October 23, 2008 @ 12:30 pm

  13. @bogdan
    Thanks for the solution - I’ll see if I can use it to implement it natively if that is OK with you.

    As for the multitree dragrules - there was an error in the source code:
    Line 484 (0.8.2@SVN) should be:
    if(_this.checkMove(_this._drag,$(event.target),mov)) {

    Please try this and the first tree’s rules will apply.
    Thanks again.

    BTW - I intend on implementing multitree dragrules support with a global set of rules, but in the mean time - just having different node types in the different trees, and using them in each tree’s config does the job (as you did yourself).

    Comment by vakata — October 23, 2008 @ 12:50 pm

  14. Thanks, that fixed the problem. I see both set of rules apply. If tree1 doesn’t have the rule to add to the root of tree2, the X icon appears. When tree1 has the rule, but tree2 doesn’t have it, the X mark doesn’t appear, but nothing happens, the node doesn’t get moved when dropped. If both are set, then it’s working perfect.

    However, since I wanted tree1 to remain unchanged (basically, tree2 gets built from a subset of nodes from tree1) I couldn’t use the rule “tabs * tabs”, so I ended up doing another hack in function “paste” so it receives an additional parameter called “rewriteRel” :)

    Another problem I had was with markers, they didn’t display at all. I have the tree inside a jQuery dialog and the markers got lost beneath the dialog.
    To fix this, I’ve set markers to highest zIndex available.
    Here is the fix, maybe it’s worths including it.

    zIndex = 0;
    $(”*”).each(function () {
    if (parseInt(this.style.zIndex) > zIndex)
    zIndex = parseInt(this.style.zIndex);
    });

    $(”")
    ……………………………..
    .css({
    …………………………
    zIndex : “” + (zIndex + 1)

    Comment by bogdan — October 23, 2008 @ 1:29 pm

  15. I’m extremely satisfied with how things are shaping out. Thanks again for this great control, it really makes the difference.

    Here is another addition for multitree mode especially, though it could prove useful for any situation. I’ve added the concept of tree focus so I know which tree receives input (such as from keyboard shortcuts). I’ve added the following bindings in the init function:

    if (!tree_component.treeEventsBound) {
    $(”body”).bind(”click”, function (event) {
    $(”.tree_focus”).removeClass(”tree_focus”);
    $(document.elementFromPoint(event.pageX, event.pageY)).parents().filter(”.tree”).addClass(”tree_focus”);
    });
    tree_component.treeEventsBound = true;
    }

    this.container.bind(”mouseup”, function (event) {
    $(”.tree_focus”).removeClass(”tree_focus”);
    _this.container.addClass(”tree_focus”);
    });

    The code works on FF and IE. Haven’t test the rest.

    I hope sharing my tweaks is constructive, let me know if you need me to stop :). I’ll probably work on this for a few more days and apply other changes as well.

    Comment by bogdan — October 23, 2008 @ 11:14 pm

  16. @bogdan
    The previous multitree fix is actually not OK. I just updated the source files at the SVN - now there is native copy functionality. You can use settings.rules.drag_copy - possible values are “on” - always copy, “ctrl” - copy if you drop the nodes you are dragging with the Ctrl Key down, false - disabled.
    As for the focus - this is very useful indeed - I was about to work on that :) Thank you for helping me out! :) Please continue with the ideas! This is very useful for me and for all jsTree users!

    Comment by vakata — October 24, 2008 @ 12:17 am

  17. Thanks, I’ll try it out.

    For the tree focus functionality to be complete I needed to be able to get tree objects by knowing the id of their containers (which has the “tree_focus” class), so I had the write a minimal instance manager.

    I’ve put the following code in init:

    tree_component.inst || (tree_component.inst = new Object());
    tree_component.inst[this.container.attr("id")] = this;
    tree_component.focusInst = function() {
    return tree_component.inst[$(".tree_focus").attr("id")];
    }

    Then, I was able to bind F2 to renaming and link it like this:
    jQuery(document).bind(’keydown’, { combi: “f2″, disableInInput: true }, function() { tree_component.focusInst() && tree_component.focusInst().rename(); });

    Another issue with renaming, on Firefox ESC was not canceling editing.

    I had to modify it as follows:
    if(key == 27) { this.cancelChanges = true; this.blur(); return }

    and in blur handler:

    _this.inp.blur(function(event) {
    if(this.value == “”) this.value == last_value;
    $(obj).html( this.cancelChanges ? last_value : $(obj).parent().find(”input”).eq(0).attr(”value”) ).get(0).style.display = “”;
    $(obj).prevAll(”span”).remove();
    if (!this.cancelChanges) _this.settings.callback.onrename.call(null, _this.get_node(obj).get(0), _this.current_lang, _this);
    _this.inp = false;
    });

    Comment by bogdan — October 24, 2008 @ 4:06 am

  18. There are some problems in getJSON:

    1. In
    for(i in nod_d.attributes)
    (obj.attributes[nod_d.attributes[i].name]=nod_d.attributes[i].value);

    This makes IE come up with all sorts of attributes (83 items more exactly). IE js also crashed because some properties were undefined.

    I’ve replaced it with explicit calls:
    obj.attributes["id"] = nod_d.attributes["id"].value;
    obj.attributes["rel"] = nod_d.attributes["rel"].value;
    obj.attributes["class"] = nod_d.attributes["class"].value;

    2. there was a problem with saving the background image
    Replaced obj.icons = a.css(”backgroundImage”); with obj.icons = a.css(”backgroundImage”).replace(”url(”,”").replace(”)”,”");

    Comment by bogdan — October 24, 2008 @ 5:58 am

  19. And a note on keyboard navigation. I think the get_left should follow standard behavior and a) close the current node branch if it is open (which it does now) or if the current node is not a branch or is already closed, then b) jump to the parent node (which currently doesn’t). Instead, it calls get_prev.

    Comment by bogdan — October 24, 2008 @ 6:16 am

  20. Same for get_right, it shouldn’t call get_next. If you open windows explorer from windows the folder tree on the left has standard behavior.

    Comment by bogdan — October 24, 2008 @ 6:19 am

  21. Here is the code, in case you agree it is a good idea:

    I’ve created a new member function:

    get_parent : function() {
    var obj = this.hovered || this.selected;
    if(obj) {
    obj = obj.parent();
    this.hover_branch(obj);
    }
    }

    This gets called in get_left instead of get_prev.
    In get_right I just commented the line that called get_next.

    Comment by bogdan — October 24, 2008 @ 6:33 am

  22. Also, I see is a conflict between the mouse hovering and the keyboard navigation. When I move the mouse over the tree my keyboard navigation gets reset to the clicked node.

    I think it’s a good idea to differentiate between active items (that are clicked), selected items (that change whenever keyboard navigation occurs) and hovered items (that only react to the mouse).

    Comment by bogdan — October 24, 2008 @ 6:44 am

  23. Notes for serving the entire jsTree thorugh AJAX (in my particular case, jsTree resides in an ASP UpdatePanel):

    1. call constructors and handlers only once. I’ve used something like:
    if (!document.myTab) {
    document.myTab = new tree_component();
    jQuery(document).bind(’keydown’, …………
    }

    2. call init methods each time the tree reloads through AJAX

    This way, jsTree objects remain in memory and they manage their own viewstate. One issue though, some internal bindings could be set twice in init function. If this is the case, it would be nice if the tree checks if handlers are already set before setting new ones.

    Comment by bogdan — October 24, 2008 @ 7:16 am

  24. @bogdan
    Thank you for all the submissions, please continue - this really helps developement!

    I commited just now - to summon the changes:
    * rename function ESC key is fixed (I decided not to populate the scope and just set “input.value = last_value” if ESC is pressed, and then only call the callback if “input.value != last_value”)
    * CSS marker - I set z-index to 1000 in tree_component.js (I feel kind of odd traversing the DOM - I guess it would be better to set it to a really high value - as far as I know this does not affect performance)
    * copy - native copy impemented
    * get_json - I modified the function - the attributes you suggested were OK, but I decided to add an additional attrib parameter. So I set your values as defaults, but still the function accepts a list of attributes to include.
    * get_left, get_right - Thanks :) I really had not noticed the default behaviour - it is now fixed.

    What is left:
    * instance manager & focused instance - a really needed improvement, I will use what you gave me and try to improve. I am just worried about elementFromPoint not working in Firefox < 3. So let’s see what else we can use!
    * states (hover, active, focus) - I’ll do a research of what is best :)

    Comment by vakata — October 24, 2008 @ 10:50 am

  25. I had to use elementFromPoint because on IE document.click gets called after container.mouseup on drag operations. When document.click got called, it turned focus of all instances off. On Firefox document.click doesn’t get call and focus remained on the drop target.
    Since it’s only needed on IE you could say ($.browser.msie && elementFromPoint).

    Comment by bogdan — October 26, 2008 @ 7:56 pm

  26. I see another problem with getJSON. If I have multiple root items it doesn’t export all of them, just the first one.
    To fix that, I’ve enclosed all the code into a
    var arr;
    nod.each(function() {
    ….
    arr.push(obj);
    }
    return arr;

    Comment by bogdan — October 29, 2008 @ 5:58 pm

  27. forgot the main think to get all root nodes
    if(!nod || $(nod).size() == 0) nod = this.container.find(”ul:eq(0) > li”);

    Comment by bogdan — October 29, 2008 @ 6:14 pm

  28. Hi all:

    In onrename function I´m using the values of NODE.firstChild.textContent to get the new name of the node and send it by ajax to my server.
    It works great for Chrome and Firefox. But, in IE it gives me “undefined” value.
    What i´m doing bad? Is there a better way to get the new name of the node?
    I´m using 0.8.1. version.
    Thank you very much in advance.

    Comment by nomen — November 3, 2008 @ 3:32 pm

  29. I’m tring to get the callback to return the href. I’m using an xml_flat type and the menu works but it always returns undefined..

    callback : {
      onchange : function(NODE) {
        alert("click:" + $(NODE).attr("href"));
      }
    }

    thank you

    Comment by Arman — November 17, 2008 @ 5:52 am

  30. @Arman
    You should do:

    $(NODE).children(”a”).attr(”href”);

    Comment by vakata — November 17, 2008 @ 1:04 pm

  31. Thank you Vakata for your time, and the tree!

    The code corks but always returns #. It looks like it’s being replaced?

    I would like to be able to set and get a link for some of the nodes. What is the best way to accomplish this?

    thank you.

    Comment by Arman — November 17, 2008 @ 6:24 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment