diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js
index ce14aaedc5..1cf934b78c 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js
@@ -8,7 +8,7 @@
scope.search = "";
scope.miniListViews = [];
scope.breadcrumb = [];
-
+
var miniListViewsHistory = [];
var goingForward = true;
var skipAnimation = true;
@@ -60,9 +60,13 @@
entityResource.getPagedChildren(miniListView.node.id, scope.entityType, miniListView.pagination)
.then(function (data) {
+
// update children
miniListView.children = data.items;
_.each(miniListView.children, function(c) {
+ // child allowed by default
+ c.allowed = true;
+
// convert legacy icon for node
if(c.icon) {
c.icon = iconHelper.convertFromLegacyIcon(c.icon);
@@ -74,6 +78,17 @@
c.published = c.metaData.IsPublished;
}
}
+
+ // filter items if there is a filter and it's not advanced
+ // ** ignores advanced filter at the moment
+ if (scope.entityTypeFilter && !scope.entityTypeFilter.filterAdvanced) {
+ var a = scope.entityTypeFilter.filter.toLowerCase().replace(/\s/g, '').split(',');
+ var found = a.indexOf(c.metaData.ContentTypeAlias.toLowerCase()) >= 0;
+
+ if (!scope.entityTypeFilter.filterExclude && !found || scope.entityTypeFilter.filterExclude && found) {
+ c.allowed = false;
+ }
+ }
});
// update pagination
miniListView.pagination.totalItems = data.totalItems;
@@ -89,7 +104,7 @@
};
scope.selectNode = function(node) {
- if(scope.onSelect) {
+ if (scope.onSelect && node.allowed) {
scope.onSelect({'node': node});
}
};
@@ -202,7 +217,8 @@
entityType: "@",
startNodeId: "=",
onSelect: "&",
- onClose: "&"
+ onClose: "&",
+ entityTypeFilter: "="
},
link: link
};
diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less
index d2393efb11..284de6e844 100644
--- a/src/Umbraco.Web.UI.Client/src/less/listview.less
+++ b/src/Umbraco.Web.UI.Client/src/less/listview.less
@@ -157,6 +157,10 @@
/* TEMP */
+.umb-minilistview {
+ .umb-table-row.not-allowed { opacity: 0.6; cursor: not-allowed; }
+}
+
.umb-listview .table-striped tbody td {
position: relative
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js
index 93f1ef6c68..fa7a797125 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js
@@ -1,509 +1,518 @@
-//used for the media picker dialog
-angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
- function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) {
-
- var tree = null;
- var dialogOptions = $scope.model;
- $scope.treeReady = false;
- $scope.dialogTreeEventHandler = $({});
- $scope.section = dialogOptions.section;
- $scope.treeAlias = dialogOptions.treeAlias;
- $scope.multiPicker = dialogOptions.multiPicker;
- $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true;
- // if you need to load a not initialized tree set this value to false - default is true
- $scope.onlyInitialized = dialogOptions.onlyInitialized;
- $scope.searchInfo = {
- searchFromId: dialogOptions.startNodeId,
- searchFromName: null,
- showSearch: false,
- results: [],
- selectedSearchResults: []
- }
-
- $scope.model.selection = [];
-
- //Used for toggling an empty-state message
- //Some trees can have no items (dictionary & forms email templates)
- $scope.hasItems = true;
- $scope.emptyStateMessage = dialogOptions.emptyStateMessage;
- var node = dialogOptions.currentNode;
-
- //This is called from ng-init
- //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init.
- //this is probably an anti pattern IMO and shouldn't be used
- $scope.init = function (contentType) {
-
- if (contentType === "content") {
- $scope.entityType = "Document";
- if (!$scope.model.title) {
- $scope.model.title = localizationService.localize("defaultdialogs_selectContent");
- }
- } else if (contentType === "member") {
- $scope.entityType = "Member";
- if (!$scope.model.title) {
- $scope.model.title = localizationService.localize("defaultdialogs_selectMember");
- }
- } else if (contentType === "media") {
- $scope.entityType = "Media";
- if (!$scope.model.title) {
- $scope.model.title = localizationService.localize("defaultdialogs_selectMedia");
- }
- }
- }
-
- var searchText = "Search...";
- localizationService.localize("general_search").then(function (value) {
- searchText = value + "...";
- });
-
- // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
- $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document";
-
-
- //min / max values
- if (dialogOptions.minNumber) {
- dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
- }
- if (dialogOptions.maxNumber) {
- dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
- }
-
- if (dialogOptions.section === "member") {
- $scope.entityType = "Member";
- }
- else if (dialogOptions.section === "media") {
- $scope.entityType = "Media";
- }
-
- // Search and listviews is only working for content, media and member section
- var searchableSections = ["content", "media", "member"];
-
- $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1;
-
- //if a alternative startnode is used, we need to check if it is a container
- if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") {
- entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function(node) {
- if (node.metaData.IsContainer) {
- openMiniListView(node);
- }
- initTree();
- });
- }
- else {
- initTree();
- }
-
- //Configures filtering
- if (dialogOptions.filter) {
-
- dialogOptions.filterExclude = false;
- dialogOptions.filterAdvanced = false;
-
- //used advanced filtering
- if (angular.isFunction(dialogOptions.filter)) {
- dialogOptions.filterAdvanced = true;
- }
- else if (angular.isObject(dialogOptions.filter)) {
- dialogOptions.filterAdvanced = true;
- }
- else {
- if (dialogOptions.filter.startsWith("!")) {
- dialogOptions.filterExclude = true;
- dialogOptions.filter = dialogOptions.filter.substring(1);
- }
-
- //used advanced filtering
- if (dialogOptions.filter.startsWith("{")) {
- dialogOptions.filterAdvanced = true;
- //convert to object
- dialogOptions.filter = angular.fromJson(dialogOptions.filter);
- }
- }
- }
-
- function initTree() {
- //create the custom query string param for this tree
- $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : "";
- $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : "";
- $scope.treeReady = true;
- }
-
- function nodeExpandedHandler(ev, args) {
-
- // open mini list view for list views
- if (args.node.metaData.isContainer) {
- openMiniListView(args.node);
- }
-
- if (angular.isArray(args.children)) {
-
- //iterate children
- _.each(args.children, function (child) {
-
- //now we need to look in the already selected search results and
- // toggle the check boxes for those ones that are listed
- var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
- return child.id == selected.id;
- });
- if (exists) {
- child.selected = true;
- }
- });
-
- //check filter
- performFiltering(args.children);
- }
- }
-
- //gets the tree object when it loads
- function treeLoadedHandler(ev, args) {
- //args.tree contains children (args.tree.root.children)
- $scope.hasItems = args.tree.root.children.length > 0;
-
- tree = args.tree;
-
- var nodeHasPath = typeof node !== "undefined" && typeof node.path !== "undefined";
- var startNodeNotDefined = typeof dialogOptions.startNodeId === "undefined" || dialogOptions.startNodeId === "" || dialogOptions.startNodeId === "-1";
- if (startNodeNotDefined && nodeHasPath) {
- $scope.dialogTreeEventHandler.syncTree({ path: node.path, activate: false });
- }
-
- }
-
- //wires up selection
- function nodeSelectHandler(ev, args) {
- args.event.preventDefault();
- args.event.stopPropagation();
-
- if (args.node.metaData.isSearchResult) {
- //check if the item selected was a search result from a list view
-
- //unselect
- select(args.node.name, args.node.id);
-
- //remove it from the list view children
- var listView = args.node.parent();
- listView.children = _.reject(listView.children, function (child) {
- return child.id == args.node.id;
- });
-
- //remove it from the custom tracked search result list
- $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
- return i.id == args.node.id;
- });
- }
- else {
- eventsService.emit("dialogs.treePickerController.select", args);
-
- if (args.node.filtered) {
- return;
- }
-
- //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
- //from the server in this method.
- if ($scope.model.select) {
- $scope.model.select(args.node)
- } else {
- select(args.node.name, args.node.id);
- //toggle checked state
- args.node.selected = args.node.selected === true ? false : true;
- }
-
- }
- }
-
- /** Method used for selecting a node */
- function select(text, id, entity) {
- //if we get the root, we just return a constructed entity, no need for server data
- if (id < 0) {
-
- var rootNode = {
- alias: null,
- icon: "icon-folder",
- id: id,
- name: text
- };
-
- if ($scope.multiPicker) {
- if (entity) {
- multiSelectItem(entity);
- } else {
- multiSelectItem(rootNode);
- }
- }
- else {
- $scope.model.selection.push(rootNode);
- $scope.model.submit($scope.model);
- }
- }
- else {
-
- if ($scope.multiPicker) {
-
- if (entity) {
- multiSelectItem(entity);
- } else {
- //otherwise we have to get it from the server
- entityResource.getById(id, $scope.entityType).then(function (ent) {
- multiSelectItem(ent);
- });
- }
-
- }
-
- else {
-
- $scope.hideSearch();
-
- //if an entity has been passed in, use it
- if (entity) {
- $scope.model.selection.push(entity);
- $scope.model.submit($scope.model);
- } else {
- //otherwise we have to get it from the server
- entityResource.getById(id, $scope.entityType).then(function (ent) {
- $scope.model.selection.push(ent);
- $scope.model.submit($scope.model);
- });
- }
- }
- }
- }
-
- function multiSelectItem(item) {
-
- var found = false;
- var foundIndex = 0;
-
- if ($scope.model.selection.length > 0) {
- for (i = 0; $scope.model.selection.length > i; i++) {
- var selectedItem = $scope.model.selection[i];
- if (selectedItem.id === item.id) {
- found = true;
- foundIndex = i;
- }
- }
- }
-
- if (found) {
- $scope.model.selection.splice(foundIndex, 1);
- } else {
- $scope.model.selection.push(item);
- }
-
- }
-
- function performFiltering(nodes) {
-
- if (!dialogOptions.filter) {
- return;
- }
-
- //remove any list view search nodes from being filtered since these are special nodes that always must
- // be allowed to be clicked on
- nodes = _.filter(nodes, function (n) {
- return !angular.isObject(n.metaData.listViewNode);
- });
-
- if (dialogOptions.filterAdvanced) {
-
- //filter either based on a method or an object
- var filtered = angular.isFunction(dialogOptions.filter)
- ? _.filter(nodes, dialogOptions.filter)
- : _.where(nodes, dialogOptions.filter);
-
- angular.forEach(filtered, function (value, key) {
- value.filtered = true;
- if (dialogOptions.filterCssClass) {
- if (!value.cssClasses) {
- value.cssClasses = [];
- }
- value.cssClasses.push(dialogOptions.filterCssClass);
- }
- });
- } else {
- var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(',');
- angular.forEach(nodes, function (value, key) {
-
- var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
-
- if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
- value.filtered = true;
-
- if (dialogOptions.filterCssClass) {
- if (!value.cssClasses) {
- value.cssClasses = [];
- }
- value.cssClasses.push(dialogOptions.filterCssClass);
- }
- }
- });
- }
- }
-
- $scope.multiSubmit = function (result) {
- entityResource.getByIds(result, $scope.entityType).then(function (ents) {
- $scope.submit(ents);
- });
- };
-
- /** method to select a search result */
- $scope.selectResult = function (evt, result) {
-
- if (result.filtered) {
- return;
- }
-
- result.selected = result.selected === true ? false : true;
-
- //since result = an entity, we'll pass it in so we don't have to go back to the server
- select(result.name, result.id, result);
-
- //add/remove to our custom tracked list of selected search results
- if (result.selected) {
- $scope.searchInfo.selectedSearchResults.push(result);
- }
- else {
- $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
- return i.id == result.id;
- });
- }
-
- //ensure the tree node in the tree is checked/unchecked if it already exists there
- if (tree) {
- var found = treeService.getDescendantNode(tree.root, result.id);
- if (found) {
- found.selected = result.selected;
- }
- }
-
- };
-
- $scope.hideSearch = function () {
-
- //Traverse the entire displayed tree and update each node to sync with the selected search results
- if (tree) {
-
- //we need to ensure that any currently displayed nodes that get selected
- // from the search get updated to have a check box!
- function checkChildren(children) {
- _.each(children, function (child) {
- //check if the id is in the selection, if so ensure it's flagged as selected
- var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
- return child.id == selected.id;
- });
- //if the curr node exists in selected search results, ensure it's checked
- if (exists) {
- child.selected = true;
- }
- //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
- else if (child.metaData.isSearchResult) {
- //if this tree node is under a list view it means that the node was added
- // to the tree dynamically under the list view that was searched, so we actually want to remove
- // it all together from the tree
- var listView = child.parent();
- listView.children = _.reject(listView.children, function (c) {
- return c.id == child.id;
- });
- }
-
- //check if the current node is a list view and if so, check if there's any new results
- // that need to be added as child nodes to it based on search results selected
- if (child.metaData.isContainer) {
-
- child.cssClasses = _.reject(child.cssClasses, function (c) {
- return c === 'tree-node-slide-up-hide-active';
- });
-
- var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
- return i.parentId == child.id;
- });
- _.each(listViewResults, function (item) {
- var childExists = _.find(child.children, function (c) {
- return c.id == item.id;
- });
- if (!childExists) {
- var parent = child;
- child.children.unshift({
- id: item.id,
- name: item.name,
- cssClass: "icon umb-tree-icon sprTree " + item.icon,
- level: child.level + 1,
- metaData: {
- isSearchResult: true
- },
- hasChildren: false,
- parent: function () {
- return parent;
- }
- });
- }
- });
- }
-
- //recurse
- if (child.children && child.children.length > 0) {
- checkChildren(child.children);
- }
- });
- }
- checkChildren(tree.root.children);
- }
-
-
- $scope.searchInfo.showSearch = false;
- $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
- $scope.searchInfo.searchFromName = null;
- $scope.searchInfo.results = [];
- }
-
- $scope.onSearchResults = function (results) {
-
- //filter all items - this will mark an item as filtered
- performFiltering(results);
-
- //now actually remove all filtered items so they are not even displayed
- results = _.filter(results, function (item) {
- return !item.filtered;
- });
-
- $scope.searchInfo.results = results;
-
- //sync with the curr selected results
- _.each($scope.searchInfo.results, function (result) {
- var exists = _.find($scope.model.selection, function (selectedId) {
- return result.id == selectedId;
- });
- if (exists) {
- result.selected = true;
- }
- });
-
- $scope.searchInfo.showSearch = true;
- };
-
- $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
-
- $scope.$on('$destroy', function () {
- $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- });
-
- $scope.selectListViewNode = function (node) {
- select(node.name, node.id);
- //toggle checked state
- node.selected = node.selected === true ? false : true;
- };
-
- $scope.closeMiniListView = function () {
- $scope.miniListView = undefined;
- };
-
- function openMiniListView(node) {
- $scope.miniListView = node;
- }
-
- });
+//used for the media picker dialog
+angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
+ function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) {
+
+ var tree = null;
+ var dialogOptions = $scope.model;
+ $scope.treeReady = false;
+ $scope.dialogTreeEventHandler = $({});
+ $scope.section = dialogOptions.section;
+ $scope.treeAlias = dialogOptions.treeAlias;
+ $scope.multiPicker = dialogOptions.multiPicker;
+ $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true;
+ // if you need to load a not initialized tree set this value to false - default is true
+ $scope.onlyInitialized = dialogOptions.onlyInitialized;
+ $scope.searchInfo = {
+ searchFromId: dialogOptions.startNodeId,
+ searchFromName: null,
+ showSearch: false,
+ results: [],
+ selectedSearchResults: []
+ }
+
+ $scope.model.selection = [];
+
+ //Used for toggling an empty-state message
+ //Some trees can have no items (dictionary & forms email templates)
+ $scope.hasItems = true;
+ $scope.emptyStateMessage = dialogOptions.emptyStateMessage;
+ var node = dialogOptions.currentNode;
+
+ //This is called from ng-init
+ //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init.
+ //this is probably an anti pattern IMO and shouldn't be used
+ $scope.init = function (contentType) {
+
+ if (contentType === "content") {
+ $scope.entityType = "Document";
+ if (!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectContent");
+ }
+ } else if (contentType === "member") {
+ $scope.entityType = "Member";
+ if (!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectMember");
+ }
+ } else if (contentType === "media") {
+ $scope.entityType = "Media";
+ if (!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectMedia");
+ }
+ }
+ }
+
+ var searchText = "Search...";
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
+
+ // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
+ $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document";
+
+
+ //min / max values
+ if (dialogOptions.minNumber) {
+ dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
+ }
+ if (dialogOptions.maxNumber) {
+ dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
+ }
+
+ if (dialogOptions.section === "member") {
+ $scope.entityType = "Member";
+ }
+ else if (dialogOptions.section === "media") {
+ $scope.entityType = "Media";
+ }
+
+ // Search and listviews is only working for content, media and member section
+ var searchableSections = ["content", "media", "member"];
+
+ $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1;
+
+ //if a alternative startnode is used, we need to check if it is a container
+ if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") {
+ entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function(node) {
+ if (node.metaData.IsContainer) {
+ openMiniListView(node);
+ }
+ initTree();
+ });
+ }
+ else {
+ initTree();
+ }
+
+ //Configures filtering
+ if (dialogOptions.filter) {
+
+ dialogOptions.filterExclude = false;
+ dialogOptions.filterAdvanced = false;
+
+ //used advanced filtering
+ if (angular.isFunction(dialogOptions.filter)) {
+ dialogOptions.filterAdvanced = true;
+ }
+ else if (angular.isObject(dialogOptions.filter)) {
+ dialogOptions.filterAdvanced = true;
+ }
+ else {
+ if (dialogOptions.filter.startsWith("!")) {
+ dialogOptions.filterExclude = true;
+ dialogOptions.filterTypes = dialogOptions.filter.substring(1);
+ } else {
+ dialogOptions.filterExclude = false;
+ dialogOptions.filterTypes = dialogOptions.filter;
+ }
+
+ //used advanced filtering
+ if (dialogOptions.filter.startsWith("{")) {
+ dialogOptions.filterAdvanced = true;
+ //convert to object
+ dialogOptions.filter = angular.fromJson(dialogOptions.filter);
+ }
+ }
+
+ $scope.filter = {
+ filterAdvanced: dialogOptions.filterAdvanced,
+ filterExclude: dialogOptions.filterExclude,
+ filter: dialogOptions.filterTypes
+ };
+ }
+
+ function initTree() {
+ //create the custom query string param for this tree
+ $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : "";
+ $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : "";
+ $scope.treeReady = true;
+ }
+
+ function nodeExpandedHandler(ev, args) {
+
+ // open mini list view for list views
+ if (args.node.metaData.isContainer) {
+ openMiniListView(args.node);
+ }
+
+ if (angular.isArray(args.children)) {
+
+ //iterate children
+ _.each(args.children, function (child) {
+
+ //now we need to look in the already selected search results and
+ // toggle the check boxes for those ones that are listed
+ var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
+ return child.id == selected.id;
+ });
+ if (exists) {
+ child.selected = true;
+ }
+ });
+
+ //check filter
+ performFiltering(args.children);
+ }
+ }
+
+ //gets the tree object when it loads
+ function treeLoadedHandler(ev, args) {
+ //args.tree contains children (args.tree.root.children)
+ $scope.hasItems = args.tree.root.children.length > 0;
+
+ tree = args.tree;
+
+ var nodeHasPath = typeof node !== "undefined" && typeof node.path !== "undefined";
+ var startNodeNotDefined = typeof dialogOptions.startNodeId === "undefined" || dialogOptions.startNodeId === "" || dialogOptions.startNodeId === "-1";
+ if (startNodeNotDefined && nodeHasPath) {
+ $scope.dialogTreeEventHandler.syncTree({ path: node.path, activate: false });
+ }
+
+ }
+
+ //wires up selection
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if (args.node.metaData.isSearchResult) {
+ //check if the item selected was a search result from a list view
+
+ //unselect
+ select(args.node.name, args.node.id);
+
+ //remove it from the list view children
+ var listView = args.node.parent();
+ listView.children = _.reject(listView.children, function (child) {
+ return child.id == args.node.id;
+ });
+
+ //remove it from the custom tracked search result list
+ $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
+ return i.id == args.node.id;
+ });
+ }
+ else {
+ eventsService.emit("dialogs.treePickerController.select", args);
+
+ if (args.node.filtered) {
+ return;
+ }
+
+ //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
+ //from the server in this method.
+ if ($scope.model.select) {
+ $scope.model.select(args.node)
+ } else {
+ select(args.node.name, args.node.id);
+ //toggle checked state
+ args.node.selected = args.node.selected === true ? false : true;
+ }
+
+ }
+ }
+
+ /** Method used for selecting a node */
+ function select(text, id, entity) {
+ //if we get the root, we just return a constructed entity, no need for server data
+ if (id < 0) {
+
+ var rootNode = {
+ alias: null,
+ icon: "icon-folder",
+ id: id,
+ name: text
+ };
+
+ if ($scope.multiPicker) {
+ if (entity) {
+ multiSelectItem(entity);
+ } else {
+ multiSelectItem(rootNode);
+ }
+ }
+ else {
+ $scope.model.selection.push(rootNode);
+ $scope.model.submit($scope.model);
+ }
+ }
+ else {
+
+ if ($scope.multiPicker) {
+
+ if (entity) {
+ multiSelectItem(entity);
+ } else {
+ //otherwise we have to get it from the server
+ entityResource.getById(id, $scope.entityType).then(function (ent) {
+ multiSelectItem(ent);
+ });
+ }
+
+ }
+
+ else {
+
+ $scope.hideSearch();
+
+ //if an entity has been passed in, use it
+ if (entity) {
+ $scope.model.selection.push(entity);
+ $scope.model.submit($scope.model);
+ } else {
+ //otherwise we have to get it from the server
+ entityResource.getById(id, $scope.entityType).then(function (ent) {
+ $scope.model.selection.push(ent);
+ $scope.model.submit($scope.model);
+ });
+ }
+ }
+ }
+ }
+
+ function multiSelectItem(item) {
+
+ var found = false;
+ var foundIndex = 0;
+
+ if ($scope.model.selection.length > 0) {
+ for (i = 0; $scope.model.selection.length > i; i++) {
+ var selectedItem = $scope.model.selection[i];
+ if (selectedItem.id === item.id) {
+ found = true;
+ foundIndex = i;
+ }
+ }
+ }
+
+ if (found) {
+ $scope.model.selection.splice(foundIndex, 1);
+ } else {
+ $scope.model.selection.push(item);
+ }
+
+ }
+
+ function performFiltering(nodes) {
+
+ if (!dialogOptions.filter) {
+ return;
+ }
+
+ //remove any list view search nodes from being filtered since these are special nodes that always must
+ // be allowed to be clicked on
+ nodes = _.filter(nodes, function (n) {
+ return !angular.isObject(n.metaData.listViewNode);
+ });
+
+ if (dialogOptions.filterAdvanced) {
+
+ //filter either based on a method or an object
+ var filtered = angular.isFunction(dialogOptions.filter)
+ ? _.filter(nodes, dialogOptions.filter)
+ : _.where(nodes, dialogOptions.filter);
+
+ angular.forEach(filtered, function (value, key) {
+ value.filtered = true;
+ if (dialogOptions.filterCssClass) {
+ if (!value.cssClasses) {
+ value.cssClasses = [];
+ }
+ value.cssClasses.push(dialogOptions.filterCssClass);
+ }
+ });
+ } else {
+ var a = dialogOptions.filterTypes.toLowerCase().replace(/\s/g, '').split(',');
+ angular.forEach(nodes, function (value, key) {
+
+ var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
+
+ if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
+ value.filtered = true;
+
+ if (dialogOptions.filterCssClass) {
+ if (!value.cssClasses) {
+ value.cssClasses = [];
+ }
+ value.cssClasses.push(dialogOptions.filterCssClass);
+ }
+ }
+ });
+ }
+ }
+
+ $scope.multiSubmit = function (result) {
+ entityResource.getByIds(result, $scope.entityType).then(function (ents) {
+ $scope.submit(ents);
+ });
+ };
+
+ /** method to select a search result */
+ $scope.selectResult = function (evt, result) {
+
+ if (result.filtered) {
+ return;
+ }
+
+ result.selected = result.selected === true ? false : true;
+
+ //since result = an entity, we'll pass it in so we don't have to go back to the server
+ select(result.name, result.id, result);
+
+ //add/remove to our custom tracked list of selected search results
+ if (result.selected) {
+ $scope.searchInfo.selectedSearchResults.push(result);
+ }
+ else {
+ $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
+ return i.id == result.id;
+ });
+ }
+
+ //ensure the tree node in the tree is checked/unchecked if it already exists there
+ if (tree) {
+ var found = treeService.getDescendantNode(tree.root, result.id);
+ if (found) {
+ found.selected = result.selected;
+ }
+ }
+
+ };
+
+ $scope.hideSearch = function () {
+
+ //Traverse the entire displayed tree and update each node to sync with the selected search results
+ if (tree) {
+
+ //we need to ensure that any currently displayed nodes that get selected
+ // from the search get updated to have a check box!
+ function checkChildren(children) {
+ _.each(children, function (child) {
+ //check if the id is in the selection, if so ensure it's flagged as selected
+ var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
+ return child.id == selected.id;
+ });
+ //if the curr node exists in selected search results, ensure it's checked
+ if (exists) {
+ child.selected = true;
+ }
+ //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
+ else if (child.metaData.isSearchResult) {
+ //if this tree node is under a list view it means that the node was added
+ // to the tree dynamically under the list view that was searched, so we actually want to remove
+ // it all together from the tree
+ var listView = child.parent();
+ listView.children = _.reject(listView.children, function (c) {
+ return c.id == child.id;
+ });
+ }
+
+ //check if the current node is a list view and if so, check if there's any new results
+ // that need to be added as child nodes to it based on search results selected
+ if (child.metaData.isContainer) {
+
+ child.cssClasses = _.reject(child.cssClasses, function (c) {
+ return c === 'tree-node-slide-up-hide-active';
+ });
+
+ var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
+ return i.parentId == child.id;
+ });
+ _.each(listViewResults, function (item) {
+ var childExists = _.find(child.children, function (c) {
+ return c.id == item.id;
+ });
+ if (!childExists) {
+ var parent = child;
+ child.children.unshift({
+ id: item.id,
+ name: item.name,
+ cssClass: "icon umb-tree-icon sprTree " + item.icon,
+ level: child.level + 1,
+ metaData: {
+ isSearchResult: true
+ },
+ hasChildren: false,
+ parent: function () {
+ return parent;
+ }
+ });
+ }
+ });
+ }
+
+ //recurse
+ if (child.children && child.children.length > 0) {
+ checkChildren(child.children);
+ }
+ });
+ }
+ checkChildren(tree.root.children);
+ }
+
+
+ $scope.searchInfo.showSearch = false;
+ $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
+ $scope.searchInfo.searchFromName = null;
+ $scope.searchInfo.results = [];
+ }
+
+ $scope.onSearchResults = function (results) {
+
+ //filter all items - this will mark an item as filtered
+ performFiltering(results);
+
+ //now actually remove all filtered items so they are not even displayed
+ results = _.filter(results, function (item) {
+ return !item.filtered;
+ });
+
+ $scope.searchInfo.results = results;
+
+ //sync with the curr selected results
+ _.each($scope.searchInfo.results, function (result) {
+ var exists = _.find($scope.model.selection, function (selectedId) {
+ return result.id == selectedId;
+ });
+ if (exists) {
+ result.selected = true;
+ }
+ });
+
+ $scope.searchInfo.showSearch = true;
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+
+ $scope.selectListViewNode = function (node) {
+ select(node.name, node.id);
+ //toggle checked state
+ node.selected = node.selected === true ? false : true;
+ };
+
+ $scope.closeMiniListView = function () {
+ $scope.miniListView = undefined;
+ };
+
+ function openMiniListView(node) {
+ $scope.miniListView = node;
+ }
+
+ });
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html
index ab5e61339f..a3a5fd6107 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html
@@ -22,7 +22,7 @@