Change NestedContent property editor controller into a component
This commit is contained in:
@@ -1,224 +1,67 @@
|
||||
angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
"$scope",
|
||||
"Umbraco.PropertyEditors.NestedContent.Resources",
|
||||
"overlayService",
|
||||
"localizationService",
|
||||
"iconHelper",
|
||||
|
||||
function ($scope, ncResources, overlayService, localizationService, iconHelper) {
|
||||
var selectElementTypeModalTitle = "";
|
||||
|
||||
$scope.elemTypeTabs = [];
|
||||
|
||||
|
||||
init();
|
||||
|
||||
|
||||
function init() {
|
||||
localizationService.localize("content_nestedContentSelectElementTypeModalTitle").then(function (value) {
|
||||
selectElementTypeModalTitle = value;
|
||||
});
|
||||
|
||||
ncResources.getContentTypes().then(function (elemTypes) {
|
||||
$scope.model.elemTypes = elemTypes;
|
||||
|
||||
// convert legacy icons
|
||||
iconHelper.formatContentTypeIcons($scope.model.elemTypes);
|
||||
|
||||
// Count doctype name occurrences
|
||||
var elTypeNameOccurrences= _.countBy(elemTypes, 'name');
|
||||
|
||||
// Populate document type tab dictionary
|
||||
// And append alias to name if multiple doctypes have the same name
|
||||
elemTypes.forEach(function (value) {
|
||||
$scope.elemTypeTabs[value.alias] = value.tabs;
|
||||
|
||||
if (elTypeNameOccurrences[value.name] > 1) {
|
||||
value.name += " (" + value.alias + ")";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
$scope.add = function () {
|
||||
$scope.model.value.push({
|
||||
// As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
|
||||
// For good measure we'll also prefix the tab alias "nc"
|
||||
ncAlias: "",
|
||||
ncTabAlias: "",
|
||||
nameTemplate: ""
|
||||
});
|
||||
}
|
||||
|
||||
$scope.canAdd = function () {
|
||||
return !$scope.model.docTypes || !$scope.model.value || $scope.model.value.length < $scope.model.docTypes.length;
|
||||
}
|
||||
|
||||
$scope.remove = function (index) {
|
||||
$scope.model.value.splice(index, 1);
|
||||
}
|
||||
|
||||
$scope.sortableOptions = {
|
||||
axis: "y",
|
||||
cursor: "move",
|
||||
handle: ".handle",
|
||||
placeholder: 'sortable-placeholder',
|
||||
forcePlaceholderSize: true,
|
||||
helper: function (e, ui) {
|
||||
// When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
|
||||
ui.children().each(function () {
|
||||
$(this).width($(this).width());
|
||||
});
|
||||
return ui;
|
||||
},
|
||||
start: function (e, ui) {
|
||||
|
||||
var cellHeight = ui.item.height();
|
||||
|
||||
// Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size
|
||||
var cellCount = 0;
|
||||
$('td, th', ui.helper).each(function () {
|
||||
// For each td or th try and get it's colspan attribute, and add that or 1 to the total
|
||||
var colspan = 1;
|
||||
var colspanAttr = $(this).attr('colspan');
|
||||
if (colspanAttr > 1) {
|
||||
colspan = colspanAttr;
|
||||
}
|
||||
cellCount += colspan;
|
||||
});
|
||||
|
||||
// Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr
|
||||
ui.placeholder.html('<td colspan="' + cellCount + '"></td>').height(cellHeight);
|
||||
angular
|
||||
.module('umbraco')
|
||||
.component('nestedContentPropertyEditor', {
|
||||
templateUrl: 'views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html',
|
||||
controller: NestedContentController,
|
||||
controllerAs: 'vm',
|
||||
bindings: {
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
$scope.placeholder = function (config) {
|
||||
return _.find($scope.model.elemTypes, function (elType) {
|
||||
return elType.alias === config.ncAlias;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.selectableElemTypesFor = function (config) {
|
||||
// return all elemTypes that are:
|
||||
// 1. either already selected for this config, or
|
||||
// 2. not selected in any other config
|
||||
return _.filter($scope.model.elemTypes, function (elType) {
|
||||
return elType.alias === config.ncAlias || !_.find($scope.model.value, function (c) {
|
||||
return elType.alias === c.ncAlias;
|
||||
});
|
||||
});
|
||||
}
|
||||
$scope.canAdd = function () {
|
||||
return !$scope.model.value || _.some($scope.model.elemTypes, function (elType) {
|
||||
return !_.find($scope.model.value, function (c) {
|
||||
return elType.alias === c.ncAlias;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$scope.openElemTypeModal = function ($event, config) {
|
||||
|
||||
//we have to add the alias to the objects (they are stored as ncAlias)
|
||||
var selectedItems = _.each($scope.model.value, function (obj) {
|
||||
obj.alias = obj.ncAlias;
|
||||
return obj;
|
||||
})
|
||||
|
||||
var elemTypeSelectorOverlay = {
|
||||
view: "itempicker",
|
||||
title: selectElementTypeModalTitle,
|
||||
availableItems: $scope.selectableElemTypesFor(config),
|
||||
selectedItems: selectedItems,
|
||||
position: "target",
|
||||
event: $event,
|
||||
submit: function (model) {
|
||||
config.ncAlias = model.selectedItem.alias;
|
||||
overlayService.close();
|
||||
},
|
||||
close: function () {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
overlayService.open(elemTypeSelectorOverlay);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!$scope.model.value) {
|
||||
$scope.model.value = [];
|
||||
$scope.add();
|
||||
}
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.PropertyEditorController", [
|
||||
|
||||
"$scope",
|
||||
"$interpolate",
|
||||
"$filter",
|
||||
"$timeout",
|
||||
"contentResource",
|
||||
"localizationService",
|
||||
"iconHelper",
|
||||
"clipboardService",
|
||||
"eventsService",
|
||||
"overlayService",
|
||||
"$routeParams",
|
||||
"editorState",
|
||||
"propertyEditorService",
|
||||
|
||||
function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState, propertyEditorService) {
|
||||
function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState, propertyEditorService) {
|
||||
|
||||
var vm = this;
|
||||
var model = $scope.$parent.$parent.model;
|
||||
|
||||
var contentTypeAliases = [];
|
||||
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||
_.each(model.config.contentTypes, function (contentType) {
|
||||
contentTypeAliases.push(contentType.ncAlias);
|
||||
});
|
||||
|
||||
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||
_.each(model.config.contentTypes, function (contentType) {
|
||||
contentType.nameExp = !!contentType.nameTemplate
|
||||
? $interpolate(contentType.nameTemplate)
|
||||
: undefined;
|
||||
});
|
||||
|
||||
$scope.nodes = [];
|
||||
$scope.currentNode = undefined;
|
||||
$scope.realCurrentNode = undefined;
|
||||
$scope.scaffolds = undefined;
|
||||
$scope.sorting = false;
|
||||
$scope.inited = false;
|
||||
vm.nodes = [];
|
||||
vm.currentNode = null;
|
||||
vm.scaffolds = null;
|
||||
vm.sorting = false;
|
||||
vm.inited = false;
|
||||
|
||||
$scope.minItems = $scope.model.config.minItems || 0;
|
||||
$scope.maxItems = $scope.model.config.maxItems || 0;
|
||||
vm.minItems = model.config.minItems || 0;
|
||||
vm.maxItems = model.config.maxItems || 0;
|
||||
|
||||
if ($scope.maxItems === 0)
|
||||
$scope.maxItems = 1000;
|
||||
if (vm.maxItems === 0)
|
||||
vm.maxItems = 1000;
|
||||
|
||||
$scope.singleMode = $scope.minItems === 1 && $scope.maxItems === 1;
|
||||
$scope.showIcons = Object.toBoolean($scope.model.config.showIcons);
|
||||
$scope.wideMode = Object.toBoolean($scope.model.config.hideLabel);
|
||||
$scope.hasContentTypes = $scope.model.config.contentTypes.length > 0;
|
||||
vm.singleMode = vm.minItems === 1 && vm.maxItems === 1;
|
||||
vm.showIcons = Object.toBoolean(model.config.showIcons);
|
||||
vm.wideMode = Object.toBoolean(model.config.hideLabel);
|
||||
vm.hasContentTypes = model.config.contentTypes.length > 0;
|
||||
|
||||
$scope.labels = {};
|
||||
var labels = {};
|
||||
localizationService.localizeMany(["grid_addElement", "content_createEmpty"]).then(function (data) {
|
||||
$scope.labels.grid_addElement = data[0];
|
||||
$scope.labels.content_createEmpty = data[1];
|
||||
labels.grid_addElement = data[0];
|
||||
labels.content_createEmpty = data[1];
|
||||
});
|
||||
|
||||
|
||||
function setCurrentNode(node) {
|
||||
vm.currentNode = node;
|
||||
updateModel();
|
||||
}
|
||||
|
||||
var copyAllEntries = function() {
|
||||
|
||||
syncCurrentNode();
|
||||
|
||||
// list aliases
|
||||
var aliases = $scope.nodes.map((node) => node.contentTypeAlias);
|
||||
var aliases = vm.nodes.map((node) => node.contentTypeAlias);
|
||||
|
||||
// remove dublicates
|
||||
aliases = aliases.filter((item, index) => aliases.indexOf(item) === index);
|
||||
@@ -229,14 +72,14 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
return !v.language || v.language.culture === culture;
|
||||
});
|
||||
|
||||
localizationService.localize("clipboard_labelForArrayOfItemsFrom", [$scope.model.label, activeVariant.name]).then(function(data) {
|
||||
clipboardService.copyArray("elementTypeArray", aliases, $scope.nodes, data, "icon-thumbnail-list", $scope.model.id);
|
||||
localizationService.localize("clipboard_labelForArrayOfItemsFrom", [model.label, activeVariant.name]).then(function(data) {
|
||||
clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id);
|
||||
});
|
||||
}
|
||||
|
||||
var copyAllEntriesAction = {
|
||||
labelKey: 'clipboard_labelForCopyAllEntries',
|
||||
labelTokens: [$scope.model.label],
|
||||
labelTokens: [model.label],
|
||||
icon: 'documents',
|
||||
method: copyAllEntries,
|
||||
isDisabled: true
|
||||
@@ -245,78 +88,78 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
|
||||
|
||||
// helper to force the current form into the dirty state
|
||||
$scope.setDirty = function () {
|
||||
if ($scope.propertyForm) {
|
||||
$scope.propertyForm.$setDirty();
|
||||
function setDirty() {
|
||||
if ($scope.$parent.$parent.propertyForm) {
|
||||
$scope.$parent.$parent.propertyForm.$setDirty();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addNode = function (alias) {
|
||||
var scaffold = $scope.getScaffold(alias);
|
||||
function addNode(alias) {
|
||||
var scaffold = getScaffold(alias);
|
||||
|
||||
var newNode = createNode(scaffold, null);
|
||||
|
||||
$scope.currentNode = newNode;
|
||||
$scope.setDirty();
|
||||
setCurrentNode(newNode);
|
||||
setDirty();
|
||||
};
|
||||
|
||||
$scope.openNodeTypePicker = function ($event) {
|
||||
if ($scope.nodes.length >= $scope.maxItems) {
|
||||
vm.openNodeTypePicker = function ($event) {
|
||||
if (vm.nodes.length >= vm.maxItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.overlayMenu = {
|
||||
vm.overlayMenu = {
|
||||
show: false,
|
||||
style: {},
|
||||
filter: $scope.scaffolds.length > 12 ? true : false,
|
||||
filter: vm.scaffolds.length > 12 ? true : false,
|
||||
orderBy: "$index",
|
||||
view: "itempicker",
|
||||
event: $event,
|
||||
clickPasteItem: function(item) {
|
||||
if (item.type === "elementTypeArray") {
|
||||
_.each(item.data, function (entry) {
|
||||
$scope.pasteFromClipboard(entry);
|
||||
pasteFromClipboard(entry);
|
||||
});
|
||||
} else {
|
||||
$scope.pasteFromClipboard(item.data);
|
||||
pasteFromClipboard(item.data);
|
||||
}
|
||||
$scope.overlayMenu.show = false;
|
||||
$scope.overlayMenu = null;
|
||||
vm.overlayMenu.show = false;
|
||||
vm.overlayMenu = null;
|
||||
},
|
||||
submit: function (model) {
|
||||
if (model && model.selectedItem) {
|
||||
$scope.addNode(model.selectedItem.alias);
|
||||
addNode(model.selectedItem.alias);
|
||||
}
|
||||
$scope.overlayMenu.show = false;
|
||||
$scope.overlayMenu = null;
|
||||
vm.overlayMenu.show = false;
|
||||
vm.overlayMenu = null;
|
||||
},
|
||||
close: function () {
|
||||
$scope.overlayMenu.show = false;
|
||||
$scope.overlayMenu = null;
|
||||
vm.overlayMenu.show = false;
|
||||
vm.overlayMenu = null;
|
||||
}
|
||||
};
|
||||
|
||||
// this could be used for future limiting on node types
|
||||
$scope.overlayMenu.availableItems = [];
|
||||
_.each($scope.scaffolds, function (scaffold) {
|
||||
$scope.overlayMenu.availableItems.push({
|
||||
vm.overlayMenu.availableItems = [];
|
||||
_.each(vm.scaffolds, function (scaffold) {
|
||||
vm.overlayMenu.availableItems.push({
|
||||
alias: scaffold.contentTypeAlias,
|
||||
name: scaffold.contentTypeName,
|
||||
icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
|
||||
});
|
||||
});
|
||||
|
||||
if ($scope.overlayMenu.availableItems.length === 0) {
|
||||
if (vm.overlayMenu.availableItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.overlayMenu.size = $scope.overlayMenu.availableItems.length > 6 ? "medium" : "small";
|
||||
vm.overlayMenu.size = vm.overlayMenu.availableItems.length > 6 ? "medium" : "small";
|
||||
|
||||
$scope.overlayMenu.pasteItems = [];
|
||||
vm.overlayMenu.pasteItems = [];
|
||||
|
||||
var singleEntriesForPaste = clipboardService.retriveEntriesOfType("elementType", contentTypeAliases);
|
||||
_.each(singleEntriesForPaste, function (entry) {
|
||||
$scope.overlayMenu.pasteItems.push({
|
||||
vm.overlayMenu.pasteItems.push({
|
||||
type: "elementType",
|
||||
name: entry.label,
|
||||
data: entry.data,
|
||||
@@ -326,7 +169,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
|
||||
var arrayEntriesForPaste = clipboardService.retriveEntriesOfType("elementTypeArray", contentTypeAliases);
|
||||
_.each(arrayEntriesForPaste, function (entry) {
|
||||
$scope.overlayMenu.pasteItems.push({
|
||||
vm.overlayMenu.pasteItems.push({
|
||||
type: "elementTypeArray",
|
||||
name: entry.label,
|
||||
data: entry.data,
|
||||
@@ -334,40 +177,40 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
});
|
||||
});
|
||||
|
||||
$scope.overlayMenu.title = $scope.overlayMenu.pasteItems.length > 0 ? $scope.labels.grid_addElement : $scope.labels.content_createEmpty;
|
||||
vm.overlayMenu.title = vm.overlayMenu.pasteItems.length > 0 ? labels.grid_addElement : labels.content_createEmpty;
|
||||
|
||||
$scope.overlayMenu.clickClearPaste = function ($event) {
|
||||
vm.overlayMenu.clickClearPaste = function ($event) {
|
||||
$event.stopPropagation();
|
||||
$event.preventDefault();
|
||||
clipboardService.clearEntriesOfType("elementType", contentTypeAliases);
|
||||
clipboardService.clearEntriesOfType("elementTypeArray", contentTypeAliases);
|
||||
$scope.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually.
|
||||
vm.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually.
|
||||
};
|
||||
|
||||
if ($scope.overlayMenu.availableItems.length === 1 && $scope.overlayMenu.pasteItems.length === 0) {
|
||||
if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) {
|
||||
// only one scaffold type - no need to display the picker
|
||||
$scope.addNode($scope.scaffolds[0].contentTypeAlias);
|
||||
addNode(vm.scaffolds[0].contentTypeAlias);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.overlayMenu.show = true;
|
||||
vm.overlayMenu.show = true;
|
||||
};
|
||||
|
||||
$scope.editNode = function (idx) {
|
||||
if ($scope.currentNode && $scope.currentNode.key === $scope.nodes[idx].key) {
|
||||
$scope.currentNode = undefined;
|
||||
vm.editNode = function (idx) {
|
||||
if (vm.currentNode && vm.currentNode.key === vm.nodes[idx].key) {
|
||||
setCurrentNode(null);
|
||||
} else {
|
||||
$scope.currentNode = $scope.nodes[idx];
|
||||
setCurrentNode(vm.nodes[idx]);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.deleteNode = function (idx) {
|
||||
$scope.nodes.splice(idx, 1);
|
||||
$scope.setDirty();
|
||||
function deleteNode(idx) {
|
||||
vm.nodes.splice(idx, 1);
|
||||
setDirty();
|
||||
updateModel();
|
||||
};
|
||||
$scope.requestDeleteNode = function (idx) {
|
||||
if ($scope.model.config.confirmDeletes === true) {
|
||||
vm.requestDeleteNode = function (idx) {
|
||||
if (model.config.confirmDeletes === true) {
|
||||
localizationService.localizeMany(["content_nestedContentDeleteItem", "general_delete", "general_cancel", "contentTypeEditor_yesDelete"]).then(function (data) {
|
||||
const overlay = {
|
||||
title: data[1],
|
||||
@@ -379,7 +222,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
overlayService.close();
|
||||
},
|
||||
submit: function () {
|
||||
$scope.deleteNode(idx);
|
||||
deleteNode(idx);
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
@@ -387,23 +230,23 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
overlayService.open(overlay);
|
||||
});
|
||||
} else {
|
||||
$scope.deleteNode(idx);
|
||||
deleteNode(idx);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getName = function (idx) {
|
||||
vm.getName = function (idx) {
|
||||
|
||||
var name = "";
|
||||
|
||||
if ($scope.model.value[idx]) {
|
||||
if (model.value[idx]) {
|
||||
|
||||
var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias);
|
||||
var contentType = getContentTypeConfig(model.value[idx].ncContentTypeAlias);
|
||||
|
||||
if (contentType != null) {
|
||||
// first try getting a name using the configured label template
|
||||
if (contentType.nameExp) {
|
||||
// Run the expression against the stored dictionary value, NOT the node object
|
||||
var item = $scope.model.value[idx];
|
||||
var item = model.value[idx];
|
||||
|
||||
// Add a temporary index property
|
||||
item["$index"] = (idx + 1);
|
||||
@@ -418,8 +261,8 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
}
|
||||
|
||||
// if we still do not have a name and we have multiple content types to choose from, use the content type name (same as is shown in the content type picker)
|
||||
if (!name && $scope.scaffolds.length > 1) {
|
||||
var scaffold = $scope.getScaffold(contentType.ncAlias);
|
||||
if (!name && vm.scaffolds.length > 1) {
|
||||
var scaffold = getScaffold(contentType.ncAlias);
|
||||
if (scaffold) {
|
||||
name = scaffold.contentTypeName;
|
||||
}
|
||||
@@ -433,18 +276,19 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
}
|
||||
|
||||
// Update the nodes actual name value
|
||||
if ($scope.nodes[idx].name !== name) {
|
||||
$scope.nodes[idx].name = name;
|
||||
if (vm.nodes[idx].name !== name) {
|
||||
vm.nodes[idx].name = name;
|
||||
}
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
$scope.getIcon = function (idx) {
|
||||
var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
|
||||
vm.getIcon = function (idx) {
|
||||
var scaffold = getScaffold(model.value[idx].ncContentTypeAlias);
|
||||
return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
|
||||
}
|
||||
$scope.sortableOptions = {
|
||||
|
||||
vm.sortableOptions = {
|
||||
axis: "y",
|
||||
cursor: "move",
|
||||
handle: '.umb-nested-content__header-bar',
|
||||
@@ -455,46 +299,45 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
start: function (ev, ui) {
|
||||
updateModel();
|
||||
// Yea, yea, we shouldn't modify the dom, sue me
|
||||
$("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
|
||||
$("#umb-nested-content--" + model.id + " .umb-rte textarea").each(function () {
|
||||
tinymce.execCommand("mceRemoveEditor", false, $(this).attr("id"));
|
||||
$(this).css("visibility", "hidden");
|
||||
});
|
||||
$scope.$apply(function () {
|
||||
$scope.sorting = true;
|
||||
vm.sorting = true;
|
||||
});
|
||||
},
|
||||
update: function (ev, ui) {
|
||||
$scope.setDirty();
|
||||
setDirty();
|
||||
},
|
||||
stop: function (ev, ui) {
|
||||
$("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
|
||||
$("#umb-nested-content--" + model.id + " .umb-rte textarea").each(function () {
|
||||
tinymce.execCommand("mceAddEditor", true, $(this).attr("id"));
|
||||
$(this).css("visibility", "visible");
|
||||
});
|
||||
$scope.$apply(function () {
|
||||
$scope.sorting = false;
|
||||
vm.sorting = false;
|
||||
updateModel();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getScaffold = function (alias) {
|
||||
return _.find($scope.scaffolds, function (scaffold) {
|
||||
function getScaffold(alias) {
|
||||
return _.find(vm.scaffolds, function (scaffold) {
|
||||
return scaffold.contentTypeAlias === alias;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.getContentTypeConfig = function (alias) {
|
||||
return _.find($scope.model.config.contentTypes, function (contentType) {
|
||||
function getContentTypeConfig(alias) {
|
||||
return _.find(model.config.contentTypes, function (contentType) {
|
||||
return contentType.ncAlias === alias;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.showCopy = clipboardService.isSupported();
|
||||
vm.showCopy = clipboardService.isSupported();
|
||||
vm.showPaste = false;
|
||||
|
||||
$scope.showPaste = false;
|
||||
|
||||
$scope.clickCopy = function ($event, node) {
|
||||
vm.clickCopy = function ($event, node) {
|
||||
|
||||
syncCurrentNode();
|
||||
|
||||
@@ -503,7 +346,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
}
|
||||
|
||||
|
||||
$scope.pasteFromClipboard = function(newNode) {
|
||||
function pasteFromClipboard(newNode) {
|
||||
|
||||
if (newNode === undefined) {
|
||||
return;
|
||||
@@ -512,15 +355,15 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
// generate a new key.
|
||||
newNode.key = String.CreateGuid();
|
||||
|
||||
$scope.nodes.push(newNode);
|
||||
$scope.setDirty();
|
||||
vm.nodes.push(newNode);
|
||||
setDirty();
|
||||
//updateModel();// done by setting current node...
|
||||
|
||||
$scope.currentNode = newNode;
|
||||
setCurrentNode(newNode);
|
||||
}
|
||||
|
||||
function checkAbilityToPasteContent() {
|
||||
$scope.showPaste = clipboardService.hasEntriesOfType("elementType", contentTypeAliases) || clipboardService.hasEntriesOfType("elementTypeArray", contentTypeAliases);
|
||||
vm.showPaste = clipboardService.hasEntriesOfType("elementType", contentTypeAliases) || clipboardService.hasEntriesOfType("elementTypeArray", contentTypeAliases);
|
||||
}
|
||||
|
||||
eventsService.on("clipboardService.storageUpdate", checkAbilityToPasteContent);
|
||||
@@ -533,8 +376,8 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
|
||||
// Initialize
|
||||
var scaffoldsLoaded = 0;
|
||||
$scope.scaffolds = [];
|
||||
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||
vm.scaffolds = [];
|
||||
_.each(model.config.contentTypes, function (contentType) {
|
||||
contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
|
||||
// make sure it's an element type before allowing the user to create new ones
|
||||
if (scaffold.isElement) {
|
||||
@@ -558,7 +401,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
}
|
||||
|
||||
// Store the scaffold object
|
||||
$scope.scaffolds.push(scaffold);
|
||||
vm.scaffolds.push(scaffold);
|
||||
}
|
||||
|
||||
scaffoldsLoaded++;
|
||||
@@ -571,22 +414,22 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
|
||||
var initIfAllScaffoldsHaveLoaded = function () {
|
||||
// Initialize when all scaffolds have loaded
|
||||
if ($scope.model.config.contentTypes.length === scaffoldsLoaded) {
|
||||
if (model.config.contentTypes.length === scaffoldsLoaded) {
|
||||
// Because we're loading the scaffolds async one at a time, we need to
|
||||
// sort them explicitly according to the sort order defined by the data type.
|
||||
contentTypeAliases = [];
|
||||
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||
_.each(model.config.contentTypes, function (contentType) {
|
||||
contentTypeAliases.push(contentType.ncAlias);
|
||||
});
|
||||
$scope.scaffolds = $filter("orderBy")($scope.scaffolds, function (s) {
|
||||
vm.scaffolds = $filter("orderBy")(vm.scaffolds, function (s) {
|
||||
return contentTypeAliases.indexOf(s.contentTypeAlias);
|
||||
});
|
||||
|
||||
// Convert stored nodes
|
||||
if ($scope.model.value) {
|
||||
for (var i = 0; i < $scope.model.value.length; i++) {
|
||||
var item = $scope.model.value[i];
|
||||
var scaffold = $scope.getScaffold(item.ncContentTypeAlias);
|
||||
if (model.value) {
|
||||
for (var i = 0; i < model.value.length; i++) {
|
||||
var item = model.value[i];
|
||||
var scaffold = getScaffold(item.ncContentTypeAlias);
|
||||
if (scaffold == null) {
|
||||
// No such scaffold - the content type might have been deleted. We need to skip it.
|
||||
continue;
|
||||
@@ -596,18 +439,18 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
}
|
||||
|
||||
// Auto-fill with elementTypes, but only if we have one type to choose from, and if this property is empty.
|
||||
if ($scope.singleMode === true && $scope.nodes.length === 0 && $scope.model.config.minItems > 0) {
|
||||
for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
|
||||
$scope.addNode($scope.scaffolds[0].contentTypeAlias);
|
||||
if (vm.singleMode === true && vm.nodes.length === 0 && model.config.minItems > 0) {
|
||||
for (var i = vm.nodes.length; i < model.config.minItems; i++) {
|
||||
addNode(vm.scaffolds[0].contentTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is only one item, set it as current node
|
||||
if ($scope.singleMode || ($scope.nodes.length === 1 && $scope.maxItems === 1)) {
|
||||
$scope.currentNode = $scope.nodes[0];
|
||||
if (vm.singleMode || (vm.nodes.length === 1 && vm.maxItems === 1)) {
|
||||
setCurrentNode(vm.nodes[0]);
|
||||
}
|
||||
|
||||
$scope.inited = true;
|
||||
vm.inited = true;
|
||||
|
||||
updatePropertyActionStates();
|
||||
checkAbilityToPasteContent();
|
||||
@@ -628,7 +471,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
var prop = tab.properties[p];
|
||||
|
||||
prop.propertyAlias = prop.alias;
|
||||
prop.alias = $scope.model.alias + "___" + prop.alias;
|
||||
prop.alias = model.alias + "___" + prop.alias;
|
||||
// Force validation to occur server side as this is the
|
||||
// only way we can have consistency between mandatory and
|
||||
// regex validation messages. Not ideal, but it works.
|
||||
@@ -643,7 +486,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
}
|
||||
}
|
||||
|
||||
$scope.nodes.push(node);
|
||||
vm.nodes.push(node);
|
||||
|
||||
return node;
|
||||
}
|
||||
@@ -670,33 +513,29 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
|
||||
|
||||
function syncCurrentNode() {
|
||||
if ($scope.realCurrentNode) {
|
||||
$scope.$broadcast("ncSyncVal", { key: $scope.realCurrentNode.key });
|
||||
if (vm.currentNode) {
|
||||
$scope.$broadcast("ncSyncVal", { key: vm.currentNode.key });
|
||||
}
|
||||
}
|
||||
|
||||
function updateModel() {
|
||||
syncCurrentNode();
|
||||
|
||||
if ($scope.inited) {
|
||||
if (vm.inited) {
|
||||
var newValues = [];
|
||||
for (var i = 0; i < $scope.nodes.length; i++) {
|
||||
newValues.push(convertNodeIntoNCEntry($scope.nodes[i]));
|
||||
for (var i = 0; i < vm.nodes.length; i++) {
|
||||
newValues.push(convertNodeIntoNCEntry(vm.nodes[i]));
|
||||
}
|
||||
$scope.model.value = newValues;
|
||||
model.value = newValues;
|
||||
}
|
||||
|
||||
updatePropertyActionStates();
|
||||
}
|
||||
|
||||
function updatePropertyActionStates() {
|
||||
copyAllEntriesAction.isDisabled = $scope.model.value.length === 0;
|
||||
copyAllEntriesAction.isDisabled = model.value.length === 0;
|
||||
}
|
||||
|
||||
$scope.$watch("currentNode", function (newVal) {
|
||||
updateModel();
|
||||
$scope.realCurrentNode = newVal;
|
||||
});
|
||||
|
||||
|
||||
var api = {};
|
||||
@@ -712,18 +551,18 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
|
||||
var watcher = $scope.$watch(
|
||||
function () {
|
||||
return $scope.nodes.length;
|
||||
return vm.nodes.length;
|
||||
},
|
||||
function () {
|
||||
//Validate!
|
||||
if ($scope.nodes.length < $scope.minItems) {
|
||||
if (vm.nodes.length < vm.minItems) {
|
||||
$scope.nestedContentForm.minCount.$setValidity("minCount", false);
|
||||
}
|
||||
else {
|
||||
$scope.nestedContentForm.minCount.$setValidity("minCount", true);
|
||||
}
|
||||
|
||||
if ($scope.nodes.length > $scope.maxItems) {
|
||||
if (vm.nodes.length > vm.maxItems) {
|
||||
$scope.nestedContentForm.maxCount.$setValidity("maxCount", false);
|
||||
}
|
||||
else {
|
||||
@@ -739,4 +578,4 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
|
||||
}
|
||||
|
||||
]);
|
||||
})();
|
||||
@@ -0,0 +1,159 @@
|
||||
angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [
|
||||
|
||||
"$scope",
|
||||
"Umbraco.PropertyEditors.NestedContent.Resources",
|
||||
"overlayService",
|
||||
"localizationService",
|
||||
"iconHelper",
|
||||
|
||||
function ($scope, ncResources, overlayService, localizationService, iconHelper) {
|
||||
var selectElementTypeModalTitle = "";
|
||||
|
||||
$scope.elemTypeTabs = [];
|
||||
|
||||
|
||||
init();
|
||||
|
||||
|
||||
function init() {
|
||||
localizationService.localize("content_nestedContentSelectElementTypeModalTitle").then(function (value) {
|
||||
selectElementTypeModalTitle = value;
|
||||
});
|
||||
|
||||
ncResources.getContentTypes().then(function (elemTypes) {
|
||||
$scope.model.elemTypes = elemTypes;
|
||||
|
||||
// convert legacy icons
|
||||
iconHelper.formatContentTypeIcons($scope.model.elemTypes);
|
||||
|
||||
// Count doctype name occurrences
|
||||
var elTypeNameOccurrences= _.countBy(elemTypes, 'name');
|
||||
|
||||
// Populate document type tab dictionary
|
||||
// And append alias to name if multiple doctypes have the same name
|
||||
elemTypes.forEach(function (value) {
|
||||
$scope.elemTypeTabs[value.alias] = value.tabs;
|
||||
|
||||
if (elTypeNameOccurrences[value.name] > 1) {
|
||||
value.name += " (" + value.alias + ")";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
$scope.add = function () {
|
||||
$scope.model.value.push({
|
||||
// As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
|
||||
// For good measure we'll also prefix the tab alias "nc"
|
||||
ncAlias: "",
|
||||
ncTabAlias: "",
|
||||
nameTemplate: ""
|
||||
});
|
||||
}
|
||||
|
||||
$scope.canAdd = function () {
|
||||
return !$scope.model.docTypes || !$scope.model.value || $scope.model.value.length < $scope.model.docTypes.length;
|
||||
}
|
||||
|
||||
$scope.remove = function (index) {
|
||||
$scope.model.value.splice(index, 1);
|
||||
}
|
||||
|
||||
$scope.sortableOptions = {
|
||||
axis: "y",
|
||||
cursor: "move",
|
||||
handle: ".handle",
|
||||
placeholder: 'sortable-placeholder',
|
||||
forcePlaceholderSize: true,
|
||||
helper: function (e, ui) {
|
||||
// When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
|
||||
ui.children().each(function () {
|
||||
$(this).width($(this).width());
|
||||
});
|
||||
return ui;
|
||||
},
|
||||
start: function (e, ui) {
|
||||
|
||||
var cellHeight = ui.item.height();
|
||||
|
||||
// Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size
|
||||
var cellCount = 0;
|
||||
$('td, th', ui.helper).each(function () {
|
||||
// For each td or th try and get it's colspan attribute, and add that or 1 to the total
|
||||
var colspan = 1;
|
||||
var colspanAttr = $(this).attr('colspan');
|
||||
if (colspanAttr > 1) {
|
||||
colspan = colspanAttr;
|
||||
}
|
||||
cellCount += colspan;
|
||||
});
|
||||
|
||||
// Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr
|
||||
ui.placeholder.html('<td colspan="' + cellCount + '"></td>').height(cellHeight);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$scope.placeholder = function (config) {
|
||||
return _.find($scope.model.elemTypes, function (elType) {
|
||||
return elType.alias === config.ncAlias;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.selectableElemTypesFor = function (config) {
|
||||
// return all elemTypes that are:
|
||||
// 1. either already selected for this config, or
|
||||
// 2. not selected in any other config
|
||||
return _.filter($scope.model.elemTypes, function (elType) {
|
||||
return elType.alias === config.ncAlias || !_.find($scope.model.value, function (c) {
|
||||
return elType.alias === c.ncAlias;
|
||||
});
|
||||
});
|
||||
}
|
||||
$scope.canAdd = function () {
|
||||
return !$scope.model.value || _.some($scope.model.elemTypes, function (elType) {
|
||||
return !_.find($scope.model.value, function (c) {
|
||||
return elType.alias === c.ncAlias;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$scope.openElemTypeModal = function ($event, config) {
|
||||
|
||||
//we have to add the alias to the objects (they are stored as ncAlias)
|
||||
var selectedItems = _.each($scope.model.value, function (obj) {
|
||||
obj.alias = obj.ncAlias;
|
||||
return obj;
|
||||
})
|
||||
|
||||
var elemTypeSelectorOverlay = {
|
||||
view: "itempicker",
|
||||
title: selectElementTypeModalTitle,
|
||||
availableItems: $scope.selectableElemTypesFor(config),
|
||||
selectedItems: selectedItems,
|
||||
position: "target",
|
||||
event: $event,
|
||||
submit: function (model) {
|
||||
config.ncAlias = model.selectedItem.alias;
|
||||
overlayService.close();
|
||||
},
|
||||
close: function () {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
overlayService.open(elemTypeSelectorOverlay);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!$scope.model.value) {
|
||||
$scope.model.value = [];
|
||||
$scope.add();
|
||||
}
|
||||
|
||||
}
|
||||
]);
|
||||
@@ -1,73 +1,5 @@
|
||||
<div id="umb-nested-content--{{model.id}}" class="umb-nested-content"
|
||||
ng-controller="Umbraco.PropertyEditors.NestedContent.PropertyEditorController"
|
||||
ng-class="{'umb-nested-content--narrow':!wideMode, 'umb-nested-content--wide':wideMode}">
|
||||
<div>
|
||||
|
||||
<umb-load-indicator ng-if="!inited"></umb-load-indicator>
|
||||
|
||||
<ng-form name="nestedContentForm">
|
||||
|
||||
<div class="umb-nested-content__items" ng-hide="nodes.length === 0" ui-sortable="sortableOptions" ng-model="nodes">
|
||||
|
||||
<div class="umb-nested-content__item" ng-repeat="node in nodes" ng-class="{ 'umb-nested-content__item--active' : $parent.realCurrentNode.key === node.key, 'umb-nested-content__item--single' : $parent.singleMode }">
|
||||
|
||||
<div class="umb-nested-content__header-bar" ng-click="$parent.editNode($index)" ng-hide="$parent.singleMode">
|
||||
|
||||
<div class="umb-nested-content__heading"><i ng-if="showIcons" class="icon" ng-class="$parent.getIcon($index)"></i><span class="umb-nested-content__item-name" ng-class="{'--has-icon': showIcons}" ng-bind="$parent.getName($index)"></span></div>
|
||||
|
||||
<div class="umb-nested-content__icons">
|
||||
<a class="umb-nested-content__icon umb-nested-content__icon--copy" title="{{copyIconTitle}}" ng-click="clickCopy($event, node);" ng-if="showCopy" prevent-default>
|
||||
<i class="icon icon-documents"></i>
|
||||
</a>
|
||||
<a class="umb-nested-content__icon umb-nested-content__icon--delete" localize="title" title="general_delete" ng-click="$parent.requestDeleteNode($index); $event.stopPropagation();" prevent-default>
|
||||
<i class="icon icon-trash"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="umb-nested-content__content" ng-if="$parent.realCurrentNode.key === node.key && !$parent.sorting">
|
||||
<umb-nested-content-editor ng-model="node" tab-alias="ncTabAlias" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-hide="hasContentTypes">
|
||||
<div class="umb-nested-content__help-text">
|
||||
<localize key="content_nestedContentNoContentTypes"></localize>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-nested-content__footer-bar" ng-hide="hasContentTypes === false">
|
||||
<button class="btn-reset umb-nested-content__add-content umb-focus" ng-class="{ '--disabled': (!scaffolds.length || nodes.length >= maxItems) }" ng-click="openNodeTypePicker($event)" prevent-default>
|
||||
<localize key="grid_addElement"></localize>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!--These are here because we need ng-form fields to validate against-->
|
||||
<input type="hidden" name="minCount" ng-model="nodes" />
|
||||
<input type="hidden" name="maxCount" ng-model="nodes" />
|
||||
|
||||
<div ng-messages="nestedContentForm.minCount.$error" show-validation-on-submit>
|
||||
<div class="help text-error" ng-message="minCount">
|
||||
<localize key="validation_entriesShort" tokens="[minItems, minItems - nodes.length]" watch-tokens="true">Minimum %0% entries, needs <strong>%1%</strong> more.</localize>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="nestedContentForm.minCount.$error === true || nodes.length > maxItems">
|
||||
<div class="help text-error">
|
||||
<localize key="validation_entriesExceed" tokens="[maxItems, nodes.length - maxItems]" watch-tokens="true">Maximum %0% entries, <strong>%1%</strong> too many.</localize>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
|
||||
<umb-overlay
|
||||
ng-if="overlayMenu.show"
|
||||
position="target"
|
||||
size="overlayMenu.size"
|
||||
view="overlayMenu.view"
|
||||
model="overlayMenu">
|
||||
</umb-overlay>
|
||||
<nested-content-property-editor></nested-content-property-editor>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<div id="umb-nested-content--{{model.id}}" class="umb-nested-content" ng-class="{'umb-nested-content--narrow':!vm.wideMode, 'umb-nested-content--wide':vm.wideMode}">
|
||||
|
||||
<umb-load-indicator ng-if="!vm.inited"></umb-load-indicator>
|
||||
|
||||
<ng-form name="nestedContentForm">
|
||||
|
||||
<div class="umb-nested-content__items" ng-hide="vm.nodes.length === 0" ui-sortable="vm.sortableOptions" ng-model="vm.nodes">
|
||||
|
||||
<div class="umb-nested-content__item" ng-repeat="node in vm.nodes" ng-class="{ 'umb-nested-content__item--active' : vm.currentNode.key === node.key, 'umb-nested-content__item--single' : vm.singleMode }">
|
||||
|
||||
<div class="umb-nested-content__header-bar" ng-click="vm.editNode($index)" ng-hide="vm.singleMode">
|
||||
|
||||
<div class="umb-nested-content__heading"><i ng-if="vm.showIcons" class="icon" ng-class="vm.getIcon($index)"></i><span class="umb-nested-content__item-name" ng-class="{'--has-icon': vm.showIcons}" ng-bind="vm.getName($index)"></span></div>
|
||||
|
||||
<div class="umb-nested-content__icons">
|
||||
<a class="umb-nested-content__icon umb-nested-content__icon--copy" title="{{copyIconTitle}}" ng-click="vm.clickCopy($event, node);" ng-if="vm.showCopy" prevent-default>
|
||||
<i class="icon icon-documents"></i>
|
||||
</a>
|
||||
<a class="umb-nested-content__icon umb-nested-content__icon--delete" localize="title" title="general_delete" ng-click="vm.requestDeleteNode($index); $event.stopPropagation();" prevent-default>
|
||||
<i class="icon icon-trash"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="umb-nested-content__content" ng-if="vm.currentNode.key === node.key && !vm.sorting">
|
||||
<umb-nested-content-editor ng-model="node" tab-alias="ncTabAlias" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-hide="vm.hasContentTypes">
|
||||
<div class="umb-nested-content__help-text">
|
||||
<localize key="content_nestedContentNoContentTypes"></localize>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-nested-content__footer-bar" ng-hide="vm.hasContentTypes === false">
|
||||
<button class="btn-reset umb-nested-content__add-content umb-focus" ng-class="{ '--disabled': (!vm.scaffolds.length || vm.nodes.length >= maxItems) }" ng-click="vm.openNodeTypePicker($event)" prevent-default>
|
||||
<localize key="grid_addElement"></localize>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!--These are here because we need ng-form fields to validate against-->
|
||||
<input type="hidden" name="minCount" ng-model="vm.nodes" />
|
||||
<input type="hidden" name="maxCount" ng-model="vm.nodes" />
|
||||
|
||||
<div ng-messages="nestedContentForm.minCount.$error" show-validation-on-submit>
|
||||
<div class="help text-error" ng-message="minCount">
|
||||
<localize key="validation_entriesShort" tokens="[vm.minItems, vm.minItems - vm.nodes.length]" watch-tokens="true">Minimum %0% entries, needs <strong>%1%</strong> more.</localize>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="nestedContentForm.minCount.$error === true || vm.nodes.length > vm.maxItems">
|
||||
<div class="help text-error">
|
||||
<localize key="validation_entriesExceed" tokens="[vm.maxItems, vm.nodes.length - vm.maxItems]" watch-tokens="true">Maximum %0% entries, <strong>%1%</strong> too many.</localize>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
|
||||
<umb-overlay
|
||||
ng-if="vm.overlayMenu.show"
|
||||
position="target"
|
||||
size="vm.overlayMenu.size"
|
||||
view="vm.overlayMenu.view"
|
||||
model="vm.overlayMenu">
|
||||
</umb-overlay>
|
||||
|
||||
</div>
|
||||
Reference in New Issue
Block a user