Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/AB3970-membership-providers

# Conflicts:
#	src/Umbraco.Web/Editors/Filters/MemberSaveModelValidator.cs
#	src/Umbraco.Web/Editors/Filters/MemberSaveValidationAttribute.cs
This commit is contained in:
Bjarke Berg
2019-12-03 08:54:31 +01:00
164 changed files with 7437 additions and 6106 deletions

View File

@@ -4,7 +4,7 @@
* @restrict E
**/
angular.module("umbraco.directives")
.directive('umbProperty', function (umbPropEditorHelper, userService) {
.directive('umbProperty', function (userService) {
return {
scope: {
property: "=",
@@ -17,7 +17,7 @@ angular.module("umbraco.directives")
templateUrl: 'views/components/property/umb-property.html',
link: function (scope) {
scope.propertyEditorAPI = {};
scope.propertyActions = [];
userService.getCurrentUser().then(function (u) {
var isAdmin = u.userGroups.indexOf('admin') !== -1;
@@ -25,28 +25,20 @@ angular.module("umbraco.directives")
});
},
//Define a controller for this directive to expose APIs to other directives
controller: function ($scope, $timeout) {
controller: function ($scope) {
var self = this;
//set the API properties/methods
self.property = $scope.property;
self.setPropertyError = function (errorMsg) {
$scope.property.propertyErrorMessage = errorMsg;
};
var unsubscribe = $scope.$on("ExposePropertyEditorAPI", function(event, api) {
//avoid eventual parent properties to capture this.
event.stopPropagation();
$scope.propertyEditorAPI = api;
});
$scope.$on("$destroy", function () {
unsubscribe();
});
self.setPropertyActions = function(actions) {
$scope.propertyActions = actions;
};
}
};

View File

@@ -65,6 +65,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
vm.reloadNode = reloadNode;
vm.syncTree = syncTree;
vm.loadChildren = loadChildren;
vm.hasTree = hasTree;
//wire up the exposed api object for hosting controllers
if ($scope.api) {
@@ -72,6 +73,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
$scope.api.load = vm.load;
$scope.api.reloadNode = vm.reloadNode;
$scope.api.syncTree = vm.syncTree;
$scope.api.hasTree = vm.hasTree;
}
//flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should
@@ -203,6 +205,25 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
});
}
//given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node
function hasTree(treeAlias) {
if (!$scope.tree) {
throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null";
}
if (!treeAlias) {
return false;
}
var treeRoots = getTreeRootNodes();
var foundTree = _.find(treeRoots, function (node) {
return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase();
});
return foundTree !== undefined;
}
//given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node
function loadActiveTree(treeAlias) {

View File

@@ -549,7 +549,9 @@
property.dataTypeIcon = propertyModel.dataTypeIcon;
property.dataTypeName = propertyModel.dataTypeName;
property.validation.mandatory = propertyModel.validation.mandatory;
property.validation.mandatoryMessage = propertyModel.validation.mandatoryMessage;
property.validation.pattern = propertyModel.validation.pattern;
property.validation.patternMessage = propertyModel.validation.patternMessage;
property.showOnMemberProfile = propertyModel.showOnMemberProfile;
property.memberCanEdit = propertyModel.memberCanEdit;
property.isSensitiveValue = propertyModel.isSensitiveValue;
@@ -632,7 +634,9 @@
propertyState: "init",
validation: {
mandatory: false,
pattern: null
mandatoryMessage: null,
pattern: null,
patternMessage: null
}
};

View File

@@ -338,6 +338,22 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
});
},
/**
* @ngdoc method
* @name umbraco.services.navigationService#hasTree
* @methodOf umbraco.services.navigationService
*
* @description
* Checks if a tree with the given alias exists.
*
* @param {String} treeAlias the tree alias to check
*/
hasTree: function (treeAlias) {
return navReadyPromise.promise.then(function () {
return mainTreeApi.hasTree(treeAlias);
});
},
/**
Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
have to set an active tree and then sync, the new API does this in one method by using syncTree

View File

@@ -89,6 +89,7 @@
}
function confirmDelete(overlay) {
overlay.confirmType = "delete";
confirm(overlay);
}

View File

@@ -1,29 +0,0 @@
(function() {
'use strict';
function propertyEditorService() {
/**
* @ngdoc function
* @name umbraco.services.propertyEditorService#expose
* @methodOf umbraco.services.propertyEditorService
* @function
*
* @param {object} scope An object containing API for the PropertyEditor
*/
function exposeAPI(scope, api) {
if (!scope) {
throw "scope cannot be null";
}
if (!api) {
throw "api cannot be null";
}
scope.$emit("ExposePropertyEditorAPI", api);
}
return {
exposeAPI: exposeAPI
};
}
angular.module('umbraco.services').factory('propertyEditorService', propertyEditorService);
})();

View File

@@ -184,7 +184,13 @@
});
if (cached) return cached[1];
nodeComputedStyle = nodeComputedStyle || this.doc.defaultView.getComputedStyle(node);
if (!nodeComputedStyle) {
if (node instanceof DocumentFragment) {
return true;// though DocumentFragment doesn't directly have display 'none', we know that it will never be visible, and therefore we return true. (and do not cache this, cause it will change if appended to the DOM)
} else {
nodeComputedStyle = this.doc.defaultView.getComputedStyle(node);
}
}
var result = false;

View File

@@ -0,0 +1,34 @@
(function () {
'use strict';
function validationMessageService($q, localizationService) {
// Gets the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type's validation object
// or a localised default.
function getMandatoryMessage(validation) {
if (!validation) {
return $q.when("");
}
if (validation.mandatoryMessage) {
return $q.when(validation.mandatoryMessage);
} else {
return localizationService.localize("general_required").then(function (value) {
return $q.when(value);
});
}
}
var service = {
getMandatoryMessage: getMandatoryMessage
};
return service;
}
angular.module('umbraco.services').factory('validationMessageService', validationMessageService);
})();

View File

@@ -493,11 +493,11 @@ input.umb-group-builder__group-sort-value {
font-weight: bold;
font-size: 14px;
color: @ui-action-type;
&:hover{
&:hover {
text-decoration: none;
color:@ui-action-type-hover;
border-color:@ui-action-border-hover;
color: @ui-action-type-hover;
border-color: @ui-action-border-hover;
}
}
@@ -554,7 +554,13 @@ input.umb-group-builder__group-sort-value {
overflow: hidden;
}
.editor-validation-pattern{
.editor-validation-message {
min-width: 100%;
min-height: 25px;
margin-top: 4px;
}
.editor-validation-pattern {
border: 1px solid @gray-7;
margin: 10px 0 0;
padding: 6px;

View File

@@ -14,6 +14,17 @@
pointer-events: none;
}
.umb-nested-content--mandatory {
/*
yeah so this is a pain, but we must be super specific in targeting the mandatory property labels,
otherwise all properties within a reqired, nested, nested content property will all appear mandatory
*/
> ng-form > .control-group > .umb-el-wrap > .control-header label:after {
content: '*';
color: @red;
}
}
.umb-nested-content-overlay {
position: absolute;
top: 0;
@@ -134,6 +145,8 @@
.umb-nested-content__icon {
background: transparent;
border: 0 none;
display: inline-block;
padding: 4px;
margin: 2px;
@@ -235,7 +248,6 @@
}
.umb-nested-content__placeholder {
height: 22px;
padding: 4px 6px;
border: 1px dashed #d8d7d9;
background: 0 0;
@@ -246,33 +258,18 @@
text-align: center;
&--selected {
border: 1px solid #d8d7d9;
border: none;
text-align: left;
padding: 0;
}
}
.umb-nested-content__placeholder-name{
font-size: 15px;
}
.umb-nested-content__placeholder:hover {
color: #2152a3;
border-color: #2152a3;
text-decoration: none;
}
.umb-nested-content__placeholder-icon-holder {
width: 20px;
text-align: center;
display: inline-block;
}
.umb-nested-content__placeholder-icon {
font-size: 18px;
vertical-align: middle;
}
.form-horizontal .umb-nested-content--narrow .controls-row {
margin-left: 40% !important;
}

View File

@@ -528,7 +528,8 @@ input[type="checkbox"][readonly] {
.help-inline {
display: inline-block;
vertical-align: middle;
padding-left: 5px;
padding-top: 4px;
padding-left: 2px;
}
div.help {

View File

@@ -97,7 +97,8 @@
.history-line {
width: 2px;
height: 100%;
top: 10px;
bottom: 10px;
margin: 0 0 0 14px;
background-color: @gray-8;
position: absolute;

View File

@@ -81,39 +81,55 @@
</div>
<div class="umb-control-group clearfix" ng-if="!model.property.locked">
<h5><localize key="validation_validation"></localize></h5>
<label>
<label>
<localize key="validation_fieldIsMandatory"></localize>
</label>
<umb-toggle data-element="validation_mandatory"
checked="model.property.validation.mandatory"
on-click="vm.toggleValidation()"
on-click="vm.toggleValidation()">
focus-when="{{vm.focusOnMandatoryField}}"
>
</umb-toggle>
<label class="mt3">
<input type="text"
class="editor-validation-message"
localize="placeholder"
placeholder="@validation_mandatoryMessage"
ng-model="model.property.validation.mandatoryMessage"
ng-if="model.property.validation.mandatory"
ng-keypress="vm.submitOnEnter($event)">
</input>
<label class="mt3">
<localize key="validation_customValidation"></localize>
</label>
<select class="umb-dropdown" ng-options="validationType.name for validationType in vm.validationTypes" ng-model="vm.selectedValidationType" ng-change="vm.changeValidationType(vm.selectedValidationType)">
<option value=""><localize key="validation_noValidation">No validation</localize></option>
</select>
<textarea class="editor-validation-pattern"
localize="placeholder"
placeholder="@validation_validationRegExp"
ng-model="model.property.validation.pattern"
ng-change="vm.changeValidationPattern()"
ng-if="vm.showValidationPattern"
umb-auto-resize
focus-when="{{vm.focusOnPatternField}}"
ng-keypress="vm.submitOnEnter($event)">
</textarea>
localize="placeholder"
placeholder="@validation_validationRegExp"
ng-model="model.property.validation.pattern"
ng-change="vm.changeValidationPattern()"
ng-if="vm.showValidationPattern"
umb-auto-resize
focus-when="{{vm.focusOnPatternField}}"
ng-keypress="vm.submitOnEnter($event)">
</textarea>
<input type="text"
class="editor-validation-message"
localize="placeholder"
placeholder="@validation_validationRegExpMessage"
ng-model="model.property.validation.patternMessage"
ng-if="vm.showValidationPattern"
ng-keypress="vm.submitOnEnter($event)" />
</div>
<div class="umb-control-group clearfix" ng-if="model.contentType === 'documentType' && model.contentTypeAllowCultureVariant">

View File

@@ -21,7 +21,7 @@
</label>
<umb-property-actions actions="propertyEditorAPI.propertyActions"></umb-property-actions>
<umb-property-actions actions="propertyActions"></umb-property-actions>
<small class="control-description" ng-bind-html="property.description | preserveNewLineInHtml"></small>
</div>

View File

@@ -24,10 +24,6 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
alias: "selectedEditor",
description: "Select a property editor",
label: "Property editor"
},
selectedEditorId: {
alias: "selectedEditorId",
label: "Property editor alias"
}
};
@@ -205,7 +201,7 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
var labelKeys = [
"general_settings",
"references_tabName"
"general_info"
];
localizationService.localizeMany(labelKeys).then(function (values) {
@@ -220,9 +216,9 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
},
{
"name": values[1],
"alias": "references",
"icon": "icon-molecular-network",
"view": "views/datatypes/views/datatype.references.html"
"alias": "info",
"icon": "icon-info",
"view": "views/datatypes/views/datatype.info.html"
}
];
});

View File

@@ -1,12 +1,12 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.DataType.ReferencesController
* @name Umbraco.Editors.DataType.InfoController
* @function
*
* @description
* The controller for the references view of the datatype editor
* The controller for the info view of the datatype editor
*/
function DataTypeReferencesController($scope, $routeParams, dataTypeResource, eventsService, $timeout) {
function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsService, $timeout) {
var vm = this;
var evts = [];
@@ -34,7 +34,7 @@ function DataTypeReferencesController($scope, $routeParams, dataTypeResource, ev
// load data type references when the references tab is activated
evts.push(eventsService.on("app.tabChange", function (event, args) {
$timeout(function () {
if (args.alias === "references") {
if (args.alias === "info") {
loadRelations();
}
});
@@ -52,4 +52,4 @@ function DataTypeReferencesController($scope, $routeParams, dataTypeResource, ev
}
angular.module("umbraco").controller("Umbraco.Editors.DataType.ReferencesController", DataTypeReferencesController);
angular.module("umbraco").controller("Umbraco.Editors.DataType.InfoController", DataTypeInfoController);

View File

@@ -0,0 +1,140 @@
<div ng-controller="Umbraco.Editors.DataType.InfoController as vm">
<div class="umb-package-details">
<div class="umb-package-details__main-content">
<umb-load-indicator ng-if="vm.view.loading === true"></umb-load-indicator>
<umb-box ng-if="vm.view.loading === false && vm.hasReferences === false">
<umb-box-header title-key="references_tabName"></umb-box-header>
<umb-box-content>
<umb-empty-state size="small">
<localize key="references_DataTypeNoReferences">This Data Type has no references.</localize>
</umb-empty-state>
</umb-box-content>
</umb-box>
<div ng-if="vm.view.loading === false && vm.hasReferences === true">
<!-- Document Types -->
<div ng-if="vm.references.documentTypes.length > 0" class="mb4">
<h5 class="mt0" style="margin-bottom: 20px;">
<localize key="references_labelUsedByDocumentTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.documentTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/documentTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
<!-- Media Types -->
<div ng-if="vm.references.mediaTypes.length > 0" class="mb4">
<h5 class="mt0" style="margin-bottom: 20px;">
<localize key="references_labelUsedByMediaTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.mediaTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/mediaTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
<!-- Member Types -->
<div ng-if="vm.references.memberTypes.length > 0" class="mb4">
<h5 class="mt0" style="margin-bottom: 20px;">
<localize key="references_labelUsedByMemberTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.memberTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/memberTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="umb-package-details__sidebar">
<umb-box data-element="node-info-general">
<umb-box-header title-key="general_general"></umb-box-header>
<umb-box-content class="block-form">
<umb-control-group label="Id" ng-if="model.showIdentifier">
<div>{{model.content.id}}</div>
<small>{{model.content.key}}</small>
</umb-control-group>
<umb-control-group label="Property Editor Alias" ng-if="model.content.selectedEditor">
<div>{{model.content.selectedEditor}}</div>
</umb-control-group>
</umb-box-content>
</umb-box>
</div>
</div>
</div>

View File

@@ -1,112 +0,0 @@
<div ng-controller="Umbraco.Editors.DataType.ReferencesController as vm">
<umb-load-indicator ng-if="vm.view.loading === true"></umb-load-indicator>
<umb-box ng-if="vm.view.loading === false && vm.hasReferences === false">
<umb-box-header title-key="references_tabName"></umb-box-header>
<umb-box-content>
<umb-empty-state size="small">
<localize key="references_DataTypeNoReferences">This Data Type has no references.</localize>
</umb-empty-state>
</umb-box-content>
</umb-box>
<div ng-if="vm.view.loading === false && vm.hasReferences === true">
<!-- Document Types -->
<div ng-if="vm.references.documentTypes.length > 0">
<h5 class="mt4" style="margin-bottom: 20px;">
<localize key="references_labelUsedByDocumentTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.documentTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/documentTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
<!-- Media Types -->
<div ng-if="vm.references.mediaTypes.length > 0">
<h5 class="mt4" style="margin-bottom: 20px;">
<localize key="references_labelUsedByMediaTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.mediaTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/mediaTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
<!-- Member Types -->
<div ng-if="vm.references.memberTypes.length > 0">
<h5 class="mt4" style="margin-bottom: 20px;">
<localize key="references_labelUsedByMemberTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.memberTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/memberTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -2,11 +2,6 @@
<umb-box-content>
<umb-control-group label="Id" ng-if="model.showIdentifier">
<div>{{model.content.id}}</div>
<small>{{model.content.key}}</small>
</umb-control-group>
<umb-property property="model.properties.selectedEditor">
<div>
<select name="selectedEditor"
@@ -20,10 +15,6 @@
</umb-property>
<umb-property property="model.properties.selectedEditorId">
<div>{{model.content.selectedEditor}}</div>
</umb-property>
<umb-property property="preValue"
ng-repeat="preValue in model.preValues">
<umb-property-editor model="preValue" is-pre-value="true"></umb-property-editor>
@@ -31,4 +22,4 @@
</umb-box-content>
</umb-box>
</umb-box>

View File

@@ -519,11 +519,16 @@
}));
evts.push(eventsService.on("editors.documentType.saved", function(name, args) {
if(args.documentType.allowedTemplates.length > 0){
navigationService.syncTree({ tree: "templates", path: [], forceReload: true })
.then(function (syncArgs) {
navigationService.reloadNode(syncArgs.node)
});
if(args.documentType.allowedTemplates.length > 0) {
navigationService.hasTree("templates").then(function (treeExists) {
if (treeExists) {
navigationService.syncTree({ tree: "templates", path: [], forceReload: true })
.then(function (syncArgs) {
navigationService.reloadNode(syncArgs.node)
}
);
}
});
}
}));

View File

@@ -71,7 +71,7 @@ function ColorPickerController($scope, $timeout) {
);
return {
isValid: isValid,
errorMsg: "Value cannot be empty",
errorMsg: $scope.model.validation.mandatoryMessage || "Value cannot be empty",
errorKey: "required"
};
}

View File

@@ -1,4 +1,4 @@
function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element, dateHelper) {
function dateTimePickerController($scope, angularHelper, dateHelper, validationMessageService) {
let flatPickr = null;
@@ -62,6 +62,11 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
setDatePickerVal();
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
$scope.mandatoryMessage = value;
});
}
$scope.clearDate = function() {

View File

@@ -32,11 +32,11 @@
</div>
<span ng-messages="datePickerForm.datepicker.$error" show-validation-on-submit >
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
<span class="help-inline" ng-message="valServer">{{datePickerForm.datepicker.errorMsg}}</span>
<span class="help-inline" ng-message="pickerError"><localize key="validation_invalidDate">Invalid date</localize></span>
</span>
<div ng-messages="datePickerForm.datepicker.$error" show-validation-on-submit >
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
<p class="help-inline" ng-message="valServer">{{datePickerForm.datepicker.errorMsg}}</p>
<p class="help-inline" ng-message="pickerError"><localize key="validation_invalidDate">Invalid date</localize></p>
</div>
<p ng-if="model.config.offsetTime === '1' && serverTimeNeedsOffsetting && model.value" class="muted">
<small><localize key="content_scheduledPublishServerTime">This translates to the following time on the server:</localize> {{serverTime}}</small><br />

View File

@@ -1,5 +1,5 @@
angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleController",
function($scope) {
function ($scope, validationMessageService) {
//setup the default config
var config = {
@@ -89,4 +89,10 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo
$scope.model.value = null;
}
}
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
$scope.mandatoryMessage = value;
});
});

View File

@@ -1,21 +1,29 @@
<div ng-controller="Umbraco.PropertyEditors.DropdownFlexibleController" ng-switch="model.config.multiple">
<select name="dropDownList"
class="umb-property-editor umb-dropdown"
ng-switch-default
ng-change="updateSingleDropdownValue()"
ng-model="model.singleDropdownValue"
ng-options="item.value as item.value for item in model.config.items"
ng-required="model.validation.mandatory">
<option></option>
</select>
<ng-form name="dropDownListForm">
<select name="dropDownList"
class="umb-property-editor umb-dropdown"
ng-switch-default
ng-change="updateSingleDropdownValue()"
ng-model="model.singleDropdownValue"
ng-options="item.value as item.value for item in model.config.items"
ng-required="model.validation.mandatory">
<option></option>
</select>
<!--NOTE: This ng-switch is required because ng-multiple doesn't actually support dynamic bindings with multi-select lists -->
<select name="dropDownList"
class="umb-property-editor umb-dropdown"
ng-switch-when="true"
multiple
ng-model="model.value"
ng-options="item.value as item.value for item in model.config.items"
ng-required="model.validation.mandatory"></select>
<!--NOTE: This ng-switch is required because ng-multiple doesn't actually support dynamic bindings with multi-select lists -->
<select name="dropDownList"
class="umb-property-editor umb-dropdown"
ng-switch-when="true"
multiple
ng-model="model.value"
ng-options="item.value as item.value for item in model.config.items"
ng-required="model.validation.mandatory"></select>
<div ng-messages="dropDownListForm.dropDownList.$error" show-validation-on-submit>
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
</div>
</ng-form>
</div>

View File

@@ -0,0 +1,10 @@
function emailController($scope, validationMessageService) {
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function(value) {
$scope.mandatoryMessage = value;
});
}
angular.module('umbraco').controller("Umbraco.PropertyEditors.EmailController", emailController);

View File

@@ -1,4 +1,4 @@
<div>
<div ng-controller="Umbraco.PropertyEditors.EmailController">
<ng-form name="emailFieldForm">
<input type="email" name="textbox"
ng-model="model.value"
@@ -8,10 +8,10 @@
ng-required="model.config.IsRequired || model.validation.mandatory"
val-server="value" />
<span ng-messages="emailFieldForm.textbox.$error" show-validation-on-submit >
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
<span class="help-inline" ng-message="valEmail"><localize key="validation_invalidEmail">Invalid email</localize></span>
<span class="help-inline" ng-message="valServer">{{emailFieldForm.textbox.errorMsg}}</span>
</span>
<div ng-messages="emailFieldForm.textbox.$error" show-validation-on-submit >
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
<p class="help-inline" ng-message="valEmail"><localize key="validation_invalidEmail">Invalid email</localize></p>
<p class="help-inline" ng-message="valServer">{{emailFieldForm.textbox.errorMsg}}</p>
</div>
</ng-form>
</div>

View File

@@ -1,322 +1,195 @@
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',
require: {
umbProperty: '?^umbProperty',
umbVariantContent: '?^^umbVariantContent'
}
};
});
$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) {
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 = {};
localizationService.localizeMany(["grid_addElement", "content_createEmpty"]).then(function (data) {
$scope.labels.grid_addElement = data[0];
$scope.labels.content_createEmpty = data[1];
var labels = {};
vm.labels = labels;
localizationService.localizeMany(["grid_addElement", "content_createEmpty", "actions_copy"]).then(function (data) {
labels.grid_addElement = data[0];
labels.content_createEmpty = data[1];
labels.copy_icon_title = data[2]
});
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);
var nodeName = "";
// Retrive variant name
var culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
var activeVariant = _.find(editorState.current.variants, function (v) {
return !v.language || v.language.culture === culture;
});
if(vm.umbVariantContent) {
nodeName = vm.umbVariantContent.editor.content.name;
}
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, nodeName]).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
}
var removeAllEntries = function () {
localizationService.localizeMany(["content_nestedContentDeleteAllItems", "general_delete"]).then(function (data) {
overlayService.confirmDelete({
title: data[1],
content: data[0],
close: function () {
overlayService.close();
},
submit: function () {
vm.nodes = [];
setDirty();
updateModel();
overlayService.close();
}
});
});
}
var removeAllEntriesAction = {
labelKey: 'clipboard_labelForRemoveAllEntries',
labelTokens: [],
icon: 'trash',
method: removeAllEntries,
isDisabled: true
}
// 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 +199,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 +207,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 +252,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
overlayService.close();
},
submit: function () {
$scope.deleteNode(idx);
deleteNode(idx);
overlayService.close();
}
};
@@ -387,23 +260,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 +291,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 +306,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 +329,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 +376,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
$scope.pasteFromClipboard = function(newNode) {
function pasteFromClipboard(newNode) {
if (newNode === undefined) {
return;
@@ -512,15 +385,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 +406,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 +431,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
// Store the scaffold object
$scope.scaffolds.push(scaffold);
vm.scaffolds.push(scaffold);
}
scaffoldsLoaded++;
@@ -571,22 +444,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 +469,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,10 +501,11 @@ 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.
prop.ncMandatory = prop.validation.mandatory;
prop.validation = {
mandatory: false,
pattern: ""
@@ -643,7 +517,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
}
$scope.nodes.push(node);
vm.nodes.push(node);
return node;
}
@@ -670,60 +544,61 @@ 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 || model.value.length === 0;
removeAllEntriesAction.isDisabled = !model.value || model.value.length === 0;
}
$scope.$watch("currentNode", function (newVal) {
updateModel();
$scope.realCurrentNode = newVal;
});
var api = {};
api.propertyActions = [
copyAllEntriesAction
var propertyActions = [
copyAllEntriesAction,
removeAllEntriesAction
];
propertyEditorService.exposeAPI($scope, api);// must be executed at a state where the API is set.
this.$onInit = function () {
if (this.umbProperty) {
this.umbProperty.setPropertyActions(propertyActions);
}
};
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
updateModel();
});
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 +614,4 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
]);
})();

View File

@@ -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();
}
}
]);

View File

@@ -24,12 +24,7 @@
<td>
{{ph = placeholder(config);""}}
<div class="umb-nested-content__placeholder" ng-class="{'umb-nested-content__placeholder--selected':ph}" ng-click="openElemTypeModal($event, config)">
<span class="umb-nested-content__placeholder-icon-holder" ng-if="ph">
<i class="umb-nested-content__placeholder-icon {{ ph.icon }}"></i>
</span>
<span class="umb-nested-content__placeholder-name" ng-if="ph">
{{ ph.name }}
</span>
<umb-node-preview ng-if="ph" icon="ph.icon" name="ph.name"></umb-node-preview>
<localize key="content_nestedContentAddElementType" ng-if="!ph">Add element type</localize>
</div>

View File

@@ -1,7 +1,7 @@
<div class="umb-pane">
<div ng-repeat="property in tab.properties" class="umb-nested-content-property-container">
<umb-property property="property" ng-class="{'umb-nested-content--not-supported': property.notSupported}" data-element="property-{{property.alias}}">
<umb-property property="property" ng-class="{'umb-nested-content--not-supported': property.notSupported, 'umb-nested-content--mandatory': property.ncMandatory}" data-element="property-{{property.alias}}">
<umb-property-editor model="property"></umb-property-editor>
</umb-property>
@@ -10,4 +10,4 @@
<p ng-if="property.notSupported">{{property.notSupportedMessage}}</p>
</div>
</div>
</div>

View File

@@ -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>

View File

@@ -0,0 +1,75 @@
<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">
<button type="button" class="umb-nested-content__icon umb-nested-content__icon--copy" title="{{vm.labels.copy_icon_title}}" ng-click="vm.clickCopy($event, node);" ng-if="vm.showCopy">
<i class="icon icon-documents" aria-hidden="true"></i>
<span class="sr-only">{{vm.labels.copy_icon_title}}</span>
</button>
<button type="button" class="umb-nested-content__icon umb-nested-content__icon--delete" localize="title" title="general_delete" ng-click="vm.requestDeleteNode($index); $event.stopPropagation();">
<i class="icon icon-trash" aria-hidden="true"></i>
<span class="sr-only">
<localize key="general_delete">Delete</localize>
</span>
</button>
</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>

View File

@@ -1,5 +1,5 @@
angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsController",
function ($scope) {
function ($scope, validationMessageService) {
var vm = this;
@@ -23,6 +23,12 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsContro
vm.viewItems = sortedItems;
}
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
$scope.mandatoryMessage = value;
});
}

View File

@@ -1,7 +1,15 @@
<div class="umb-property-editor umb-radiobuttons" ng-controller="Umbraco.PropertyEditors.RadioButtonsController as vm">
<ul class="unstyled">
<li ng-repeat="item in vm.viewItems track by item.key">
<umb-radiobutton name="{{model.alias}}" value="{{item.value}}" model="model.value" text="{{item.value}}" required="model.validation.mandatory && model.value == ''"></umb-radiobutton>
</li>
</ul>
<ng-form name="radioButtonsFieldForm">
<ul class="unstyled">
<li ng-repeat="item in vm.viewItems track by item.key">
<umb-radiobutton name="{{model.alias}}" value="{{item.value}}" model="model.value" text="{{item.value}}" required="model.validation.mandatory && model.value == ''"></umb-radiobutton>
</li>
</ul>
<div ng-messages="radioButtonsFieldForm[model.alias].$error" show-validation-on-submit>
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
</div>
</ng-form>
</div>

View File

@@ -1,4 +1,4 @@
function textAreaController($scope) {
function textAreaController($scope, validationMessageService) {
// macro parameter editor doesn't contains a config object,
// so we create a new one to hold any properties
@@ -22,5 +22,11 @@ function textAreaController($scope) {
}
}
$scope.model.change();
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
$scope.mandatoryMessage = value;
});
}
angular.module('umbraco').controller("Umbraco.PropertyEditors.textAreaController", textAreaController);

View File

@@ -3,7 +3,7 @@
<textarea ng-model="model.value" id="{{model.alias}}" name="textarea" rows="{{model.config.rows || 10}}" class="umb-property-editor umb-textarea textstring" val-server="value" ng-keyup="model.change()" ng-required="model.validation.mandatory" aria-required="{{model.validation.mandatory}}"></textarea>
<span ng-messages="textareaFieldForm.textarea.$error" show-validation-on-submit >
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
<span class="help-inline" ng-message="required">{{mandatoryMessage}}</span>
<span class="help-inline" ng-message="valServer">{{textareaFieldForm.textarea.errorMsg}}</span>
</span>

View File

@@ -1,4 +1,4 @@
function textboxController($scope) {
function textboxController($scope, validationMessageService) {
// macro parameter editor doesn't contains a config object,
// so we create a new one to hold any properties
if (!$scope.model.config) {
@@ -18,6 +18,11 @@ function textboxController($scope) {
}
}
$scope.model.change();
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function(value) {
$scope.mandatoryMessage = value;
});
}
angular.module('umbraco').controller("Umbraco.PropertyEditors.textboxController", textboxController);

View File

@@ -12,7 +12,7 @@
<div ng-messages="textboxFieldForm.textbox.$error" show-validation-on-submit>
<p class="sr-only" ng-message="valServer" tabindex="0">{{model.label}} {{textboxFieldForm.textbox.errorMsg}}</p>
<p class="help-inline" ng-message="valServer" tabindex="0" aria-hidden="true">{{textboxFieldForm.textbox.errorMsg}}</p>
<p class="help-inline" ng-message="required"><localize key="general_required">Required</localize></p>
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
</div>
<div class="help" ng-if="model.count >= (model.config.maxChars*.8) && model.count <= model.config.maxChars">