215 lines
8.5 KiB
JavaScript
215 lines
8.5 KiB
JavaScript
/**
|
|
* @ngdoc directive
|
|
* @name umbraco.directives.directive:umbTreeItem
|
|
* @element li
|
|
* @function
|
|
*
|
|
* @description
|
|
* Renders a list item, representing a single node in the tree.
|
|
* Includes element to toggle children, and a menu toggling button
|
|
*
|
|
* **note:** This directive is only used internally in the umbTree directive
|
|
*
|
|
* @example
|
|
<example module="umbraco">
|
|
<file name="index.html">
|
|
<umb-tree-item ng-repeat="child in tree.children" node="child" callback="callback" section="content"></umb-tree-item>
|
|
</file>
|
|
</example>
|
|
*/
|
|
angular.module("umbraco.directives")
|
|
.directive('umbTreeItem', function(treeService, $timeout, localizationService, eventsService, appState) {
|
|
return {
|
|
restrict: 'E',
|
|
replace: true,
|
|
require: '^umbTree',
|
|
templateUrl: 'views/components/tree/umb-tree-item.html',
|
|
scope: {
|
|
section: '@',
|
|
currentNode: '=',
|
|
enablelistviewexpand: '@',
|
|
node: '=',
|
|
tree: '='
|
|
},
|
|
|
|
link: function (scope, element, attrs, umbTreeCtrl) {
|
|
|
|
localizationService.localize("general_search").then(function (value) {
|
|
scope.searchAltText = value;
|
|
});
|
|
|
|
// updates the node's DOM/styles
|
|
function setupNodeDom(node, tree) {
|
|
|
|
//get the first div element
|
|
element.children(":first")
|
|
//set the padding
|
|
.css("padding-left", (node.level * 20) + "px");
|
|
|
|
// add a unique data element to each tree item so it is easy to navigate with code
|
|
if(!node.metaData.treeAlias) {
|
|
node.dataElement = node.name;
|
|
} else {
|
|
node.dataElement = node.metaData.treeAlias;
|
|
}
|
|
|
|
}
|
|
|
|
/** Returns the css classses assigned to the node (div element) */
|
|
scope.getNodeCssClass = function (node) {
|
|
if (!node) {
|
|
return '';
|
|
}
|
|
|
|
//TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time
|
|
// it would be better if we could cache the processing. The problem is that some of these things are dynamic.
|
|
|
|
var css = [];
|
|
if (node.cssClasses) {
|
|
_.each(node.cssClasses, function(c) {
|
|
css.push(c);
|
|
});
|
|
}
|
|
if (node.selected) {
|
|
css.push("umb-tree-node-checked");
|
|
}
|
|
|
|
//is this the current action node (this is not the same as the current selected node!)
|
|
var actionNode = appState.getMenuState("currentNode");
|
|
if (actionNode) {
|
|
if (actionNode.id === node.id) {
|
|
css.push("active");
|
|
}
|
|
}
|
|
|
|
return css.join(" ");
|
|
};
|
|
|
|
//add a method to the node which we can use to call to update the node data if we need to ,
|
|
// this is done by sync tree, we don't want to add a $watch for each node as that would be crazy insane slow
|
|
// so we have to do this
|
|
scope.node.updateNodeData = function (newNode) {
|
|
_.extend(scope.node, newNode);
|
|
//now update the styles
|
|
setupNodeDom(scope.node, scope.tree);
|
|
};
|
|
|
|
/**
|
|
Method called when the options button next to a node is called
|
|
In the main tree this opens the menu, but internally the tree doesnt
|
|
know about this, so it simply raises an event to tell the parent controller
|
|
about it.
|
|
*/
|
|
scope.options = function (n, ev) {
|
|
umbTreeCtrl.emitEvent("treeOptionsClick", { element: element, tree: scope.tree, node: n, event: ev });
|
|
};
|
|
|
|
/**
|
|
Method called when an item is clicked in the tree, this passes the
|
|
DOM element, the tree node object and the original click
|
|
and emits it as a treeNodeSelect element if there is a callback object
|
|
defined on the tree
|
|
*/
|
|
scope.select = function (n, ev) {
|
|
if (ev.ctrlKey ||
|
|
ev.shiftKey ||
|
|
ev.metaKey || // apple
|
|
(ev.button && ev.button === 1) // middle click, >IE9 + everyone else
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (n.metaData && n.metaData.noAccess === true) {
|
|
ev.preventDefault();
|
|
return;
|
|
}
|
|
|
|
umbTreeCtrl.emitEvent("treeNodeSelect", { element: element, tree: scope.tree, node: n, event: ev });
|
|
ev.preventDefault();
|
|
};
|
|
|
|
/**
|
|
Method called when an item is right-clicked in the tree, this passes the
|
|
DOM element, the tree node object and the original click
|
|
and emits it as a treeNodeSelect element if there is a callback object
|
|
defined on the tree
|
|
*/
|
|
scope.altSelect = function(n, ev) {
|
|
umbTreeCtrl.emitEvent("treeNodeAltSelect", { element: element, tree: scope.tree, node: n, event: ev });
|
|
};
|
|
|
|
/**
|
|
Method called when a node in the tree is expanded, when clicking the arrow
|
|
takes the arrow DOM element and node data as parameters
|
|
emits treeNodeCollapsing event if already expanded and treeNodeExpanding if collapsed
|
|
*/
|
|
scope.load = function (node) {
|
|
if (node.expanded && !node.metaData.isContainer) {
|
|
umbTreeCtrl.emitEvent("treeNodeCollapsing", { tree: scope.tree, node: node, element: element });
|
|
node.expanded = false;
|
|
}
|
|
else {
|
|
scope.loadChildren(node, false);
|
|
}
|
|
};
|
|
|
|
/* helper to force reloading children of a tree node */
|
|
scope.loadChildren = function(node, forceReload) {
|
|
return umbTreeCtrl.loadChildren(node, forceReload);
|
|
};
|
|
|
|
//if the current path contains the node id, we will auto-expand the tree item children
|
|
setupNodeDom(scope.node, scope.tree);
|
|
|
|
// load the children if the current user don't have access to the node
|
|
// it is used to auto expand the tree to the start nodes the user has access to
|
|
if(scope.node.hasChildren && scope.node.metaData.noAccess) {
|
|
scope.loadChildren(scope.node);
|
|
}
|
|
|
|
var evts = [];
|
|
|
|
//listen for section changes
|
|
evts.push(eventsService.on("appState.sectionState.changed", function(e, args) {
|
|
if (args.key === "currentSection") {
|
|
//when the section changes disable all delete animations
|
|
scope.node.deleteAnimations = false;
|
|
}
|
|
}));
|
|
|
|
/** Depending on if any menu is shown and if the menu is shown for the current node, toggle delete animations */
|
|
function toggleDeleteAnimations() {
|
|
//if both are false then remove animations
|
|
var hide = !appState.getMenuState("showMenuDialog") && !appState.getMenuState("showMenu");
|
|
if (hide) {
|
|
scope.node.deleteAnimations = false;
|
|
}
|
|
else {
|
|
//enable animations for this node if it is the node currently showing a context menu
|
|
var currentNode = appState.getMenuState("currentNode");
|
|
if (currentNode && currentNode.id == scope.node.id) {
|
|
scope.node.deleteAnimations = true;
|
|
}
|
|
else {
|
|
scope.node.deleteAnimations = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//listen for context menu and current node changes
|
|
evts.push(eventsService.on("appState.menuState.changed", function(e, args) {
|
|
if (args.key === "showMenuDialog" || args.key == "showMenu" || args.key == "currentNode") {
|
|
toggleDeleteAnimations();
|
|
}
|
|
}));
|
|
|
|
//cleanup events
|
|
scope.$on('$destroy', function() {
|
|
for (var e in evts) {
|
|
eventsService.unsubscribe(evts[e]);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
});
|