Built motion from commit 76eb00b9e.|1.0.24
[motion.git] / public / bower_components / jstree / jstree.js
index 48ee5ce..f48bbc0 100644 (file)
@@ -13,7 +13,7 @@
 }(function ($, undefined) {
        "use strict";
 /*!
- * jsTree 3.2.1
+ * jsTree 3.3.4
  * http://jstree.com/
  *
  * Copyright (c) 2014 Ivan Bozhanov (http://vakata.com)
@@ -23,8 +23,9 @@
  */
 /*!
  * if using jslint please allow for the jQuery global and use following options:
- * jslint: browser: true, ass: true, bitwise: true, continue: true, nomen: true, plusplus: true, regexp: true, unparam: true, todo: true, white: true
+ * jslint: loopfunc: true, browser: true, ass: true, bitwise: true, continue: true, nomen: true, plusplus: true, regexp: true, unparam: true, todo: true, white: true
  */
+/*jshint -W083 */
 
        // prevent another load? maybe there is a better way?
        if($.jstree) {
                ccp_inst = false,
                themes_loaded = [],
                src = $('script:last').attr('src'),
-               document = window.document, // local variable is always faster to access then a global
-               _node = document.createElement('LI'), _temp1, _temp2;
-
-       _node.setAttribute('role', 'treeitem');
-       _temp1 = document.createElement('I');
-       _temp1.className = 'jstree-icon jstree-ocl';
-       _temp1.setAttribute('role', 'presentation');
-       _node.appendChild(_temp1);
-       _temp1 = document.createElement('A');
-       _temp1.className = 'jstree-anchor';
-       _temp1.setAttribute('href','#');
-       _temp1.setAttribute('tabindex','-1');
-       _temp2 = document.createElement('I');
-       _temp2.className = 'jstree-icon jstree-themeicon';
-       _temp2.setAttribute('role', 'presentation');
-       _temp1.appendChild(_temp2);
-       _node.appendChild(_temp1);
-       _temp1 = _temp2 = null;
-
+               document = window.document; // local variable is always faster to access then a global
 
        /**
         * holds all jstree related functions and variables, including the actual class and methods to create, access and manipulate instances.
@@ -71,7 +54,7 @@
                 * specifies the jstree version in use
                 * @name $.jstree.version
                 */
-               version : '3.2.1',
+               version : '3.3.4',
                /**
                 * holds all the default options used when creating new instances
                 * @name $.jstree.defaults
@@ -92,6 +75,7 @@
                idregex : /[\\:&!^|()\[\]<>@*'+~#";.,=\- \/${}%?`]/g,
                root : '#'
        };
+       
        /**
         * creates a jstree instance
         * @name $.jstree.create(el [, options])
                                themes : {
                                        name : false,
                                        dots : false,
-                                       icons : false
+                                       icons : false,
+                                       ellipsis : false
                                },
                                selected : [],
                                last_error : {},
         * @name $(':jstree')
         * @return {jQuery}
         */
-       $.expr[':'].jstree = $.expr.createPseudo(function(search) {
+       $.expr.pseudos.jstree = $.expr.createPseudo(function(search) {
                return function(a) {
                        return $(a).hasClass('jstree') &&
                                $(a).data('jstree') !== undefined;
                 *                                      'children' : [ { 'text' : 'Child 1' }, 'Child 2']
                 *                              }
                 *                      ]
-                *              });
+                *              }
+                *      });
                 *
                 *      // function
                 *      $('#tree').jstree({
                 *      $('#tree').jstree({
                 *              'core' : {
                 *                      'check_callback' : function (operation, node, node_parent, node_position, more) {
-                *                              // operation can be 'create_node', 'rename_node', 'delete_node', 'move_node' or 'copy_node'
+                *                              // operation can be 'create_node', 'rename_node', 'delete_node', 'move_node', 'copy_node' or 'edit'
                 *                              // in case of 'rename_node' node_position is filled with the new node name
                 *                              return operation === 'rename_node' ? true : false;
                 *                      }
                         */
                        icons                   : true,
                        /**
+                        * a boolean indicating if node ellipsis should be shown - this only works with a fixed with on the container
+                        * @name $.jstree.defaults.core.themes.ellipsis
+                        */
+                       ellipsis                : false,
+                       /**
                         * a boolean indicating if the tree background is striped
                         * @name $.jstree.defaults.core.themes.stripes
                         */
                                .remove();
                        this.element.html("<"+"ul class='jstree-container-ul jstree-children' role='group'><"+"li id='j"+this._id+"_loading' class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='tree-item'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>");
                        this.element.attr('aria-activedescendant','j' + this._id + '_loading');
-                       this._data.core.li_height = this.get_container_ul().children("li").first().height() || 24;
+                       this._data.core.li_height = this.get_container_ul().children("li").first().outerHeight() || 24;
+                       this._data.core.node = this._create_prototype_node();
                        /**
                         * triggered after the loading text is shown and before loading starts
                         * @event
                 * @param  {Boolean} keep_html if not set to `true` the container will be emptied, otherwise the current DOM elements will be kept intact
                 */
                destroy : function (keep_html) {
+                       /**
+                        * triggered before the tree is destroyed
+                        * @event
+                        * @name destroy.jstree
+                        */
+                       this.trigger("destroy");
                        if(this._wrk) {
                                try {
                                        window.URL.revokeObjectURL(this._wrk);
                        this.teardown();
                },
                /**
+                * Create prototype node
+                */
+               _create_prototype_node : function () {
+                       var _node = document.createElement('LI'), _temp1, _temp2;
+                       _node.setAttribute('role', 'treeitem');
+                       _temp1 = document.createElement('I');
+                       _temp1.className = 'jstree-icon jstree-ocl';
+                       _temp1.setAttribute('role', 'presentation');
+                       _node.appendChild(_temp1);
+                       _temp1 = document.createElement('A');
+                       _temp1.className = 'jstree-anchor';
+                       _temp1.setAttribute('href','#');
+                       _temp1.setAttribute('tabindex','-1');
+                       _temp2 = document.createElement('I');
+                       _temp2.className = 'jstree-icon jstree-themeicon';
+                       _temp2.setAttribute('role', 'presentation');
+                       _temp1.appendChild(_temp2);
+                       _node.appendChild(_temp1);
+                       _temp1 = _temp2 = null;
+
+                       return _node;
+               },
+               /**
                 * part of the destroying of an instance. Used internally.
                 * @private
                 * @name teardown()
                                                                e.type = "click";
                                                                $(e.currentTarget).trigger(e);
                                                                break;
-                                                       case 37: // right
+                                                       case 37: // left
                                                                e.preventDefault();
                                                                if(this.is_open(e.currentTarget)) {
                                                                        this.close_node(e.currentTarget);
                                                                o = this.get_prev_dom(e.currentTarget);
                                                                if(o && o.length) { o.children('.jstree-anchor').focus(); }
                                                                break;
-                                                       case 39: // left
+                                                       case 39: // right
                                                                e.preventDefault();
                                                                if(this.is_closed(e.currentTarget)) {
                                                                        this.open_node(e.currentTarget, function (o) { this.get_node(o, true).children('.jstree-anchor').focus(); });
                                                                e.preventDefault();
                                                                this.element.find('.jstree-anchor').filter(':visible').last().focus();
                                                                break;
+                                                       case 113: // f2 - safe to include - if check_callback is false it will fail
+                                                               e.preventDefault();
+                                                               this.edit(e.currentTarget);
+                                                               break;
+                                                       default:
+                                                               break;
                                                        /*!
                                                        // delete
                                                        case 46:
                                                                        this.delete_node(o);
                                                                }
                                                                break;
-                                                       // f2
-                                                       case 113:
-                                                               e.preventDefault();
-                                                               o = this.get_node(e.currentTarget);
-                                                               if(o && o.id && o.id !== $.jstree.root) {
-                                                                       // this.edit(o);
-                                                               }
-                                                               break;
-                                                       default:
-                                                               // console.log(e.which);
-                                                               break;
+
                                                        */
                                                }
                                        }, this))
                                                this._data.core.themes.dots                     = s.dots;
                                                this._data.core.themes.stripes          = s.stripes;
                                                this._data.core.themes.icons            = s.icons;
+                                               this._data.core.themes.ellipsis         = s.ellipsis;
                                                this.set_theme(s.name || "default", s.url);
                                                this.set_theme_variant(s.variant);
                                        }, this))
                                                this[ this._data.core.themes.dots ? "show_dots" : "hide_dots" ]();
                                                this[ this._data.core.themes.icons ? "show_icons" : "hide_icons" ]();
                                                this[ this._data.core.themes.stripes ? "show_stripes" : "hide_stripes" ]();
+                                               this[ this._data.core.themes.ellipsis ? "show_ellipsis" : "hide_ellipsis" ]();
                                        }, this))
                                .on('blur.jstree', '.jstree-anchor', $.proxy(function (e) {
                                                this._data.core.focused = null;
                        // if(obj.state.loading) { } // the node is already loading - just wait for it to load and invoke callback? but if called implicitly it should be loaded again?
                        if(obj.state.loaded) {
                                obj.state.loaded = false;
+                               for(i = 0, j = obj.parents.length; i < j; i++) {
+                                       this._model.data[obj.parents[i]].children_d = $.vakata.array_filter(this._model.data[obj.parents[i]].children_d, function (v) {
+                                               return $.inArray(v, obj.children_d) === -1;
+                                       });
+                               }
                                for(k = 0, l = obj.children_d.length; k < l; k++) {
-                                       for(i = 0, j = obj.parents.length; i < j; i++) {
-                                               this._model.data[obj.parents[i]].children_d = $.vakata.array_remove_item(this._model.data[obj.parents[i]].children_d, obj.children_d[k]);
-                                       }
                                        if(this._model.data[obj.children_d[k]].state.selected) {
                                                c = true;
-                                               this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, obj.children_d[k]);
                                        }
                                        delete this._model.data[obj.children_d[k]];
                                }
+                               if (c) {
+                                       this._data.core.selected = $.vakata.array_filter(this._data.core.selected, function (v) {
+                                               return $.inArray(v, obj.children_d) === -1;
+                                       });
+                               }
                                obj.children = [];
                                obj.children_d = [];
                                if(c) {
                                                break;
                                        }
                                }
-                               if(obj.state.loaded && !has_children && dom && dom.length && !dom.hasClass('jstree-leaf')) {
-                                       dom.removeClass('jstree-closed jstree-open').addClass('jstree-leaf');
+                               if(obj.state.loaded && dom && dom.length) {
+                                       dom.removeClass('jstree-closed jstree-open jstree-leaf');
+                                       if (!has_children) {
+                                               dom.addClass('jstree-leaf');
+                                       }
+                                       else {
+                                               if (obj.id !== '#') {
+                                                       dom.addClass(obj.state.opened ? 'jstree-open' : 'jstree-closed');
+                                               }
+                                       }
                                }
                                dom.removeClass("jstree-loading").attr('aria-busy',false);
                                /**
                 * @param  {array} nodes
                 * @param  {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - the array passed to _load_nodes
                 */
-               _load_nodes : function (nodes, callback, is_callback) {
+               _load_nodes : function (nodes, callback, is_callback, force_reload) {
                        var r = true,
                                c = function () { this._load_nodes(nodes, callback, true); },
                                m = this._model.data, i, j, tmp = [];
                        for(i = 0, j = nodes.length; i < j; i++) {
-                               if(m[nodes[i]] && ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || !is_callback)) {
+                               if(m[nodes[i]] && ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || (!is_callback && force_reload) )) {
                                        if(!this.is_loading(nodes[i])) {
                                                this.load_node(nodes[i], c);
                                        }
                 */
                _load_node : function (obj, callback) {
                        var s = this.settings.core.data, t;
+                       var notTextOrCommentNode = function notTextOrCommentNode () {
+                               return this.nodeType !== 3 && this.nodeType !== 8;
+                       };
                        // use original HTML
                        if(!s) {
                                if(obj.id === $.jstree.root) {
                                        if(d === false) {
                                                callback.call(this, false);
                                        }
-                                       this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $($.parseHTML(d)).filter(function () { return this.nodeType !== 3; }) : d, function (status) {
-                                               callback.call(this, status);
-                                       });
+                                       else {
+                                               this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $($.parseHTML(d)).filter(notTextOrCommentNode) : d, function (status) {
+                                                       callback.call(this, status);
+                                               });
+                                       }
                                        // return d === false ? callback.call(this, false) : callback.call(this, this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $(d) : d));
                                }, this));
                        }
                                                                        //return callback.call(this, this._append_json_data(obj, d));
                                                                }
                                                                if((type && type.indexOf('html') !== -1) || typeof d === "string") {
-                                                                       return this._append_html_data(obj, $($.parseHTML(d)).filter(function () { return this.nodeType !== 3; }), function (status) { callback.call(this, status); });
+                                                                       return this._append_html_data(obj, $($.parseHTML(d)).filter(notTextOrCommentNode), function (status) { callback.call(this, status); });
                                                                        // return callback.call(this, this._append_html_data(obj, $(d)));
                                                                }
                                                                this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : x }) };
                                                                return callback.call(this, false);
                                                        }, this))
                                                .fail($.proxy(function (f) {
-                                                               callback.call(this, false);
                                                                this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : f }) };
+                                                               callback.call(this, false);
                                                                this.settings.core.error.call(this, this._data.core.last_error);
                                                        }, this));
                                }
-                               t = ($.isArray(s) || $.isPlainObject(s)) ? JSON.parse(JSON.stringify(s)) : s;
+                               if ($.isArray(s)) {
+                                       t = $.extend(true, [], s);
+                               } else if ($.isPlainObject(s)) {
+                                       t = $.extend(true, {}, s);
+                               } else {
+                                       t = s;
+                               }
                                if(obj.id === $.jstree.root) {
                                        return this._append_json_data(obj, t, function (status) {
                                                callback.call(this, status);
                        }
                        if(typeof s === 'string') {
                                if(obj.id === $.jstree.root) {
-                                       return this._append_html_data(obj, $($.parseHTML(s)).filter(function () { return this.nodeType !== 3; }), function (status) {
+                                       return this._append_html_data(obj, $($.parseHTML(s)).filter(notTextOrCommentNode), function (status) {
                                                callback.call(this, status);
                                        });
                                }
                                rslt = function (rslt, worker) {
                                        if(this.element === null) { return; }
                                        this._cnt = rslt.cnt;
+                                       var i, m = this._model.data;
+                                       for (i in m) {
+                                               if (m.hasOwnProperty(i) && m[i].state && m[i].state.loading && rslt.mod[i]) {
+                                                       rslt.mod[i].state.loading = true;
+                                               }
+                                       }
                                        this._model.data = rslt.mod; // breaks the reference in load_node - careful
 
                                        if(worker) {
-                                               var i, j, a = rslt.add, r = rslt.sel, s = this._data.core.selected.slice(), m = this._model.data;
+                                               var j, a = rslt.add, r = rslt.sel, s = this._data.core.selected.slice();
+                                               m = this._model.data;
                                                // if selection was changed while calculating in worker
                                                if(r.length !== s.length || $.vakata.array_unique(r.concat(s)).length !== r.length) {
                                                        // deselect nodes that are no longer selected
                                //node = d.createElement('LI');
                                //node = node[0];
                        }
-                       node = _node.cloneNode(true);
+                       node = this._data.core.node.cloneNode(true);
                        // node is DOM, deep is boolean
 
                        c = 'jstree-node ';
                                        node.childNodes[1].childNodes[0].className += ' ' + obj.icon + ' jstree-themeicon-custom';
                                }
                                else {
-                                       node.childNodes[1].childNodes[0].style.backgroundImage = 'url('+obj.icon+')';
+                                       node.childNodes[1].childNodes[0].style.backgroundImage = 'url("'+obj.icon+'")';
                                        node.childNodes[1].childNodes[0].style.backgroundPosition = 'center center';
                                        node.childNodes[1].childNodes[0].style.backgroundSize = 'auto';
                                        node.childNodes[1].childNodes[0].className += ' jstree-themeicon-custom';
                                                        .children(".jstree-children").stop(true, true)
                                                                .slideDown(animation, function () {
                                                                        this.style.display = "";
-                                                                       t.trigger("after_open", { "node" : obj });
+                                                                       if (t.element) {
+                                                                               t.trigger("after_open", { "node" : obj });
+                                                                       }
                                                                });
                                        }
                                }
                        animation = animation === undefined ? this.settings.core.animation : animation;
                        t = this;
                        d = this.get_node(obj, true);
-                       if(d.length) {
-                               if(!animation) {
-                                       d[0].className = d[0].className.replace('jstree-open', 'jstree-closed');
-                                       d.attr("aria-expanded", false).children('.jstree-children').remove();
-                               }
-                               else {
-                                       d
-                                               .children(".jstree-children").attr("style","display:block !important").end()
-                                               .removeClass("jstree-open").addClass("jstree-closed").attr("aria-expanded", false)
-                                               .children(".jstree-children").stop(true, true).slideUp(animation, function () {
-                                                       this.style.display = "";
-                                                       d.children('.jstree-children').remove();
-                                                       t.trigger("after_close", { "node" : obj });
-                                               });
-                               }
-                       }
+
                        obj.state.opened = false;
                        /**
                         * triggered when a node is closed (if there is an animation it will not be complete yet)
                         * @param {Object} node the closed node
                         */
                        this.trigger('close_node',{ "node" : obj });
-                       if(!animation || !d.length) {
+                       if(!d.length) {
                                /**
                                 * triggered when a node is closed and the animation is complete
                                 * @event
                                 */
                                this.trigger("after_close", { "node" : obj });
                        }
+                       else {
+                               if(!animation) {
+                                       d[0].className = d[0].className.replace('jstree-open', 'jstree-closed');
+                                       d.attr("aria-expanded", false).children('.jstree-children').remove();
+                                       this.trigger("after_close", { "node" : obj });
+                               }
+                               else {
+                                       d
+                                               .children(".jstree-children").attr("style","display:block !important").end()
+                                               .removeClass("jstree-open").addClass("jstree-closed").attr("aria-expanded", false)
+                                               .children(".jstree-children").stop(true, true).slideUp(animation, function () {
+                                                       this.style.display = "";
+                                                       d.children('.jstree-children').remove();
+                                                       if (t.element) {
+                                                               t.trigger("after_close", { "node" : obj });
+                                                       }
+                                               });
+                               }
+                       }
                },
                /**
                 * toggles a node - closing it if it is open, opening it if it is closed
                        this.trigger('disable_node', { 'node' : obj });
                },
                /**
+                * determines if a node is hidden
+                * @name is_hidden(obj)
+                * @param {mixed} obj the node
+                */
+               is_hidden : function (obj) {
+                       obj = this.get_node(obj);
+                       return obj.state.hidden === true;
+               },
+               /**
                 * hides a node - it is still in the structure but will not be visible
                 * @name hide_node(obj)
                 * @param {mixed} obj the node to hide
-                * @param {Boolean} redraw internal parameter controlling if redraw is called
+                * @param {Boolean} skip_redraw internal parameter controlling if redraw is called
                 * @trigger hide_node.jstree
                 */
                hide_node : function (obj, skip_redraw) {
                                for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
                                        this.hide_node(obj[t1], true);
                                }
-                               this.redraw();
+                               if (!skip_redraw) {
+                                       this.redraw();
+                               }
                                return true;
                        }
                        obj = this.get_node(obj);
                                for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
                                        this.show_node(obj[t1], true);
                                }
-                               this.redraw();
+                               if (!skip_redraw) {
+                                       this.redraw();
+                               }
                                return true;
                        }
                        obj = this.get_node(obj);
                                                        c = !c;
                                                }
                                                if(!this.is_disabled(p[i]) && (c || p[i] === o || p[i] === l)) {
-                                                       this.select_node(p[i], true, false, e);
+                                                       if (!this.is_hidden(p[i])) {
+                                                               this.select_node(p[i], true, false, e);
+                                                       }
                                                }
                                                else {
                                                        this.deselect_node(p[i], true, e);
                 */
                set_state : function (state, callback) {
                        if(state) {
+                               if(state.core && state.core.selected && state.core.initial_selection === undefined) {
+                                       state.core.initial_selection = this._data.core.selected.concat([]).sort().join(',');
+                               }
                                if(state.core) {
                                        var res, n, t, _this, i;
                                        if(state.core.open) {
                                                                this.open_node(nodes, false, 0);
                                                                delete state.core.open;
                                                                this.set_state(state, callback);
-                                                       }, true);
+                                                       });
                                                }
                                                return false;
                                        }
                                        }
                                        if(state.core.selected) {
                                                _this = this;
-                                               this.deselect_all();
-                                               $.each(state.core.selected, function (i, v) {
-                                                       _this.select_node(v, false, true);
-                                               });
+                                               if (state.core.initial_selection === undefined ||
+                                                       state.core.initial_selection === this._data.core.selected.concat([]).sort().join(',')
+                                               ) {
+                                                       this.deselect_all();
+                                                       $.each(state.core.selected, function (i, v) {
+                                                               _this.select_node(v, false, true);
+                                                       });
+                                               }
+                                               delete state.core.initial_selection;
                                                delete state.core.selected;
                                                this.set_state(state, callback);
                                                return false;
                        var opened = [], to_load = [], s = this._data.core.selected.concat([]);
                        to_load.push(obj.id);
                        if(obj.state.opened === true) { opened.push(obj.id); }
-                       this.get_node(obj, true).find('.jstree-open').each(function() { opened.push(this.id); });
+                       this.get_node(obj, true).find('.jstree-open').each(function() { to_load.push(this.id); opened.push(this.id); });
                        this._load_nodes(to_load, $.proxy(function (nodes) {
                                this.open_node(opened, false, 0);
-                               this.select_node(this._data.core.selected);
+                               this.select_node(s);
                                /**
                                 * triggered when a node is refreshed
                                 * @event
                                 * @param {Array} nodes - an array of the IDs of the nodes that were reloaded
                                 */
                                this.trigger('refresh_node', { 'node' : obj, 'nodes' : nodes });
-                       }, this));
+                       }, this), false, true);
                },
                /**
                 * set (change) the ID of a node
                 * @param  {mixed} obj the node
                 * @param  {String} id the new ID
                 * @return {Boolean}
+                * @trigger set_id.jstree
                 */
                set_id : function (obj, id) {
                        obj = this.get_node(obj);
                        if(!obj || obj.id === $.jstree.root) { return false; }
-                       var i, j, m = this._model.data;
+                       var i, j, m = this._model.data, old = obj.id;
                        id = id.toString();
                        // update parents (replace current ID with new one in children and children_d)
                        m[obj.parent].children[$.inArray(obj.id, m[obj.parent].children)] = id;
                        // update model and obj itself (obj.id, this._model.data[KEY])
                        i = this.get_node(obj.id, true);
                        if(i) {
-                               i.attr('id', id).children('.jstree-anchor').attr('id', id + '_anchor').end().attr('aria-labelledby', id + '_anchor');
+                               i.attr('id', id); //.children('.jstree-anchor').attr('id', id + '_anchor').end().attr('aria-labelledby', id + '_anchor');
                                if(this.element.attr('aria-activedescendant') === obj.id) {
                                        this.element.attr('aria-activedescendant', id);
                                }
                        obj.id = id;
                        obj.li_attr.id = id;
                        m[id] = obj;
+                       /**
+                        * triggered when a node id value is changed
+                        * @event
+                        * @name set_id.jstree
+                        * @param {Object} node
+                        * @param {String} old the old id
+                        */
+                       this.trigger('set_id',{ "node" : obj, "new" : obj.id, "old" : old });
                        return true;
                },
                /**
                 * @param  {Boolean} options.no_id do not return ID
                 * @param  {Boolean} options.no_children do not include children
                 * @param  {Boolean} options.no_data do not include node data
+                * @param  {Boolean} options.no_li_attr do not include LI attributes
+                * @param  {Boolean} options.no_a_attr do not include A attributes
                 * @param  {Boolean} options.flat return flat JSON instead of nested
                 * @return {Object}
                 */
                                'li_attr' : $.extend(true, {}, obj.li_attr),
                                'a_attr' : $.extend(true, {}, obj.a_attr),
                                'state' : {},
-                               'data' : options && options.no_data ? false : $.extend(true, {}, obj.data)
+                               'data' : options && options.no_data ? false : $.extend(true, $.isArray(obj.data)?[]:{}, obj.data)
                                //( this.get_node(obj, true).length ? this.get_node(obj, true).data() : obj.data ),
                        }, i, j;
                        if(options && options.flat) {
                                                tmp.state[i] = obj.state[i];
                                        }
                                }
+                       } else {
+                               delete tmp.state;
+                       }
+                       if(options && options.no_li_attr) {
+                               delete tmp.li_attr;
+                       }
+                       if(options && options.no_a_attr) {
+                               delete tmp.a_attr;
                        }
                        if(options && options.no_id) {
                                delete tmp.id;
                },
                /**
                 * create a new node (do not confuse with load_node)
-                * @name create_node([obj, node, pos, callback, is_loaded])
+                * @name create_node([par, node, pos, callback, is_loaded])
                 * @param  {mixed}   par       the parent node (to create a root node use either "#" (string) or `null`)
                 * @param  {mixed}   node      the data for the new node (a valid JSON object, or a simple string with the name)
                 * @param  {mixed}   pos       the index at which to insert the node, "first" and "last" are also supported, default is "last"
                                return this.load_node(par, function () { this.create_node(par, node, pos, callback, true); });
                        }
                        if(!node) { node = { "text" : this.get_string('New node') }; }
-                       if(typeof node === "string") { node = { "text" : node }; }
+                       if(typeof node === "string") {
+                               node = { "text" : node };
+                       } else {
+                               node = $.extend(true, {}, node);
+                       }
                        if(node.text === undefined) { node.text = this.get_string('New node'); }
                        var tmp, dpc, i, j;
 
                        par.children = tmp;
 
                        this.redraw_node(par, true);
-                       if(callback) { callback.call(this, this.get_node(node)); }
                        /**
                         * triggered when a node is created
                         * @event
                         * @param {Number} position the position of the new node among the parent's children
                         */
                        this.trigger('create_node', { "node" : this.get_node(node), "parent" : par.id, "position" : pos });
+                       if(callback) { callback.call(this, this.get_node(node)); }
                        return node.id;
                },
                /**
                        }
                        tmp = obj.children_d.concat([]);
                        tmp.push(obj.id);
+                       for(i = 0, j = obj.parents.length; i < j; i++) {
+                               this._model.data[obj.parents[i]].children_d = $.vakata.array_filter(this._model.data[obj.parents[i]].children_d, function (v) {
+                                       return $.inArray(v, tmp) === -1;
+                               });
+                       }
                        for(k = 0, l = tmp.length; k < l; k++) {
-                               for(i = 0, j = obj.parents.length; i < j; i++) {
-                                       pos = $.inArray(tmp[k], this._model.data[obj.parents[i]].children_d);
-                                       if(pos !== -1) {
-                                               this._model.data[obj.parents[i]].children_d = $.vakata.array_remove(this._model.data[obj.parents[i]].children_d, pos);
-                                       }
-                               }
                                if(this._model.data[tmp[k]].state.selected) {
                                        c = true;
-                                       pos = $.inArray(tmp[k], this._data.core.selected);
-                                       if(pos !== -1) {
-                                               this._data.core.selected = $.vakata.array_remove(this._data.core.selected, pos);
-                                       }
+                                       break;
                                }
                        }
+                       if (c) {
+                               this._data.core.selected = $.vakata.array_filter(this._data.core.selected, function (v) {
+                                       return $.inArray(v, tmp) === -1;
+                               });
+                       }
                        /**
                         * triggered when a node is deleted
                         * @event
                                top = this.element[0].scrollTop;
                                lft = this.element[0].scrollLeft;
                                if(par.id === $.jstree.root) {
-                                       this.get_node(this._model.data[$.jstree.root].children[0], true).children('.jstree-anchor').focus();
+                                       if (this._model.data[$.jstree.root].children[0]) {
+                                               this.get_node(this._model.data[$.jstree.root].children[0], true).children('.jstree-anchor').focus();
+                                       }
                                }
                                else {
                                        this.get_node(par, true).children('.jstree-anchor').focus();
                        var tmp = chk.match(/^move_node|copy_node|create_node$/i) ? par : obj,
                                chc = this.settings.core.check_callback;
                        if(chk === "move_node" || chk === "copy_node") {
-                               if((!more || !more.is_multi) && (obj.id === par.id || $.inArray(obj.id, par.children) === pos || $.inArray(par.id, obj.children_d) !== -1)) {
+                               if((!more || !more.is_multi) && (obj.id === par.id || (chk === "move_node" && $.inArray(obj.id, par.children) === pos) || $.inArray(par.id, obj.children_d) !== -1)) {
                                        this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_01', 'reason' : 'Moving parent inside child', 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
                                        return false;
                                }
                        var rtl, w, a, s, t, h1, h2, fn, tmp, cancel = false;
                        obj = this.get_node(obj);
                        if(!obj) { return false; }
-                       if(this.settings.core.check_callback === false) {
-                               this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_07', 'reason' : 'Could not edit node because of check_callback' };
+                       if(!this.check("edit", obj, this.get_parent(obj))) {
                                this.settings.core.error.call(this, this._data.core.last_error);
                                return false;
                        }
                                                        if(callback) {
                                                                callback.call(this, tmp, nv, cancel);
                                                        }
+                                                       h2 = null;
                                                }, this),
                                                "keydown" : function (e) {
                                                        var key = e.which;
                        a.replaceWith(s);
                        h1.css(fn);
                        h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
+                       $(document).one('mousedown.jstree touchstart.jstree dnd_start.vakata', function (e) {
+                               if (h2 && e.target !== h2) {
+                                       $(h2).blur();
+                               }
+                       });
                },
 
 
                 * shows a striped background on the container (if the theme supports it)
                 * @name show_stripes()
                 */
-               show_stripes : function () { this._data.core.themes.stripes = true; this.get_container_ul().addClass("jstree-striped"); },
+               show_stripes : function () {
+                       this._data.core.themes.stripes = true;
+                       this.get_container_ul().addClass("jstree-striped");
+                       /**
+                        * triggered when stripes are shown
+                        * @event
+                        * @name show_stripes.jstree
+                        */
+                       this.trigger('show_stripes');
+               },
                /**
                 * hides the striped background on the container
                 * @name hide_stripes()
                 */
-               hide_stripes : function () { this._data.core.themes.stripes = false; this.get_container_ul().removeClass("jstree-striped"); },
+               hide_stripes : function () {
+                       this._data.core.themes.stripes = false;
+                       this.get_container_ul().removeClass("jstree-striped");
+                       /**
+                        * triggered when stripes are hidden
+                        * @event
+                        * @name hide_stripes.jstree
+                        */
+                       this.trigger('hide_stripes');
+               },
                /**
                 * toggles the striped background on the container
                 * @name toggle_stripes()
                 * shows the connecting dots (if the theme supports it)
                 * @name show_dots()
                 */
-               show_dots : function () { this._data.core.themes.dots = true; this.get_container_ul().removeClass("jstree-no-dots"); },
+               show_dots : function () {
+                       this._data.core.themes.dots = true;
+                       this.get_container_ul().removeClass("jstree-no-dots");
+                       /**
+                        * triggered when dots are shown
+                        * @event
+                        * @name show_dots.jstree
+                        */
+                       this.trigger('show_dots');
+               },
                /**
                 * hides the connecting dots
                 * @name hide_dots()
                 */
-               hide_dots : function () { this._data.core.themes.dots = false; this.get_container_ul().addClass("jstree-no-dots"); },
+               hide_dots : function () {
+                       this._data.core.themes.dots = false;
+                       this.get_container_ul().addClass("jstree-no-dots");
+                       /**
+                        * triggered when dots are hidden
+                        * @event
+                        * @name hide_dots.jstree
+                        */
+                       this.trigger('hide_dots');
+               },
                /**
                 * toggles the connecting dots
                 * @name toggle_dots()
                 * show the node icons
                 * @name show_icons()
                 */
-               show_icons : function () { this._data.core.themes.icons = true; this.get_container_ul().removeClass("jstree-no-icons"); },
+               show_icons : function () {
+                       this._data.core.themes.icons = true;
+                       this.get_container_ul().removeClass("jstree-no-icons");
+                       /**
+                        * triggered when icons are shown
+                        * @event
+                        * @name show_icons.jstree
+                        */
+                       this.trigger('show_icons');
+               },
                /**
                 * hide the node icons
                 * @name hide_icons()
                 */
-               hide_icons : function () { this._data.core.themes.icons = false; this.get_container_ul().addClass("jstree-no-icons"); },
+               hide_icons : function () {
+                       this._data.core.themes.icons = false;
+                       this.get_container_ul().addClass("jstree-no-icons");
+                       /**
+                        * triggered when icons are hidden
+                        * @event
+                        * @name hide_icons.jstree
+                        */
+                       this.trigger('hide_icons');
+               },
                /**
                 * toggle the node icons
                 * @name toggle_icons()
                 */
                toggle_icons : function () { if(this._data.core.themes.icons) { this.hide_icons(); } else { this.show_icons(); } },
                /**
+                * show the node ellipsis
+                * @name show_icons()
+                */
+               show_ellipsis : function () {
+                       this._data.core.themes.ellipsis = true;
+                       this.get_container_ul().addClass("jstree-ellipsis");
+                       /**
+                        * triggered when ellisis is shown
+                        * @event
+                        * @name show_ellipsis.jstree
+                        */
+                       this.trigger('show_ellipsis');
+               },
+               /**
+                * hide the node ellipsis
+                * @name hide_ellipsis()
+                */
+               hide_ellipsis : function () {
+                       this._data.core.themes.ellipsis = false;
+                       this.get_container_ul().removeClass("jstree-ellipsis");
+                       /**
+                        * triggered when ellisis is hidden
+                        * @event
+                        * @name hide_ellipsis.jstree
+                        */
+                       this.trigger('hide_ellipsis');
+               },
+               /**
+                * toggle the node ellipsis
+                * @name toggle_icons()
+                */
+               toggle_ellipsis : function () { if(this._data.core.themes.ellipsis) { this.hide_ellipsis(); } else { this.show_ellipsis(); } },
+               /**
                 * set the node icon for a node
                 * @name set_icon(obj, icon)
                 * @param {mixed} obj
                return a;
        };
        // remove item from array
-       $.vakata.array_remove = function(array, from, to) {
-               var rest = array.slice((to || from) + 1 || array.length);
-               array.length = from < 0 ? array.length + from : from;
-               array.push.apply(array, rest);
+       $.vakata.array_remove = function(array, from) {
+               array.splice(from, 1);
                return array;
+               //var rest = array.slice((to || from) + 1 || array.length);
+               //array.length = from < 0 ? array.length + from : from;
+               //array.push.apply(array, rest);
+               //return array;
        };
        // remove item from array
        $.vakata.array_remove_item = function(array, item) {
                var tmp = $.inArray(item, array);
                return tmp !== -1 ? $.vakata.array_remove(array, tmp) : array;
        };
+       $.vakata.array_filter = function(c,a,b,d,e) {
+               if (c.filter) {
+                       return c.filter(a, b);
+               }
+               d=[];
+               for (e in c) {
+                       if (~~e+''===e+'' && e>=0 && a.call(b,c[e],+e,c)) {
+                               d.push(c[e]);
+                       }
+               }
+               return d;
+       };
 
 
 /**
                 * @name $.jstree.defaults.checkbox.tie_selection
                 * @plugin checkbox
                 */
-               tie_selection           : true
+               tie_selection           : true,
+
+               /**
+                * This setting controls if cascading down affects disabled checkboxes
+                * @name $.jstree.defaults.checkbox.cascade_to_disabled
+                * @plugin checkbox
+                */
+               cascade_to_disabled : true,
+
+               /**
+                * This setting controls if cascading down affects hidden checkboxes
+                * @name $.jstree.defaults.checkbox.cascade_to_hidden
+                * @plugin checkbox
+                */
+               cascade_to_hidden : true
        };
        $.jstree.plugins.checkbox = function (options, parent) {
                this.bind = function () {
                                                                        for(i = 0, j = dpc.length; i < j; i++) {
                                                                                m[dpc[i]].state[ t ? 'selected' : 'checked' ] = true;
                                                                        }
+
                                                                        this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(dpc);
                                                                }
                                                                else {
                                                        this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected);
                                                }, this))
                                        .on(this.settings.checkbox.tie_selection ? 'select_node.jstree' : 'check_node.jstree', $.proxy(function (e, data) {
-                                                       var obj = data.node,
+                                                       var self = this,
+                                                               obj = data.node,
                                                                m = this._model.data,
                                                                par = this.get_node(obj.parent),
-                                                               dom = this.get_node(obj, true),
-                                                               i, j, c, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection;
+                                                               i, j, c, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
+                                                               sel = {}, cur = this._data[ t ? 'core' : 'checkbox' ].selected;
+
+                                                       for (i = 0, j = cur.length; i < j; i++) {
+                                                               sel[cur[i]] = true;
+                                                       }
 
                                                        // apply down
                                                        if(s.indexOf('down') !== -1) {
-                                                               this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected.concat(obj.children_d));
-                                                               for(i = 0, j = obj.children_d.length; i < j; i++) {
-                                                                       tmp = m[obj.children_d[i]];
-                                                                       tmp.state[ t ? 'selected' : 'checked' ] = true;
-                                                                       if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
-                                                                               tmp.original.state.undetermined = false;
-                                                                       }
-                                                               }
+                                                               //this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected.concat(obj.children_d));
+                                                               var selectedIds = this._cascade_new_checked_state(obj.id, true);
+                                obj.children_d.concat(obj.id).forEach(function(id) {
+                                    if (selectedIds.indexOf(id) > -1) {
+                                        sel[id] = true;
+                                    }
+                                    else {
+                                        delete sel[id];
+                                    }
+                                });
                                                        }
 
                                                        // apply up
                                                                        }
                                                                        if(c === j) {
                                                                                par.state[ t ? 'selected' : 'checked' ] = true;
-                                                                               this._data[ t ? 'core' : 'checkbox' ].selected.push(par.id);
+                                                                               sel[par.id] = true;
+                                                                               //this._data[ t ? 'core' : 'checkbox' ].selected.push(par.id);
                                                                                tmp = this.get_node(par, true);
                                                                                if(tmp && tmp.length) {
                                                                                        tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
                                                                }
                                                        }
 
-                                                       // apply down (process .children separately?)
-                                                       if(s.indexOf('down') !== -1 && dom.length) {
-                                                               dom.find('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked').parent().attr('aria-selected', true);
+                                                       cur = [];
+                                                       for (i in sel) {
+                                                               if (sel.hasOwnProperty(i)) {
+                                                                       cur.push(i);
+                                                               }
                                                        }
+                                                       this._data[ t ? 'core' : 'checkbox' ].selected = cur;
                                                }, this))
                                        .on(this.settings.checkbox.tie_selection ? 'deselect_all.jstree' : 'uncheck_all.jstree', $.proxy(function (e, data) {
                                                        var obj = this.get_node($.jstree.root),
                                                        }
                                                }, this))
                                        .on(this.settings.checkbox.tie_selection ? 'deselect_node.jstree' : 'uncheck_node.jstree', $.proxy(function (e, data) {
-                                                       var obj = data.node,
+                                                       var self = this,
+                                                               obj = data.node,
                                                                dom = this.get_node(obj, true),
-                                                               i, j, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection;
-                                                       if(obj && obj.original && obj.original.state && obj.original.state.undetermined) {
-                                                               obj.original.state.undetermined = false;
-                                                       }
+                                                               i, j, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
+                                                               cur = this._data[ t ? 'core' : 'checkbox' ].selected, sel = {},
+                                                               stillSelectedIds = [],
+                                                               allIds = obj.children_d.concat(obj.id);
 
                                                        // apply down
                                                        if(s.indexOf('down') !== -1) {
-                                                               for(i = 0, j = obj.children_d.length; i < j; i++) {
-                                                                       tmp = this._model.data[obj.children_d[i]];
-                                                                       tmp.state[ t ? 'selected' : 'checked' ] = false;
-                                                                       if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
-                                                                               tmp.original.state.undetermined = false;
-                                                                       }
-                                                               }
+                                                               var selectedIds = this._cascade_new_checked_state(obj.id, false);
+
+                                                               cur = cur.filter(function(id) {
+                                                                       return allIds.indexOf(id) === -1 || selectedIds.indexOf(id) > -1;
+                                                               });
                                                        }
 
-                                                       // apply up
-                                                       if(s.indexOf('up') !== -1) {
+                                                       // only apply up if cascade up is enabled and if this node is not selected
+                                                       // (if all child nodes are disabled and cascade_to_disabled === false then this node will till be selected).
+                                                       if(s.indexOf('up') !== -1 && cur.indexOf(obj.id) === -1) {
                                                                for(i = 0, j = obj.parents.length; i < j; i++) {
                                                                        tmp = this._model.data[obj.parents[i]];
                                                                        tmp.state[ t ? 'selected' : 'checked' ] = false;
                                                                                tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
                                                                        }
                                                                }
-                                                       }
-                                                       tmp = [];
-                                                       for(i = 0, j = this._data[ t ? 'core' : 'checkbox' ].selected.length; i < j; i++) {
-                                                               // apply down + apply up
-                                                               if(
-                                                                       (s.indexOf('down') === -1 || $.inArray(this._data[ t ? 'core' : 'checkbox' ].selected[i], obj.children_d) === -1) &&
-                                                                       (s.indexOf('up') === -1 || $.inArray(this._data[ t ? 'core' : 'checkbox' ].selected[i], obj.parents) === -1)
-                                                               ) {
-                                                                       tmp.push(this._data[ t ? 'core' : 'checkbox' ].selected[i]);
-                                                               }
-                                                       }
-                                                       this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(tmp);
 
-                                                       // apply down (process .children separately?)
-                                                       if(s.indexOf('down') !== -1 && dom.length) {
-                                                               dom.find('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked').parent().attr('aria-selected', false);
+                                                               cur = cur.filter(function(id) {
+                                                                       return obj.parents.indexOf(id) === -1;
+                                                               });
                                                        }
+
+                                                       this._data[ t ? 'core' : 'checkbox' ].selected = cur;
                                                }, this));
                        }
                        if(this.settings.checkbox.cascade.indexOf('up') !== -1) {
                                                }, this));
                        }
                };
+
                /**
                 * set the undetermined state where and if necessary. Used internally.
                 * @private
                        for(i = 0, j = s.length; i < j; i++) {
                                if(m[s[i]] && m[s[i]].parents) {
                                        for(k = 0, l = m[s[i]].parents.length; k < l; k++) {
-                                               if(o[m[s[i]].parents[k]] === undefined && m[s[i]].parents[k] !== $.jstree.root) {
+                                               if(o[m[s[i]].parents[k]] !== undefined) {
+                                                       break;
+                                               }
+                                               if(m[s[i]].parents[k] !== $.jstree.root) {
                                                        o[m[s[i]].parents[k]] = true;
                                                        p.push(m[s[i]].parents[k]);
                                                }
                        this.element.find('.jstree-closed').not(':has(.jstree-children)')
                                .each(function () {
                                        var tmp = tt.get_node(this), tmp2;
+                                       
+                                       if(!tmp) { return; }
+                                       
                                        if(!tmp.state.loaded) {
                                                if(tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) {
                                                        if(o[tmp.id] === undefined && tmp.id !== $.jstree.root) {
                };
 
                /**
+                * Unchecks a node and all its descendants. This function does NOT affect hidden and disabled nodes (or their descendants).
+                * However if these unaffected nodes are already selected their ids will be included in the returned array.
+                * @param id
+                * @param checkedState
+                * @returns {Array} Array of all node id's (in this tree branch) that are checked.
+                */
+               this._cascade_new_checked_state = function(id, checkedState) {
+                       var self = this;
+                       var t = this.settings.checkbox.tie_selection;
+                       var node = this._model.data[id];
+                       var selectedNodeIds = [];
+                       var selectedChildrenIds = [];
+
+                       if (
+                               (this.settings.checkbox.cascade_to_disabled || !node.state.disabled) &&
+                               (this.settings.checkbox.cascade_to_hidden || !node.state.hidden)
+                       ) {
+                //First try and check/uncheck the children
+                if (node.children) {
+                                       node.children.forEach(function(childId) {
+                                               var selectedChildIds = self._cascade_new_checked_state(childId, checkedState);
+                                               selectedNodeIds = selectedNodeIds.concat(selectedChildIds);
+                                               if (selectedChildIds.indexOf(childId) > -1) {
+                                                       selectedChildrenIds.push(childId);
+                                               }
+                                       });
+                               }
+
+                               var dom = self.get_node(node, true);
+
+                //A node's state is undetermined if some but not all of it's children are checked/selected .
+                               var undetermined = selectedChildrenIds.length > 0 && selectedChildrenIds.length < node.children.length;
+
+                               if(node.original && node.original.state && node.original.state.undetermined) {
+                                       node.original.state.undetermined = undetermined;
+                               }
+
+                //If a node is undetermined then remove selected class
+                               if (undetermined) {
+                    node.state[ t ? 'selected' : 'checked' ] = false;
+                    dom.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
+                               }
+                //Otherwise, if the checkedState === true (i.e. the node is being checked now) and all of the node's children are checked (if it has any children),
+                //check the node and style it correctly.
+                               else if (checkedState && selectedChildrenIds.length === node.children.length) {
+                    node.state[ t ? 'selected' : 'checked' ] = checkedState;
+                                       selectedNodeIds.push(node.id);
+
+                                       dom.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
+                               }
+                               else {
+                    node.state[ t ? 'selected' : 'checked' ] = false;
+                                       dom.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
+                               }
+                       }
+                       else {
+                               var selectedChildIds = this.get_checked_descendants(id);
+
+                               if (node.state[ t ? 'selected' : 'checked' ]) {
+                                       selectedChildIds.push(node.id);
+                               }
+
+                               selectedNodeIds = selectedNodeIds.concat(selectedChildIds);
+                       }
+
+                       return selectedNodeIds;
+               };
+
+               /**
+                * Gets ids of nodes selected in branch (of tree) specified by id (does not include the node specified by id)
+                * @param id
+                */
+               this.get_checked_descendants = function(id) {
+                       var self = this;
+                       var t = self.settings.checkbox.tie_selection;
+                       var node = self._model.data[id];
+
+                       return node.children_d.filter(function(_id) {
+                               return self._model.data[_id].state[ t ? 'selected' : 'checked' ];
+                       });
+               };
+
+               /**
                 * check a node (only if tie_selection in checkbox settings is false, otherwise select_node will be called internally)
                 * @name check_node(obj)
                 * @param {mixed} obj an array can be used to check multiple nodes
                                this.trigger('uncheck_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
                        }
                };
+               
                /**
                 * checks all nodes in the tree (only if tie_selection in checkbox settings is false, otherwise select_all will be called internally)
                 * @name check_all()
        // include the checkbox plugin by default
        // $.jstree.defaults.plugins.push("checkbox");
 
+
 /**
  * ### Conditionalselect plugin
  *
                /**
                 * an object of actions, or a function that accepts a node and a callback function and calls the callback function with an object of actions available for that node (you can also return the items too).
                 *
-                * Each action consists of a key (a unique name) and a value which is an object with the following properties (only label and action are required):
+                * Each action consists of a key (a unique name) and a value which is an object with the following properties (only label and action are required). Once a menu item is activated the `action` function will be invoked with an object containing the following keys: item - the contextmenu item definition as seen below, reference - the DOM node that was used (the tree node), element - the contextmenu DOM element, position - an object with x/y properties indicating the position of the menu.
                 *
                 * * `separator_before` - a boolean indicating if there should be a separator before this item
                 * * `separator_after` - a boolean indicating if there should be a separator after this item
                 * * `_disabled` - a boolean indicating if this action should be disabled
                 * * `label` - a string - the name of the action (could be a function returning a string)
-                * * `action` - a function to be executed if this item is chosen
+                * * `title` - a string - an optional tooltip for the item
+                * * `action` - a function to be executed if this item is chosen, the function will receive 
                 * * `icon` - a string, can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class
                 * * `shortcut` - keyCode which will trigger the action if the menu is open (for example `113` for rename, which equals F2)
                 * * `shortcut_label` - shortcut label (like for example `F2` for rename)
+                * * `submenu` - an object with the same structure as $.jstree.defaults.contextmenu.items which can be used to create a submenu - each key will be rendered as a separate option in a submenu that will appear once the current item is hovered
                 *
                 * @name $.jstree.defaults.contextmenu.items
                 * @plugin contextmenu
                                                var inst = $.jstree.reference(data.reference),
                                                        obj = inst.get_node(data.reference);
                                                inst.create_node(obj, {}, "last", function (new_node) {
-                                                       setTimeout(function () { inst.edit(new_node); },0);
+                                                       try {
+                                                               inst.edit(new_node);
+                                                       } catch (ex) {
+                                                               setTimeout(function () { inst.edit(new_node); },0);
+                                                       }
                                                });
                                        }
                                },
 
                        var last_ts = 0, cto = null, ex, ey;
                        this.element
+                               .on("init.jstree loading.jstree ready.jstree", $.proxy(function () {
+                                               this.get_container_ul().addClass('jstree-contextmenu');
+                                       }, this))
                                .on("contextmenu.jstree", ".jstree-anchor", $.proxy(function (e, data) {
+                                               if (e.target.tagName.toLowerCase() === 'input') {
+                                                       return;
+                                               }
                                                e.preventDefault();
                                                last_ts = e.ctrlKey ? +new Date() : 0;
                                                if(data || cto) {
                                                if(!e.originalEvent || !e.originalEvent.changedTouches || !e.originalEvent.changedTouches[0]) {
                                                        return;
                                                }
-                                               ex = e.pageX;
-                                               ey = e.pageY;
+                                               ex = e.originalEvent.changedTouches[0].clientX;
+                                               ey = e.originalEvent.changedTouches[0].clientY;
                                                cto = setTimeout(function () {
                                                        $(e.currentTarget).trigger('contextmenu', true);
                                                }, 750);
                                        })
                                .on('touchmove.vakata.jstree', function (e) {
-                                               if(cto && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0] && (Math.abs(ex - e.pageX) > 50 || Math.abs(ey - e.pageY) > 50)) {
+                                               if(cto && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0] && (Math.abs(ex - e.originalEvent.changedTouches[0].clientX) > 50 || Math.abs(ey - e.originalEvent.changedTouches[0].clientY) > 50)) {
                                                        clearTimeout(cto);
                                                }
                                        })
                                        });
                        }
                        */
-                       $(document).on("context_hide.vakata.jstree", $.proxy(function () { this._data.contextmenu.visible = false; }, this));
+                       $(document).on("context_hide.vakata.jstree", $.proxy(function (e, data) {
+                               this._data.contextmenu.visible = false;
+                               $(data.reference).removeClass('jstree-context');
+                       }, this));
                };
                this.teardown = function () {
                        if(this._data.contextmenu.visible) {
                        $(document).one("context_show.vakata.jstree", $.proxy(function (e, data) {
                                var cls = 'jstree-contextmenu jstree-' + this.get_theme() + '-contextmenu';
                                $(data.element).addClass(cls);
+                               a.addClass('jstree-context');
                        }, this));
                        this._data.contextmenu.visible = true;
                        $.vakata.context.show(a, { 'x' : x, 'y' : y }, i);
                                        }
                                        sep = false;
                                        str += "<"+"li class='" + (val._class || "") + (val._disabled === true || ($.isFunction(val._disabled) && val._disabled({ "item" : val, "reference" : vakata_context.reference, "element" : vakata_context.element })) ? " vakata-contextmenu-disabled " : "") + "' "+(val.shortcut?" data-shortcut='"+val.shortcut+"' ":'')+">";
-                                       str += "<"+"a href='#' rel='" + (vakata_context.items.length - 1) + "'>";
+                                       str += "<"+"a href='#' rel='" + (vakata_context.items.length - 1) + "' " + (val.title ? "title='" + val.title + "'" : "") + ">";
                                        if($.vakata.context.settings.icons) {
                                                str += "<"+"i ";
                                                if(val.icon) {
                                o = $(o);
                                if(!o.length || !o.children("ul").length) { return; }
                                var e = o.children("ul"),
-                                       x = o.offset().left + o.outerWidth(),
+                                       xl = o.offset().left,
+                                       x = xl + o.outerWidth(),
                                        y = o.offset().top,
                                        w = e.width(),
                                        h = e.height(),
                                        o[x - (w + 10 + o.outerWidth()) < 0 ? "addClass" : "removeClass"]("vakata-context-left");
                                }
                                else {
-                                       o[x + w + 10 > dw ? "addClass" : "removeClass"]("vakata-context-right");
+                                       o[x + w > dw  && xl > dw - x ? "addClass" : "removeClass"]("vakata-context-right");
                                }
                                if(y + h + 10 > dh) {
                                        e.css("bottom","-1px");
                                }
+
+                               //if does not fit - stick it to the side
+                               if (o.hasClass('vakata-context-right')) {
+                                       if (xl < w) {
+                                               e.css("margin-right", xl - w);
+                                       }
+                               } else {
+                                       if (dw - x < w) {
+                                               e.css("margin-left", dw - x - w);
+                                       }
+                               }
+
                                e.show();
                        },
                        show : function (reference, position, data) {
                                                switch(e.which) {
                                                        case 13:
                                                        case 32:
-                                                               e.type = "mouseup";
+                                                               e.type = "click";
                                                                e.preventDefault();
                                                                $(e.currentTarget).trigger(e);
                                                                break;
 
                        $(document)
                                .on("mousedown.vakata.jstree", function (e) {
-                                       if(vakata_context.is_visible && !$.contains(vakata_context.element[0], e.target)) {
+                                       if(vakata_context.is_visible && vakata_context.element[0] !== e.target  && !$.contains(vakata_context.element[0], e.target)) {
                                                $.vakata.context.hide();
                                        }
                                })
        }($));
        // $.jstree.defaults.plugins.push("contextmenu");
 
+
 /**
  * ### Drag'n'drop plugin
  *
                 * @name $.jstree.defaults.dnd.large_drag_target
                 * @plugin dnd
                 */
-               large_drag_target : false
+               large_drag_target : false,
+               /**
+                * controls whether use HTML5 dnd api instead of classical. That will allow better integration of dnd events with other HTML5 controls.
+                * @reference http://caniuse.com/#feat=dragndrop
+                * @name $.jstree.defaults.dnd.use_html5
+                * @plugin dnd
+                */
+               use_html5: false
        };
+       var drg, elm;
        // TODO: now check works by checking for each node individually, how about max_children, unique, etc?
        $.jstree.plugins.dnd = function (options, parent) {
+               this.init = function (el, options) {
+                       parent.init.call(this, el, options);
+                       this.settings.dnd.use_html5 = this.settings.dnd.use_html5 && ('draggable' in document.createElement('span'));
+               };
                this.bind = function () {
                        parent.bind.call(this);
 
                        this.element
-                               .on('mousedown.jstree touchstart.jstree', this.settings.dnd.large_drag_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) {
-                                       if(this.settings.dnd.large_drag_target && $(e.target).closest('.jstree-node')[0] !== e.currentTarget) {
-                                               return true;
-                                       }
-                                       if(e.type === "touchstart" && (!this.settings.dnd.touch || (this.settings.dnd.touch === 'selected' && !$(e.currentTarget).closest('.jstree-node').children('.jstree-anchor').hasClass('jstree-clicked')))) {
-                                               return true;
-                                       }
-                                       var obj = this.get_node(e.target),
-                                               mlt = this.is_selected(obj) && this.settings.dnd.drag_selection ? this.get_top_selected().length : 1,
-                                               txt = (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget));
-                                       if(this.settings.core.force_text) {
-                                               txt = $.vakata.html.escape(txt);
+                               .on(this.settings.dnd.use_html5 ? 'dragstart.jstree' : 'mousedown.jstree touchstart.jstree', this.settings.dnd.large_drag_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) {
+                                               if(this.settings.dnd.large_drag_target && $(e.target).closest('.jstree-node')[0] !== e.currentTarget) {
+                                                       return true;
+                                               }
+                                               if(e.type === "touchstart" && (!this.settings.dnd.touch || (this.settings.dnd.touch === 'selected' && !$(e.currentTarget).closest('.jstree-node').children('.jstree-anchor').hasClass('jstree-clicked')))) {
+                                                       return true;
+                                               }
+                                               var obj = this.get_node(e.target),
+                                                       mlt = this.is_selected(obj) && this.settings.dnd.drag_selection ? this.get_top_selected().length : 1,
+                                                       txt = (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget));
+                                               if(this.settings.core.force_text) {
+                                                       txt = $.vakata.html.escape(txt);
+                                               }
+                                               if(obj && obj.id && obj.id !== $.jstree.root && (e.which === 1 || e.type === "touchstart" || e.type === "dragstart") &&
+                                                       (this.settings.dnd.is_draggable === true || ($.isFunction(this.settings.dnd.is_draggable) && this.settings.dnd.is_draggable.call(this, (mlt > 1 ? this.get_top_selected(true) : [obj]), e)))
+                                               ) {
+                                                       drg = { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_top_selected() : [obj.id] };
+                                                       elm = e.currentTarget;
+                                                       if (this.settings.dnd.use_html5) {
+                                                               $.vakata.dnd._trigger('start', e, { 'helper': $(), 'element': elm, 'data': drg });
+                                                       } else {
+                                                               this.element.trigger('mousedown.jstree');
+                                                               return $.vakata.dnd.start(e, drg, '<div id="jstree-dnd" class="jstree-' + this.get_theme() + ' jstree-' + this.get_theme() + '-' + this.get_theme_variant() + ' ' + ( this.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ) + '"><i class="jstree-icon jstree-er"></i>' + txt + '<ins class="jstree-copy" style="display:none;">+</ins></div>');
+                                                       }
+                                               }
+                                       }, this));
+                       if (this.settings.dnd.use_html5) {
+                               this.element
+                                       .on('dragover.jstree', function (e) {
+                                                       e.preventDefault();
+                                                       $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
+                                                       return false;
+                                               })
+                                       //.on('dragenter.jstree', this.settings.dnd.large_drop_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) {
+                                       //              e.preventDefault();
+                                       //              $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
+                                       //              return false;
+                                       //      }, this))
+                                       .on('drop.jstree', $.proxy(function (e) {
+                                                       e.preventDefault();
+                                                       $.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg });
+                                                       return false;
+                                               }, this));
+                       }
+               };
+               this.redraw_node = function(obj, deep, callback, force_render) {
+                       obj = parent.redraw_node.apply(this, arguments);
+                       if (obj && this.settings.dnd.use_html5) {
+                               if (this.settings.dnd.large_drag_target) {
+                                       obj.setAttribute('draggable', true);
+                               } else {
+                                       var i, j, tmp = null;
+                                       for(i = 0, j = obj.childNodes.length; i < j; i++) {
+                                               if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
+                                                       tmp = obj.childNodes[i];
+                                                       break;
+                                               }
                                        }
-                                       if(obj && obj.id && obj.id !== $.jstree.root && (e.which === 1 || e.type === "touchstart") &&
-                                               (this.settings.dnd.is_draggable === true || ($.isFunction(this.settings.dnd.is_draggable) && this.settings.dnd.is_draggable.call(this, (mlt > 1 ? this.get_top_selected(true) : [obj]), e)))
-                                       ) {
-                                               this.element.trigger('mousedown.jstree');
-                                               return $.vakata.dnd.start(e, { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_top_selected() : [obj.id] }, '<div id="jstree-dnd" class="jstree-' + this.get_theme() + ' jstree-' + this.get_theme() + '-' + this.get_theme_variant() + ' ' + ( this.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ) + '"><i class="jstree-icon jstree-er"></i>' + txt + '<ins class="jstree-copy" style="display:none;">+</ins></div>');
+                                       if(tmp) {
+                                               tmp.setAttribute('draggable', true);
                                        }
-                               }, this));
+                               }
+                       }
+                       return obj;
                };
        };
 
                                marker.appendTo('body'); //.show();
                        })
                        .on('dnd_move.vakata.jstree', function (e, data) {
-                               if(opento) { clearTimeout(opento); }
+                               var isDifferentNode = data.event.target !== lastev.target;
+                               if(opento) {
+                                       if (!data.event || data.event.type !== 'dragover' || isDifferentNode) {
+                                               clearTimeout(opento);
+                                       }
+                               }
                                if(!data || !data.data || !data.data.jstree) { return; }
 
                                // if we are hovering the marker image do nothing (can happen on "inside" drags)
                                        ref = false,
                                        off = false,
                                        rel = false,
-                                       tmp, l, t, h, p, i, o, ok, t1, t2, op, ps, pr, ip, tm;
+                                       tmp, l, t, h, p, i, o, ok, t1, t2, op, ps, pr, ip, tm, is_copy, pn;
                                // if we are over an instance
                                if(ins && ins._data && ins._data.dnd) {
                                        marker.attr('class', 'jstree-' + ins.get_theme() + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ));
+                                       is_copy = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)));
                                        data.helper
                                                .children().attr('class', 'jstree-' + ins.get_theme() + ' jstree-' + ins.get_theme() + '-' + ins.get_theme_variant() + ' ' + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ))
-                                               .find('.jstree-copy').first()[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? 'show' : 'hide' ]();
-
+                                               .find('.jstree-copy').first()[ is_copy ? 'show' : 'hide' ]();
 
                                        // if are hovering the container itself add a new root node
+                                       //console.log(data.event);
                                        if( (data.event.target === ins.element[0] || data.event.target === ins.get_container_ul()[0]) && ins.get_container_ul().children().length === 0) {
                                                ok = true;
                                                for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
                                                        lastmv = { 'ins' : ins, 'par' : $.jstree.root, 'pos' : 'last' };
                                                        marker.hide();
                                                        data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
+                                                       if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
+                                                               data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
+                                                       }
                                                        return;
                                                }
                                        }
                                                ref = ins.settings.dnd.large_drop_target ? $(data.event.target).closest('.jstree-node').children('.jstree-anchor') : $(data.event.target).closest('.jstree-anchor');
                                                if(ref && ref.length && ref.parent().is('.jstree-closed, .jstree-open, .jstree-leaf')) {
                                                        off = ref.offset();
-                                                       rel = data.event.pageY - off.top;
+                                                       rel = (data.event.pageY !== undefined ? data.event.pageY : data.event.originalEvent.pageY) - off.top;
                                                        h = ref.outerHeight();
                                                        if(rel < h / 3) {
                                                                o = ['b', 'i', 'a'];
                                                                        }
                                                                }
                                                                if(v === 'i' && ref.parent().is('.jstree-closed') && ins.settings.dnd.open_timeout) {
-                                                                       opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout);
+                                                                       if (!data.event || data.event.type !== 'dragover' || isDifferentNode) {
+                                                                               if (opento) { clearTimeout(opento); }
+                                                                               opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout);
+                                                                       }
                                                                }
                                                                if(ok) {
+                                                                       pn = ins.get_node(p, true);
+                                                                       if (!pn.hasClass('.jstree-dnd-parent')) {
+                                                                               $('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
+                                                                               pn.addClass('jstree-dnd-parent');
+                                                                       }
                                                                        lastmv = { 'ins' : ins, 'par' : p, 'pos' : v === 'i' && ip === 'last' && i === 0 && !ins.is_loaded(tm) ? 'last' : i };
                                                                        marker.css({ 'left' : l + 'px', 'top' : t + 'px' }).show();
                                                                        data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
+                                                                       if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
+                                                                               data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
+                                                                       }
                                                                        laster = {};
                                                                        o = true;
                                                                        return false;
                                                }
                                        }
                                }
+                               $('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
                                lastmv = false;
                                data.helper.find('.jstree-icon').removeClass('jstree-ok').addClass('jstree-er');
+                               if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
+                                       data.event.originalEvent.dataTransfer.dropEffect = 'none';
+                               }
                                marker.hide();
                        })
                        .on('dnd_scroll.vakata.jstree', function (e, data) {
                                data.helper.find('.jstree-icon').first().removeClass('jstree-ok').addClass('jstree-er');
                        })
                        .on('dnd_stop.vakata.jstree', function (e, data) {
+                               $('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
                                if(opento) { clearTimeout(opento); }
                                if(!data || !data.data || !data.data.jstree) { return; }
                                marker.hide().detach();
                        .on('keyup.jstree keydown.jstree', function (e, data) {
                                data = $.vakata.dnd._get();
                                if(data && data.data && data.data.jstree) {
-                                       data.helper.find('.jstree-copy').first()[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (e.metaKey || e.ctrlKey))) ? 'show' : 'hide' ]();
-                                       if(lastev) {
-                                               lastev.metaKey = e.metaKey;
-                                               lastev.ctrlKey = e.ctrlKey;
-                                               $.vakata.dnd._trigger('move', lastev);
+                                       if (e.type === "keyup" && e.which === 27) {
+                                               if (opento) { clearTimeout(opento); }
+                                               lastmv = false;
+                                               laster = false;
+                                               lastev = false;
+                                               opento = false;
+                                               marker.hide().detach();
+                                               $.vakata.dnd._clean();
+                                       } else {
+                                               data.helper.find('.jstree-copy').first()[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (e.metaKey || e.ctrlKey))) ? 'show' : 'hide' ]();
+                                               if(lastev) {
+                                                       lastev.metaKey = e.metaKey;
+                                                       lastev.ctrlKey = e.ctrlKey;
+                                                       $.vakata.dnd._trigger('move', lastev);
+                                               }
                                        }
                                }
                        });
                                threshold                       : 5,
                                threshold_touch         : 50
                        },
-                       _trigger : function (event_name, e) {
-                               var data = $.vakata.dnd._get();
+                       _trigger : function (event_name, e, data) {
+                               if (data === undefined) {
+                                       data = $.vakata.dnd._get();
+                               }
                                data.event = e;
                                $(document).triggerHandler("dnd_" + event_name + ".vakata", data);
                        },
                                try {
                                        e.currentTarget.unselectable = "on";
                                        e.currentTarget.onselectstart = function() { return false; };
-                                       if(e.currentTarget.style) { e.currentTarget.style.MozUserSelect = "none"; }
+                                       if(e.currentTarget.style) {
+                                               e.currentTarget.style.touchAction = "none";
+                                               e.currentTarget.style.msTouchAction = "none";
+                                               e.currentTarget.style.MozUserSelect = "none";
+                                       }
                                } catch(ignore) { }
                                vakata_dnd.init_x       = e.pageX;
                                vakata_dnd.init_y       = e.pageY;
                                                        vakata_dnd.helper_w = vakata_dnd.helper.outerWidth();
                                                }
                                                vakata_dnd.is_drag = true;
+                                               $(vakata_dnd.target).one('click.vakata', false);
                                                /**
                                                 * triggered on the document when a drag starts
                                                 * @event
                                         * @param {jQuery} helper the helper shown next to the mouse
                                         * @param {Object} event the event that caused the stop
                                         */
+                                       if (e.target !== vakata_dnd.target) {
+                                               $(vakata_dnd.target).off('click.vakata');
+                                       }
                                        $.vakata.dnd._trigger("stop", e);
                                }
                                else {
        $.jstree.defaults.massload = null;
        $.jstree.plugins.massload = function (options, parent) {
                this.init = function (el, options) {
-                       parent.init.call(this, el, options);
                        this._data.massload = {};
+                       parent.init.call(this, el, options);
                };
-               this._load_nodes = function (nodes, callback, is_callback) {
-                       var s = this.settings.massload;
-                       if(is_callback && !$.isEmptyObject(this._data.massload)) {
-                               return parent._load_nodes.call(this, nodes, callback, is_callback);
-                       }
-                       if($.isFunction(s)) {
-                               return s.call(this, nodes, $.proxy(function (data) {
-                                       if(data) {
-                                               for(var i in data) {
-                                                       if(data.hasOwnProperty(i)) {
-                                                               this._data.massload[i] = data[i];
-                                                       }
+               this._load_nodes = function (nodes, callback, is_callback, force_reload) {
+                       var s = this.settings.massload,
+                               nodesString = JSON.stringify(nodes),
+                               toLoad = [],
+                               m = this._model.data,
+                               i, j, dom;
+                       if (!is_callback) {
+                               for(i = 0, j = nodes.length; i < j; i++) {
+                                       if(!m[nodes[i]] || ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || force_reload) ) {
+                                               toLoad.push(nodes[i]);
+                                               dom = this.get_node(nodes[i], true);
+                                               if (dom && dom.length) {
+                                                       dom.addClass("jstree-loading").attr('aria-busy',true);
                                                }
                                        }
-                                       parent._load_nodes.call(this, nodes, callback, is_callback);
-                               }, this));
-                       }
-                       if(typeof s === 'object' && s && s.url) {
-                               s = $.extend(true, {}, s);
-                               if($.isFunction(s.url)) {
-                                       s.url = s.url.call(this, nodes);
-                               }
-                               if($.isFunction(s.data)) {
-                                       s.data = s.data.call(this, nodes);
                                }
-                               return $.ajax(s)
-                                       .done($.proxy(function (data,t,x) {
+                               this._data.massload = {};
+                               if (toLoad.length) {
+                                       if($.isFunction(s)) {
+                                               return s.call(this, toLoad, $.proxy(function (data) {
+                                                       var i, j;
                                                        if(data) {
-                                                               for(var i in data) {
+                                                               for(i in data) {
                                                                        if(data.hasOwnProperty(i)) {
                                                                                this._data.massload[i] = data[i];
                                                                        }
                                                                }
                                                        }
-                                                       parent._load_nodes.call(this, nodes, callback, is_callback);
-                                               }, this))
-                                       .fail($.proxy(function (f) {
-                                                       parent._load_nodes.call(this, nodes, callback, is_callback);
+                                                       for(i = 0, j = nodes.length; i < j; i++) {
+                                                               dom = this.get_node(nodes[i], true);
+                                                               if (dom && dom.length) {
+                                                                       dom.removeClass("jstree-loading").attr('aria-busy',false);
+                                                               }
+                                                       }
+                                                       parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
                                                }, this));
+                                       }
+                                       if(typeof s === 'object' && s && s.url) {
+                                               s = $.extend(true, {}, s);
+                                               if($.isFunction(s.url)) {
+                                                       s.url = s.url.call(this, toLoad);
+                                               }
+                                               if($.isFunction(s.data)) {
+                                                       s.data = s.data.call(this, toLoad);
+                                               }
+                                               return $.ajax(s)
+                                                       .done($.proxy(function (data,t,x) {
+                                                                       var i, j;
+                                                                       if(data) {
+                                                                               for(i in data) {
+                                                                                       if(data.hasOwnProperty(i)) {
+                                                                                               this._data.massload[i] = data[i];
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                                       for(i = 0, j = nodes.length; i < j; i++) {
+                                                                               dom = this.get_node(nodes[i], true);
+                                                                               if (dom && dom.length) {
+                                                                                       dom.removeClass("jstree-loading").attr('aria-busy',false);
+                                                                               }
+                                                                       }
+                                                                       parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
+                                                               }, this))
+                                                       .fail($.proxy(function (f) {
+                                                                       parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
+                                                               }, this));
+                                       }
+                               }
                        }
-                       return parent._load_nodes.call(this, nodes, callback, is_callback);
+                       return parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
                };
                this._load_node = function (obj, callback) {
-                       var d = this._data.massload[obj.id];
-                       if(d) {
-                               return this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $($.parseHTML(d)).filter(function () { return this.nodeType !== 3; }) : d, function (status) {
-                                       callback.call(this, status);
-                                       delete this._data.massload[obj.id];
-                               });
+                       var data = this._data.massload[obj.id],
+                               rslt = null, dom;
+                       if(data) {
+                               rslt = this[typeof data === 'string' ? '_append_html_data' : '_append_json_data'](
+                                       obj,
+                                       typeof data === 'string' ? $($.parseHTML(data)).filter(function () { return this.nodeType !== 3; }) : data,
+                                       function (status) { callback.call(this, status); }
+                               );
+                               dom = this.get_node(obj.id, true);
+                               if (dom && dom.length) {
+                                       dom.removeClass("jstree-loading").attr('aria-busy',false);
+                               }
+                               delete this._data.massload[obj.id];
+                               return rslt;
                        }
                        return parent._load_node.call(this, obj, callback);
                };
         */
        $.jstree.defaults.search = {
                /**
-                * a jQuery-like AJAX config, which jstree uses if a server should be queried for results. 
-                * 
+                * a jQuery-like AJAX config, which jstree uses if a server should be queried for results.
+                *
                 * A `str` (which is the search string) parameter will be added with the request, an optional `inside` parameter will be added if the search is limited to a node id. The expected result is a JSON array with nodes that need to be opened so that matching nodes will be revealed.
-                * Leave this setting as `false` to not query the server. You can also set this to a function, which will be invoked in the instance's scope and receive 3 parameters - the search string, the callback to call with the array of nodes to load, and the optional node ID to limit the search to 
+                * Leave this setting as `false` to not query the server. You can also set this to a function, which will be invoked in the instance's scope and receive 3 parameters - the search string, the callback to call with the array of nodes to load, and the optional node ID to limit the search to
                 * @name $.jstree.defaults.search.ajax
                 * @plugin search
                 */
                 */
                case_sensitive : false,
                /**
-                * Indicates if the tree should be filtered (by default) to show only matching nodes (keep in mind this can be a heavy on large trees in old browsers). 
+                * Indicates if the tree should be filtered (by default) to show only matching nodes (keep in mind this can be a heavy on large trees in old browsers).
                 * This setting can be changed at runtime when calling the search method. Default is `false`.
                 * @name $.jstree.defaults.search.show_only_matches
                 * @plugin search
                        this.element
                                .on("search.jstree", $.proxy(function (e, data) {
                                                if(this._data.search.som && data.res.length) {
-                                                       var m = this._model.data, i, j, p = [];
+                                                       var m = this._model.data, i, j, p = [], k, l;
                                                        for(i = 0, j = data.res.length; i < j; i++) {
                                                                if(m[data.res[i]] && !m[data.res[i]].state.hidden) {
                                                                        p.push(data.res[i]);
                                                                        p = p.concat(m[data.res[i]].parents);
                                                                        if(this._data.search.smc) {
-                                                                               p = p.concat(m[data.res[i]].children_d);
+                                                                               for (k = 0, l = m[data.res[i]].children_d.length; k < l; k++) {
+                                                                                       if (m[m[data.res[i]].children_d[k]] && !m[m[data.res[i]].children_d[k]].state.hidden) {
+                                                                                               p.push(m[data.res[i]].children_d[k]);
+                                                                                       }
+                                                                               }
                                                                        }
                                                                }
                                                        }
                                                        p = $.vakata.array_remove_item($.vakata.array_unique(p), $.jstree.root);
                                                        this._data.search.hdn = this.hide_all(true);
-                                                       this.show_node(p);
+                                                       this.show_node(p, true);
+                                                       this.redraw(true);
                                                }
                                        }, this))
                                .on("clear_search.jstree", $.proxy(function (e, data) {
                                                if(this._data.search.som && data.res.length) {
-                                                       this.show_node(this._data.search.hdn);
+                                                       this.show_node(this._data.search.hdn, true);
+                                                       this.redraw(true);
                                                }
                                        }, this));
                };
                                        return a.call(this, str, $.proxy(function (d) {
                                                        if(d && d.d) { d = d.d; }
                                                        this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () {
-                                                               this.search(str, true, show_only_matches, inside, append);
-                                                       }, true);
+                                                               this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
+                                                       });
                                                }, this), inside);
                                }
                                else {
                                        if(inside) {
                                                a.data.inside = inside;
                                        }
-                                       return $.ajax(a)
+                                       if (this._data.search.lastRequest) {
+                                               this._data.search.lastRequest.abort();
+                                       }
+                                       this._data.search.lastRequest = $.ajax(a)
                                                .fail($.proxy(function () {
                                                        this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) };
                                                        this.settings.core.error.call(this, this._data.core.last_error);
                                                .done($.proxy(function (d) {
                                                        if(d && d.d) { d = d.d; }
                                                        this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () {
-                                                               this.search(str, true, show_only_matches, inside, append);
-                                                       }, true);
+                                                               this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
+                                                       });
                                                }, this));
+                                       return this._data.search.lastRequest;
                                }
                        }
                        if(!append) {
                        f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy });
                        $.each(m[inside ? inside : $.jstree.root].children_d, function (ii, i) {
                                var v = m[i];
-                               if(v.text && (!s.search_leaves_only || (v.state.loaded && v.children.length === 0)) && ( (s.search_callback && s.search_callback.call(this, str, v)) || (!s.search_callback && f.search(v.text).isMatch) ) ) {
+                               if(v.text && !v.state.hidden && (!s.search_leaves_only || (v.state.loaded && v.children.length === 0)) && ( (s.search_callback && s.search_callback.call(this, str, v)) || (!s.search_callback && f.search(v.text).isMatch) ) ) {
                                        r.push(i);
                                        p = p.concat(v.parents);
                                }
         * * `max_depth` the maximum number of nesting this node type can have. A value of `1` would mean that the node can have children, but no grandchildren. Do not specify or set to `-1` for unlimited.
         * * `valid_children` an array of node type strings, that nodes of this type can have as children. Do not specify or set to `-1` for no limits.
         * * `icon` a string - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class. Omit to use the default icon from your theme.
+        * * `li_attr` an object of values which will be used to add HTML attributes on the resulting LI DOM node (merged with the node's own data)
+        * * `a_attr` an object of values which will be used to add HTML attributes on the resulting A DOM node (merged with the node's own data)
         *
         * There are two predefined types:
         *
                                                var m = this._model.data,
                                                        dpc = data.nodes,
                                                        t = this.settings.types,
-                                                       i, j, c = 'default';
+                                                       i, j, c = 'default', k;
                                                for(i = 0, j = dpc.length; i < j; i++) {
                                                        c = 'default';
                                                        if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) {
                                                        if(m[dpc[i]].icon === true && t[c].icon !== undefined) {
                                                                m[dpc[i]].icon = t[c].icon;
                                                        }
+                                                       if(t[c].li_attr !== undefined && typeof t[c].li_attr === 'object') {
+                                                               for (k in t[c].li_attr) {
+                                                                       if (t[c].li_attr.hasOwnProperty(k)) {
+                                                                               if (k === 'id') {
+                                                                                       continue;
+                                                                               }
+                                                                               else if (m[dpc[i]].li_attr[k] === undefined) {
+                                                                                       m[dpc[i]].li_attr[k] = t[c].li_attr[k];
+                                                                               }
+                                                                               else if (k === 'class') {
+                                                                                       m[dpc[i]].li_attr['class'] = t[c].li_attr['class'] + ' ' + m[dpc[i]].li_attr['class'];
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                                       if(t[c].a_attr !== undefined && typeof t[c].a_attr === 'object') {
+                                                               for (k in t[c].a_attr) {
+                                                                       if (t[c].a_attr.hasOwnProperty(k)) {
+                                                                               if (k === 'id') {
+                                                                                       continue;
+                                                                               }
+                                                                               else if (m[dpc[i]].a_attr[k] === undefined) {
+                                                                                       m[dpc[i]].a_attr[k] = t[c].a_attr[k];
+                                                                               }
+                                                                               else if (k === 'href' && m[dpc[i]].a_attr[k] === '#') {
+                                                                                       m[dpc[i]].a_attr['href'] = t[c].a_attr['href'];
+                                                                               }
+                                                                               else if (k === 'class') {
+                                                                                       m[dpc[i]].a_attr['class'] = t[c].a_attr['class'] + ' ' + m[dpc[i]].a_attr['class'];
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
                                                }
                                                m[$.jstree.root].type = $.jstree.root;
                                        }, this));
                 * @plugin types
                 */
                this.set_type = function (obj, type) {
-                       var t, t1, t2, old_type, old_icon;
+                       var m = this._model.data, t, t1, t2, old_type, old_icon, k, d, a;
                        if($.isArray(obj)) {
                                obj = obj.slice();
                                for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
                        t = this.settings.types;
                        obj = this.get_node(obj);
                        if(!t[type] || !obj) { return false; }
+                       d = this.get_node(obj, true);
+                       if (d && d.length) {
+                               a = d.children('.jstree-anchor');
+                       }
                        old_type = obj.type;
                        old_icon = this.get_icon(obj);
                        obj.type = type;
-                       if(old_icon === true || (t[old_type] && t[old_type].icon !== undefined && old_icon === t[old_type].icon)) {
+                       if(old_icon === true || !t[old_type] || (t[old_type].icon !== undefined && old_icon === t[old_type].icon)) {
                                this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true);
                        }
+
+                       // remove old type props
+                       if(t[old_type] && t[old_type].li_attr !== undefined && typeof t[old_type].li_attr === 'object') {
+                               for (k in t[old_type].li_attr) {
+                                       if (t[old_type].li_attr.hasOwnProperty(k)) {
+                                               if (k === 'id') {
+                                                       continue;
+                                               }
+                                               else if (k === 'class') {
+                                                       m[obj.id].li_attr['class'] = (m[obj.id].li_attr['class'] || '').replace(t[old_type].li_attr[k], '');
+                                                       if (d) { d.removeClass(t[old_type].li_attr[k]); }
+                                               }
+                                               else if (m[obj.id].li_attr[k] === t[old_type].li_attr[k]) {
+                                                       m[obj.id].li_attr[k] = null;
+                                                       if (d) { d.removeAttr(k); }
+                                               }
+                                       }
+                               }
+                       }
+                       if(t[old_type] && t[old_type].a_attr !== undefined && typeof t[old_type].a_attr === 'object') {
+                               for (k in t[old_type].a_attr) {
+                                       if (t[old_type].a_attr.hasOwnProperty(k)) {
+                                               if (k === 'id') {
+                                                       continue;
+                                               }
+                                               else if (k === 'class') {
+                                                       m[obj.id].a_attr['class'] = (m[obj.id].a_attr['class'] || '').replace(t[old_type].a_attr[k], '');
+                                                       if (a) { a.removeClass(t[old_type].a_attr[k]); }
+                                               }
+                                               else if (m[obj.id].a_attr[k] === t[old_type].a_attr[k]) {
+                                                       if (k === 'href') {
+                                                               m[obj.id].a_attr[k] = '#';
+                                                               if (a) { a.attr('href', '#'); }
+                                                       }
+                                                       else {
+                                                               delete m[obj.id].a_attr[k];
+                                                               if (a) { a.removeAttr(k); }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+
+                       // add new props
+                       if(t[type].li_attr !== undefined && typeof t[type].li_attr === 'object') {
+                               for (k in t[type].li_attr) {
+                                       if (t[type].li_attr.hasOwnProperty(k)) {
+                                               if (k === 'id') {
+                                                       continue;
+                                               }
+                                               else if (m[obj.id].li_attr[k] === undefined) {
+                                                       m[obj.id].li_attr[k] = t[type].li_attr[k];
+                                                       if (d) {
+                                                               if (k === 'class') {
+                                                                       d.addClass(t[type].li_attr[k]);
+                                                               }
+                                                               else {
+                                                                       d.attr(k, t[type].li_attr[k]);
+                                                               }
+                                                       }
+                                               }
+                                               else if (k === 'class') {
+                                                       m[obj.id].li_attr['class'] = t[type].li_attr[k] + ' ' + m[obj.id].li_attr['class'];
+                                                       if (d) { d.addClass(t[type].li_attr[k]); }
+                                               }
+                                       }
+                               }
+                       }
+                       if(t[type].a_attr !== undefined && typeof t[type].a_attr === 'object') {
+                               for (k in t[type].a_attr) {
+                                       if (t[type].a_attr.hasOwnProperty(k)) {
+                                               if (k === 'id') {
+                                                       continue;
+                                               }
+                                               else if (m[obj.id].a_attr[k] === undefined) {
+                                                       m[obj.id].a_attr[k] = t[type].a_attr[k];
+                                                       if (a) {
+                                                               if (k === 'class') {
+                                                                       a.addClass(t[type].a_attr[k]);
+                                                               }
+                                                               else {
+                                                                       a.attr(k, t[type].a_attr[k]);
+                                                               }
+                                                       }
+                                               }
+                                               else if (k === 'href' && m[obj.id].a_attr[k] === '#') {
+                                                       m[obj.id].a_attr['href'] = t[type].a_attr['href'];
+                                                       if (a) { a.attr('href', t[type].a_attr['href']); }
+                                               }
+                                               else if (k === 'class') {
+                                                       m[obj.id].a_attr['class'] = t[type].a_attr['class'] + ' ' + m[obj.id].a_attr['class'];
+                                                       if (a) { a.addClass(t[type].a_attr[k]); }
+                                               }
+                                       }
+                               }
+                       }
+
                        return true;
                };
        };
        // include the types plugin by default
        // $.jstree.defaults.plugins.push("types");
 
+
 /**
  * ### Unique plugin
  *
                                                this.get_node(data.node, true).children('.jstree-wholerow')[e.type === "hover_node"?"addClass":"removeClass"]('jstree-wholerow-hovered');
                                        }, this))
                                .on("contextmenu.jstree", ".jstree-wholerow", $.proxy(function (e) {
-                                               e.preventDefault();
-                                               var tmp = $.Event('contextmenu', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey, pageX : e.pageX, pageY : e.pageY });
-                                               $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp);
+                                               if (this._data.contextmenu) {
+                                                       e.preventDefault();
+                                                       var tmp = $.Event('contextmenu', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey, pageX : e.pageX, pageY : e.pageY });
+                                                       $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp);
+                                               }
                                        }, this))
                                /*!
                                .on("mousedown.jstree touchstart.jstree", ".jstree-wholerow", function (e) {
                                                var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
                                                $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
                                        })
+                               .on("dblclick.jstree", ".jstree-wholerow", function (e) {
+                                               e.stopImmediatePropagation();
+                                               var tmp = $.Event('dblclick', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
+                                               $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
+                                       })
                                .on("click.jstree", ".jstree-leaf > .jstree-ocl", $.proxy(function (e) {
                                                e.stopImmediatePropagation();
                                                var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });