857 lines
36 KiB
JavaScript
857 lines
36 KiB
JavaScript
|
/**
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* as published by the Free Software Foundation; under version 2
|
||
|
* of the License (non-upgradable).
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; if not, write to the Free Software
|
||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
*
|
||
|
* Copyright (c) 2014-2017 Open Assessment Technologies SA;
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Tree provider : jstree
|
||
|
*
|
||
|
* @author Bertrand Chevrier <bertrand@taotesting.com>
|
||
|
*/
|
||
|
define([
|
||
|
'jquery',
|
||
|
'lodash',
|
||
|
'i18n',
|
||
|
'context',
|
||
|
'core/store',
|
||
|
'core/promise',
|
||
|
'util/url',
|
||
|
'layout/generisRouter',
|
||
|
'layout/actions',
|
||
|
'layout/section',
|
||
|
'layout/permissions',
|
||
|
'ui/feedback',
|
||
|
'uri',
|
||
|
'jquery.tree'
|
||
|
], function($, _, __, context, store, Promise, urlUtil, generisRouter, actionManager, sectionManager, permissionsManager, feedback, uri){
|
||
|
'use strict';
|
||
|
|
||
|
var pageRange = 30;
|
||
|
|
||
|
return {
|
||
|
|
||
|
/**
|
||
|
* Tree provider name
|
||
|
*/
|
||
|
name : 'jstree',
|
||
|
|
||
|
/**
|
||
|
* The tree factory helps you to instantiate a new tree from the TAO ontology
|
||
|
* @exports layout/tree/provider/jstree
|
||
|
*
|
||
|
* @param {jQueryElement} $container - that will contain the tree
|
||
|
* @param {Object} [options] - additional configuration options
|
||
|
* @param {String} [options.url] - the endpoint to load data
|
||
|
* @param {String} [options.rootClassUri] - the URI of the root class
|
||
|
* @param {Object} [options.serverParameters] - add parameters to send to the endpoint (defaults are hideInstance, filter, offset and limit)
|
||
|
* @param {Object} [options.actions] - which actions to perform from the tree
|
||
|
* @param {String} [options.actions.moveInstance] - the id of the action bound (using actionManager.register) on move
|
||
|
* @param {String} [options.actions.selectInstance] - the id of the action bound (using actionManager.register) on item selection
|
||
|
* @param {String} [options.actions.selectClass] - the id of the action bound (using actionManager.register) on class selection
|
||
|
* @param {String} [options.actions.deleteInstance] - the id of the action bound (using actionManager.register) on delete
|
||
|
* @param {String} [options.selectNode] - the URI of the node to be selected by default, the node must be loaded.
|
||
|
* @param {String} [options.loadNode] - the URI of a node to be loaded from the server side and selected.
|
||
|
|
||
|
* @returns {Promise} resolves when the tree is ready
|
||
|
*/
|
||
|
init : function init($container, options){
|
||
|
var lastOpened;
|
||
|
var lastSelected;
|
||
|
|
||
|
var moreNode = {
|
||
|
data : __('More'),
|
||
|
type : 'more',
|
||
|
attributes : {
|
||
|
class : 'more'
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//these are the parameters added to the server call to load data
|
||
|
var serverParams = _.defaults(options.serverParameters || {}, {
|
||
|
extension : context.shownExtension,
|
||
|
perspective : context.shownStructure,
|
||
|
section : context.section,
|
||
|
// eslint-disable-next-line no-undefined
|
||
|
classUri : options.rootClassUri ? options.rootClassUri : undefined,
|
||
|
hideInstances : options.hideInstances || 0,
|
||
|
filter : '*',
|
||
|
offset : 0,
|
||
|
limit : pageRange
|
||
|
});
|
||
|
|
||
|
//list of events callbacks to be bound to the tree
|
||
|
var events = {
|
||
|
|
||
|
/**
|
||
|
* Refresh the tree
|
||
|
*
|
||
|
* @event layout/tree#refresh.taotree
|
||
|
* @param {Object} [data] - some data to bind to the tree
|
||
|
* @param {String} [data.filter] - reload the tree in filtering mode
|
||
|
* @param {String} [data.selectNode] - reload the tree and select the given node (by URI) if it is already loaded.
|
||
|
* @param {String} [data.loadNode] - the URI of a node to display in filtering mode (it will load only this node)
|
||
|
*/
|
||
|
refresh : function refresh(data){
|
||
|
var treeState, node;
|
||
|
var tree = $.tree.reference($container);
|
||
|
if(tree){
|
||
|
|
||
|
// try to select the node within the current loaded tree
|
||
|
if (data && data.loadNode) {
|
||
|
node = $container.find('[data-uri="' + data.loadNode + '"]');
|
||
|
if (node.length) {
|
||
|
tree.select_branch(node);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//update the state with data to be used later (ie. filter value, etc.)
|
||
|
treeState = _.merge($container.data('tree-state') || {}, data);
|
||
|
treeState = _.omit(treeState, 'selectNode');
|
||
|
|
||
|
if (data && data.loadNode) {
|
||
|
tree.deselect_branch(tree.selected);
|
||
|
tree.settings.selected = false;
|
||
|
treeState.selectNode = data.loadNode;
|
||
|
} else if (data && data.selectNode) { //node will be selected in `onload` function
|
||
|
tree.deselect_branch(tree.selected);
|
||
|
tree.settings.selected = false;
|
||
|
}
|
||
|
|
||
|
setTreeState(treeState);
|
||
|
tree.refresh();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Rollback the tree.
|
||
|
* The rollback state must have been set in the state previously, otherwise runs a refresh.
|
||
|
*
|
||
|
* @event layout/tree#rollback.taotree
|
||
|
*/
|
||
|
rollback : function rollback(){
|
||
|
var treeState;
|
||
|
var tree = $.tree.reference($container);
|
||
|
if(tree){
|
||
|
|
||
|
treeState = $container.data('tree-state');
|
||
|
if(treeState.rollback){
|
||
|
$.tree.rollback(treeState.rollback);
|
||
|
|
||
|
//remove the rollback infos.
|
||
|
setTreeState(_.omit(treeState, 'rollback'));
|
||
|
} else {
|
||
|
//trigger a full refresh
|
||
|
$container.trigger('refresh.taotree');
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Add a node to the tree.
|
||
|
*
|
||
|
* @event layout/tree#addnode.taotree
|
||
|
* @param {Object} data - the data about the node to add
|
||
|
* @param {String} data.parent - the id/uri of the node that will contain the new node
|
||
|
* @param {String} data.id - the id of the new node
|
||
|
* @param {String} data.cssClass - the css class for the new node (node-instance or node-class at least).
|
||
|
*/
|
||
|
addnode : function addnode(data) {
|
||
|
var tree = $.tree.reference($container);
|
||
|
var parentNode = tree.get_node($('#' + uri.encode(data.parent), $container).get(0));
|
||
|
|
||
|
var params = _.clone(serverParams);
|
||
|
|
||
|
params.classUri = data.parent;
|
||
|
if (data.cssClass === 'node-class') {
|
||
|
params.hideInstances = 1; //load only class nodes
|
||
|
} else {
|
||
|
params.loadNode = data.uri; //load particular instance
|
||
|
}
|
||
|
//load tree branch with new node to get new node permissions
|
||
|
$.ajax(tree.settings.data.opts.url, {
|
||
|
type : tree.settings.data.opts.method,
|
||
|
dataType : tree.settings.data.type,
|
||
|
async : tree.settings.data.async,
|
||
|
data : params,
|
||
|
success : function (response) {
|
||
|
var treeData = getTreeData(response);
|
||
|
var items = treeData.children || treeData;
|
||
|
var node = _.filter(items, function (child) {
|
||
|
return child.attributes && child.attributes['data-uri'] === data.uri;
|
||
|
});
|
||
|
if (node.length) {
|
||
|
tree.select_branch(
|
||
|
tree.create(node[0], parentNode)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Remove a node from the tree.
|
||
|
*
|
||
|
* @event layout/tree#removenode.taotree
|
||
|
* @param {Object} data - the data about the node to remove
|
||
|
* @param {String} data.id - the id of the node to remove
|
||
|
*/
|
||
|
removenode : function removenode(data){
|
||
|
var tree = $.tree.reference($container);
|
||
|
var node = tree.get_node($('#' + data.id, $container).get(0));
|
||
|
tree.remove(node);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Select a node
|
||
|
*
|
||
|
* @event layout/tree#selectnode.taotree
|
||
|
* @param {Object} data - the data about the node to select
|
||
|
* @param {String} data.id - the id of the node to select
|
||
|
*/
|
||
|
selectnode : function selectnode(data){
|
||
|
var tree = $.tree.reference($container);
|
||
|
var node = tree.get_node($('#' + data.id, $container).get(0));
|
||
|
$('li a', $container).removeClass('clicked');
|
||
|
tree.select_branch(node);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Opens a tree branch
|
||
|
*
|
||
|
* @event layout/tree#openbranch.taotree
|
||
|
* @param {Object} data - the data about the node to remove
|
||
|
* @param {String} data.id - the id of the node to remove
|
||
|
*/
|
||
|
openbranch : function openbranch(data){
|
||
|
var tree = $.tree.reference($container);
|
||
|
var node = tree.get_node($('#' + data.id, $container).get(0));
|
||
|
$('li a', $container).removeClass('clicked');
|
||
|
tree.open_branch(node);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Options given to the jsTree plugin
|
||
|
*/
|
||
|
var treeOptions = {
|
||
|
|
||
|
//data call
|
||
|
data: {
|
||
|
type: "json",
|
||
|
async : true,
|
||
|
opts: {
|
||
|
method : "GET",
|
||
|
url: options.url
|
||
|
}
|
||
|
},
|
||
|
|
||
|
//theme
|
||
|
ui: {
|
||
|
"theme_name" : "css",
|
||
|
"theme_path" : context.taobase_www + 'js/lib/jsTree/themes/css/style.css'
|
||
|
},
|
||
|
|
||
|
//nodes types
|
||
|
types: {
|
||
|
"default" : {
|
||
|
renameable : false,
|
||
|
deletable : true,
|
||
|
creatable : true,
|
||
|
draggable : function($node) {
|
||
|
return $node.hasClass('node-instance') && !$node.hasClass('node-undraggable') && options.actions && options.actions.moveInstance;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
//lifecycle callbacks
|
||
|
callback: {
|
||
|
|
||
|
/**
|
||
|
* Delete node callback.
|
||
|
* @fires layout/tree#delete.taotree
|
||
|
* @returns {undefined}
|
||
|
*/
|
||
|
ondelete: function ondelete() {
|
||
|
$container.trigger('delete.taotree', Array.prototype.slice.call(arguments));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Additional parameters to send to the server to retrieve data.
|
||
|
* It uses the serverParams object previously defined
|
||
|
* @param {jQueryElement} [$node] - the node that represents a class. Used to add the classUri to the call
|
||
|
* @returns {Object} params
|
||
|
*/
|
||
|
beforedata: function beforedata($node) {
|
||
|
var treeData = $container.data('tree-state');
|
||
|
var params = _.clone(serverParams);
|
||
|
if($node && $node.length){
|
||
|
params.classUri = $node.data('uri');
|
||
|
}
|
||
|
if(lastSelected){
|
||
|
params.selected = lastSelected;
|
||
|
}
|
||
|
|
||
|
//check for additionnal parameters in tree state
|
||
|
if(treeData){
|
||
|
|
||
|
//the tree has been loaded/refreshed with the filtering
|
||
|
if(_.isString(treeData.filter) && treeData.filter.length){
|
||
|
params.filter = treeData.filter;
|
||
|
treeData = _.omit(treeData, 'filter');
|
||
|
}
|
||
|
|
||
|
//the tree has been loaded/refreshed with the loadNode parameter, so it has to be selected
|
||
|
if(_.isString(treeData.loadNode) && treeData.loadNode.length){
|
||
|
params.selected = treeData.loadNode;
|
||
|
treeData.selectNode = uri.encode(treeData.loadNode);
|
||
|
treeData = _.omit(treeData, 'loadNode');
|
||
|
}
|
||
|
|
||
|
setTreeState(treeData);
|
||
|
}
|
||
|
return params;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Called back once the data are received.
|
||
|
* Used to modify them before building the tree.
|
||
|
*
|
||
|
* @param {Object} data - the received data
|
||
|
* @returns {Object} data the modified data
|
||
|
*/
|
||
|
ondata: function ondata(data) {
|
||
|
|
||
|
var treeData;
|
||
|
if(data.error){
|
||
|
feedback().error(data.error);
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
treeData = getTreeData(data);
|
||
|
|
||
|
//automatically open the children of the received node
|
||
|
if (treeData.children) {
|
||
|
treeData.state = 'open';
|
||
|
}
|
||
|
|
||
|
computeSelectionAccess(treeData);
|
||
|
|
||
|
needMore(treeData);
|
||
|
|
||
|
addTitle(treeData);
|
||
|
|
||
|
return treeData;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Once the data are loaded and the tree is ready
|
||
|
* Used to modify them before building the tree.
|
||
|
*
|
||
|
* @param {Object} tree - the tree instance
|
||
|
*
|
||
|
* @fires layout/tree#ready.taotree
|
||
|
*/
|
||
|
onload: function onload(tree){
|
||
|
var $firstClass = $(".node-class:not(.private):first", $container);
|
||
|
var $firstInstance = $(".node-instance:not(.private):first", $container);
|
||
|
var treeState = $container.data('tree-state') || {};
|
||
|
var selectNode = treeState.selectNode || options.selectNode;
|
||
|
var nodeSelection = function nodeSelection() {
|
||
|
//the node to select is given
|
||
|
if (selectNodeById(selectNode, tree)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//after refreshing tree previously node will be already selected.
|
||
|
if (tree.selected) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//if selectNode was not given and there is no selected node on the tree then try to find node to select:
|
||
|
|
||
|
//try to select the last one
|
||
|
if (selectNodeById(lastSelected, tree)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//or the 1st instance
|
||
|
if ($firstInstance.length) {
|
||
|
return tree.select_branch($firstInstance);
|
||
|
}
|
||
|
|
||
|
//or something
|
||
|
tree.select_branch($('.node-class,.node-instance', $container).get(0));
|
||
|
};
|
||
|
|
||
|
if($firstClass.hasClass('leaf')){
|
||
|
tree.select_branch($firstClass);
|
||
|
} else {
|
||
|
//open the first class
|
||
|
tree.open_branch($firstClass, false, function(){
|
||
|
_.delay(nodeSelection, 10); //delay needed as jstree seems to doesn't know the callbacks right now...,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The tree is now ready
|
||
|
* @event layout/tree#ready.taotree
|
||
|
* @param {Object} [context] - the tree context (uri, classUri)
|
||
|
*/
|
||
|
$container.trigger('ready.taotree');
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* After a branch is initialized
|
||
|
*/
|
||
|
oninit : function oninit() {
|
||
|
//execute initTree action
|
||
|
if (options.actions && options.actions.init) {
|
||
|
actionManager.exec(options.actions.init, {
|
||
|
uri: $container.data('rootnode')
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Before a branch is opened
|
||
|
* @param {HTMLElement} node - the opened node
|
||
|
*/
|
||
|
beforeopen: function beforeopen(node) {
|
||
|
lastOpened = $(node);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* A node is selected.
|
||
|
*
|
||
|
* @param {HTMLElement} node - the opened node
|
||
|
* @param {Object} tree - the tree instance
|
||
|
*
|
||
|
* @fires layout/tree#change.taotree
|
||
|
* @fires layout/tree#select.taotree
|
||
|
*/
|
||
|
onselect: function onselect(node, tree) {
|
||
|
|
||
|
var $node = $(node);
|
||
|
var classActions = [];
|
||
|
var nodeId = $node.attr('id');
|
||
|
var nodeUri = $node.data('uri');
|
||
|
var $parentNode = tree.parent($node);
|
||
|
var nodeContext = {
|
||
|
rootClassUri: options.rootClassUri,
|
||
|
signature: $node.data('signature')
|
||
|
};
|
||
|
|
||
|
lastSelected = nodeId;
|
||
|
|
||
|
//mark all unselected
|
||
|
$('a.clicked', $container)
|
||
|
.parent('li')
|
||
|
.not('[id="' + nodeId + '"]')
|
||
|
.removeClass('clicked');
|
||
|
|
||
|
//the more node makes you load more resources
|
||
|
if($node.hasClass('more')){
|
||
|
loadMore($node, $parentNode, tree);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//exec the selectClass action
|
||
|
if ($node.hasClass('node-class')) {
|
||
|
if ($node.hasClass('closed')) {
|
||
|
tree.open_branch($node);
|
||
|
}
|
||
|
nodeContext.classUri = nodeId;
|
||
|
nodeContext.classSignature = $node.data('signature');
|
||
|
nodeContext.id = nodeUri;
|
||
|
nodeContext.context = ['class', 'resource'];
|
||
|
|
||
|
//Check if any class-level action is defined in the structures.xml file
|
||
|
classActions = _.intersection(_.pluck(options.actions, 'context'), ['class', 'resource', '*']);
|
||
|
if (classActions.length > 0) {
|
||
|
generisRouter.pushNodeState(location.href, uri.decode(nodeContext.classUri));
|
||
|
executePossibleAction(options.actions, nodeContext, ['delete']);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//exec the selectInstance action
|
||
|
if ($node.hasClass('node-instance')){
|
||
|
nodeContext.uri = nodeId;
|
||
|
nodeContext.classUri = $parentNode.attr('id');
|
||
|
nodeContext.classSignature = $parentNode.data('signature');
|
||
|
nodeContext.id = nodeUri;
|
||
|
nodeContext.context = ['instance', 'resource'];
|
||
|
|
||
|
//the last selected node is stored
|
||
|
store('taotree').then(function(treeStore){
|
||
|
treeStore.setItem(context.section, nodeId).then(function(){
|
||
|
generisRouter.pushNodeState(location.href, uri.decode(nodeContext.uri));
|
||
|
executePossibleAction(options.actions, nodeContext, ['moveInstance', 'delete']);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A node has been selected
|
||
|
* @event layout/tree#select.taotree
|
||
|
* @param {Object} [context] - the tree context (uri, classUri)
|
||
|
*/
|
||
|
$container
|
||
|
.trigger('select.taotree', [nodeContext])
|
||
|
.trigger('change.taotree', [nodeContext]);
|
||
|
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
//when a node is move by drag n'drop
|
||
|
onmove: function onmove(node, refNode, type, tree, rollback) {
|
||
|
if (!options.actions.moveInstance) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//do not move an instance into an instance...
|
||
|
if ($(refNode).hasClass('node-instance') && type === 'inside') {
|
||
|
$.tree.rollback(rollback);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (type === 'after' || type === 'before') {
|
||
|
refNode = tree.parent(refNode);
|
||
|
}
|
||
|
|
||
|
if (!(refNode instanceof $) && !(refNode instanceof window.HTMLElement)) {
|
||
|
$.tree.rollback(rollback);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//set the rollback data
|
||
|
setTreeState(_.merge($container.data('tree-state'), {rollback : rollback}));
|
||
|
|
||
|
//execute the selectInstance action
|
||
|
actionManager.exec(options.actions.moveInstance, {
|
||
|
uri: $(node).data('uri'),
|
||
|
destinationClassUri: $(refNode).data('uri'),
|
||
|
signature: $(node).data('signature'),
|
||
|
tree: node
|
||
|
});
|
||
|
|
||
|
$container.trigger('change.taotree');
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Set up the tree using the defined options
|
||
|
* @private
|
||
|
*/
|
||
|
var setUpTree = function setUpTree(){
|
||
|
return new Promise( function (resolve) {
|
||
|
|
||
|
//bind events from the definition below
|
||
|
_.forEach(events, function(callback, name){
|
||
|
$container
|
||
|
.off(name + '.taotree')
|
||
|
.on(name + '.taotree', function(){
|
||
|
callback.apply(this, Array.prototype.slice.call(arguments, 1));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
//forward some events
|
||
|
actionManager.on('refresh', function(node){
|
||
|
var params = node;
|
||
|
if(node && node.uri){
|
||
|
params = {
|
||
|
loadNode : uri.encode(params.uri)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if($container.is(':visible')){
|
||
|
$container.trigger('refresh.taotree', [params]);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// workaround to fix dublicate tree bindings on multiple page loads
|
||
|
if (!$container.hasClass('tree')) {
|
||
|
|
||
|
store('taotree').then(function(treeStore){
|
||
|
treeStore.getItem(context.section).then(function(node){
|
||
|
//create the tree
|
||
|
setTreeState({ loadNode: options.loadNode });
|
||
|
$container.tree(treeOptions);
|
||
|
sectionManager.on('show.section', function (section) {
|
||
|
if (options.sectionId === section.id) {
|
||
|
$container.trigger('refresh.taotree');
|
||
|
}
|
||
|
});
|
||
|
generisRouter.on('urichange', function(nodeUri, sectionId) {
|
||
|
if (options.sectionId === sectionId) {
|
||
|
$container.trigger('refresh.taotree', [{loadNode : uri.encode(nodeUri)}]);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
$container.on('ready.taotree', function() {
|
||
|
resolve();
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Set tree state
|
||
|
* @param treeState
|
||
|
*/
|
||
|
function setTreeState(treeState) {
|
||
|
$container.data('tree-state', treeState);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if a node has access to a type of action regarding it's permissions
|
||
|
* @private
|
||
|
* @param {String} actionType - in selectClass, selectInstance, moveInstance and delete
|
||
|
* @param {Object} node - the node data as recevied from the server
|
||
|
* @returns {Boolean} true if the action is allowed
|
||
|
*/
|
||
|
function hasAccessTo(actionType, node){
|
||
|
var action = options.actions[actionType];
|
||
|
if(node && action && node.permissions && action.rights){
|
||
|
return permissionsManager.isContextAllowed(action.rights, {
|
||
|
uri : node.attributes['data-uri'],
|
||
|
classUri : node.attributes['data-classUri'],
|
||
|
id : node.attributes.id
|
||
|
});
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check whether the nodes in a tree are selectable. If not, we add the <strong>private</strong> class.
|
||
|
* @private
|
||
|
* @param {Object} node - the tree node as recevied from the server
|
||
|
*/
|
||
|
function computeSelectionAccess(node){
|
||
|
|
||
|
if(_.isArray(node)){
|
||
|
_.forEach(node, computeSelectionAccess);
|
||
|
return;
|
||
|
}
|
||
|
if(node.type){
|
||
|
addClassToNode(node, getPermissionClass(node));
|
||
|
if (!hasAccessTo('moveInstance', node)) {
|
||
|
addClassToNode(node, 'node-undraggable');
|
||
|
}
|
||
|
}
|
||
|
if(node.children){
|
||
|
_.forEach(node.children, computeSelectionAccess);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the CSS class to apply to the node regarding the computed permissions
|
||
|
* @private
|
||
|
* @param {Object} node - the tree node
|
||
|
* @returns {String} the CSS class
|
||
|
*/
|
||
|
function getPermissionClass(node){
|
||
|
var nodeId = node.attributes['data-uri'];
|
||
|
|
||
|
var rights = permissionsManager.getRights();
|
||
|
var count = _.reduce(rights, function(acc, right){
|
||
|
if(permissionsManager.hasPermission(nodeId, right)){
|
||
|
acc++;
|
||
|
}
|
||
|
return acc;
|
||
|
}, 0);
|
||
|
|
||
|
if (rights.length === 0 || count === rights.length) {
|
||
|
return 'permissions-full';
|
||
|
}
|
||
|
if(count === 0){
|
||
|
return 'permissions-none';
|
||
|
}
|
||
|
|
||
|
return 'permissions-partial';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a title attribute to the nodes
|
||
|
* @private
|
||
|
* @param {Object} node - the tree node as recevied from the server
|
||
|
*/
|
||
|
function addTitle(node){
|
||
|
if(_.isArray(node)){
|
||
|
_.forEach(node, addTitle);
|
||
|
return;
|
||
|
}
|
||
|
if(node.attributes && node.data){
|
||
|
node.attributes.title = node.data;
|
||
|
}
|
||
|
if(node.children){
|
||
|
_.forEach(node.children, addTitle);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function needMore(node){
|
||
|
if(_.isArray(node) && lastOpened && lastOpened.length && lastOpened.data('count') > pageRange){
|
||
|
node.push(moreNode);
|
||
|
} else {
|
||
|
if(node.count){
|
||
|
node.attributes['data-count'] = node.count;
|
||
|
|
||
|
if (node.children && node.count > node.children.length) {
|
||
|
node.children.push(moreNode);
|
||
|
}
|
||
|
}
|
||
|
if(node.children){
|
||
|
_.forEach(node.children, needMore);
|
||
|
}
|
||
|
if(_.isArray(node)){
|
||
|
_.forEach(node, needMore);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function loadMore($node, $parentNode, tree){
|
||
|
var current = $parentNode.children('ul').children('li.node-instance').length;
|
||
|
var count = $parentNode.data('count');
|
||
|
var left = count - current;
|
||
|
var params = _.defaults({
|
||
|
'classUri' : $parentNode.attr('id'),
|
||
|
'subclasses' : 0,
|
||
|
'offset' : current,
|
||
|
'limit' : left < 0 ? pageRange : (left < pageRange ? left : pageRange)
|
||
|
}, serverParams);
|
||
|
|
||
|
$.ajax(tree.settings.data.opts.url, {
|
||
|
type : tree.settings.data.opts.method,
|
||
|
dataType : tree.settings.data.type,
|
||
|
async : tree.settings.data.async,
|
||
|
data : params
|
||
|
}).done(function(response){
|
||
|
var treeData = getTreeData(response);
|
||
|
if(treeData && _.isArray(treeData.children)){
|
||
|
treeData = treeData.children;
|
||
|
}
|
||
|
if(_.isArray(treeData)){
|
||
|
_.forEach(treeData, function(newNode){
|
||
|
if(newNode.type === 'instance'){ //yes the server send also the class, even though I ask him gently...
|
||
|
tree.create(newNode, $parentNode);
|
||
|
}
|
||
|
});
|
||
|
tree.deselect_branch($node);
|
||
|
tree.remove($node);
|
||
|
if(left - treeData.length > 0){
|
||
|
tree.create(moreNode, $parentNode);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Function executes first found allowed action for tree node.
|
||
|
* @param {object} actions - All tree actions
|
||
|
* @param {object} [context] - Node context
|
||
|
* @param {object} [context.permissions] - Node permissions
|
||
|
* @param {object} [context.context] - The context of the action: (class|instance|resource|*)
|
||
|
* @param {array} exclude - list of actions to be excluded.
|
||
|
* @returns {undefined}
|
||
|
*/
|
||
|
function executePossibleAction(actions, nodeContext, exclude) {
|
||
|
var possibleActions;
|
||
|
if (!_.isArray(exclude)) {
|
||
|
exclude = [];
|
||
|
}
|
||
|
|
||
|
possibleActions = _.filter(actions, function (action, name) {
|
||
|
var possible = _.contains(nodeContext.context, action.context);
|
||
|
return possible && !_.contains(exclude, name);
|
||
|
});
|
||
|
//execute the first allowed action
|
||
|
if(possibleActions.length > 0){
|
||
|
actionManager.exec(possibleActions[0], nodeContext);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function addClassToNode(node, clazz){
|
||
|
if(node && node.attributes){
|
||
|
|
||
|
node.attributes['class'] = node.attributes['class'] || '';
|
||
|
|
||
|
if(node.attributes['class'].length) {
|
||
|
node.attributes['class'] = node.attributes['class'] + ' ' + clazz;
|
||
|
} else {
|
||
|
node.attributes['class'] = clazz;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse a response from a request to get the tree data
|
||
|
* and extract the permissions if given
|
||
|
* @param {Object} response - from a request
|
||
|
* @returns {Object} the tree data
|
||
|
*/
|
||
|
function getTreeData(response){
|
||
|
var treeData = response.tree || response;
|
||
|
var currentRights;
|
||
|
|
||
|
if(response.permissions){
|
||
|
currentRights = permissionsManager.getRights();
|
||
|
|
||
|
if(response.permissions.supportedRights &&
|
||
|
response.permissions.supportedRights.length &&
|
||
|
currentRights.length === 0) {
|
||
|
|
||
|
permissionsManager.setSupportedRights(response.permissions.supportedRights);
|
||
|
|
||
|
}
|
||
|
if(response.permissions.data){
|
||
|
permissionsManager.addPermissions(response.permissions.data);
|
||
|
}
|
||
|
}
|
||
|
return treeData;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {String} id
|
||
|
* @param {Object} tree
|
||
|
*
|
||
|
* @returns {Boolean} Whether or not the selection succeed
|
||
|
*/
|
||
|
function selectNodeById(id, tree) {
|
||
|
var $node;
|
||
|
|
||
|
if (!id) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$node = $('#' + id, $container);
|
||
|
|
||
|
if(!$node.length || $node.hasClass('private')){
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
tree.select_branch($node);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return setUpTree();
|
||
|
}
|
||
|
};
|
||
|
});
|