Merge remote-tracking branch 'origin/temp-u4-8604-1' into dev-v7.7
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
angular.module("umbraco.directives").directive('nestedContentEditor', [
|
||||
|
||||
function () {
|
||||
|
||||
var link = function ($scope) {
|
||||
|
||||
// Clone the model because some property editors
|
||||
// do weird things like updating and config values
|
||||
// so we want to ensure we start from a fresh every
|
||||
// time, we'll just sync the value back when we need to
|
||||
$scope.model = angular.copy($scope.ngModel);
|
||||
$scope.nodeContext = $scope.model;
|
||||
|
||||
// Find the selected tab
|
||||
var selectedTab = $scope.model.tabs[0];
|
||||
|
||||
if ($scope.tabAlias) {
|
||||
angular.forEach($scope.model.tabs, function (tab) {
|
||||
if (tab.alias.toLowerCase() === $scope.tabAlias.toLowerCase()) {
|
||||
selectedTab = tab;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.tab = selectedTab;
|
||||
|
||||
// Listen for sync request
|
||||
var unsubscribe = $scope.$on("ncSyncVal", function (ev, args) {
|
||||
if (args.key === $scope.model.key) {
|
||||
|
||||
// Tell inner controls we are submitting
|
||||
$scope.$broadcast("formSubmitting", { scope: $scope });
|
||||
|
||||
// Sync the values back
|
||||
angular.forEach($scope.ngModel.tabs, function (tab) {
|
||||
if (tab.alias.toLowerCase() === selectedTab.alias.toLowerCase()) {
|
||||
|
||||
var localPropsMap = selectedTab.properties.reduce(function (map, obj) {
|
||||
map[obj.alias] = obj;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
angular.forEach(tab.properties, function (prop) {
|
||||
if (localPropsMap.hasOwnProperty(prop.alias)) {
|
||||
prop.value = localPropsMap[prop.alias].value;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
unsubscribe();
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
replace: true,
|
||||
templateUrl: Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/views/propertyeditors/nestedcontent/nestedcontent.editor.html",
|
||||
scope: {
|
||||
ngModel: '=',
|
||||
tabAlias: '='
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
//angular.module("umbraco.directives").directive('nestedContentSubmitWatcher', function () {
|
||||
// var link = function (scope) {
|
||||
// // call the load callback on scope to obtain the ID of this submit watcher
|
||||
// var id = scope.loadCallback();
|
||||
// scope.$on("formSubmitting", function (ev, args) {
|
||||
// // on the "formSubmitting" event, call the submit callback on scope to notify the nestedContent controller to do it's magic
|
||||
// if (id === scope.activeSubmitWatcher) {
|
||||
// scope.submitCallback();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// return {
|
||||
// restrict: "E",
|
||||
// replace: true,
|
||||
// template: "",
|
||||
// scope: {
|
||||
// loadCallback: '=',
|
||||
// submitCallback: '=',
|
||||
// activeSubmitWatcher: '='
|
||||
// },
|
||||
// link: link
|
||||
// }
|
||||
//});
|
||||
@@ -0,0 +1,47 @@
|
||||
// Filter to take a node id and grab it's name instead
|
||||
// Usage: {{ pickerAlias | ncNodeName }}
|
||||
|
||||
// Cache for node names so we don't make a ton of requests
|
||||
var ncNodeNameCache = {
|
||||
id: "",
|
||||
keys: {}
|
||||
};
|
||||
|
||||
angular.module("umbraco.filters").filter("ncNodeName", function (editorState, entityResource) {
|
||||
|
||||
return function (input) {
|
||||
|
||||
// Check we have a value at all
|
||||
if (input === "" || input.toString() === "0") {
|
||||
return "";
|
||||
}
|
||||
|
||||
var currentNode = editorState.getCurrent();
|
||||
|
||||
// Ensure a unique cache per editor instance
|
||||
var key = "ncNodeName_" + currentNode.key;
|
||||
if (ncNodeNameCache.id !== key) {
|
||||
ncNodeNameCache.id = key;
|
||||
ncNodeNameCache.keys = {};
|
||||
}
|
||||
|
||||
// See if there is a value in the cache and use that
|
||||
if (ncNodeNameCache.keys[input]) {
|
||||
return ncNodeNameCache.keys[input];
|
||||
}
|
||||
|
||||
// No value, so go fetch one
|
||||
// We'll put a temp value in the cache though so we don't
|
||||
// make a load of requests while we wait for a response
|
||||
ncNodeNameCache.keys[input] = "Loading...";
|
||||
|
||||
entityResource.getById(input, "Document")
|
||||
.then(function (ent) {
|
||||
ncNodeNameCache.keys[input] = ent.name;
|
||||
});
|
||||
|
||||
// Return the current value for now
|
||||
return ncNodeNameCache.keys[input];
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
angular.module('umbraco.resources').factory('Umbraco.PropertyEditors.NestedContent.Resources',
|
||||
function ($q, $http, umbRequestHelper) {
|
||||
return {
|
||||
getContentTypes: function () {
|
||||
var url = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/UmbracoApi/NestedContent/GetContentTypes";
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(url),
|
||||
'Failed to retrieve content types'
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -120,6 +120,7 @@
|
||||
@import "components/umb-querybuilder.less";
|
||||
@import "components/umb-pagination.less";
|
||||
@import "components/umb-mini-list-view.less";
|
||||
@import "components/umb-nested-content.less";
|
||||
|
||||
@import "components/buttons/umb-button.less";
|
||||
@import "components/buttons/umb-button-group.less";
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
.nested-content
|
||||
{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nested-content__item
|
||||
{
|
||||
position: relative;
|
||||
text-align: left;
|
||||
border-top: solid 1px transparent;
|
||||
background: white;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.nested-content__item--active:not(.nested-content__item--single)
|
||||
{
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.nested-content__item.ui-sortable-placeholder
|
||||
{
|
||||
background: #f8f8f8;
|
||||
border: 1px dashed #d9d9d9;
|
||||
visibility: visible !important;
|
||||
height: 55px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.nested-content__item--single > .nested-content__content
|
||||
{
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.nested-content__item--single > .nested-content__content > .umb-pane
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nested-content__header-bar
|
||||
{
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px dashed #e0e0e0;
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-o-user-select: none;
|
||||
}
|
||||
|
||||
.nested-content__heading
|
||||
{
|
||||
float: left;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.nested-content__heading i
|
||||
{
|
||||
vertical-align: text-top;
|
||||
color: #999; /* same icon color as the icons in the item type picker */
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.nested-content__icons
|
||||
{
|
||||
margin: -6px 0;
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity .15s ease-in-out;
|
||||
-moz-transition: opacity .15s ease-in-out;
|
||||
-webkit-transition: opacity .15s ease-in-out;
|
||||
}
|
||||
|
||||
.nested-content__header-bar:hover .nested-content__icons,
|
||||
.nested-content__item--active > .nested-content__header-bar .nested-content__icons
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nested-content__icon,
|
||||
.nested-content__icon.nested-content__icon--disabled:hover
|
||||
{
|
||||
display: inline-block;
|
||||
padding: 4px 6px;
|
||||
margin: 2px;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border: 1px solid #b6b6b6;
|
||||
border-radius: 200px;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.nested-content__icon:hover,
|
||||
.nested-content__icon--active
|
||||
{
|
||||
color: white;
|
||||
background: #2e8aea;
|
||||
border-color: #2e8aea;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nested-content__icon .icon,
|
||||
.nested-content__icon.nested-content__icon--disabled:hover .icon
|
||||
{
|
||||
display: block;
|
||||
font-size: 16px !important;
|
||||
color: #5f5f5f;
|
||||
}
|
||||
|
||||
.nested-content__icon:hover .icon,
|
||||
.nested-content__icon--active .icon
|
||||
{
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nested-content__icon--disabled
|
||||
{
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
|
||||
.nested-content__footer-bar
|
||||
{
|
||||
text-align: center;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.nested-content__content
|
||||
{
|
||||
border-bottom: 1px dashed #e0e0e0;
|
||||
}
|
||||
|
||||
.nested-content__content .umb-control-group {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.nested-content__item.ui-sortable-helper .nested-content__content
|
||||
{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.nested-content__help-text
|
||||
{
|
||||
display: inline-block;
|
||||
padding: 10px 20px 10px 20px;
|
||||
clear: both;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
background: #f8f8f8;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.nested-content__doctypepicker table input, .nested-content__doctypepicker table select {
|
||||
width: 100%;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.nested-content__doctypepicker table td.icon-navigation, .nested-content__doctypepicker i.nested-content__help-icon {
|
||||
vertical-align: middle;
|
||||
color: #CCC;
|
||||
}
|
||||
|
||||
.nested-content__doctypepicker table td.icon-navigation:hover, .nested-content__doctypepicker i.nested-content__help-icon:hover {
|
||||
color: #343434;
|
||||
}
|
||||
|
||||
.nested-content__doctypepicker i.nested-content__help-icon {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.form-horizontal .nested-content--narrow .controls-row
|
||||
{
|
||||
margin-left: 40% !important;
|
||||
}
|
||||
|
||||
.form-horizontal .nested-content--narrow .controls-row .umb-textstring,
|
||||
.form-horizontal .nested-content--narrow .controls-row .umb-textarea
|
||||
{
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.form-horizontal .nested-content--narrow .controls-row .umb-dropdown {
|
||||
width: 99%;
|
||||
}
|
||||
|
||||
.usky-grid.nested-content__node-type-picker .cell-tools-menu {
|
||||
position: relative;
|
||||
transform: translate(-50%, -25%);
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [
|
||||
|
||||
"$scope",
|
||||
"Umbraco.PropertyEditors.NestedContent.Resources",
|
||||
|
||||
function ($scope, ncResources) {
|
||||
|
||||
$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.remove = function (index) {
|
||||
$scope.model.value.splice(index, 1);
|
||||
}
|
||||
|
||||
$scope.sortableOptions = {
|
||||
axis: 'y',
|
||||
cursor: "move",
|
||||
handle: ".icon-navigation"
|
||||
};
|
||||
|
||||
$scope.selectedDocTypeTabs = {};
|
||||
|
||||
ncResources.getContentTypes().then(function (docTypes) {
|
||||
$scope.model.docTypes = docTypes;
|
||||
|
||||
// Populate document type tab dictionary
|
||||
docTypes.forEach(function (value) {
|
||||
$scope.selectedDocTypeTabs[value.alias] = value.tabs;
|
||||
});
|
||||
});
|
||||
|
||||
if (!$scope.model.value) {
|
||||
$scope.model.value = [];
|
||||
$scope.add();
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.PropertyEditorController", [
|
||||
|
||||
"$scope",
|
||||
"$interpolate",
|
||||
"$filter",
|
||||
"$timeout",
|
||||
"contentResource",
|
||||
"localizationService",
|
||||
"iconHelper",
|
||||
"Umbraco.PropertyEditors.NestedContent.Resources",
|
||||
|
||||
function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, ncResources) {
|
||||
|
||||
//$scope.model.config.contentTypes;
|
||||
//$scope.model.config.minItems;
|
||||
//$scope.model.config.maxItems;
|
||||
//console.log($scope);
|
||||
|
||||
var inited = false;
|
||||
|
||||
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||
contentType.nameExp = !!contentType.nameTemplate
|
||||
? $interpolate(contentType.nameTemplate)
|
||||
: undefined;
|
||||
});
|
||||
|
||||
$scope.editIconTitle = '';
|
||||
$scope.moveIconTitle = '';
|
||||
$scope.deleteIconTitle = '';
|
||||
|
||||
// localize the edit icon title
|
||||
localizationService.localize('general_edit').then(function (value) {
|
||||
$scope.editIconTitle = value;
|
||||
});
|
||||
|
||||
// localize the delete icon title
|
||||
localizationService.localize('general_delete').then(function (value) {
|
||||
$scope.deleteIconTitle = value;
|
||||
});
|
||||
|
||||
// localize the move icon title
|
||||
localizationService.localize('actions_move').then(function (value) {
|
||||
$scope.moveIconTitle = value;
|
||||
});
|
||||
|
||||
$scope.nodes = [];
|
||||
$scope.currentNode = undefined;
|
||||
$scope.realCurrentNode = undefined;
|
||||
$scope.scaffolds = undefined;
|
||||
$scope.sorting = false;
|
||||
|
||||
$scope.minItems = $scope.model.config.minItems || 0;
|
||||
$scope.maxItems = $scope.model.config.maxItems || 0;
|
||||
|
||||
if ($scope.maxItems == 0)
|
||||
$scope.maxItems = 1000;
|
||||
|
||||
$scope.singleMode = $scope.minItems == 1 && $scope.maxItems == 1;
|
||||
$scope.showIcons = $scope.model.config.showIcons || true;
|
||||
$scope.wideMode = $scope.model.config.hideLabel == "1";
|
||||
|
||||
$scope.overlayMenu = {
|
||||
show: false,
|
||||
style: {}
|
||||
};
|
||||
|
||||
// helper to force the current form into the dirty state
|
||||
$scope.setDirty = function () {
|
||||
if ($scope.propertyForm) {
|
||||
$scope.propertyForm.$setDirty();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addNode = function (alias) {
|
||||
var scaffold = $scope.getScaffold(alias);
|
||||
|
||||
var newNode = initNode(scaffold, null);
|
||||
|
||||
$scope.currentNode = newNode;
|
||||
$scope.setDirty();
|
||||
|
||||
$scope.closeNodeTypePicker();
|
||||
};
|
||||
|
||||
$scope.openNodeTypePicker = function (event) {
|
||||
if ($scope.nodes.length >= $scope.maxItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this could be used for future limiting on node types
|
||||
$scope.overlayMenu.scaffolds = [];
|
||||
_.each($scope.scaffolds, function (scaffold) {
|
||||
$scope.overlayMenu.scaffolds.push({
|
||||
alias: scaffold.contentTypeAlias,
|
||||
name: scaffold.contentTypeName,
|
||||
icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
|
||||
});
|
||||
});
|
||||
|
||||
if ($scope.overlayMenu.scaffolds.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.overlayMenu.scaffolds.length == 1) {
|
||||
// only one scaffold type - no need to display the picker
|
||||
$scope.addNode($scope.scaffolds[0].contentTypeAlias);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.overlayMenu.show = true;
|
||||
};
|
||||
|
||||
$scope.closeNodeTypePicker = function () {
|
||||
$scope.overlayMenu.show = false;
|
||||
};
|
||||
|
||||
$scope.editNode = function (idx) {
|
||||
if ($scope.currentNode && $scope.currentNode.key == $scope.nodes[idx].key) {
|
||||
$scope.currentNode = undefined;
|
||||
} else {
|
||||
$scope.currentNode = $scope.nodes[idx];
|
||||
}
|
||||
};
|
||||
|
||||
$scope.deleteNode = function (idx) {
|
||||
if ($scope.nodes.length > $scope.model.config.minItems) {
|
||||
if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes == 1) {
|
||||
if (confirm("Are you sure you want to delete this item?")) {
|
||||
$scope.nodes.splice(idx, 1);
|
||||
$scope.setDirty();
|
||||
updateModel();
|
||||
}
|
||||
} else {
|
||||
$scope.nodes.splice(idx, 1);
|
||||
$scope.setDirty();
|
||||
updateModel();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getName = function (idx) {
|
||||
|
||||
var name = "Item " + (idx + 1);
|
||||
|
||||
if ($scope.model.value[idx]) {
|
||||
|
||||
var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias);
|
||||
|
||||
if (contentType != null && contentType.nameExp) {
|
||||
// Run the expression against the stored dictionary value, NOT the node object
|
||||
var item = $scope.model.value[idx];
|
||||
|
||||
// Add a temporary index property
|
||||
item['$index'] = (idx + 1);
|
||||
|
||||
var newName = contentType.nameExp(item);
|
||||
if (newName && (newName = $.trim(newName))) {
|
||||
name = newName;
|
||||
}
|
||||
|
||||
// Delete the index property as we don't want to persist it
|
||||
delete item['$index'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update the nodes actual name value
|
||||
if ($scope.nodes[idx].name !== name) {
|
||||
$scope.nodes[idx].name = name;
|
||||
}
|
||||
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
$scope.getIcon = function (idx) {
|
||||
var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
|
||||
return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
|
||||
}
|
||||
|
||||
$scope.sortableOptions = {
|
||||
axis: 'y',
|
||||
cursor: "move",
|
||||
handle: ".nested-content__icon--move",
|
||||
start: function (ev, ui) {
|
||||
// Yea, yea, we shouldn't modify the dom, sue me
|
||||
$("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
|
||||
tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id'));
|
||||
$(this).css("visibility", "hidden");
|
||||
});
|
||||
$scope.$apply(function () {
|
||||
$scope.sorting = true;
|
||||
});
|
||||
},
|
||||
update: function (ev, ui) {
|
||||
$scope.setDirty();
|
||||
},
|
||||
stop: function (ev, ui) {
|
||||
$("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
|
||||
tinymce.execCommand('mceAddEditor', true, $(this).attr('id'));
|
||||
$(this).css("visibility", "visible");
|
||||
});
|
||||
$scope.$apply(function () {
|
||||
$scope.sorting = false;
|
||||
updateModel();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getScaffold = function (alias) {
|
||||
return _.find($scope.scaffolds, function (scaffold) {
|
||||
return scaffold.contentTypeAlias == alias;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.getContentTypeConfig = function (alias) {
|
||||
return _.find($scope.model.config.contentTypes, function (contentType) {
|
||||
return contentType.ncAlias == alias;
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize
|
||||
var scaffoldsLoaded = 0;
|
||||
$scope.scaffolds = [];
|
||||
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||
contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
|
||||
// remove all tabs except the specified tab
|
||||
var tab = _.find(scaffold.tabs, function (tab) {
|
||||
return tab.id != 0 && (tab.alias.toLowerCase() == contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias == "");
|
||||
});
|
||||
scaffold.tabs = [];
|
||||
if (tab) {
|
||||
scaffold.tabs.push(tab);
|
||||
}
|
||||
|
||||
// Store the scaffold object
|
||||
$scope.scaffolds.push(scaffold);
|
||||
|
||||
scaffoldsLoaded++;
|
||||
initIfAllScaffoldsHaveLoaded();
|
||||
}, function (error) {
|
||||
scaffoldsLoaded++;
|
||||
initIfAllScaffoldsHaveLoaded();
|
||||
});
|
||||
});
|
||||
|
||||
var initIfAllScaffoldsHaveLoaded = function () {
|
||||
// Initialize when all scaffolds have loaded
|
||||
if ($scope.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.
|
||||
var contentTypeAliases = [];
|
||||
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||
contentTypeAliases.push(contentType.ncAlias);
|
||||
});
|
||||
$scope.scaffolds = $filter('orderBy')($scope.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 (scaffold == null) {
|
||||
// No such scaffold - the content type might have been deleted. We need to skip it.
|
||||
continue;
|
||||
}
|
||||
initNode(scaffold, item);
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce min items
|
||||
if ($scope.nodes.length < $scope.model.config.minItems) {
|
||||
for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
|
||||
$scope.addNode($scope.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];
|
||||
}
|
||||
|
||||
inited = true;
|
||||
}
|
||||
}
|
||||
|
||||
var initNode = function (scaffold, item) {
|
||||
var node = angular.copy(scaffold);
|
||||
|
||||
node.key = guid();
|
||||
node.ncContentTypeAlias = scaffold.contentTypeAlias;
|
||||
|
||||
for (var t = 0; t < node.tabs.length; t++) {
|
||||
var tab = node.tabs[t];
|
||||
for (var p = 0; p < tab.properties.length; p++) {
|
||||
var prop = tab.properties[p];
|
||||
prop.propertyAlias = prop.alias;
|
||||
prop.alias = $scope.model.alias + "___" + prop.alias;
|
||||
// Force validation to occur server side as this is the
|
||||
// only way we can have consistancy between mandatory and
|
||||
// regex validation messages. Not ideal, but it works.
|
||||
prop.validation = {
|
||||
mandatory: false,
|
||||
pattern: ""
|
||||
};
|
||||
if (item) {
|
||||
if (item[prop.propertyAlias]) {
|
||||
prop.value = item[prop.propertyAlias];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.nodes.push(node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
var updateModel = function () {
|
||||
if ($scope.realCurrentNode) {
|
||||
$scope.$broadcast("ncSyncVal", { key: $scope.realCurrentNode.key });
|
||||
}
|
||||
if (inited) {
|
||||
var newValues = [];
|
||||
for (var i = 0; i < $scope.nodes.length; i++) {
|
||||
var node = $scope.nodes[i];
|
||||
var newValue = {
|
||||
key: node.key,
|
||||
name: node.name,
|
||||
ncContentTypeAlias: node.ncContentTypeAlias
|
||||
};
|
||||
for (var t = 0; t < node.tabs.length; t++) {
|
||||
var tab = node.tabs[t];
|
||||
for (var p = 0; p < tab.properties.length; p++) {
|
||||
var prop = tab.properties[p];
|
||||
if (typeof prop.value !== "function") {
|
||||
newValue[prop.propertyAlias] = prop.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
newValues.push(newValue);
|
||||
}
|
||||
$scope.model.value = newValues;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch("currentNode", function (newVal) {
|
||||
updateModel();
|
||||
$scope.realCurrentNode = newVal;
|
||||
});
|
||||
|
||||
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
|
||||
updateModel();
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
var guid = function () {
|
||||
function _p8(s) {
|
||||
var p = (Math.random().toString(16) + "000000000").substr(2, 8);
|
||||
return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p;
|
||||
}
|
||||
return _p8() + _p8(true) + _p8(true) + _p8();
|
||||
};
|
||||
}
|
||||
|
||||
]);
|
||||
@@ -0,0 +1,58 @@
|
||||
<div id="{{model.alias}}" class="nested-content__doctypepicker" ng-controller="Umbraco.PropertyEditors.NestedContent.DocTypePickerController">
|
||||
<div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th/>
|
||||
<th>
|
||||
Document Type
|
||||
</th>
|
||||
<th>
|
||||
Tab
|
||||
</th>
|
||||
<th>
|
||||
Name Template
|
||||
</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ui-sortable="sortableOptions" ng-model="model.value">
|
||||
<tr ng-repeat="config in model.value">
|
||||
<td class="icon icon-navigation">
|
||||
</td>
|
||||
<td>
|
||||
<select id="{{model.alias}}_doctype_select"
|
||||
ng-options="dt.alias as dt.name for dt in model.docTypes | orderBy: 'name'"
|
||||
ng-model="config.ncAlias" required></select>
|
||||
</td>
|
||||
<td>
|
||||
<select id="{{model.alias}}_tab_select"
|
||||
ng-options="t for t in selectedDocTypeTabs[config.ncAlias]"
|
||||
ng-model="config.ncTabAlias" required></select>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" ng-model="config.nameTemplate" />
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-danger" ng-click="remove($index)" ng-show="model.value.length > 1">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<a class="btn" ng-click="add()">Add</a>
|
||||
<i class="icon icon-help-alt medium nested-content__help-icon" ng-click="showHelpText = !showHelpText"></i>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="nested-content__help-text" ng-show="showHelpText">
|
||||
<p>
|
||||
<b>Tab:</b><br/>
|
||||
Select the tab who's properties should be displayed. If left blank, the first tab on the doc type will be used.
|
||||
</p>
|
||||
<p>
|
||||
<b>Name template:</b><br/>
|
||||
Enter an angular expression to evaluate against each item for its name. Use <code ng-non-bindable>{{$index}}</code> to display the item index
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="umb-pane">
|
||||
|
||||
<umb-property
|
||||
property="property"
|
||||
ng-repeat="property in tab.properties">
|
||||
<umb-editor model="property"></umb-editor>
|
||||
</umb-property>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,62 @@
|
||||
<div id="nested-content--{{model.id}}" class="nested-content"
|
||||
ng-controller="Umbraco.PropertyEditors.NestedContent.PropertyEditorController"
|
||||
ng-class="{'nested-content--narrow':!wideMode, 'nested-content--wide':wideMode}">
|
||||
<ng-form>
|
||||
|
||||
<div class="nested-content__items" ng-hide="nodes.length == 0" ui-sortable="sortableOptions" ng-model="nodes">
|
||||
|
||||
<div class="nested-content__item" ng-repeat="node in nodes" ng-class="{ 'nested-content__item--active' : $parent.realCurrentNode.key == node.key, 'nested-content__item--single' : $parent.singleMode }">
|
||||
|
||||
<div class="nested-content__header-bar" ng-click="$parent.editNode($index)" ng-hide="$parent.singleMode">
|
||||
|
||||
<div class="nested-content__heading"><i ng-if="showIcons" class="icon" ng-class="$parent.getIcon($index)"></i><span ng-bind="$parent.getName($index)"></span></div>
|
||||
|
||||
<div class="nested-content__icons">
|
||||
<a class="nested-content__icon nested-content__icon--edit" title="{{editIconTitle}}" ng-class="{ 'nested-content__icon--active' : $parent.realCurrentNode.id == node.id }" ng-click="$parent.editNode($index); $event.stopPropagation();" ng-show="$parent.maxItems > 1" prevent-default>
|
||||
<i class="icon icon-edit"></i>
|
||||
</a>
|
||||
<a class="nested-content__icon nested-content__icon--move" title="{{moveIconTitle}}" ng-click="$event.stopPropagation();" ng-show="$parent.nodes.length > 1" prevent-default>
|
||||
<i class="icon icon-navigation"></i>
|
||||
</a>
|
||||
<a class="nested-content__icon nested-content__icon--delete" title="{{deleteIconTitle}}" ng-class="{ 'nested-content__icon--disabled': $parent.nodes.length <= $parent.minItems }" ng-click="$parent.deleteNode($index); $event.stopPropagation();" prevent-default>
|
||||
<i class="icon icon-trash"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="nested-content__content" ng-if="$parent.realCurrentNode.key == node.key && !$parent.sorting">
|
||||
<nested-content-editor ng-model="node" tab-alias="ncTabAlias" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="nested-content__help-text" ng-show="nodes.length == 0">
|
||||
<localize key="grid_addElement"></localize>
|
||||
</div>
|
||||
|
||||
<div class="nested-content__footer-bar" ng-hide="nodes.length >= maxItems">
|
||||
<a class="nested-content__icon" ng-click="openNodeTypePicker($event)" prevent-default>
|
||||
<i class="icon icon-add"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="usky-grid nested-content__node-type-picker" ng-if="overlayMenu.show">
|
||||
<div class="cell-tools-menu" ng-style="overlayMenu.style" style="margin: 0;" delayed-mouseleave="closeNodeTypePicker()" on-delayed-mouseleave="closeNodeTypePicker()">
|
||||
<h5>
|
||||
<localize key="grid_insertControl" />
|
||||
</h5>
|
||||
<ul class="elements">
|
||||
<li ng-repeat="scaffold in overlayMenu.scaffolds">
|
||||
<a ng-click="addNode(scaffold.alias)" href>
|
||||
<i class="icon {{scaffold.icon}}"></i>
|
||||
{{scaffold.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
</div>
|
||||
Reference in New Issue
Block a user