ng-js-tree
Version:
Angular Directive for the famous JsTree
298 lines (275 loc) • 12.3 kB
JavaScript
(function ($, undefined) {
'use strict';
$.jstree.defaults.alltrigger = null;
$.jstree.plugins.alltrigger = function (options, parent) {
this.init = function (el, opts) {
if (options) {
this.trigger = function (ev, data) {
parent.trigger.call(this, ev, data);
options(ev.replace('.jstree', '') + '.jstree', data);
};
var contextTrigger = $.vakata.context._trigger;
$.vakata.context._trigger = function (event_name) {
contextTrigger(event_name);
options('context_' + event_name + '.vakata');
};
var dndTrigger = $.vakata.dnd._trigger;
$.vakata.dnd._trigger = function (event_name, e, data) {
dndTrigger(event_name, e, data);
options('dnd_' + event_name + '.vakata', e, data);
};
}
parent.init.call(this, el, opts);
};
};
})(jQuery);
(function (angular) {
'use strict';
//// JavaScript Code ////
function jsTreeCtrl() {
/*jshint validthis:true */
var nodeSerialId = 1;
var vm = this;
vm.nodesFingerprint = function (e) {
if (!e.__uiNodeId) {
e.__uiNodeId = nodeSerialId++;
}
return '' + e.__uiNodeId + (e.id || '') + (e.text || '') + (e.type || '');
};
vm.changeWatcher = function (arraySource, tokenFn) {
var self;
var getTokens = function () {
var result = [], token, el;
if (arraySource) {
var array = angular.isFunction(arraySource) ? arraySource() : arraySource;
for (var i = 0, n = array.length; i < n; i++) {
el = array[i];
token = tokenFn(el);
map[token] = el;
result.push(token);
}
}
return result;
};
// returns elements in that are in a but not in b
// subtractAsSets([4, 5, 6], [4, 5, 7]) => [6]
var subtractAsSets = function (a, b) {
var result = [], inB = {}, i, n;
for (i = 0, n = b.length; i < n; i++) {
inB[b[i]] = true;
}
for (i = 0, n = a.length; i < n; i++) {
if (!inB[a[i]]) {
result.push(a[i]);
}
}
return result;
};
// Map objects to tokens and vice-versa
var map = {};
var applyChanges = function (newTokens, oldTokens) {
var i, n, el, token;
var replacedTokens = {};
var removedTokens = subtractAsSets(oldTokens, newTokens);
for (i = 0, n = removedTokens.length; i < n; i++) {
var removedToken = removedTokens[i];
el = map[removedToken];
delete map[removedToken];
var newToken = tokenFn(el);
// if the element wasn't removed but simply got a new token, its old token will be different from the current one
if (newToken === removedToken) {
self.onRemoved(el);
} else {
replacedTokens[newToken] = removedToken;
self.onChanged(el);
}
}
var addedTokens = subtractAsSets(newTokens, oldTokens);
for (i = 0, n = addedTokens.length; i < n; i++) {
token = addedTokens[i];
el = map[token];
if (!replacedTokens[token]) {
self.onAdded(el);
}
}
};
self = {
subscribe: function (scope, onChanged) {
scope.$watch(getTokens, function (newTokens, oldTokens) {
if (!onChanged || onChanged(newTokens, oldTokens) !== false) {
applyChanges(newTokens, oldTokens);
}
}, true);
},
onAdded: angular.noop,
onChanged: angular.noop,
onRemoved: angular.noop
};
return self;
};
}
function jsTreeDirective($timeout) {
return {
restrict: 'A',
scope: {
treeData: '=ngModel',
treeEventsObj: '=?treeEventsObj',
shouldApply: '&'
},
controller: 'jsTreeCtrl',
link: function link(scope, elm, attrs, controller) {
var config = null,
nodesWatcher = controller.changeWatcher(scope.treeData, controller.nodesFingerprint),
events = [];
var blocked = false;
function treeEventHandler(s, cb) {
return function () {
var args = arguments;
var fn = s.$parent.$eval(cb);
if (!s.$root.$$phase) {
s.$parent.$apply(function () {
fn.apply(s.$parent, args);
});
} else {
fn.apply(s.$parent, args);
}
};
}
function manageEvents(s, e, a) {
if (a.treeEvents) {
var evMap = a.treeEvents.split(';');
for (var i = 0; i < evMap.length; i++) {
if (evMap[i].length > 0) {
var name = evMap[i].split(':')[0];
var cb = evMap[i].split(':')[1];
if (name.indexOf('.') === -1) {
name += '.jstree';
}
events.push(name);
if (name.indexOf('.vakata') === -1) {
s.tree.on(name, treeEventHandler(s, cb));
} else {
$(document).on(name, treeEventHandler(s, cb));
}
}
}
}
if (angular.isObject(s.treeEventsObj)) {
angular.forEach(s.treeEventsObj, function (cb, name) {
if (name.indexOf('.') === -1) {
name += '.jstree';
}
events.push(name);
if (name.indexOf('.vakata') === -1) {
s.tree.on(name, function () {
var args = arguments;
if (!s.$root.$$phase) {
s.$parent.$apply(function () {
cb.apply(s.$parent, args);
});
} else {
cb.apply(s.$parent, args);
}
});
} else {
$(document).on(name, function () {
var args = arguments;
if (!s.$root.$$phase) {
s.$parent.$apply(function () { cb.apply(s.$parent, args); });
} else {
cb.apply(s.$parent, args);
}
});
}
});
}
}
function getOptions() {
var jsTreeSettings = attrs.jsTree ? scope.$parent.$eval(attrs.jsTree) : {};
config = {};
angular.copy(jsTreeSettings, config);
var result = JSON.stringify(config);
if (config.core) {
config.core.data = scope.treeData;
}
else {
config.core = { data: scope.treeData };
}
if (config.plugins) {
config.plugins.push('alltrigger');
} else {
config.plugins = ['alltrigger'];
}
config.alltrigger = function (name) {
if (!scope.$root.$$phase && events.indexOf(name) === -1) {
scope.$apply();
}
};
return result;
}
scope.destroy = function () {
events = [];
if (attrs.tree) {
if (attrs.tree.indexOf('.') !== -1) {
var split = attrs.tree.split('.');
var tree = split.pop();
var context = scope.$parent;
for (var i = 0; i < split.length; i++) {
context = context[split[i]];
}
scope.tree = context[tree] = elm;
}
else {
scope.tree = scope.$parent[attrs.tree] = elm;
}
} else {
scope.tree = elm;
}
scope.tree.jstree('destroy');
};
scope.init = function () {
scope.tree.jstree(config);
$timeout(function () {
manageEvents(scope, elm, attrs);
});
};
nodesWatcher.onChanged = function (node) {
if (angular.isDefined(scope.tree.jstree(true).set_type)) {
scope.tree.jstree(true).set_type(node.id, node.type);
}
scope.tree.jstree(true).rename_node(node.id, node.text);
};
nodesWatcher.onAdded = function (node) {
$timeout(function () {
while (blocked) { }
blocked = true;
var parent = scope.tree.jstree(true).get_node(node.parent);
var res = scope.tree.jstree(true).create_node(parent, node, 'inside', function () {
blocked = false;
});
if (!res) {
blocked = false;
}
});
};
nodesWatcher.onRemoved = function (node) {
scope.tree.jstree(true).delete_node(node.id);
};
nodesWatcher.subscribe(scope, function () {
if (!scope.shouldApply) {
return true;
}
return scope.shouldApply();
});
scope.$watch(getOptions, function () {
scope.destroy();
scope.init();
});
}
};
}
//// Angular Code ////
var mi = angular.module('ngJsTree', []);
mi.controller('jsTreeCtrl', jsTreeCtrl);
mi.directive('jsTree', ['$timeout', jsTreeDirective]);
})(angular);