node-http-server
Version:
A very simple and fast http server for node, bash, and spawnable from C, Python etc. It is lightweight and great for embedded solutions as well as everyday development or public facing apps.
1,498 lines (1,480 loc) • 298 kB
JavaScript
/*globals jQuery, define, module, exports, require, window, document, postMessage */
(function (factory) {
"use strict";
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
}
else if(typeof module !== 'undefined' && module.exports) {
module.exports = factory(require('jquery'));
}
else {
factory(jQuery);
}
}(function ($, undefined) {
"use strict";
/*!
* jsTree 3.3.4
* http://jstree.com/
*
* Copyright (c) 2014 Ivan Bozhanov (http://vakata.com)
*
* Licensed same as jquery - under the terms of the MIT License
* http://www.opensource.org/licenses/mit-license.php
*/
/*!
* if using jslint please allow for the jQuery global and use following options:
* 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) {
return;
}
/**
* ### jsTree core functionality
*/
// internal variables
var instance_counter = 0,
ccp_node = false,
ccp_mode = false,
ccp_inst = false,
themes_loaded = [],
src = $('script:last').attr('src'),
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.
* @name $.jstree
*/
$.jstree = {
/**
* specifies the jstree version in use
* @name $.jstree.version
*/
version : '3.3.4',
/**
* holds all the default options used when creating new instances
* @name $.jstree.defaults
*/
defaults : {
/**
* configure which plugins will be active on an instance. Should be an array of strings, where each element is a plugin name. The default is `[]`
* @name $.jstree.defaults.plugins
*/
plugins : []
},
/**
* stores all loaded jstree plugins (used internally)
* @name $.jstree.plugins
*/
plugins : {},
path : src && src.indexOf('/') !== -1 ? src.replace(/\/[^\/]+$/,'') : '',
idregex : /[\\:&!^|()\[\]<>@*'+~#";.,=\- \/${}%?`]/g,
root : '#'
};
/**
* creates a jstree instance
* @name $.jstree.create(el [, options])
* @param {DOMElement|jQuery|String} el the element to create the instance on, can be jQuery extended or a selector
* @param {Object} options options for this instance (extends `$.jstree.defaults`)
* @return {jsTree} the new instance
*/
$.jstree.create = function (el, options) {
var tmp = new $.jstree.core(++instance_counter),
opt = options;
options = $.extend(true, {}, $.jstree.defaults, options);
if(opt && opt.plugins) {
options.plugins = opt.plugins;
}
$.each(options.plugins, function (i, k) {
if(i !== 'core') {
tmp = tmp.plugin(k, options[k]);
}
});
$(el).data('jstree', tmp);
tmp.init(el, options);
return tmp;
};
/**
* remove all traces of jstree from the DOM and destroy all instances
* @name $.jstree.destroy()
*/
$.jstree.destroy = function () {
$('.jstree:jstree').jstree('destroy');
$(document).off('.jstree');
};
/**
* the jstree class constructor, used only internally
* @private
* @name $.jstree.core(id)
* @param {Number} id this instance's index
*/
$.jstree.core = function (id) {
this._id = id;
this._cnt = 0;
this._wrk = null;
this._data = {
core : {
themes : {
name : false,
dots : false,
icons : false,
ellipsis : false
},
selected : [],
last_error : {},
working : false,
worker_queue : [],
focused : null
}
};
};
/**
* get a reference to an existing instance
*
* __Examples__
*
* // provided a container with an ID of "tree", and a nested node with an ID of "branch"
* // all of there will return the same instance
* $.jstree.reference('tree');
* $.jstree.reference('#tree');
* $.jstree.reference($('#tree'));
* $.jstree.reference(document.getElementByID('tree'));
* $.jstree.reference('branch');
* $.jstree.reference('#branch');
* $.jstree.reference($('#branch'));
* $.jstree.reference(document.getElementByID('branch'));
*
* @name $.jstree.reference(needle)
* @param {DOMElement|jQuery|String} needle
* @return {jsTree|null} the instance or `null` if not found
*/
$.jstree.reference = function (needle) {
var tmp = null,
obj = null;
if(needle && needle.id && (!needle.tagName || !needle.nodeType)) { needle = needle.id; }
if(!obj || !obj.length) {
try { obj = $(needle); } catch (ignore) { }
}
if(!obj || !obj.length) {
try { obj = $('#' + needle.replace($.jstree.idregex,'\\$&')); } catch (ignore) { }
}
if(obj && obj.length && (obj = obj.closest('.jstree')).length && (obj = obj.data('jstree'))) {
tmp = obj;
}
else {
$('.jstree').each(function () {
var inst = $(this).data('jstree');
if(inst && inst._model.data[needle]) {
tmp = inst;
return false;
}
});
}
return tmp;
};
/**
* Create an instance, get an instance or invoke a command on a instance.
*
* If there is no instance associated with the current node a new one is created and `arg` is used to extend `$.jstree.defaults` for this new instance. There would be no return value (chaining is not broken).
*
* If there is an existing instance and `arg` is a string the command specified by `arg` is executed on the instance, with any additional arguments passed to the function. If the function returns a value it will be returned (chaining could break depending on function).
*
* If there is an existing instance and `arg` is not a string the instance itself is returned (similar to `$.jstree.reference`).
*
* In any other case - nothing is returned and chaining is not broken.
*
* __Examples__
*
* $('#tree1').jstree(); // creates an instance
* $('#tree2').jstree({ plugins : [] }); // create an instance with some options
* $('#tree1').jstree('open_node', '#branch_1'); // call a method on an existing instance, passing additional arguments
* $('#tree2').jstree(); // get an existing instance (or create an instance)
* $('#tree2').jstree(true); // get an existing instance (will not create new instance)
* $('#branch_1').jstree().select_node('#branch_1'); // get an instance (using a nested element and call a method)
*
* @name $().jstree([arg])
* @param {String|Object} arg
* @return {Mixed}
*/
$.fn.jstree = function (arg) {
// check for string argument
var is_method = (typeof arg === 'string'),
args = Array.prototype.slice.call(arguments, 1),
result = null;
if(arg === true && !this.length) { return false; }
this.each(function () {
// get the instance (if there is one) and method (if it exists)
var instance = $.jstree.reference(this),
method = is_method && instance ? instance[arg] : null;
// if calling a method, and method is available - execute on the instance
result = is_method && method ?
method.apply(instance, args) :
null;
// if there is no instance and no method is being called - create one
if(!instance && !is_method && (arg === undefined || $.isPlainObject(arg))) {
$.jstree.create(this, arg);
}
// if there is an instance and no method is called - return the instance
if( (instance && !is_method) || arg === true ) {
result = instance || false;
}
// if there was a method call which returned a result - break and return the value
if(result !== null && result !== undefined) {
return false;
}
});
// if there was a method call with a valid return value - return that, otherwise continue the chain
return result !== null && result !== undefined ?
result : this;
};
/**
* used to find elements containing an instance
*
* __Examples__
*
* $('div:jstree').each(function () {
* $(this).jstree('destroy');
* });
*
* @name $(':jstree')
* @return {jQuery}
*/
$.expr.pseudos.jstree = $.expr.createPseudo(function(search) {
return function(a) {
return $(a).hasClass('jstree') &&
$(a).data('jstree') !== undefined;
};
});
/**
* stores all defaults for the core
* @name $.jstree.defaults.core
*/
$.jstree.defaults.core = {
/**
* data configuration
*
* If left as `false` the HTML inside the jstree container element is used to populate the tree (that should be an unordered list with list items).
*
* You can also pass in a HTML string or a JSON array here.
*
* It is possible to pass in a standard jQuery-like AJAX config and jstree will automatically determine if the response is JSON or HTML and use that to populate the tree.
* In addition to the standard jQuery ajax options here you can suppy functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node is being loaded, the return value of those functions will be used.
*
* The last option is to specify a function, that function will receive the node being loaded as argument and a second param which is a function which should be called with the result.
*
* __Examples__
*
* // AJAX
* $('#tree').jstree({
* 'core' : {
* 'data' : {
* 'url' : '/get/children/',
* 'data' : function (node) {
* return { 'id' : node.id };
* }
* }
* });
*
* // direct data
* $('#tree').jstree({
* 'core' : {
* 'data' : [
* 'Simple root node',
* {
* 'id' : 'node_2',
* 'text' : 'Root node with options',
* 'state' : { 'opened' : true, 'selected' : true },
* 'children' : [ { 'text' : 'Child 1' }, 'Child 2']
* }
* ]
* }
* });
*
* // function
* $('#tree').jstree({
* 'core' : {
* 'data' : function (obj, callback) {
* callback.call(this, ['Root 1', 'Root 2']);
* }
* });
*
* @name $.jstree.defaults.core.data
*/
data : false,
/**
* configure the various strings used throughout the tree
*
* You can use an object where the key is the string you need to replace and the value is your replacement.
* Another option is to specify a function which will be called with an argument of the needed string and should return the replacement.
* If left as `false` no replacement is made.
*
* __Examples__
*
* $('#tree').jstree({
* 'core' : {
* 'strings' : {
* 'Loading ...' : 'Please wait ...'
* }
* }
* });
*
* @name $.jstree.defaults.core.strings
*/
strings : false,
/**
* determines what happens when a user tries to modify the structure of the tree
* If left as `false` all operations like create, rename, delete, move or copy are prevented.
* You can set this to `true` to allow all interactions or use a function to have better control.
*
* __Examples__
*
* $('#tree').jstree({
* 'core' : {
* 'check_callback' : function (operation, node, node_parent, node_position, more) {
* // 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;
* }
* }
* });
*
* @name $.jstree.defaults.core.check_callback
*/
check_callback : false,
/**
* a callback called with a single object parameter in the instance's scope when something goes wrong (operation prevented, ajax failed, etc)
* @name $.jstree.defaults.core.error
*/
error : $.noop,
/**
* the open / close animation duration in milliseconds - set this to `false` to disable the animation (default is `200`)
* @name $.jstree.defaults.core.animation
*/
animation : 200,
/**
* a boolean indicating if multiple nodes can be selected
* @name $.jstree.defaults.core.multiple
*/
multiple : true,
/**
* theme configuration object
* @name $.jstree.defaults.core.themes
*/
themes : {
/**
* the name of the theme to use (if left as `false` the default theme is used)
* @name $.jstree.defaults.core.themes.name
*/
name : false,
/**
* the URL of the theme's CSS file, leave this as `false` if you have manually included the theme CSS (recommended). You can set this to `true` too which will try to autoload the theme.
* @name $.jstree.defaults.core.themes.url
*/
url : false,
/**
* the location of all jstree themes - only used if `url` is set to `true`
* @name $.jstree.defaults.core.themes.dir
*/
dir : false,
/**
* a boolean indicating if connecting dots are shown
* @name $.jstree.defaults.core.themes.dots
*/
dots : true,
/**
* a boolean indicating if node icons are shown
* @name $.jstree.defaults.core.themes.icons
*/
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
*/
stripes : false,
/**
* a string (or boolean `false`) specifying the theme variant to use (if the theme supports variants)
* @name $.jstree.defaults.core.themes.variant
*/
variant : false,
/**
* a boolean specifying if a reponsive version of the theme should kick in on smaller screens (if the theme supports it). Defaults to `false`.
* @name $.jstree.defaults.core.themes.responsive
*/
responsive : false
},
/**
* if left as `true` all parents of all selected nodes will be opened once the tree loads (so that all selected nodes are visible to the user)
* @name $.jstree.defaults.core.expand_selected_onload
*/
expand_selected_onload : true,
/**
* if left as `true` web workers will be used to parse incoming JSON data where possible, so that the UI will not be blocked by large requests. Workers are however about 30% slower. Defaults to `true`
* @name $.jstree.defaults.core.worker
*/
worker : true,
/**
* Force node text to plain text (and escape HTML). Defaults to `false`
* @name $.jstree.defaults.core.force_text
*/
force_text : false,
/**
* Should the node should be toggled if the text is double clicked . Defaults to `true`
* @name $.jstree.defaults.core.dblclick_toggle
*/
dblclick_toggle : true
};
$.jstree.core.prototype = {
/**
* used to decorate an instance with a plugin. Used internally.
* @private
* @name plugin(deco [, opts])
* @param {String} deco the plugin to decorate with
* @param {Object} opts options for the plugin
* @return {jsTree}
*/
plugin : function (deco, opts) {
var Child = $.jstree.plugins[deco];
if(Child) {
this._data[deco] = {};
Child.prototype = this;
return new Child(opts, this);
}
return this;
},
/**
* initialize the instance. Used internally.
* @private
* @name init(el, optons)
* @param {DOMElement|jQuery|String} el the element we are transforming
* @param {Object} options options for this instance
* @trigger init.jstree, loading.jstree, loaded.jstree, ready.jstree, changed.jstree
*/
init : function (el, options) {
this._model = {
data : {},
changed : [],
force_full_redraw : false,
redraw_timeout : false,
default_state : {
loaded : true,
opened : false,
selected : false,
disabled : false
}
};
this._model.data[$.jstree.root] = {
id : $.jstree.root,
parent : null,
parents : [],
children : [],
children_d : [],
state : { loaded : false }
};
this.element = $(el).addClass('jstree jstree-' + this._id);
this.settings = options;
this._data.core.ready = false;
this._data.core.loaded = false;
this._data.core.rtl = (this.element.css("direction") === "rtl");
this.element[this._data.core.rtl ? 'addClass' : 'removeClass']("jstree-rtl");
this.element.attr('role','tree');
if(this.settings.core.multiple) {
this.element.attr('aria-multiselectable', true);
}
if(!this.element.attr('tabindex')) {
this.element.attr('tabindex','0');
}
this.bind();
/**
* triggered after all events are bound
* @event
* @name init.jstree
*/
this.trigger("init");
this._data.core.original_container_html = this.element.find(" > ul > li").clone(true);
this._data.core.original_container_html
.find("li").addBack()
.contents().filter(function() {
return this.nodeType === 3 && (!this.nodeValue || /^\s+$/.test(this.nodeValue));
})
.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().outerHeight() || 24;
this._data.core.node = this._create_prototype_node();
/**
* triggered after the loading text is shown and before loading starts
* @event
* @name loading.jstree
*/
this.trigger("loading");
this.load_node($.jstree.root);
},
/**
* destroy an instance
* @name destroy()
* @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._wrk = null;
}
catch (ignore) { }
}
if(!keep_html) { this.element.empty(); }
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()
*/
teardown : function () {
this.unbind();
this.element
.removeClass('jstree')
.removeData('jstree')
.find("[class^='jstree']")
.addBack()
.attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
this.element = null;
},
/**
* bind all events. Used internally.
* @private
* @name bind()
*/
bind : function () {
var word = '',
tout = null,
was_click = 0;
this.element
.on("dblclick.jstree", function (e) {
if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
if(document.selection && document.selection.empty) {
document.selection.empty();
}
else {
if(window.getSelection) {
var sel = window.getSelection();
try {
sel.removeAllRanges();
sel.collapse();
} catch (ignore) { }
}
}
})
.on("mousedown.jstree", $.proxy(function (e) {
if(e.target === this.element[0]) {
e.preventDefault(); // prevent losing focus when clicking scroll arrows (FF, Chrome)
was_click = +(new Date()); // ie does not allow to prevent losing focus
}
}, this))
.on("mousedown.jstree", ".jstree-ocl", function (e) {
e.preventDefault(); // prevent any node inside from losing focus when clicking the open/close icon
})
.on("click.jstree", ".jstree-ocl", $.proxy(function (e) {
this.toggle_node(e.target);
}, this))
.on("dblclick.jstree", ".jstree-anchor", $.proxy(function (e) {
if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
if(this.settings.core.dblclick_toggle) {
this.toggle_node(e.target);
}
}, this))
.on("click.jstree", ".jstree-anchor", $.proxy(function (e) {
e.preventDefault();
if(e.currentTarget !== document.activeElement) { $(e.currentTarget).focus(); }
this.activate_node(e.currentTarget, e);
}, this))
.on('keydown.jstree', '.jstree-anchor', $.proxy(function (e) {
if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
if(e.which !== 32 && e.which !== 13 && (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey)) { return true; }
var o = null;
if(this._data.core.rtl) {
if(e.which === 37) { e.which = 39; }
else if(e.which === 39) { e.which = 37; }
}
switch(e.which) {
case 32: // aria defines space only with Ctrl
if(e.ctrlKey) {
e.type = "click";
$(e.currentTarget).trigger(e);
}
break;
case 13: // enter
e.type = "click";
$(e.currentTarget).trigger(e);
break;
case 37: // left
e.preventDefault();
if(this.is_open(e.currentTarget)) {
this.close_node(e.currentTarget);
}
else {
o = this.get_parent(e.currentTarget);
if(o && o.id !== $.jstree.root) { this.get_node(o, true).children('.jstree-anchor').focus(); }
}
break;
case 38: // up
e.preventDefault();
o = this.get_prev_dom(e.currentTarget);
if(o && o.length) { o.children('.jstree-anchor').focus(); }
break;
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(); });
}
else if (this.is_open(e.currentTarget)) {
o = this.get_node(e.currentTarget, true).children('.jstree-children')[0];
if(o) { $(this._firstChild(o)).children('.jstree-anchor').focus(); }
}
break;
case 40: // down
e.preventDefault();
o = this.get_next_dom(e.currentTarget);
if(o && o.length) { o.children('.jstree-anchor').focus(); }
break;
case 106: // aria defines * on numpad as open_all - not very common
this.open_all();
break;
case 36: // home
e.preventDefault();
o = this._firstChild(this.get_container_ul()[0]);
if(o) { $(o).children('.jstree-anchor').filter(':visible').focus(); }
break;
case 35: // end
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:
e.preventDefault();
o = this.get_node(e.currentTarget);
if(o && o.id && o.id !== $.jstree.root) {
o = this.is_selected(o) ? this.get_selected() : o;
this.delete_node(o);
}
break;
*/
}
}, this))
.on("load_node.jstree", $.proxy(function (e, data) {
if(data.status) {
if(data.node.id === $.jstree.root && !this._data.core.loaded) {
this._data.core.loaded = true;
if(this._firstChild(this.get_container_ul()[0])) {
this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id);
}
/**
* triggered after the root node is loaded for the first time
* @event
* @name loaded.jstree
*/
this.trigger("loaded");
}
if(!this._data.core.ready) {
setTimeout($.proxy(function() {
if(this.element && !this.get_container_ul().find('.jstree-loading').length) {
this._data.core.ready = true;
if(this._data.core.selected.length) {
if(this.settings.core.expand_selected_onload) {
var tmp = [], i, j;
for(i = 0, j = this._data.core.selected.length; i < j; i++) {
tmp = tmp.concat(this._model.data[this._data.core.selected[i]].parents);
}
tmp = $.vakata.array_unique(tmp);
for(i = 0, j = tmp.length; i < j; i++) {
this.open_node(tmp[i], false, 0);
}
}
this.trigger('changed', { 'action' : 'ready', 'selected' : this._data.core.selected });
}
/**
* triggered after all nodes are finished loading
* @event
* @name ready.jstree
*/
this.trigger("ready");
}
}, this), 0);
}
}
}, this))
// quick searching when the tree is focused
.on('keypress.jstree', $.proxy(function (e) {
if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
if(tout) { clearTimeout(tout); }
tout = setTimeout(function () {
word = '';
}, 500);
var chr = String.fromCharCode(e.which).toLowerCase(),
col = this.element.find('.jstree-anchor').filter(':visible'),
ind = col.index(document.activeElement) || 0,
end = false;
word += chr;
// match for whole word from current node down (including the current node)
if(word.length > 1) {
col.slice(ind).each($.proxy(function (i, v) {
if($(v).text().toLowerCase().indexOf(word) === 0) {
$(v).focus();
end = true;
return false;
}
}, this));
if(end) { return; }
// match for whole word from the beginning of the tree
col.slice(0, ind).each($.proxy(function (i, v) {
if($(v).text().toLowerCase().indexOf(word) === 0) {
$(v).focus();
end = true;
return false;
}
}, this));
if(end) { return; }
}
// list nodes that start with that letter (only if word consists of a single char)
if(new RegExp('^' + chr.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '+$').test(word)) {
// search for the next node starting with that letter
col.slice(ind + 1).each($.proxy(function (i, v) {
if($(v).text().toLowerCase().charAt(0) === chr) {
$(v).focus();
end = true;
return false;
}
}, this));
if(end) { return; }
// search from the beginning
col.slice(0, ind + 1).each($.proxy(function (i, v) {
if($(v).text().toLowerCase().charAt(0) === chr) {
$(v).focus();
end = true;
return false;
}
}, this));
if(end) { return; }
}
}, this))
// THEME RELATED
.on("init.jstree", $.proxy(function () {
var s = this.settings.core.themes;
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))
.on("loading.jstree", $.proxy(function () {
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;
$(e.currentTarget).filter('.jstree-hovered').mouseleave();
this.element.attr('tabindex', '0');
}, this))
.on('focus.jstree', '.jstree-anchor', $.proxy(function (e) {
var tmp = this.get_node(e.currentTarget);
if(tmp && tmp.id) {
this._data.core.focused = tmp.id;
}
this.element.find('.jstree-hovered').not(e.currentTarget).mouseleave();
$(e.currentTarget).mouseenter();
this.element.attr('tabindex', '-1');
}, this))
.on('focus.jstree', $.proxy(function () {
if(+(new Date()) - was_click > 500 && !this._data.core.focused) {
was_click = 0;
var act = this.get_node(this.element.attr('aria-activedescendant'), true);
if(act) {
act.find('> .jstree-anchor').focus();
}
}
}, this))
.on('mouseenter.jstree', '.jstree-anchor', $.proxy(function (e) {
this.hover_node(e.currentTarget);
}, this))
.on('mouseleave.jstree', '.jstree-anchor', $.proxy(function (e) {
this.dehover_node(e.currentTarget);
}, this));
},
/**
* part of the destroying of an instance. Used internally.
* @private
* @name unbind()
*/
unbind : function () {
this.element.off('.jstree');
$(document).off('.jstree-' + this._id);
},
/**
* trigger an event. Used internally.
* @private
* @name trigger(ev [, data])
* @param {String} ev the name of the event to trigger
* @param {Object} data additional data to pass with the event
*/
trigger : function (ev, data) {
if(!data) {
data = {};
}
data.instance = this;
this.element.triggerHandler(ev.replace('.jstree','') + '.jstree', data);
},
/**
* returns the jQuery extended instance container
* @name get_container()
* @return {jQuery}
*/
get_container : function () {
return this.element;
},
/**
* returns the jQuery extended main UL node inside the instance container. Used internally.
* @private
* @name get_container_ul()
* @return {jQuery}
*/
get_container_ul : function () {
return this.element.children(".jstree-children").first();
},
/**
* gets string replacements (localization). Used internally.
* @private
* @name get_string(key)
* @param {String} key
* @return {String}
*/
get_string : function (key) {
var a = this.settings.core.strings;
if($.isFunction(a)) { return a.call(this, key); }
if(a && a[key]) { return a[key]; }
return key;
},
/**
* gets the first child of a DOM node. Used internally.
* @private
* @name _firstChild(dom)
* @param {DOMElement} dom
* @return {DOMElement}
*/
_firstChild : function (dom) {
dom = dom ? dom.firstChild : null;
while(dom !== null && dom.nodeType !== 1) {
dom = dom.nextSibling;
}
return dom;
},
/**
* gets the next sibling of a DOM node. Used internally.
* @private
* @name _nextSibling(dom)
* @param {DOMElement} dom
* @return {DOMElement}
*/
_nextSibling : function (dom) {
dom = dom ? dom.nextSibling : null;
while(dom !== null && dom.nodeType !== 1) {
dom = dom.nextSibling;
}
return dom;
},
/**
* gets the previous sibling of a DOM node. Used internally.
* @private
* @name _previousSibling(dom)
* @param {DOMElement} dom
* @return {DOMElement}
*/
_previousSibling : function (dom) {
dom = dom ? dom.previousSibling : null;
while(dom !== null && dom.nodeType !== 1) {
dom = dom.previousSibling;
}
return dom;
},
/**
* get the JSON representation of a node (or the actual jQuery extended DOM node) by using any input (child DOM element, ID string, selector, etc)
* @name get_node(obj [, as_dom])
* @param {mixed} obj
* @param {Boolean} as_dom
* @return {Object|jQuery}
*/
get_node : function (obj, as_dom) {
if(obj && obj.id) {
obj = obj.id;
}
var dom;
try {
if(this._model.data[obj]) {
obj = this._model.data[obj];
}
else if(typeof obj === "string" && this._model.data[obj.replace(/^#/, '')]) {
obj = this._model.data[obj.replace(/^#/, '')];
}
else if(typeof obj === "string" && (dom = $('#' + obj.replace($.jstree.idregex,'\\$&'), this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) {
obj = this._model.data[dom.closest('.jstree-node').attr('id')];
}
else if((dom = $(obj, this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) {
obj = this._model.data[dom.closest('.jstree-node').attr('id')];
}
else if((dom = $(obj, this.element)).length && dom.hasClass('jstree')) {
obj = this._model.data[$.jstree.root];
}
else {
return false;
}
if(as_dom) {
obj = obj.id === $.jstree.root ? this.element : $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
}
return obj;
} catch (ex) { return false; }
},
/**
* get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array)
* @name get_path(obj [, glue, ids])
* @param {mixed} obj the node
* @param {String} glue if you want the path as a string - pass the glue here (for example '/'), if a falsy value is supplied here, an array is returned
* @param {Boolean} ids if set to true build the path using ID, otherwise node text is used
* @return {mixed}
*/
get_path : function (obj, glue, ids) {
obj = obj.parents ? obj : this.get_node(obj);
if(!obj || obj.id === $.jstree.root || !obj.parents) {
return false;
}
var i, j, p = [];
p.push(ids ? obj.id : obj.text);
for(i = 0, j = obj.parents.length; i < j; i++) {
p.push(ids ? obj.parents[i] : this.get_text(obj.parents[i]));
}
p = p.reverse().slice(1);
return glue ? p.join(glue) : p;
},
/**
* get the next visible node that is below the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
* @name get_next_dom(obj [, strict])
* @param {mixed} obj
* @param {Boolean} strict
* @return {jQuery}
*/
get_next_dom : function (obj, strict) {
var tmp;
obj = this.get_node(obj, true);
if(obj[0] === this.element[0]) {
tmp = this._firstChild(this.get_container_ul()[0]);
while (tmp && tmp.offsetHeight === 0) {
tmp = this._nextSibling(tmp);
}
return tmp ? $(tmp) : false;
}
if(!obj || !obj.length) {
return false;
}
if(strict) {
tmp = obj[0];
do {
tmp = this._nextSibling(tmp);
} while (tmp && tmp.offsetHeight === 0);
return tmp ? $(tmp) : false;
}
if(obj.hasClass("jstree-open")) {
tmp = this._firstChild(obj.children('.jstree-children')[0]);
while (tmp && tmp.offsetHeight === 0) {
tmp = this._nextSibling(tmp);
}
if(tmp !== null) {
return $(tmp);
}
}
tmp = obj[0];
do {
tmp = this._nextSibling(tmp);
} while (tmp && tmp.offsetHeight === 0);
if(tmp !== null) {
return $(tmp);
}
return obj.parentsUntil(".jstree",".jstree-node").nextAll(".jstree-node:visible").first();
},
/**
* get the previous visible node that is above the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
* @name get_prev_dom(obj [, strict])
* @param {mixed} obj
* @param {Boolean} strict
* @return {jQuery}
*/
get_prev_dom : function (obj, strict) {
var tmp;
obj = this.get_node(obj, true);
if(obj[0] === this.element[0]) {
tmp = this.get_container_ul()[0].lastChild;
while (tmp && tmp.offsetHeight === 0) {
tmp = this._previousSibling(tmp);
}
return tmp ? $(tmp) : false;
}
if(!obj || !obj.length) {
return false;
}
if(strict) {
tmp = obj[0];
do {
tmp = this._previousSibling(tmp);
} while (tmp && tmp.offsetHeight === 0);
return tmp ? $(tmp) : false;
}
tmp = obj[0];
do {
tmp = this._previousSibling(tmp);
} while (tmp && tmp.offsetHeight === 0);
if(tmp !== null) {
obj = $(tmp);
while(obj.hasClass("jstree-open")) {
obj = obj.children(".jstree-children").first().children(".jstree-node:visible:last");
}
return obj;
}
tmp = obj[0].parentNode.parentNode;
return tmp && tmp.className && tmp.className.indexOf('jstree-node') !== -1 ? $(tmp) : false;
},
/**
* get the parent ID of a node
* @name get_parent(obj)
* @param {mixed} obj
* @return {String}
*/
get_parent : function (obj) {
obj = this.get_node(obj);
if(!obj || obj.id === $.jstree.root) {
return false;
}
return obj.parent;
},
/**
* get a jQuery collection of all the children of a node (node must be rendered)
* @name get_children_dom(obj)
* @param {mixed} obj
* @return {jQuery}
*/
get_children_dom : function (obj) {
obj = this.get_node(obj, true);
if(obj[0] === this.element[0]) {
return this.get_container_ul().children(".jstree-node");
}
if(!obj || !obj.length) {
return false;
}
return obj.children(".jstree-children").children(".jstree-node");
},
/**
* checks if a node has children
* @name is_parent(obj)
* @param {mixed} obj
* @return {Boolean}
*/
is_parent : function (obj) {
obj = this.get_node(obj);
return obj && (obj.state.loaded === false || obj.children.length > 0);
},
/**
* checks if a node is loaded (its children are available)
* @name is_loaded(obj)
* @param {mixed} obj
* @return {Boolean}
*/
is_loaded : function (obj) {
obj = this.get_node(obj);
return obj && obj.state.loaded;
},
/**
* check if a node is currently loading (fetching children)
* @name is_loading(obj)
* @param {mixed} obj
* @return {Boolean}
*/
is_loading : function (obj) {
obj = this.get_node(obj);
return obj && obj.state && obj.state.loading;
},
/**
* check if a node is opened
* @name is_open(obj)
* @param {mixed} obj
* @return {Boolean}
*/
is_open : function (obj) {
obj = this.get_node(obj);
return obj && obj.state.opened;
},
/**
* check if a node is in a closed state
* @name is_closed(obj)
* @param {mixed} obj
* @return {Boolean}
*/
is_closed : function (obj) {
obj = this.get_node(obj);
return obj && this.is_parent(obj) && !obj.state.opened;
},
/**
* check if a node has no children
* @name is_leaf(obj)
* @param {mixed} obj
* @return {Boolean}
*/
is_leaf : function (obj) {
return !this.is_parent(obj);
},
/**
* loads a node (fetches its children using the `core.data` setting). Multiple nodes can be passed to by using an array.
* @name load_node(obj [, callback])
* @param {mixed} obj
* @param {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives two arguments - the node and a boolean status
* @return {Boolean}
* @trigger load_node.jstree
*/
load_node : function (obj, callback) {
var k, l, i, j, c;
if($.isArray(obj)) {
this._load_nodes(obj.slice(), callback);
return true;
}
obj = this.get_node(obj);
if(!obj) {
if(callback) { callback.call(this, obj, false); }
return false;
}
// 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++) {
if(this._model.data[obj.children_d[k]].state.selected) {
c = true;
}
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) {
this.trigger('changed', { 'action' : 'load_node', 'node' : obj, 'selected' : this._data.core.selected });
}
}
obj.state.failed = false;
obj.state.loading = true;
this.get_node(obj, true).addClass("jstree-loading").attr('aria-busy',true);
this._load_node(obj, $.proxy(function (status) {
obj = this._model.data[obj.id];
obj.state.loading = false;
obj.state.loaded = status;
obj.state.failed = !obj.state.loaded;
var dom = this.get_node(obj, true), i = 0, j = 0, m = this._model.data, has_children = false;
for(i = 0, j = obj.children.length; i < j; i++) {
if(m[obj.children[i]] && !m[obj.children[i]].state.hidden) {
has_children = true;
break;
}
}
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);
/**
* triggered after a node is loaded
* @event
* @name load_node.jstree
* @param {Object} node the node that was loading
* @param {Boolean} status was the node loaded successfully
*/
this.trigger('load_node', { "node" : obj, "status" : status });
if(callback) {
callback.call(this, obj, status);
}
}, this));
return true;
},
/**
* load an array of nodes (will also load unavailable nodes as soon as the appear in the structure). Used internally.
* @private
* @name _load_nodes(nodes [, callback])
* @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, 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 && force_reload) )) {
if(!this.is_loading(nodes[i])) {
this.load_node(nodes[i], c);
}
r = false;
}
}
if(r) {
for(i = 0, j = nodes.length; i < j; i++) {
if(m[nodes[i]] && m[nodes[i]].state.loaded) {
tmp.push(nodes[i]);
}
}
if(callback && !callback.done) {
callback.call(this, tmp);
callback.done = true;
}
}
},
/**
* loads all unloaded nodes
* @name load_all([obj, callback])
* @param {mixed} obj the node to load recursively, omit to load all nodes in the tree
* @param {function} callback a function to be executed once loading all the nodes is complete,
* @trigger load_all.jstree
*/
load_all : function (obj, callback) {
if(!obj) { obj = $.jstree.root; }
obj = this.get_node(obj);
if(!obj) { return false; }
var to_load = [],
m = this._model.data,
c = m[obj.id].children_d,
i, j;
if(obj.state && !obj.state.loaded) {
to_load.push(obj.id);
}
for(i = 0, j = c.length; i < j; i++) {
if(m[c[i]] && m[c[i]].state && !m[c[i]].state.loaded) {
to_load.push(c[i]);
}
}
if(to_load.length) {
this._load_nodes(to_load, function () {
this.load_all(obj, callback);
});
}
else {
/**
* triggered after a load_all call completes
* @event
* @name load_all.jstree
* @param {Object} node the recursively loaded node
*/
if(callback) { callback.call(this, obj); }
this.trigger('load_all', { "node" : obj });
}
},
/**
* handles the actual loading of a node. Used only internally.
* @private
* @name _load_node(obj [, callback])
* @param {mixed} obj
* @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 - a boolean status
* @return {Boolean}
*/
_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) {
return this._append_html_data(obj, this._data.core.original_container_html.clone(true), function (status) {
callback.call(this, status);
});
}
else {
return callback.call(this, false);
}
// return callback.call(this, obj.id === $.jstree.root ? this._append_html_data(obj, this._data.core.original_container_html.clone(true)) : false);
}
if($.isFunction(s)) {
return s.call(this, obj, $.proxy(function (d) {
if(d === false) {
callback.call(this, false);
}
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));
}
if(typeof s === 'object') {
if(s.url) {
s = $.extend(true, {}, s);
if($.isFunction(s.url)) {
s.url = s.url.call(this, obj);
}
if($.isFunction(s.data)) {
s.data = s.data.call(this, obj);
}
return $.ajax(s)
.done($.proxy(function (d,t,x) {
var type = x.getResponseHeader('Content-Type');
if((type && type.indexOf('json') !== -1) || typeof d === "object") {
return this._append_json_data(obj, d, function (status) { callback.call(this, status); });
//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(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 }) };
this.settings.core.error.call(this, this._data.core.last_error);
return callback.call(this, false);
}, this))
.fail($.proxy(function (f) {
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));
}
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);
});
}
else {
this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_05', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) };
this.settings.core.error.call(this, this._data.core.last_error);
return callback.call(this, false);
}
//return callback.call(this, (obj.id === $.jstree.root ? this._append_json_data(obj, t) : false) );
}
if(typeof s === 'string') {
if(obj.id === $.jstree.root) {
return this._append_html_data(obj, $($.parseHTML(s)).filter(notTextOrCommentNode), function (status) {
callback.call(this, status);
});
}
else {
this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_06', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) };
this.settings.core.error.call(this, this._data.core.last_error);
return callback.call(this, false);
}
//return callback.call(this, (obj.id === $.jstree.root ? this._append_html_data(obj, $(s)) : false) );
}
return callback.call(this, false);
},
/**
* adds a node to the list of nodes to redraw. Used only internally.
* @private
* @name _node_changed(obj [, callback])
* @param {mixed} obj
*/
_node_changed : function (obj) {
obj = this.get_node(obj);
if(obj) {
this._model.changed.push(obj.id);
}
},
/**
* appends HTML content to the tree. Used internally.
* @private
* @name _append_html_data(obj, data)
* @param {mixed} obj the node to append to
* @param {String} data the HTML string to parse and append
* @trigger model.jstree, changed.jstree
*/
_append_html_data : function (dom, data, cb) {
dom = this.get_node(dom);
dom.children = [];
dom.children_d = [];
var dat = data.is('ul') ? data.children() : data,
par = dom.id,
chd = [],
dpc = [],
m = this._model.data,
p = m[par],
s = this._data.core.selected.length,
tmp, i, j;
dat.each($.proxy(function (i, v) {
tmp = this._parse_model_from_html($(v), par, p.parents.concat());
if(tmp) {
chd.push(tmp);
dpc.push(tmp);
if(m[tmp].children_d.length) {
dpc = dpc.concat(m[tmp].children_d);
}
}
}, this));
p.children = chd;
p.children_d = dpc;
for(i = 0, j = p.parents.length; i < j; i++) {
m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
}
/**
* triggered when new data is inserted to the tree model
* @event
* @name model.j