Re-formats the code structure of nav service and umbtree to be easier to read, adds better error checking to treeservice
This commit is contained in:
@@ -3,346 +3,352 @@
|
||||
* @name umbraco.directives.directive:umbTree
|
||||
* @restrict E
|
||||
**/
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbTree', function ($compile, $log, $q, $rootScope, navigationService, treeService, notificationsService, $timeout) {
|
||||
|
||||
function umbTreeDirective($compile, $log, $q, $rootScope, navigationService, treeService, notificationsService, $timeout) {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
terminal: false,
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
terminal: false,
|
||||
|
||||
scope: {
|
||||
section: '@',
|
||||
treealias: '@',
|
||||
showoptions: '@',
|
||||
showheader: '@',
|
||||
cachekey: '@',
|
||||
isdialog: '@',
|
||||
eventhandler: '='
|
||||
},
|
||||
scope: {
|
||||
section: '@',
|
||||
treealias: '@',
|
||||
showoptions: '@',
|
||||
showheader: '@',
|
||||
cachekey: '@',
|
||||
isdialog: '@',
|
||||
eventhandler: '='
|
||||
},
|
||||
|
||||
compile: function (element, attrs) {
|
||||
//config
|
||||
var hideheader = (attrs.showheader === 'false') ? true : false;
|
||||
var hideoptions = (attrs.showoptions === 'false') ? "hide-options" : "";
|
||||
|
||||
var template = '<ul class="umb-tree ' + hideoptions + '">' +
|
||||
'<li class="root">';
|
||||
compile: function(element, attrs) {
|
||||
//config
|
||||
var hideheader = (attrs.showheader === 'false') ? true : false;
|
||||
var hideoptions = (attrs.showoptions === 'false') ? "hide-options" : "";
|
||||
|
||||
if(!hideheader){
|
||||
template +='<div>' +
|
||||
'<h5><a href="#/{{section}}" ng-click="select(this, tree.root, $event)" on-right-click="altSelect(this, tree.root, $event)" class="root-link">{{tree.name}}</a></h5>' +
|
||||
'<a href class="umb-options" ng-hide="tree.root.isContainer || !tree.root.menuUrl" ng-click="options(this, tree.root, $event)" ng-swipe-right="options(this, tree.root, $event)"><i></i><i></i><i></i></a>' +
|
||||
'</div>';
|
||||
}
|
||||
template += '<ul>' +
|
||||
'<umb-tree-item ng-repeat="child in tree.root.children" eventhandler="eventhandler" path="{{path}}" activetree="{{activetree}}" node="child" current-node="currentNode" tree="child" section="{{section}}" ng-animate="animation()"></umb-tree-item>' +
|
||||
'</ul>' +
|
||||
var template = '<ul class="umb-tree ' + hideoptions + '">' +
|
||||
'<li class="root">';
|
||||
|
||||
if (!hideheader) {
|
||||
template += '<div>' +
|
||||
'<h5><a href="#/{{section}}" ng-click="select(this, tree.root, $event)" on-right-click="altSelect(this, tree.root, $event)" class="root-link">{{tree.name}}</a></h5>' +
|
||||
'<a href class="umb-options" ng-hide="tree.root.isContainer || !tree.root.menuUrl" ng-click="options(this, tree.root, $event)" ng-swipe-right="options(this, tree.root, $event)"><i></i><i></i><i></i></a>' +
|
||||
'</div>';
|
||||
}
|
||||
template += '<ul>' +
|
||||
'<umb-tree-item ng-repeat="child in tree.root.children" eventhandler="eventhandler" path="{{path}}" activetree="{{activetree}}" node="child" current-node="currentNode" tree="child" section="{{section}}" ng-animate="animation()"></umb-tree-item>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>';
|
||||
'</ul>';
|
||||
|
||||
element.replaceWith(template);
|
||||
element.replaceWith(template);
|
||||
|
||||
return function (scope, elem, attr, controller) {
|
||||
return function(scope, elem, attr, controller) {
|
||||
|
||||
//flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should
|
||||
// re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover
|
||||
// outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the
|
||||
// entire tree again since we already still have it in memory. Of course if the section is different we will
|
||||
// reload it. This saves a lot on processing if someone is navigating in and out of the same section many times
|
||||
// since it saves on data retreival and DOM processing.
|
||||
var lastSection = "";
|
||||
|
||||
//keeps track of the currently active tree being called by editors syncing
|
||||
var activeTree;
|
||||
//flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should
|
||||
// re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover
|
||||
// outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the
|
||||
// entire tree again since we already still have it in memory. Of course if the section is different we will
|
||||
// reload it. This saves a lot on processing if someone is navigating in and out of the same section many times
|
||||
// since it saves on data retreival and DOM processing.
|
||||
var lastSection = "";
|
||||
|
||||
//setup a default internal handler
|
||||
if(!scope.eventhandler){
|
||||
scope.eventhandler = $({});
|
||||
}
|
||||
|
||||
//flag to enable/disable delete animations
|
||||
var enableDeleteAnimations = false;
|
||||
//keeps track of the currently active tree being called by editors syncing
|
||||
var activeTree;
|
||||
|
||||
//setup a default internal handler
|
||||
if (!scope.eventhandler) {
|
||||
scope.eventhandler = $({});
|
||||
}
|
||||
|
||||
//flag to enable/disable delete animations
|
||||
var enableDeleteAnimations = false;
|
||||
|
||||
|
||||
/** Helper function to emit tree events */
|
||||
function emitEvent(eventName, args) {
|
||||
if (scope.eventhandler) {
|
||||
$(scope.eventhandler).trigger(eventName, args);
|
||||
}
|
||||
}
|
||||
/** Helper function to emit tree events */
|
||||
|
||||
|
||||
/*this is the only external interface a tree has */
|
||||
function setupExternalEvents() {
|
||||
if (scope.eventhandler) {
|
||||
|
||||
scope.eventhandler.clearCache = function(section){
|
||||
treeService.clearCache({ section: section });
|
||||
};
|
||||
|
||||
scope.eventhandler.load = function(section){
|
||||
scope.section = section;
|
||||
loadTree();
|
||||
};
|
||||
|
||||
scope.eventhandler.reloadNode = function(node){
|
||||
|
||||
if(!node){
|
||||
node = scope.currentNode;
|
||||
function emitEvent(eventName, args) {
|
||||
if (scope.eventhandler) {
|
||||
$(scope.eventhandler).trigger(eventName, args);
|
||||
}
|
||||
|
||||
if(node){
|
||||
scope.loadChildren(node, true);
|
||||
}
|
||||
|
||||
|
||||
/*this is the only external interface a tree has */
|
||||
|
||||
function setupExternalEvents() {
|
||||
if (scope.eventhandler) {
|
||||
|
||||
scope.eventhandler.clearCache = function(section) {
|
||||
treeService.clearCache({ section: section });
|
||||
};
|
||||
|
||||
scope.eventhandler.load = function(section) {
|
||||
scope.section = section;
|
||||
loadTree();
|
||||
};
|
||||
|
||||
scope.eventhandler.reloadNode = function(node) {
|
||||
|
||||
if (!node) {
|
||||
node = scope.currentNode;
|
||||
}
|
||||
|
||||
if (node) {
|
||||
scope.loadChildren(node, true);
|
||||
}
|
||||
};
|
||||
|
||||
scope.eventhandler.syncPath = function(path, forceReload) {
|
||||
|
||||
if (angular.isString(path)) {
|
||||
path = path.replace('"', '').split(',');
|
||||
}
|
||||
|
||||
//reset current node selection
|
||||
scope.currentNode = undefined;
|
||||
navigationService.ui.currentNode = undefined;
|
||||
|
||||
//filter the path for root node ids
|
||||
path = _.filter(path, function(item) { return (item !== "init" && item !== "-1"); });
|
||||
loadPath(path, forceReload);
|
||||
};
|
||||
|
||||
scope.eventhandler.setActiveTreeType = function(treeAlias) {
|
||||
loadActiveTree(treeAlias);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
scope.eventhandler.syncPath = function(path, forceReload){
|
||||
|
||||
if(angular.isString(path)){
|
||||
path = path.replace('"', '').split(',');
|
||||
}
|
||||
|
||||
//reset current node selection
|
||||
scope.currentNode = undefined;
|
||||
navigationService.ui.currentNode = undefined;
|
||||
|
||||
//filter the path for root node ids
|
||||
path = _.filter(path, function(item){ return (item !== "init" && item !== "-1"); });
|
||||
loadPath(path, forceReload);
|
||||
};
|
||||
|
||||
scope.eventhandler.setActiveTreeType = function(treeAlias){
|
||||
loadActiveTree(treeAlias);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//helper to load a specific path on the active tree as soon as its ready
|
||||
function loadPath(path, forceReload){
|
||||
function _load(tree, path, forceReload){
|
||||
syncTree(tree, path, forceReload);
|
||||
}
|
||||
|
||||
if(scope.activeTree){
|
||||
_load(scope.activeTree, path, forceReload);
|
||||
}else{
|
||||
scope.eventhandler.one("activeTreeLoaded", function(e, args){
|
||||
_load(args.tree, path, forceReload);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//expands the first child with a tree alias as soon as the tree has loaded
|
||||
function loadActiveTree(treeAlias){
|
||||
scope.activeTree = undefined;
|
||||
|
||||
function _load(tree, alias){
|
||||
scope.activeTree = _.find(tree.children, function(node){ return node.metaData.treeAlias === treeAlias; });
|
||||
scope.activeTree.expanded = true;
|
||||
|
||||
scope.loadChildren(scope.activeTree, false).then(function(){
|
||||
emitEvent("activeTreeLoaded", {tree: scope.activeTree});
|
||||
});
|
||||
}
|
||||
|
||||
if(scope.tree){
|
||||
_load(scope.tree.root, treeAlias);
|
||||
}else{
|
||||
scope.eventhandler.one("treeLoaded", function(e, args){
|
||||
_load(args.tree, treeAlias);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//helper to load a specific path on the active tree as soon as its ready
|
||||
|
||||
/** Method to load in the tree data */
|
||||
function loadTree() {
|
||||
if (!scope.loading && scope.section) {
|
||||
scope.loading = true;
|
||||
function loadPath(path, forceReload) {
|
||||
|
||||
//anytime we want to load the tree we need to disable the delete animations
|
||||
enableDeleteAnimations = false;
|
||||
function _load(tree, path, forceReload) {
|
||||
syncTree(tree, path, forceReload);
|
||||
}
|
||||
|
||||
//use $q.when because a promise OR raw data might be returned.
|
||||
treeService.getTree({ section: scope.section, tree: scope.treealias, cacheKey: scope.cachekey, isDialog: scope.isdialog ? scope.isdialog : false })
|
||||
.then(function (data) {
|
||||
//set the data once we have it
|
||||
scope.tree = data;
|
||||
|
||||
|
||||
|
||||
//do timeout so that it re-enables them after this digest
|
||||
$timeout(function() {
|
||||
//enable delete animations
|
||||
enableDeleteAnimations = true;
|
||||
},0,false);
|
||||
|
||||
scope.loading = false;
|
||||
|
||||
//set the root as the current active tree
|
||||
scope.activeTree = scope.tree.root;
|
||||
emitEvent("treeLoaded", {tree: scope.tree.root});
|
||||
|
||||
}, function (reason) {
|
||||
scope.loading = false;
|
||||
notificationsService.error("Tree Error", reason);
|
||||
if (scope.activeTree) {
|
||||
_load(scope.activeTree, path, forceReload);
|
||||
}
|
||||
else {
|
||||
scope.eventhandler.one("activeTreeLoaded", function(e, args) {
|
||||
_load(args.tree, path, forceReload);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function syncTree(node, path, forceReload) {
|
||||
if(!node || !path || path.length === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
//we are directly above the changed node
|
||||
var onParent = (path.length === 1);
|
||||
var needsReload = true;
|
||||
|
||||
node.expanded = true;
|
||||
|
||||
//if we are not directly above, we will just try to locate
|
||||
//the node and continue down the path
|
||||
if(!onParent){
|
||||
//if we can find the next node in the path
|
||||
var child = treeService.getChildNode(node, path[0]);
|
||||
if(child){
|
||||
needsReload = false;
|
||||
path.splice(0,1);
|
||||
syncTree(child, path, forceReload);
|
||||
}
|
||||
}
|
||||
|
||||
//if a reload is needed, all children will be loaded from server
|
||||
if(needsReload){
|
||||
scope.loadChildren(node, forceReload)
|
||||
.then(function(children){
|
||||
var child = treeService.getChildNode(node, path[0]);
|
||||
|
||||
if(!onParent){
|
||||
path.splice(0,1);
|
||||
syncTree(child, path, forceReload);
|
||||
}
|
||||
else {
|
||||
navigationService.ui.currentNode = child;
|
||||
scope.currentNode = child;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** method to set the current animation for the node.
|
||||
* This changes dynamically based on if we are changing sections or just loading normal tree data.
|
||||
* When changing sections we don't want all of the tree-ndoes to do their 'leave' animations.
|
||||
*/
|
||||
scope.animation = function () {
|
||||
if (enableDeleteAnimations && scope.tree && scope.tree.root && scope.tree.root.expanded) {
|
||||
return { leave: 'tree-node-delete-leave' };
|
||||
}
|
||||
else {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/* helper to force reloading children of a tree node */
|
||||
scope.loadChildren = function(node, forceReload){
|
||||
var deferred = $q.defer();
|
||||
|
||||
//emit treeNodeExpanding event, if a callback object is set on the tree
|
||||
emitEvent("treeNodeExpanding", {tree: scope.tree, node: node });
|
||||
|
||||
//standardising
|
||||
if(!node.children){
|
||||
node.children = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (forceReload || (node.hasChildren && node.children.length === 0)) {
|
||||
//get the children from the tree service
|
||||
treeService.loadNodeChildren({ node: node, section: scope.section })
|
||||
.then(function(data) {
|
||||
//emit expanded event
|
||||
emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data });
|
||||
enableDeleteAnimations = true;
|
||||
//expands the first child with a tree alias as soon as the tree has loaded
|
||||
|
||||
deferred.resolve(data);
|
||||
function loadActiveTree(treeAlias) {
|
||||
scope.activeTree = undefined;
|
||||
|
||||
function _load(tree, alias) {
|
||||
scope.activeTree = _.find(tree.children, function(node) { return node.metaData.treeAlias === treeAlias; });
|
||||
scope.activeTree.expanded = true;
|
||||
|
||||
scope.loadChildren(scope.activeTree, false).then(function() {
|
||||
emitEvent("activeTreeLoaded", { tree: scope.activeTree });
|
||||
});
|
||||
}
|
||||
|
||||
if (scope.tree) {
|
||||
_load(scope.tree.root, treeAlias);
|
||||
}
|
||||
else {
|
||||
scope.eventhandler.one("treeLoaded", function(e, args) {
|
||||
_load(args.tree, treeAlias);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
emitEvent("treeNodeExpanded", {tree: scope.tree, node: node, children: node.children });
|
||||
|
||||
|
||||
/** Method to load in the tree data */
|
||||
|
||||
function loadTree() {
|
||||
if (!scope.loading && scope.section) {
|
||||
scope.loading = true;
|
||||
|
||||
//anytime we want to load the tree we need to disable the delete animations
|
||||
enableDeleteAnimations = false;
|
||||
|
||||
//use $q.when because a promise OR raw data might be returned.
|
||||
treeService.getTree({ section: scope.section, tree: scope.treealias, cacheKey: scope.cachekey, isDialog: scope.isdialog ? scope.isdialog : false })
|
||||
.then(function(data) {
|
||||
//set the data once we have it
|
||||
scope.tree = data;
|
||||
|
||||
|
||||
//do timeout so that it re-enables them after this digest
|
||||
$timeout(function() {
|
||||
//enable delete animations
|
||||
enableDeleteAnimations = true;
|
||||
}, 0, false);
|
||||
|
||||
scope.loading = false;
|
||||
|
||||
//set the root as the current active tree
|
||||
scope.activeTree = scope.tree.root;
|
||||
emitEvent("treeLoaded", { tree: scope.tree.root });
|
||||
|
||||
}, function(reason) {
|
||||
scope.loading = false;
|
||||
notificationsService.error("Tree Error", reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function syncTree(node, path, forceReload) {
|
||||
if (!node || !path || path.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//we are directly above the changed node
|
||||
var onParent = (path.length === 1);
|
||||
var needsReload = true;
|
||||
|
||||
node.expanded = true;
|
||||
enableDeleteAnimations = true;
|
||||
|
||||
deferred.resolve(node.children);
|
||||
//if we are not directly above, we will just try to locate
|
||||
//the node and continue down the path
|
||||
if (!onParent) {
|
||||
//if we can find the next node in the path
|
||||
var child = treeService.getChildNode(node, path[0]);
|
||||
if (child) {
|
||||
needsReload = false;
|
||||
path.splice(0, 1);
|
||||
syncTree(child, path, forceReload);
|
||||
}
|
||||
}
|
||||
|
||||
//if a reload is needed, all children will be loaded from server
|
||||
if (needsReload) {
|
||||
scope.loadChildren(node, forceReload)
|
||||
.then(function(children) {
|
||||
var child = treeService.getChildNode(node, path[0]);
|
||||
|
||||
if (!onParent) {
|
||||
path.splice(0, 1);
|
||||
syncTree(child, path, forceReload);
|
||||
}
|
||||
else {
|
||||
navigationService.ui.currentNode = child;
|
||||
scope.currentNode = child;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
|
||||
/** method to set the current animation for the node.
|
||||
* This changes dynamically based on if we are changing sections or just loading normal tree data.
|
||||
* When changing sections we don't want all of the tree-ndoes to do their 'leave' animations.
|
||||
*/
|
||||
scope.animation = function() {
|
||||
if (enableDeleteAnimations && scope.tree && scope.tree.root && scope.tree.root.expanded) {
|
||||
return { leave: 'tree-node-delete-leave' };
|
||||
}
|
||||
else {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/* helper to force reloading children of a tree node */
|
||||
scope.loadChildren = function(node, forceReload) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
//emit treeNodeExpanding event, if a callback object is set on the tree
|
||||
emitEvent("treeNodeExpanding", { tree: scope.tree, node: node });
|
||||
|
||||
//standardising
|
||||
if (!node.children) {
|
||||
node.children = [];
|
||||
}
|
||||
|
||||
if (forceReload || (node.hasChildren && node.children.length === 0)) {
|
||||
//get the children from the tree service
|
||||
treeService.loadNodeChildren({ node: node, section: scope.section })
|
||||
.then(function(data) {
|
||||
//emit expanded event
|
||||
emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data });
|
||||
enableDeleteAnimations = true;
|
||||
|
||||
deferred.resolve(data);
|
||||
});
|
||||
}
|
||||
else {
|
||||
emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: node.children });
|
||||
node.expanded = true;
|
||||
enableDeleteAnimations = true;
|
||||
|
||||
deferred.resolve(node.children);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
Method called when the options button next to the root node is called.
|
||||
The tree doesnt know about this, so it raises an event to tell the parent controller
|
||||
about it.
|
||||
*/
|
||||
scope.options = function(e, n, ev) {
|
||||
emitEvent("treeOptionsClick", { element: e, 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(e, n, ev) {
|
||||
emitEvent("treeNodeSelect", { element: e, node: n, event: ev });
|
||||
};
|
||||
|
||||
scope.altSelect = function(e, n, ev) {
|
||||
emitEvent("treeNodeAltSelect", { element: e, tree: scope.tree, node: n, event: ev });
|
||||
};
|
||||
|
||||
|
||||
//watch for section changes
|
||||
scope.$watch("section", function(newVal, oldVal) {
|
||||
|
||||
if (!scope.tree) {
|
||||
loadTree();
|
||||
}
|
||||
|
||||
if (!newVal) {
|
||||
//store the last section loaded
|
||||
lastSection = oldVal;
|
||||
}
|
||||
else if (newVal !== oldVal && newVal !== lastSection) {
|
||||
//only reload the tree data and Dom if the newval is different from the old one
|
||||
// and if the last section loaded is different from the requested one.
|
||||
loadTree();
|
||||
|
||||
//store the new section to be loaded as the last section
|
||||
//clear any active trees to reset lookups
|
||||
lastSection = newVal;
|
||||
activeTree = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
//When the user logs in
|
||||
scope.$on("authenticated", function(evt, data) {
|
||||
//populate the tree if the user has changed
|
||||
if (data.lastUserId !== data.user.id) {
|
||||
treeService.clearCache();
|
||||
scope.tree = null;
|
||||
|
||||
setupExternalEvents();
|
||||
loadTree();
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
Method called when the options button next to the root node is called.
|
||||
The tree doesnt know about this, so it raises an event to tell the parent controller
|
||||
about it.
|
||||
*/
|
||||
scope.options = function (e, n, ev) {
|
||||
emitEvent("treeOptionsClick", { element: e, 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(e,n,ev){
|
||||
emitEvent("treeNodeSelect", { element: e, node: n, event: ev });
|
||||
};
|
||||
|
||||
scope.altSelect = function(e,n,ev){
|
||||
emitEvent("treeNodeAltSelect", { element: e, tree: scope.tree, node: n, event: ev });
|
||||
};
|
||||
|
||||
|
||||
//watch for section changes
|
||||
scope.$watch("section", function (newVal, oldVal) {
|
||||
|
||||
if(!scope.tree){
|
||||
loadTree();
|
||||
}
|
||||
|
||||
if (!newVal) {
|
||||
//store the last section loaded
|
||||
lastSection = oldVal;
|
||||
}else if (newVal !== oldVal && newVal !== lastSection) {
|
||||
//only reload the tree data and Dom if the newval is different from the old one
|
||||
// and if the last section loaded is different from the requested one.
|
||||
loadTree();
|
||||
|
||||
//store the new section to be loaded as the last section
|
||||
//clear any active trees to reset lookups
|
||||
lastSection = newVal;
|
||||
activeTree = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
//When the user logs in
|
||||
scope.$on("authenticated", function (evt, data) {
|
||||
//populate the tree if the user has changed
|
||||
if (data.lastUserId !== data.user.id) {
|
||||
treeService.clearCache();
|
||||
scope.tree = null;
|
||||
|
||||
setupExternalEvents();
|
||||
loadTree();
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
angular.module("umbraco.directives").directive('umbTree', umbTreeDirective);
|
||||
@@ -15,10 +15,8 @@
|
||||
* Section navigation and search, and maintain their state for the entire application lifetime
|
||||
*
|
||||
*/
|
||||
function navigationService($rootScope, $routeParams, $log, $location, $q, $timeout, dialogService, treeService, notificationsService, historyService) {
|
||||
|
||||
angular.module('umbraco.services')
|
||||
.factory('navigationService', function ($rootScope, $routeParams, $log, $location, $q, $timeout, dialogService, treeService, notificationsService, historyService) {
|
||||
|
||||
var minScreenSize = 1100;
|
||||
|
||||
//Define all sub-properties for the UI object here
|
||||
@@ -38,12 +36,13 @@ angular.module('umbraco.services')
|
||||
actions: undefined,
|
||||
currentDialog: undefined,
|
||||
dialogTitle: undefined,
|
||||
|
||||
|
||||
//a string/name reference for the currently set ui mode
|
||||
currentMode: "default"
|
||||
};
|
||||
|
||||
$rootScope.$on("closeDialogs", function(){});
|
||||
|
||||
$rootScope.$on("closeDialogs", function() {
|
||||
});
|
||||
|
||||
function setTreeMode() {
|
||||
ui.tablet = ($(window).width() <= minScreenSize);
|
||||
@@ -52,57 +51,57 @@ angular.module('umbraco.services')
|
||||
|
||||
function setMode(mode) {
|
||||
switch (mode) {
|
||||
case 'tree':
|
||||
ui.currentMode = "tree";
|
||||
ui.showNavigation = true;
|
||||
ui.showContextMenu = false;
|
||||
ui.showContextMenuDialog = false;
|
||||
ui.stickyNavigation = false;
|
||||
ui.showTray = false;
|
||||
service.hideUserDialog();
|
||||
service.hideHelpDialog();
|
||||
//$("#search-form input").focus();
|
||||
break;
|
||||
case 'menu':
|
||||
ui.currentMode = "menu";
|
||||
ui.showNavigation = true;
|
||||
ui.showContextMenu = true;
|
||||
ui.showContextMenuDialog = false;
|
||||
ui.stickyNavigation = true;
|
||||
break;
|
||||
case 'dialog':
|
||||
ui.currentMode = "dialog";
|
||||
ui.stickyNavigation = true;
|
||||
ui.showNavigation = true;
|
||||
ui.showContextMenu = false;
|
||||
ui.showContextMenuDialog = true;
|
||||
break;
|
||||
case 'search':
|
||||
ui.currentMode = "search";
|
||||
ui.stickyNavigation = false;
|
||||
ui.showNavigation = true;
|
||||
ui.showContextMenu = false;
|
||||
ui.showSearchResults = true;
|
||||
ui.showContextMenuDialog = false;
|
||||
case 'tree':
|
||||
ui.currentMode = "tree";
|
||||
ui.showNavigation = true;
|
||||
ui.showContextMenu = false;
|
||||
ui.showContextMenuDialog = false;
|
||||
ui.stickyNavigation = false;
|
||||
ui.showTray = false;
|
||||
service.hideUserDialog();
|
||||
service.hideHelpDialog();
|
||||
//$("#search-form input").focus();
|
||||
break;
|
||||
case 'menu':
|
||||
ui.currentMode = "menu";
|
||||
ui.showNavigation = true;
|
||||
ui.showContextMenu = true;
|
||||
ui.showContextMenuDialog = false;
|
||||
ui.stickyNavigation = true;
|
||||
break;
|
||||
case 'dialog':
|
||||
ui.currentMode = "dialog";
|
||||
ui.stickyNavigation = true;
|
||||
ui.showNavigation = true;
|
||||
ui.showContextMenu = false;
|
||||
ui.showContextMenuDialog = true;
|
||||
break;
|
||||
case 'search':
|
||||
ui.currentMode = "search";
|
||||
ui.stickyNavigation = false;
|
||||
ui.showNavigation = true;
|
||||
ui.showContextMenu = false;
|
||||
ui.showSearchResults = true;
|
||||
ui.showContextMenuDialog = false;
|
||||
|
||||
$timeout(function(){
|
||||
$("#search-field").focus();
|
||||
});
|
||||
|
||||
break;
|
||||
default:
|
||||
ui.currentMode = "default";
|
||||
ui.showContextMenu = false;
|
||||
ui.showContextMenuDialog = false;
|
||||
ui.showSearchResults = false;
|
||||
ui.stickyNavigation = false;
|
||||
ui.showTray = false;
|
||||
$timeout(function() {
|
||||
$("#search-field").focus();
|
||||
});
|
||||
|
||||
if(ui.tablet){
|
||||
ui.showNavigation = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ui.currentMode = "default";
|
||||
ui.showContextMenu = false;
|
||||
ui.showContextMenuDialog = false;
|
||||
ui.showSearchResults = false;
|
||||
ui.stickyNavigation = false;
|
||||
ui.showTray = false;
|
||||
|
||||
break;
|
||||
if (ui.tablet) {
|
||||
ui.showNavigation = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,15 +111,15 @@ angular.module('umbraco.services')
|
||||
userDialog: undefined,
|
||||
ui: ui,
|
||||
|
||||
init: function(){
|
||||
init: function() {
|
||||
|
||||
//TODO: detect tablet mode, subscribe to window resizing
|
||||
//for now we just hardcode it to non-tablet mode
|
||||
setTreeMode();
|
||||
this.ui.currentSection = $routeParams.section;
|
||||
|
||||
$(window).bind("resize", function () {
|
||||
setTreeMode();
|
||||
$(window).bind("resize", function() {
|
||||
setTreeMode();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -133,7 +132,7 @@ angular.module('umbraco.services')
|
||||
* Shows the legacy iframe and loads in the content based on the source url
|
||||
* @param {String} source The URL to load into the iframe
|
||||
*/
|
||||
loadLegacyIFrame: function (source) {
|
||||
loadLegacyIFrame: function(source) {
|
||||
$location.path("/" + this.ui.currentSection + "/framed/" + encodeURIComponent(source));
|
||||
},
|
||||
|
||||
@@ -148,16 +147,16 @@ angular.module('umbraco.services')
|
||||
* and load the dashboard related to the section
|
||||
* @param {string} sectionAlias The alias of the section
|
||||
*/
|
||||
changeSection: function (sectionAlias, force) {
|
||||
changeSection: function(sectionAlias, force) {
|
||||
setMode("default-opensection");
|
||||
|
||||
if(force && this.ui.currentSection === sectionAlias){
|
||||
|
||||
if (force && this.ui.currentSection === sectionAlias) {
|
||||
this.ui.currentSection = "";
|
||||
}
|
||||
|
||||
this.ui.currentSection = sectionAlias;
|
||||
this.showTree(sectionAlias);
|
||||
|
||||
|
||||
$location.path(sectionAlias);
|
||||
},
|
||||
|
||||
@@ -171,52 +170,52 @@ angular.module('umbraco.services')
|
||||
* only changes if the section is different from the current one
|
||||
* @param {string} sectionAlias The alias of the section the tree should load data from
|
||||
*/
|
||||
showTree: function (sectionAlias, treeAlias, path) {
|
||||
showTree: function(sectionAlias, treeAlias, path) {
|
||||
if (sectionAlias !== this.ui.currentSection) {
|
||||
this.ui.currentSection = sectionAlias;
|
||||
if(treeAlias){
|
||||
if (treeAlias) {
|
||||
this.setActiveTreeType(treeAlias);
|
||||
}
|
||||
if(path){
|
||||
if (path) {
|
||||
this.syncpath(path, true);
|
||||
}
|
||||
}
|
||||
setMode("tree");
|
||||
},
|
||||
|
||||
showTray: function () {
|
||||
showTray: function() {
|
||||
ui.showTray = true;
|
||||
},
|
||||
|
||||
hideTray: function () {
|
||||
hideTray: function() {
|
||||
ui.showTray = false;
|
||||
},
|
||||
|
||||
//adding this to get clean global access to the main tree directive
|
||||
//there will only ever be one main tree event handler
|
||||
//we need to pass in the current scope for binding these actions
|
||||
setupTreeEvents: function(treeEventHandler, scope){
|
||||
setupTreeEvents: function(treeEventHandler, scope) {
|
||||
this.ui.treeEventHandler = treeEventHandler;
|
||||
|
||||
//this reacts to the options item in the tree
|
||||
this.ui.treeEventHandler.bind("treeOptionsClick", function (ev, args) {
|
||||
this.ui.treeEventHandler.bind("treeOptionsClick", function(ev, args) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
|
||||
scope.currentNode = args.node;
|
||||
args.scope = scope;
|
||||
|
||||
if(args.event && args.event.altKey){
|
||||
if (args.event && args.event.altKey) {
|
||||
args.skipDefault = true;
|
||||
}
|
||||
|
||||
service.showMenu(ev, args);
|
||||
});
|
||||
|
||||
this.ui.treeEventHandler.bind("treeNodeAltSelect", function (ev, args) {
|
||||
this.ui.treeEventHandler.bind("treeNodeAltSelect", function(ev, args) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
|
||||
scope.currentNode = args.node;
|
||||
args.scope = scope;
|
||||
|
||||
@@ -226,11 +225,11 @@ angular.module('umbraco.services')
|
||||
|
||||
//this reacts to tree items themselves being clicked
|
||||
//the tree directive should not contain any handling, simply just bubble events
|
||||
this.ui.treeEventHandler.bind("treeNodeSelect", function (ev, args) {
|
||||
this.ui.treeEventHandler.bind("treeNodeSelect", function(ev, args) {
|
||||
var n = args.node;
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
|
||||
|
||||
if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") {
|
||||
//this is a legacy tree node!
|
||||
@@ -253,14 +252,15 @@ angular.module('umbraco.services')
|
||||
$log.error("Error evaluating js callback from legacy tree node: " + ex);
|
||||
}
|
||||
}
|
||||
else if(n.routePath){
|
||||
else if (n.routePath) {
|
||||
//add action to the history service
|
||||
historyService.add({ name: n.name, link: n.routePath, icon: n.icon });
|
||||
//not legacy, lets just set the route value and clear the query string if there is one.
|
||||
|
||||
|
||||
ui.currentNode = n;
|
||||
$location.path(n.routePath).search("");
|
||||
} else if(args.element.section){
|
||||
}
|
||||
else if (args.element.section) {
|
||||
$location.path(args.element.section).search("");
|
||||
}
|
||||
|
||||
@@ -269,11 +269,11 @@ angular.module('umbraco.services')
|
||||
},
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.navigationService#syncTree
|
||||
* @name umbraco.services.navigationService#syncPath
|
||||
* @methodOf umbraco.services.navigationService
|
||||
*
|
||||
* @description
|
||||
* Syncs the tree with a given section alias and a given path
|
||||
* Syncs the tree with a given path
|
||||
* The path format is: ["itemId","itemId"], and so on
|
||||
* so to sync to a specific document type node do:
|
||||
* <pre>
|
||||
@@ -282,27 +282,27 @@ angular.module('umbraco.services')
|
||||
* @param {array} path array of ascendant ids, ex: ["1023","1243"] (loads a specific document type into the settings tree)
|
||||
* @param {bool} forceReload forces a reload of data from the server
|
||||
*/
|
||||
syncPath: function (path, forceReload) {
|
||||
if(this.ui.treeEventHandler){
|
||||
this.ui.treeEventHandler.syncPath(path,forceReload);
|
||||
syncPath: function(path, forceReload) {
|
||||
if (this.ui.treeEventHandler) {
|
||||
this.ui.treeEventHandler.syncPath(path, forceReload);
|
||||
}
|
||||
},
|
||||
|
||||
reloadNode: function (node) {
|
||||
if(this.ui.treeEventHandler){
|
||||
reloadNode: function(node) {
|
||||
if (this.ui.treeEventHandler) {
|
||||
this.ui.treeEventHandler.reloadNode(node);
|
||||
}
|
||||
},
|
||||
|
||||
reloadSection: function (sectionAlias) {
|
||||
if(this.ui.treeEventHandler){
|
||||
reloadSection: function(sectionAlias) {
|
||||
if (this.ui.treeEventHandler) {
|
||||
this.ui.treeEventHandler.clearCache({ section: sectionAlias });
|
||||
this.ui.treeEventHandler.load(sectionAlias);
|
||||
}
|
||||
},
|
||||
|
||||
setActiveTreeType: function (treeAlias) {
|
||||
if(this.ui.treeEventHandler){
|
||||
setActiveTreeType: function(treeAlias) {
|
||||
if (this.ui.treeEventHandler) {
|
||||
this.ui.treeEventHandler.setActiveTreeType(treeAlias);
|
||||
}
|
||||
},
|
||||
@@ -316,7 +316,7 @@ angular.module('umbraco.services')
|
||||
* Sets a service variable as soon as the user hovers the navigation with the mouse
|
||||
* used by the leaveTree method to delay hiding
|
||||
*/
|
||||
enterTree: function (event) {
|
||||
enterTree: function(event) {
|
||||
service.active = true;
|
||||
},
|
||||
|
||||
@@ -328,18 +328,18 @@ angular.module('umbraco.services')
|
||||
* @description
|
||||
* Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again
|
||||
*/
|
||||
leaveTree: function (event) {
|
||||
leaveTree: function(event) {
|
||||
//this is a hack to handle IE touch events
|
||||
//which freaks out due to no mouse events
|
||||
//so the tree instantly shuts down
|
||||
if(!event){
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!service.touchDevice){
|
||||
if (!service.touchDevice) {
|
||||
service.active = false;
|
||||
$timeout(function(){
|
||||
if(!service.active){
|
||||
$timeout(function() {
|
||||
if (!service.active) {
|
||||
service.hideTree();
|
||||
}
|
||||
}, 300);
|
||||
@@ -354,7 +354,7 @@ angular.module('umbraco.services')
|
||||
* @description
|
||||
* Hides the tree by hiding the containing dom element
|
||||
*/
|
||||
hideTree: function () {
|
||||
hideTree: function() {
|
||||
|
||||
if (this.ui.tablet && !this.ui.stickyNavigation) {
|
||||
//reset it to whatever is in the url
|
||||
@@ -375,14 +375,14 @@ angular.module('umbraco.services')
|
||||
*
|
||||
* @param {Event} event the click event triggering the method, passed from the DOM element
|
||||
*/
|
||||
showMenu: function (event, args) {
|
||||
showMenu: function(event, args) {
|
||||
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
|
||||
treeService.getMenu({ treeNode: args.node })
|
||||
.then(function(data) {
|
||||
|
||||
|
||||
//check for a default
|
||||
//NOTE: event will be undefined when a call to hideDialog is made so it won't re-load the default again.
|
||||
// but perhaps there's a better way to deal with with an additional parameter in the args ? it works though.
|
||||
@@ -393,7 +393,7 @@ angular.module('umbraco.services')
|
||||
});
|
||||
|
||||
if (found) {
|
||||
|
||||
|
||||
self.ui.currentNode = args.node;
|
||||
//ensure the current dialog is cleared before creating another!
|
||||
if (self.ui.currentDialog) {
|
||||
@@ -414,18 +414,18 @@ angular.module('umbraco.services')
|
||||
}
|
||||
|
||||
//there is no default or we couldn't find one so just continue showing the menu
|
||||
|
||||
|
||||
setMode("menu");
|
||||
|
||||
|
||||
ui.actions = data.menuItems;
|
||||
|
||||
|
||||
ui.currentNode = args.node;
|
||||
ui.dialogTitle = args.node.name;
|
||||
|
||||
//we're not opening a dialog, return null.
|
||||
deferred.resolve(null);
|
||||
});
|
||||
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
@@ -437,7 +437,7 @@ angular.module('umbraco.services')
|
||||
* @description
|
||||
* Hides the menu by hiding the containing dom element
|
||||
*/
|
||||
hideMenu: function () {
|
||||
hideMenu: function() {
|
||||
var selectedId = $routeParams.id;
|
||||
this.ui.currentNode = undefined;
|
||||
this.ui.actions = [];
|
||||
@@ -454,7 +454,7 @@ angular.module('umbraco.services')
|
||||
* Opens the user dialog, next to the sections navigation
|
||||
* template is located in views/common/dialogs/user.html
|
||||
*/
|
||||
showUserDialog: function () {
|
||||
showUserDialog: function() {
|
||||
service.userDialog = dialogService.open(
|
||||
{
|
||||
template: "views/common/dialogs/user.html",
|
||||
@@ -474,7 +474,7 @@ angular.module('umbraco.services')
|
||||
* Opens the user dialog, next to the sections navigation
|
||||
* template is located in views/common/dialogs/user.html
|
||||
*/
|
||||
showHelpDialog: function () {
|
||||
showHelpDialog: function() {
|
||||
service.helpDialog = dialogService.open(
|
||||
{
|
||||
template: "views/common/dialogs/help.html",
|
||||
@@ -494,18 +494,18 @@ angular.module('umbraco.services')
|
||||
* Hides the user dialog, next to the sections navigation
|
||||
* template is located in views/common/dialogs/user.html
|
||||
*/
|
||||
hideUserDialog: function () {
|
||||
if(service.userDialog){
|
||||
hideUserDialog: function() {
|
||||
if (service.userDialog) {
|
||||
service.userDialog.close();
|
||||
service.userDialog = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
hideHelpDialog: function () {
|
||||
if(service.helpDialog){
|
||||
hideHelpDialog: function() {
|
||||
if (service.helpDialog) {
|
||||
service.helpDialog.close();
|
||||
service.helpDialog = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -529,7 +529,7 @@ angular.module('umbraco.services')
|
||||
* @param {Scope} args.scope current scope passed to the dialog
|
||||
* @param {Object} args.action the clicked action containing `name` and `alias`
|
||||
*/
|
||||
showDialog: function (args) {
|
||||
showDialog: function(args) {
|
||||
|
||||
if (!args) {
|
||||
throw "showDialog is missing the args parameter";
|
||||
@@ -573,7 +573,7 @@ angular.module('umbraco.services')
|
||||
iframe = false;
|
||||
}
|
||||
else {
|
||||
|
||||
|
||||
//by convention we will look into the /views/{treetype}/{action}.html
|
||||
// for example: /views/content/create.html
|
||||
|
||||
@@ -588,7 +588,7 @@ angular.module('umbraco.services')
|
||||
}
|
||||
|
||||
if (packageTreeFolder) {
|
||||
templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath +
|
||||
templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath +
|
||||
"/" + packageTreeFolder +
|
||||
"/umbraco/" + treeAlias + "/" + args.action.alias + ".html";
|
||||
}
|
||||
@@ -630,8 +630,8 @@ angular.module('umbraco.services')
|
||||
* @description
|
||||
* hides the currently open dialog
|
||||
*/
|
||||
hideDialog: function () {
|
||||
this.showMenu(undefined, {skipDefault: true, node: this.ui.currentNode });
|
||||
hideDialog: function() {
|
||||
this.showMenu(undefined, { skipDefault: true, node: this.ui.currentNode });
|
||||
},
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -641,7 +641,7 @@ angular.module('umbraco.services')
|
||||
* @description
|
||||
* shows the search pane
|
||||
*/
|
||||
showSearch: function () {
|
||||
showSearch: function() {
|
||||
setMode("search");
|
||||
},
|
||||
/**
|
||||
@@ -652,7 +652,7 @@ angular.module('umbraco.services')
|
||||
* @description
|
||||
* hides the search pane
|
||||
*/
|
||||
hideSearch: function () {
|
||||
hideSearch: function() {
|
||||
setMode("default-hidesearch");
|
||||
},
|
||||
/**
|
||||
@@ -663,7 +663,7 @@ angular.module('umbraco.services')
|
||||
* @description
|
||||
* hides any open navigation panes and resets the tree, actions and the currently selected node
|
||||
*/
|
||||
hideNavigation: function () {
|
||||
hideNavigation: function() {
|
||||
this.ui.actions = [];
|
||||
//this.ui.currentNode = undefined;
|
||||
setMode("default");
|
||||
@@ -671,4 +671,6 @@ angular.module('umbraco.services')
|
||||
};
|
||||
|
||||
return service;
|
||||
});
|
||||
}
|
||||
|
||||
angular.module('umbraco.services').factory('navigationService', navigationService);
|
||||
|
||||
@@ -204,7 +204,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc
|
||||
/** Gets a child node by id */
|
||||
getChildNode: function (treeNode, id) {
|
||||
if (!treeNode.children) {
|
||||
throw "The current tree node has no assigned children, ensure it's children are loaded before calling this method";
|
||||
return null;
|
||||
}
|
||||
var found = _.find(treeNode.children, function (child) {
|
||||
return String(child.id) === String(id);
|
||||
@@ -221,6 +221,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc
|
||||
}
|
||||
|
||||
//check each child of this node
|
||||
if (!treeNode.children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var i = 0; i < treeNode.children.length; i++) {
|
||||
if (treeNode.children[i].children && angular.isArray(treeNode.children[i].children) && treeNode.children[i].children.length > 0) {
|
||||
//recurse
|
||||
@@ -236,11 +240,15 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc
|
||||
},
|
||||
|
||||
/** Gets the root node of the current tree type for a given tree node */
|
||||
getTreeRoot: function(treeNode) {
|
||||
getTreeRoot: function (treeNode) {
|
||||
if (!treeNode) {
|
||||
throw "treeNode cannot be null";
|
||||
}
|
||||
|
||||
//all root nodes have metadata key 'treeAlias'
|
||||
var root = null;
|
||||
var current = treeNode;
|
||||
while (root === null && current !== undefined) {
|
||||
while (root === null && current) {
|
||||
|
||||
if (current.metaData && current.metaData["treeAlias"]) {
|
||||
root = current;
|
||||
|
||||
Reference in New Issue
Block a user