event/callback after a node is created _and_ renamed

3,605 views
Skip to first unread message

kofifus

unread,
Aug 2, 2015, 10:36:59 PM8/2/15
to jsTree
I wonder if somebody has implemented, or can help me implement the following behavior on node creation (which to be honest I expected to be the default) ..

When the user presses the Insert key on a node, a new node appears below in edit mode.
If the user then presses escape, or leaves without entering text, the operation is cancelled and the node disappears. 
If the user enters a name for the node and then leaves (Enter or navigation away), the node is created with the given name and I get a callback/event with the new node so that I can update the DB.

Any help ?
Thanks!

Ivan Bozhanov

unread,
Aug 3, 2015, 2:50:57 AM8/3/15
to jsTree
Use the built-in "edit" function callback. You get your callback invoked when the user completes the rename, along with information whether escape was pressed (rename was canceled). Read the docs on the edit method - this is the easiest way, since you are calling edit anyway - just add one more parameter. In pseudo code it is something like this:

$('#tree')
  .jstree({ ... })
  .on('keydown', '.jstree-anchor', function () {
    if(insert was pressed) {
      var instance = $('#tree').jstree(true);
      instance.create_node(this, { ... data about the node ... }, "first", function (node) {
        instance.edit(node, 'default-text-pass-null-if-you-want', function (node, status, is_cancel) {
          if(!status || is_cancel) { instance.delete_node(node); }
          else { /* sync to DB *. }
        })
      })
    }
  })

This is not tested but should get you started - enhance as needed and fill in missing parts.

Best regards,
Ivan

kofifus

unread,
Aug 3, 2015, 8:51:10 PM8/3/15
to jsTree
Thanks Ivan !

OK, I got most of what I wanted.


I still have some problems though. The tree seems to loose focus after the edit. If the user enters a name for the node I can solve this with inst.element.focus(); but I don't understand why I need this.
Worse, if the user presses Escape and I remove the node I can't return the focus to the tree even with focus().

What I want is that if the user presses Escape everything returns to how it was before, that is the cursor stays on the node it was before the failed create, and whatever node(s) was selected stays selected.

Any help ?


kofifus

unread,
Aug 3, 2015, 11:32:18 PM8/3/15
to jsTree
(ps you can tell the focus is lost if you try to change the 'cursor' with the arrow keys)

Ivan Bozhanov

unread,
Aug 4, 2015, 4:58:52 PM8/4/15
to jsTree
That is a bug - I am working on it. It will be fixed and in the repo tomorrow.
Thank you for finding it.

Best regards,
Ivan

Ivan Bozhanov

unread,
Aug 4, 2015, 5:52:26 PM8/4/15
to jsTree
The focus fix is in the repo. Thank you again and sorry the issue was therein the first place.

Best regards,
Ivan

kofifus

unread,
Aug 4, 2015, 7:50:16 PM8/4/15
to jsTree
Thanks Ivan! works gerat

kofifus

unread,
Aug 4, 2015, 8:29:13 PM8/4/15
to jsTree
just another comment, while this works it is IMO not an ideal solution. Firstly I have to write all this non trivial code and second and more important I can't use the create_node.jstree and model.jstree events at all in this scenario. A cleaner solution would be to provide another flag to create_node that will put the node in an edit mode after creation and will only fire the create_node.jstree and model.jstree events if a name was given successfully (ie the user didn't press Escape).

Thanks!

kofifus

unread,
Aug 4, 2015, 10:00:05 PM8/4/15
to jsTree
(or better, a separate create_named_node.jstree method ...)

Ivan Bozhanov

unread,
Aug 5, 2015, 2:44:24 AM8/5/15
to jsTree
Sorry, but the order and timing of events will not change - rename and create are two separate operations, there is a reason why all explorer programs work this way. Adding 20 lines of code is not way too much work, but I will consider an additional separate event.

Best regards,
Ivan

Ivan Bozhanov

unread,
Aug 5, 2015, 2:44:55 AM8/5/15
to jsTree
Oh, and you should remove your .focus() calls - they are no longer needed.

kofifus

unread,
Aug 5, 2015, 8:24:28 AM8/5/15
to jsTree
I don't think an event will do. Let me try to explain my situation so you'll understand more why I think this is important.

I'm combining jstree with Firebase to create a tree that updates in real time across multiple clients. When the tree changes I update the Firebase DB which will fire an event on all clients connected to that instance and then update their tree accordingly. In this scenario updating the DB is an expensive operation with side effects on multiple remote clients.

Since the tree can change in multiple situations (creating nodes, drag&drop, rename etc) I naturally want to have the DB updating logic in the model event but the way things are happening now, when the user creates a node and gives it a name (which is really one atomic operation) I get two calls to model which means all clients will have their trees updated twice which is not workable for me. 

Likewise if a naming after create is cancelled and I delete the node I will get two model events (for creation and deletion) which has the same problem.

I guess I can get over this with setting flags to decide if a model event happened just after create etc but that is ugly. What is needed is a single method that will do update _and_ edit and will fire a single create event and a single model event (and no rename event) if the naming was not cancelled.

Anyways I hope that is clearer.
Thanks for the help!
 

Ivan Bozhanov

unread,
Aug 5, 2015, 8:52:43 AM8/5/15
to jsTree
I understood you the first time, but I do not agree that create and rename is an atomic operation - the example with the filesystem managers I gave should be clear enough. If you create a node with a predefined name (which you can) - that is an atomic operation, if you create a node and then offer the user to change properties of that node (on a remote client) - this is not an atomic operation and I will not treat it as such. I understand that in your app's logic it should be an atomic operation, but that is not universally true. For example in other applications I may need to create a node with a default name and offer the user the possibility to change the name of that node, but even if the user decides to pull the plug of his/hers computer without renaming I want that node to stay created with the default name. I hope that example is clear enough, it all depends on the logic of the app being created, BUT jstree can handle both scenarios - you have all you need.

jsTree provides all the means you need to treat create and rename as an atomic operation - I tried to explain that several times. Let me clarify:
1) the model.jstree event fires when jstree inserts some nodes in its internal representation - even when simply loading nodes from a server, it also does not fire when a node is renamed - my guess is you are misusing the event.
2) create_node.jstree fires when a node is created via a call to "create_node"
3) rename_node.jstree fires when a node is renamed using "rename_node"
4) the "edit" function takes an already existing node and adds an input to it, once the user is done using the text input, "rename_node" is invoked on the already existing node

So the solution is simple (provided you have node IDs in your database) - excluding the copy_node.jstree and move_node.jstree events all you need to react to is delete_node.jstree and rename_node.jstree, but in this manner:
1) when delete_node.jstree fires, if the ID of the deleted node is in the form jNUMBER_NUMBER - ignore it - the user just pressed Esc when renaming a new node, which your code then deletes.
2) when delete_node.jstree fires, if the ID of the deleted node is NOT in the form jNUMBER_NUMBER - sync to the DB
3) when rename_node.jstree fires, if the ID of the renamed node is in the form jNUMBER_NUMBER, sync to DB and treat it as creating a new node
4) when rename_node.jstree fires, if the ID of the renamed node is NOT in the form jNUMBER_NUMBER, sync to DB and treat it as renaming and existing node

I believe this is clear and simple enough, and I hope that you understand that treating create and rename as an atomic operation is up to the developer and the app that is being created, and it is easier to have separate events and treat them as atomic if needed, than to have an atomic operation, which you can not treat as two separate events. This is why jstree took the path of most filesystem managers - create with default name first, rename later and handle both operations separately.

If you need me to - I can demonstrate the above 4 points with code? Although I doubt you will need that.

Best regards,
Ivan

PS. Keep in mind you have one extra option - not using the "edit" function, and creating nodes using a separate textbox somewhere in your app - let the user enter the title in some modal for example, and then call create_node with the defined title, then process the resulting "create_node.jstree" event. This is far from optimal but I just wanted to let you know it is possible too. "create_node" does not work with default titles only and is not linked to the "edit" function in any way, so this is possible too.

kofifus

unread,
Aug 5, 2015, 9:11:35 PM8/5/15
to jsTree
Thanks Ivan!

I understand that there may be need for a non-atomic create and rename. It is my opinion that the atomic case is by far the more common and generally useful one and so it should be supported in the API. We can agree to disagree on that :)

I misunderstood the model event, I now see it's not useful in my case.

I must be missing something but I can't get your solution to work - in my tests node id is always in the form jx_x.

I did notice rename has an 'old' element and managed to work a solution with that:

$('#jstree').on('rename_node.jstree', function(e, obj) {
if (obj.text && !obj.old) updateDB(obj.node, 'create'); else if (true) {}; (obj.text!==obj.old) updateDB(obj.node, 'rename');
});

$('#jstree').on('delete_node.jstree', function(e, obj) {
if (obj.node.text) updateDB(obj.node, 'delete');
});

This seems to work well. See https://jsfiddle.net/0fzbx8dg/2/  (also update with latest focus changes)

Does this seems like a good solution ?

Also focus is still buggy, to reproduce run the jsfiddle, select the first node by clicking on it, press ctrl+up, press Esc. Same will happen if you click on the bottom one, press ctrl+down, press Esc. Also if you delete the selected node.

Thanks!
Micha






Ivan Bozhanov

unread,
Aug 6, 2015, 2:44:12 AM8/6/15
to jsTree
Yes, let's agree to disagree - just to be clear - I want to emphasize on the fact that it is possible to treat those operations as atomic but it depends on the code outside of jstree (just like you just confirmed).

As for the IDs - I included in my initial answer that I assume that you have your own IDs - if you use the jstree generated ones then there is no way to distinguish the new node (aside from the old parameter which you already discovered).

As for the focus - it did not work with root nodes, I just fixed that - it is in the repo.

Best regards,
Ivan

kofifus

unread,
Aug 6, 2015, 7:35:11 AM8/6/15
to jsTree
Thanks Ivan!

Still a little bug with the root nodes, to reproduce:

click on the first node
press down to go to the node below
press ctrl+down and enter a new node name+enter
press ok in the alert box
you'll see no node now have the cursor. 
(strangely if you now press up, the second node gets the cursor)

(by 'cursor' I mean the node with the jstree-hovered style)



Ivan Bozhanov

unread,
Aug 6, 2015, 8:00:22 AM8/6/15
to jsTree
In my browser the newly created node still has focus, even if some browsers lose focus it is because the "alert" messes with the browser focus.

Best regards,
Ivan

kofifus

unread,
Aug 6, 2015, 9:30:21 PM8/6/15
to jsTree
True. If I remove the alert it works (using chrome).. thanks :)

kofifus

unread,
Aug 6, 2015, 9:32:34 PM8/6/15
to jsTree
for reference here's updated code 


also takes into account spaces at the end of node text for rename comparison of old and new, and has a node click twice to rename functionality

Thanks!

kofifus

unread,
Aug 13, 2015, 12:18:24 AM8/13/15
to jsTree
Here is a better solution - this adds a create_named_node to jstree itself and will only send a single create_node event when/if a node has been created and named (so there's no need for any additional code in delete_node, rename_node etc).

$.jstree.core.prototype.create_named_node = function(par, node, pos, callback) {
var inst = this, elem = inst.element[0];
var events = $._data(elem, 'events'); // save events
$._data(elem, 'events', {}); // cancel all events

inst.create_node(par, node, pos, function(newNode) {
inst.edit(newNode, '', function(newNode, status, cancel) {
if (!newNode.text || !status || cancel) {
inst.delete_node(newNode);
$._data(elem, 'events', events); // restore events
inst.element.focus();
} else {
$._data(elem, 'events', events); // restore events
if (callback) callback.call(inst, newNode);
inst.trigger('create_node', { node: newNode, parent: par.id, position: pos });
}
});
});
};

see  jsfiddle

Ivan there seems to still be focus problems, notice I had to add the inst.element.focus() after delete


Reply all
Reply to author
Forward
0 new messages