diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index e772cd3df7..be3fcb238e 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -33,7 +33,10 @@ namespace Umbraco.Core.Manifest /// internal static IEnumerable GetPropertyEditors(JArray jsonEditors) { - return JsonConvert.DeserializeObject>(jsonEditors.ToString(), new PropertyEditorConverter()); + return JsonConvert.DeserializeObject>( + jsonEditors.ToString(), + new PropertyEditorConverter(), + new PreValueFieldConverter()); } /// diff --git a/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs b/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs index 9b502315e7..a5199af379 100644 --- a/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs +++ b/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs @@ -27,6 +27,28 @@ namespace Umbraco.Core.Manifest target.StaticallyDefinedValueEditor.Validators = new List(); } + if (jObject["preValueEditor"] != null) + { + target.StaticallyDefinedPreValueEditor.Fields = new List(); + } + + base.Deserialize(jObject, target, serializer); + } + } + + internal class PreValueFieldConverter : JsonCreationConverter + { + protected override PreValueField Create(Type objectType, JObject jObject) + { + return new PreValueField() + { + //assign manifest validators so the serialization process can continue + Validators = new List() + }; + } + + protected override void Deserialize(JObject jObject, PreValueField target, JsonSerializer serializer) + { base.Deserialize(jObject, target, serializer); } } diff --git a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs index 3bc978177a..c3917e2227 100644 --- a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; namespace Umbraco.Core.PropertyEditors @@ -7,20 +8,26 @@ namespace Umbraco.Core.PropertyEditors /// Defines a pre-value editor /// /// - /// The Json serialization attributes are required for manifest property editors to work + /// A pre-value editor is made up of multiple pre-value fields, each field defines a key that the value is stored against. + /// Each field can have any editor and the value from each field can store any data such as a simple string or a json structure. + /// + /// The Json serialization attributes are required for manifest property editors to work. /// public class PreValueEditor { - /// - /// The full virtual path or the relative path to the current Umbraco folder for the angular view - /// - [JsonProperty("view")] - public string View { get; set; } + public PreValueEditor() + { + Fields = Enumerable.Empty(); + } /// - /// A collection of validators for the pre value editor + /// A collection of pre-value fields to be edited /// - [JsonProperty("validation")] - public IEnumerable Validators { get; set; } + /// + /// If fields are specified then the master View and Validators will be ignored + /// + [JsonProperty("fields")] + public IEnumerable Fields { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/PreValueField.cs b/src/Umbraco.Core/PropertyEditors/PreValueField.cs new file mode 100644 index 0000000000..ec8283c1b9 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/PreValueField.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Defines a pre value editable field + /// + public class PreValueField + { + /// + /// The name to display for this pre-value field + /// + [JsonProperty("label", Required = Required.Always)] + public string Name { get; set; } + + /// + /// The description to display for this pre-value field + /// + [JsonProperty("description")] + public string Description { get; set; } + + /// + /// Specifies whether to hide the label for the pre-value + /// + [JsonProperty("hideLabel")] + public bool HideLabel { get; set; } + + /// + /// The key to store the pre-value against + /// + [JsonProperty("key", Required = Required.Always)] + public string Key { get; set; } + + /// + /// The view to render for the field + /// + [JsonProperty("view", Required = Required.Always)] + public string View { get; set; } + + /// + /// A collection of validators for the pre value field + /// + [JsonProperty("validation")] + public IEnumerable Validators { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs index bb69b0a445..e86915bb49 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs @@ -32,7 +32,6 @@ namespace Umbraco.Core.PropertyEditors StaticallyDefinedValueEditor.ValueType = att.ValueType; StaticallyDefinedValueEditor.View = att.EditorView; - StaticallyDefinedPreValueEditor.View = att.PreValueEditorView; } } @@ -60,7 +59,7 @@ namespace Umbraco.Core.PropertyEditors [JsonProperty("name", Required = Required.Always)] public string Name { get; internal set; } - [JsonProperty("editor")] + [JsonProperty("editor", Required = Required.Always)] public ValueEditor ValueEditor { get { return CreateValueEditor(); } @@ -105,7 +104,18 @@ namespace Umbraco.Core.PropertyEditors /// /// protected virtual PreValueEditor CreatePreValueEditor() - { + { + if (StaticallyDefinedPreValueEditor != null) + { + foreach (var f in StaticallyDefinedPreValueEditor.Fields) + { + //detect if the view is a virtual path (in most cases, yes) then convert it + if (f.View.StartsWith("~/")) + { + f.View = IOHelper.ResolveUrl(f.View); + } + } + } return StaticallyDefinedPreValueEditor; } diff --git a/src/Umbraco.Core/PropertyEditors/ValidatorBase.cs b/src/Umbraco.Core/PropertyEditors/ValidatorBase.cs index 1ce79bf130..c75d1eb2d6 100644 --- a/src/Umbraco.Core/PropertyEditors/ValidatorBase.cs +++ b/src/Umbraco.Core/PropertyEditors/ValidatorBase.cs @@ -11,7 +11,11 @@ namespace Umbraco.Core.PropertyEditors /// /// Validates the object with the resolved ValueValidator found for this type /// - /// + /// + /// Depending on what is being validated, this value can be a json structure representing an editor's model, it could be a single + /// string representing an editor's model, this class structure is also used to validate pre-values and in that case this value + /// could be a json structure representing the entire pre-value model or it could ba a single value representing a pre-value field. + /// /// The current pre-values stored for the data type /// The property editor instance that we are validating for /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 72d2d64f53..cabf7e59e4 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -571,6 +571,7 @@ + diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index f5da535b70..1e631bb09a 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -14,6 +14,63 @@ namespace Umbraco.Belle.Tests public class ManifestParserTests { + [Test] + public void Parse_Property_Editors_With_Pre_Vals() + { + + var a = JsonConvert.DeserializeObject(@"[ + { + id: '0EEBB7CE-51BA-4F6B-9D9C-78BB3314366C', + name: 'Test 1', + editor: { + view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html', + valueType: 'int', + validation: [ + { + type: 'Required' + }, + { + type: 'Regex', + value: '\\d*' + }, + ] + }, + preValueEditor: { + fields: [ + { + label: 'Some config 1', + key: 'key1', + view: '~/App_Plugins/MyPackage/PropertyEditors/Views/pre-val1.html', + validation: [ + { + type: 'Required' + } + ] + }, + { + label: 'Some config 2', + key: 'key2', + view: '~/App_Plugins/MyPackage/PropertyEditors/Views/pre-val2.html' + } + ] + } + } +]"); + var parser = ManifestParser.GetPropertyEditors(a); + + Assert.AreEqual(1, parser.Count()); + Assert.AreEqual(2, parser.ElementAt(0).PreValueEditor.Fields.Count()); + Assert.AreEqual("key1", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(0).Key); + Assert.AreEqual("Some config 1", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(0).Name); + Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/Views/pre-val1.html", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(0).View); + Assert.AreEqual(1, parser.ElementAt(0).PreValueEditor.Fields.ElementAt(0).Validators.Count()); + + Assert.AreEqual("key2", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(1).Key); + Assert.AreEqual("Some config 2", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(1).Name); + Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/Views/pre-val2.html", parser.ElementAt(0).PreValueEditor.Fields.ElementAt(1).View); + Assert.AreEqual(0, parser.ElementAt(0).PreValueEditor.Fields.ElementAt(1).Validators.Count()); + } + [Test] public void Parse_Property_Editors() { @@ -39,22 +96,25 @@ namespace Umbraco.Belle.Tests { id: '1FCF5C39-5FC7-4BCE-AFBE-6500D9EBA261', name: 'Test 2', + defaultConfig: { key1: 'some default pre val' }, editor: { view: '~/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html' } - }, + } ]"); var parser = ManifestParser.GetPropertyEditors(a); Assert.AreEqual(2, parser.Count()); Assert.AreEqual(new Guid("0EEBB7CE-51BA-4F6B-9D9C-78BB3314366C"), parser.ElementAt(0).Id); Assert.AreEqual("Test 1", parser.ElementAt(0).Name); - Assert.AreEqual("~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html", parser.ElementAt(0).ValueEditor.View); + Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/MyEditor.html", parser.ElementAt(0).ValueEditor.View); Assert.AreEqual("int", parser.ElementAt(0).ValueEditor.ValueType); Assert.AreEqual(2, parser.ElementAt(0).ValueEditor.Validators.Count()); Assert.AreEqual(new Guid("1FCF5C39-5FC7-4BCE-AFBE-6500D9EBA261"), parser.ElementAt(1).Id); Assert.AreEqual("Test 2", parser.ElementAt(1).Name); + Assert.IsTrue(parser.ElementAt(1).DefaultPreValues.ContainsKey("key1")); + Assert.AreEqual("some default pre val", parser.ElementAt(1).DefaultPreValues["key1"]); } [Test] diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbeditor.directive.js index dfb802e4b3..7446ef8f16 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbeditor.directive.js @@ -9,7 +9,8 @@ angular.module("umbraco.directives") .directive('umbEditor', function (umbPropEditorHelper) { return { scope: { - model: "=" + model: "=", + isPreValue: "@" }, require: "^form", restrict: 'E', @@ -26,7 +27,7 @@ angular.module("umbraco.directives") scope.model.alias = Math.random().toString(36).slice(2); } - scope.propertyEditorView = umbPropEditorHelper.getViewPath(scope.model.view); + scope.propertyEditorView = umbPropEditorHelper.getViewPath(scope.model.view, scope.isPreValue); } }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/valpropertymsg.directive.js index 8ad52c4df4..119f6202db 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/valpropertymsg.directive.js @@ -36,11 +36,7 @@ function valPropertyMsg(serverValidationManager) { //listen for error changes scope.$watch("formCtrl.$error", function () { - if (formCtrl.$valid === undefined) { - return; - } - - if (!formCtrl.$valid) { + if (formCtrl.$invalid) { //first we need to check if the valPropertyMsg validity is invalid if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { @@ -53,7 +49,11 @@ function valPropertyMsg(serverValidationManager) { hasError = true; //update the validation message if we don't already have one assigned. if (showValidation && scope.errorMsg === "") { - var err = serverValidationManager.getPropertyError(scope.property.alias, ""); + var err; + //this can be null if no property was assigned + if (scope.property) { + err = serverValidationManager.getPropertyError(scope.property.alias, ""); + } scope.errorMsg = err ? err.errorMsg : "Property has errors"; } } @@ -72,7 +72,11 @@ function valPropertyMsg(serverValidationManager) { scope.$on("saving", function (ev, args) { showValidation = true; if (hasError && scope.errorMsg === "") { - var err = serverValidationManager.getPropertyError(scope.property.alias, ""); + var err; + //this can be null if no property was assigned + if (scope.property) { + err = serverValidationManager.getPropertyError(scope.property.alias, ""); + } scope.errorMsg = err ? err.errorMsg : "Property has errors"; } else if (!hasError) { @@ -109,27 +113,30 @@ function valPropertyMsg(serverValidationManager) { // It's important to note that we need to subscribe to server validation changes here because we always must // indicate that a content property is invalid at the property level since developers may not actually implement // the correct field validation in their property editors. - serverValidationManager.subscribe(scope.property.alias, "", function (isValid, propertyErrors, allErrors) { - hasError = !isValid; - if (hasError) { - //set the error message to the server message - scope.errorMsg = propertyErrors[0].errorMsg; - //flag that the current validator is invalid - formCtrl.$setValidity('valPropertyMsg', false); - } - else { - scope.errorMsg = ""; - //flag that the current validator is valid - formCtrl.$setValidity('valPropertyMsg', true); - } - }); - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function () { - serverValidationManager.unsubscribe(scope.property.alias, ""); - }); + if (scope.property) { //this can be null if no property was assigned + serverValidationManager.subscribe(scope.property.alias, "", function(isValid, propertyErrors, allErrors) { + hasError = !isValid; + if (hasError) { + //set the error message to the server message + scope.errorMsg = propertyErrors[0].errorMsg; + //flag that the current validator is invalid + formCtrl.$setValidity('valPropertyMsg', false); + } + else { + scope.errorMsg = ""; + //flag that the current validator is valid + formCtrl.$setValidity('valPropertyMsg', true); + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function() { + serverValidationManager.unsubscribe(scope.property.alias, ""); + }); + } } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valregex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/valregex.directive.js index c3b832f17e..3edb230d0d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valregex.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/valregex.directive.js @@ -12,13 +12,12 @@ function valRegex() { link: function (scope, elm, attrs, ctrl) { var regex; - if (scope[attrs.valRegex]) { + try { regex = new RegExp(scope.$eval(attrs.valRegex)); } - else { + catch(e) { regex = new RegExp(attrs.valRegex); } - var patternValidator = function (viewValue) { //NOTE: we don't validate on empty values, use required validator for that diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/valtab.directive.js index 5d6d3ec5d9..1df0c3459d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valtab.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/valtab.directive.js @@ -18,14 +18,10 @@ function valTab() { scope.tabHasError = false; //watch the current form's validation for the current field name - scope.$watch("formCtrl.$valid", function (isValid, lastValue) { - if (isValid === undefined) { - return; - } - + scope.$watch("formCtrl.$valid", function () { var tabContent = element.closest(".umb-panel").find("#" + tabId); - if (!isValid) { + if (formCtrl.$invalid) { //check if the validation messages are contained inside of this tabs if (tabContent.find(".ng-invalid").length > 0) { scope.tabHasError = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valtogglemsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/valtogglemsg.directive.js index e324e6ae4e..273e153b37 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valtogglemsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/valtogglemsg.directive.js @@ -14,18 +14,19 @@ function valToggleMsg(serverValidationManager) { if (!attr.valMsgFor){ throw "valToggleMsg requires that the attribute valMsgFor exists on the element"; } + if (!formCtrl[attr.valMsgFor]) { + throw "valToggleMsg cannot find field " + attr.valMsgFor + " on form " + formCtrl.$name; + } //assign the form control to our isolated scope so we can watch it's values scope.formCtrl = formCtrl; //if there's any remaining errors in the server validation service then we should show them. var showValidation = serverValidationManager.items.length > 0; - var hasError = false; //add a watch to the validator for the value (i.e. myForm.value.$error.required ) - scope.$watch(formCtrl.$name + "." + attr.valMsgFor + ".$error." + attr.valToggleMsg, function (isInvalid, oldValue) { - hasError = isInvalid; - if (hasError && showValidation) { + scope.$watch("formCtrl." + attr.valMsgFor + ".$error." + attr.valToggleMsg, function () { + if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) { element.show(); } else { @@ -35,7 +36,7 @@ function valToggleMsg(serverValidationManager) { scope.$on("saving", function(ev, args) { showValidation = true; - if (hasError) { + if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) { element.show(); } else { diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js index 6f0c21fa67..7cb3fe5daf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/datatype.mocks.js @@ -2,30 +2,84 @@ angular.module('umbraco.mocks'). factory('dataTypeMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { 'use strict'; - function returnNodebyId(status, data, headers) { + function returnById(status, data, headers) { if (!mocksUtils.checkAuth()) { return [401, null, null]; } var id = mocksUtils.getParameterByName(data, "id") || 1234; - + + var selectedId = String.CreateGuid(); + var dataType = { id: id, - name: "Data type " + id, - selectedEditor: String.CreateGuid() + name: "Simple editor " + id, + selectedEditor: selectedId, + availableEditors: [ + { name: "Simple editor 1", editorId: String.CreateGuid() }, + { name: "Simple editor 2", editorId: String.CreateGuid() }, + { name: "Simple editor 3", editorId: selectedId }, + { name: "Simple editor 4", editorId: String.CreateGuid() }, + { name: "Simple editor 5", editorId: String.CreateGuid() }, + { name: "Simple editor 6", editorId: String.CreateGuid() } + ], + preValues: [ + { + label: "Custom pre value 1", + description: "Enter a value for this pre-value", + key: "myPreVal", + view: "requiredfield", + validation: [ + { + type: "Required" + } + ] + }, + { + label: "Custom pre value 2", + description: "Enter a value for this pre-value", + key: "myPreVal", + view: "requiredfield", + validation: [ + { + type: "Required" + } + ] + } + ] }; return [200, dataType, null]; } + function returnEmpty(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var response = returnById(200, "", null); + var node = response[1]; + + node.name = ""; + node.selectedEditor = ""; + node.id = 0; + node.preValues = []; + + return response; + } return { register: function() { - $httpBackend - .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetById')) - .respond(returnNodebyId); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetById')) + .respond(returnById); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/DataType/GetEmpty')) + .respond(returnEmpty); }, expectGetById: function() { $httpBackend diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js index 6df4bf4071..3e92067787 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js @@ -63,6 +63,38 @@ angular.module('umbraco.mocks'). return [200, children, null]; } + function returnDataTypes(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var children = [ + { name: "Textstring", childNodesUrl: null, id: 10, icon: "icon-file-alt", children: [], expanded: false, hasChildren: false, level: 1, defaultAction: null, menuUrl: null }, + { name: "Multiple textstring", childNodesUrl: null, id: 11, icon: "icon-file-alt", children: [], expanded: false, hasChildren: false, level: 1, defaultAction: null, menuUrl: null }, + { name: "Yes/No", childNodesUrl: null, id: 12, icon: "icon-file-alt", children: [], expanded: false, hasChildren: false, level: 1, defaultAction: null, menuUrl: null }, + { name: "Rich Text Editor", childNodesUrl: null, id: 13, icon: "icon-file-alt", children: [], expanded: false, hasChildren: false, level: 1, defaultAction: null, menuUrl: null } + ]; + + return [200, children, null]; + } + + function returnDataTypeMenu(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var menu = [ + { + name: "Create", cssclass: "plus", alias: "create", metaData: { + jsAction: "umbracoMenuActions.CreateChildEntity" + } + }, + { seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} } + ]; + + return [200, menu, null]; + } + function returnApplicationTrees(status, data, headers) { if (!mocksUtils.checkAuth()) { @@ -111,12 +143,16 @@ angular.module('umbraco.mocks'). }; break; - case "developer": + case "developer": + + var dataTypeChildrenUrl = "/umbraco/UmbracoTrees/DataTypeTree/GetNodes?id=-1&application=developer"; + var dataTypeMenuUrl = "/umbraco/UmbracoTrees/DataTypeTree/GetMenu?id=-1&application=developer"; + t = { name: "developer", id: -1, children: [ - { name: "Data types", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "datatype" } }, + { name: "Data types", childNodesUrl: dataTypeChildrenUrl, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: dataTypeMenuUrl, metaData: { treeAlias: "datatype" } }, { name: "Macros", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "macros" } }, { name: "Packages", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "packager" } }, { name: "XSLT Files", childNodesUrl: url, id: -1, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, metaData: { treeAlias: "xslt" } }, @@ -184,6 +220,15 @@ angular.module('umbraco.mocks'). .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/ApplicationTreeApi/GetChildren')) .respond(returnChildren); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/DataTypeTree/GetNodes')) + .respond(returnDataTypes); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/DataTypeTree/GetMenu')) + .respond(returnDataTypeMenu); + $httpBackend .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoTrees/ApplicationTreeApi/GetMenu')) .respond(getMenuItems); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js index 160603e98b..e459cd6128 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js @@ -34,12 +34,12 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { /** saves or updates a data type object */ save: function (dataType, isNew) { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "PostSave", - [{ id: id }])), - 'Failed to save data for data type id ' + id); + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), + { + //TODO: SD: I need to finish this on Monday! + action: "save" + (isNew ? "New" : "") + }), + 'Failed to save data for data type id ' + id); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js index 6b18077070..302834194c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js @@ -8,7 +8,7 @@ * @description * Defines the methods that are called when menu items declare only an action to execute */ -function umbracoMenuActions($q, treeService) { +function umbracoMenuActions($q, treeService, $location) { return { @@ -26,6 +26,24 @@ function umbracoMenuActions($q, treeService) { */ "RefreshNodeMenuItem": function (args) { treeService.loadNodeChildren({ node: args.treeNode, section: args.section }); + }, + + /** + * @ngdoc method + * @name umbraco.services.umbracoMenuActions#CreateChildEntity + * @methodOf umbraco.services.umbracoMenuActions + * @function + * + * @description + * This will re-route to a route for creating a new entity as a child of the current node + * @param {object} args An arguments object + * @param {object} args.treeNode The tree node + * @param {object} args.section The current section + */ + "CreateChildEntity": function (args) { + var route = "/" + args.section + "/" + treeService.getTreeAlias(args.treeNode) + "/edit/" + args.treeNode.id; + //change to new path + $location.path(route).search({ create: true }); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 452a442a11..96c1f22497 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -196,15 +196,21 @@ function umbPropEditorHelper() { * * @param {string} input the view path currently stored for the property editor */ - getViewPath: function (input) { + getViewPath: function (input, isPreValue) { var path = String(input); if (path.startsWith('/')) { return path; } else { var pathName = path.replace('.', '/'); - //i.e. views/propertyeditors/fileupload/fileupload.html - return "views/propertyeditors/" + pathName + "/" + pathName + ".html"; + if (!isPreValue) { + //i.e. views/propertyeditors/fileupload/fileupload.html + return "views/propertyeditors/" + pathName + "/" + pathName + ".html"; + } + else { + //i.e. views/prevalueeditors/requiredfield.html + return "views/prevalueeditors/" + pathName + ".html"; + } } } }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property editors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/packages/property editors/relatedlinks/relatedlinks.html deleted file mode 100644 index 87180e4324..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/property editors/relatedlinks/relatedlinks.html +++ /dev/null @@ -1 +0,0 @@ -{{model | json}} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/property editors/relatedlinks/relatedlinks.js b/src/Umbraco.Web.UI.Client/src/packages/property editors/relatedlinks/relatedlinks.js deleted file mode 100644 index c8a6f62e5e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/property editors/relatedlinks/relatedlinks.js +++ /dev/null @@ -1 +0,0 @@ -alert("bong"); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/packages/propertyeditors/relatedlinks/relatedlinks.html index e69de29bb2..87180e4324 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/packages/propertyeditors/relatedlinks/relatedlinks.html @@ -0,0 +1 @@ +{{model | json}} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/propertyeditors/relatedlinks/relatedlinks.js b/src/Umbraco.Web.UI.Client/src/packages/propertyeditors/relatedlinks/relatedlinks.js index e69de29bb2..c8a6f62e5e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/propertyeditors/relatedlinks/relatedlinks.js +++ b/src/Umbraco.Web.UI.Client/src/packages/propertyeditors/relatedlinks/relatedlinks.js @@ -0,0 +1 @@ +alert("bong"); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/content/edit.html index fbdeb707de..fc32829941 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/edit.html @@ -1,50 +1,51 @@ -
- - - - - - - -
-
- - -
- Publish - - - - - - -
-
-
-
- - - - -
- - - - -
- -
-
- -
-
+
+ + + + + + + +
+
+
+ Preview page + data-hotkey="ctrl+s">Preview page +
+ +
+ Publish + + + + + + +
+
+
+
+ + + + +
+ + + + +
+ +
+
+ +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js index 6649921165..4bd72c084e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js @@ -7,13 +7,43 @@ * The controller for the content editor */ function DataTypeEditController($scope, $routeParams, $location, dataTypeResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper) { - + + //set up the standard data type props + function createDisplayProps() { + $scope.properties = { + selectedEditor: { + alias: "selectedEditor", + description: "Select a property editor", + label: "Property editor" + }, + selectedEditorId: { + alias: "selectedEditorId", + label: "Property editor GUID" + } + }; + } + + //setup the pre-values as props + function createPreValueProps(preVals) { + $scope.preValues = []; + for (var i = 0; i < preVals.length; i++) { + $scope.preValues.push({ + hideLabel: preVals[i].hideLabel, + alias: preVals[i].key, + description: preVals[i].description, + label: preVals[i].label, + view: preVals[i].view, + }); + } + } + if ($routeParams.create) { //we are creating so get an empty content item dataTypeResource.getScaffold($routeParams.id, $routeParams.doctype) .then(function(data) { $scope.loaded = true; $scope.content = data; + createDisplayProps(); }); } else { @@ -22,6 +52,8 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc .then(function(data) { $scope.loaded = true; $scope.content = data; + createDisplayProps(); + createPreValueProps($scope.content.preValues); //in one particular special case, after we've created a new item we redirect back to the edit // route but there might be server validation errors in the collection which we need to display @@ -34,6 +66,8 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc //ensure there is a form object assigned. var currentForm = angularHelper.getRequiredCurrentForm($scope); + //TODO: We need to handle the dynamic loading of the pre-value editor view whenever the drop down changes! + $scope.save = function (cnt) { $scope.$broadcast("saving", { scope: $scope }); @@ -42,12 +76,12 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc serverValidationManager.reset(); - dataTypeResource.save(cnt, $routeParams.create, $scope.files) + dataTypeResource.save(cnt, $routeParams.create) .then(function (data) { - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - newContent: data - }); + + //TODO: SD: I need to finish this on monday! + alert("Woot!"); + }, function (err) { contentEditingHelper.handleSaveError(err, $scope); }); @@ -55,4 +89,4 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc } -angular.module("umbraco").controller("Umbraco.Editors.DataType.EditController", DataTypeEditController); +angular.module("umbraco").controller("Umbraco.Editors.DataType.EditController", DataTypeEditController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html b/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html index c4ae0d5a31..87987bc464 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html @@ -1,4 +1,4 @@ -
+
@@ -13,28 +13,44 @@
- Save +
- - - - + +
+
- - - - -
+ + +
+ + Required +
- - +
+ + +
{{content.selectedEditor}}
+
+ +
+ + + + + + +
+
+ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/media/edit.html b/src/Umbraco.Web.UI.Client/src/views/media/edit.html index d8a9d668de..8f031bb0b0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/edit.html @@ -1,4 +1,4 @@ - +
@@ -9,8 +9,7 @@
- Save +
@@ -32,4 +31,4 @@
- \ No newline at end of file +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html new file mode 100644 index 0000000000..621a0cb9d7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html @@ -0,0 +1,7 @@ +
+ + + Required +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/validationtest/validationtest.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/validationtest/validationtest.html index 0258e9f518..b114564780 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/validationtest/validationtest.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/validationtest/validationtest.html @@ -1,12 +1,12 @@ 

Enter a numeric value

- - Required! - The value entered is not a number - A server error occurred + Required! + The value entered is not a number + A server error occurred
diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest index 5893b50449..a542d5b3ff 100644 --- a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest @@ -3,6 +3,9 @@ { id: "0BA0F832-D759-4526-9B3E-94BBFC98F92E", name: "Regex", + defaultConfig: { + regexStatement: "^\\d*$" + }, editor: { view: "~/App_Plugins/MyPackage/PropertyEditors/Views/RegexEditor.html", validation: [ @@ -15,8 +18,20 @@ } ] }, - preValues: { - view: "myPreValues1" + preValueEditor: { + fields: [ + { + label: "Regular expression", + description: "Enter a regular expression to use to validate this editor", + key: "regex", + view: "~/App_Plugins/MyPackage/PropertyEditors/Views/regexStatement.html", + validation: [ + { + type: "Required" + } + ] + } + ] } }, { diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/PostcodeEditor.html b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/PostcodeEditor.html index 1e2a9e525d..c1d1e5ea7a 100644 --- a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/PostcodeEditor.html +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/PostcodeEditor.html @@ -4,7 +4,7 @@

+ val-postcode="model.config.country"/> {{propertyForm.myPackage_postcode.errorMsg}} {{propertyForm.myPackage_postcode.errorMsg}} diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/RegexEditor.html b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/RegexEditor.html index 75e6633415..ad86065019 100644 --- a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/RegexEditor.html +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/RegexEditor.html @@ -8,7 +8,7 @@ Required! diff --git a/src/Umbraco.Web/HttpUrlHelperExtensions.cs b/src/Umbraco.Web/HttpUrlHelperExtensions.cs index 829bc419c7..a8988ac264 100644 --- a/src/Umbraco.Web/HttpUrlHelperExtensions.cs +++ b/src/Umbraco.Web/HttpUrlHelperExtensions.cs @@ -96,11 +96,11 @@ namespace Umbraco.Web routeName = string.Format("umbraco-{0}-{1}-{2}", "api", area, controllerName); if (id == null) { - return url.Link(routeName, new {controller = controllerName, action = actionName, area = area}); + return url.Link(routeName, new {controller = controllerName, action = actionName}); } else { - return url.Link(routeName, new { controller = controllerName, action = actionName, area = area, id = id }); + return url.Link(routeName, new { controller = controllerName, action = actionName, id = id }); } } } diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index ee85b0ccdb..27e787575e 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -9,15 +9,18 @@ using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Services; +using Umbraco.Web.Mvc; using Umbraco.Web.Trees.Menu; using umbraco; using umbraco.BusinessLogic.Actions; using umbraco.businesslogic; using umbraco.interfaces; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [Tree(Constants.Applications.Content, Constants.Trees.Content, "Content")] + [PluginController("UmbracoTrees")] public class ContentTreeController : ContentTreeControllerBase { protected override TreeNode CreateRootNode(FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index c9159b336b..73477e0f71 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -5,12 +5,15 @@ using System.Net; using System.Net.Http.Formatting; using System.Web.Http; using Umbraco.Core; +using Umbraco.Web.Mvc; using Umbraco.Web.Trees.Menu; using umbraco.BusinessLogic.Actions; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes, "Data Types")] + [PluginController("UmbracoTrees")] public class DataTypeTreeController : TreeApiController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 0ea1954456..7078e7d2ac 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -3,12 +3,15 @@ using System.Net.Http.Formatting; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.Mvc; using Umbraco.Web.Trees.Menu; using umbraco.BusinessLogic.Actions; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [Tree(Constants.Applications.Media, Constants.Trees.Media, "Media")] + [PluginController("UmbracoTrees")] public class MediaTreeController : ContentTreeControllerBase { protected override int RecycleBinId