diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Common/Js/MyPackage.js b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Common/Js/MyPackage.js new file mode 100644 index 0000000000..287e54b6a3 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Common/Js/MyPackage.js @@ -0,0 +1,54 @@ +'use strict'; + +define(['myApp'], function (app) { + + app.directive('valPostcode', function () { + + /// + /// A custom directive to validate for postcodes + /// + + return { + require: 'ngModel', + link: function (scope, elm, attrs, ctrl) { + + if (!attrs.valPostcode) + throw "valPostcode requires an attribute value specifying the country for the postcode"; + + var patternValidator = function (viewValue) { + //NOTE: we don't validate on empty values, use required validator for that + if (viewValue) { + var country = scope.$eval(attrs.valPostcode); + switch (country) { + case "Australia": + if (/^\d{4}$/.test(viewValue)) { + ctrl.$setValidity('valPostcode', true); + //reset the error msg + ctrl.errorMsg = ""; + return viewValue; + } + else { + // it is invalid, return undefined (no model update) + ctrl.$setValidity('valPostcode', false); + //assign an error msg property to the current validator + ctrl.errorMsg = "Australian postcodes must be a 4 digit number"; + return undefined; + } + + default: + throw "The country specified does not have validation logic applied"; + } + } + else { + // there is no value to validate so return that it is valid. + ctrl.$setValidity('valPostcode', true); + return viewValue; + } + }; + + ctrl.$formatters.push(patternValidator); + ctrl.$parsers.push(patternValidator); + } + }; + }); +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Controllers/ServerSidePropertyEditorsController.cs b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Controllers/ServerSidePropertyEditorsController.cs new file mode 100644 index 0000000000..c6afc1e5b6 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Controllers/ServerSidePropertyEditorsController.cs @@ -0,0 +1,15 @@ +using System.Web.Mvc; + +namespace Umbraco.Belle.App_Plugins.MyPackage.Controllers +{ + + public class ServerSidePropertyEditorsController : Controller + { + [HttpGet] + public ActionResult ServerEnvironment() + { + return View(); + } + + } +} diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest new file mode 100644 index 0000000000..b6472e3358 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest @@ -0,0 +1,50 @@ +{ + propertyEditors: [ + { + id: "0BA0F832-D759-4526-9B3E-94BBFC98F92E", + name: "Regex", + editor: { + view: "~/App_Plugins/MyPackage/PropertyEditors/Views/RegexEditor.html", + validation: [ + { + type: "Required" + }, + { + type: "Regex", + config: "\\d*" + } + ] + }, + preValues: { + view: "myPreValues1" + } + }, + { + id: "A24C4A00-29BF-4A57-BDE6-B1E305A96A4C", + name: "CSV Editor", + editor: { + view: "~/App_Plugins/MyPackage/PropertyEditors/Views/CsvEditor.html", + validation: [ + { + type: "Delimited", + config: { + delimiter: ",", + pattern: "^[a-zA-Z]*$" + } + }, + ] + } + } + ], + config: { + paths: { + myPackageCommon: '~/App_Plugins/MyPackage/Common/Js/MyPackage' + }, + shim: { + 'myPackageCommon': { 'exports': 'myPackageCommon' } + } + }, + init: [ + 'myPackageCommon' + ] +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadPropertyEditor.cs new file mode 100644 index 0000000000..d6ae8c0730 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadPropertyEditor.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Umbraco.Belle.System.PropertyEditors; + +namespace Umbraco.Belle.App_Plugins.MyPackage.PropertyEditors +{ + [PropertyEditor("23A66468-30E2-4537-8039-625F8BC5CA1E", "File upload", + "~/App_Plugins/MyPackage/PropertyEditors/Views/FileUploadEditor.html")] + public class FileUploadPropertyEditor : PropertyEditor + { + /// + /// Creates the value editor with custom validators + /// + /// + protected override ValueEditor CreateValueEditor() + { + var editor = base.CreateValueEditor(); + + editor.Validators = new List { new PostcodeValidator() }; + + return editor; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/CsvEditor.js b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/CsvEditor.js new file mode 100644 index 0000000000..729709a6d6 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/CsvEditor.js @@ -0,0 +1,39 @@ +'use strict'; + +//requires namespaceMgr +define(['namespaceMgr'], function () { + + Umbraco.Sys.registerNamespace("MyPackage.PropertyEditors"); + + MyPackage.PropertyEditors.CsvEditor = function ($scope, $http, $filter) { + + var values = []; + + //this will be comma delimited + if ($scope.model && $scope.model.value && (typeof $scope.model.value == "string")) { + var splitVals = $scope.model.value.split(","); + //set the values of our object + for (var i = 0; i < splitVals.length; i++) { + values.push({ + index: i, + value: splitVals[i].trim() + }); + } + } + + //set the scope values to bind on our view to the new object + $scope.values = values; + + //set up listeners for the object to write back to our comma delimited property value + $scope.$watch('values', function (newValue, oldValue) { + var csv = []; + for (var v in newValue) { + csv.push(newValue[v].value); + } + //write the csv value back to the property + $scope.model.value = csv.join(); + }, true); + + }; + +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js new file mode 100644 index 0000000000..be3f41df08 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js @@ -0,0 +1,80 @@ +'use strict'; + +//requires namespaceMgr +define(['namespaceMgr'], function () { + + Umbraco.Sys.registerNamespace("MyPackage.PropertyEditors"); + + MyPackage.PropertyEditors.FileUploadEditor = function ($scope) { + $scope.$on("fileSelected", function(event, args) { + $scope.$apply(function() { + //assign the file name to the model property + $scope.model.value = args.file.name; + //save the file object to the scope's files collection + $scope.files.push({ id: $scope.model.id, file: args.file }); + }); + }); + + }; + +}); + +function Ctrl($scope, $http) { + + //a simple model to bind to and send to the server + $scope.model = { + name: "", + comments: "" + }; + + //an array of files selected + $scope.files = []; + + //listen for the file selected event + $scope.$on("fileSelected", function (event, args) { + $scope.$apply(function () { + //add the file object to the scope's files collection + $scope.files.push(args.file); + }); + }); + + //the save method + $scope.save = function() { + $http({ + method: 'POST', + url: "/Api/SaveStuff", + //IMPORTANT!!! You might think this should be set to 'multipart/form-data' + // but this is not true because when we are sending up files the request + // needs to include a 'boundary' parameter which identifies the boundary + // name between parts in this multi-part request and setting the Content-type + // manually will not set this boundary parameter. For whatever reason, + // setting the Content-type to 'false' will force the request to automatically + // populate the headers properly including the boundary parameter. + headers: { 'Content-Type': false }, + //This method will allow us to change how the data is sent up to the server + // for which we'll need to encapsulate the model data in 'FormData' + transformRequest: function (data) { + var formData = new FormData(); + //need to convert our json object to a string version of json otherwise + // the browser will do a 'toString()' on the object which will result + // in the value '[Object object]' on the server. + formData.append("model", angular.toJson(data.model)); + //now add all of the assigned files + for (var i = 0; i < data.files; i++) { + //add each file to the form data and iteratively name them + formData.append("file" + i, data.files[i]); + } + return formData; + }, + //Create an object that contains the model and files which will be transformed + // in the above transformRequest method + data: { model: $scope.model, files: $scope.files } + }). + success(function (data, status, headers, config) { + alert("success!"); + }). + error(function (data, status, headers, config) { + alert("failed!"); + }); + }; +}; \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/PostcodeEditor.js b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/PostcodeEditor.js new file mode 100644 index 0000000000..2099e8875d --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/PostcodeEditor.js @@ -0,0 +1,11 @@ +'use strict'; + +define(['myApp'], function (app) { + + Umbraco.Sys.registerNamespace("MyPackage.PropertyEditors"); + + MyPackage.PropertyEditors.PostcodeEditor = function ($scope, $http, $filter) { + //change the config json model into something usable + $scope.model.config = $scope.$eval($scope.model.config); + }; +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/RegexEditor.js b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/RegexEditor.js new file mode 100644 index 0000000000..a7b6716526 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/RegexEditor.js @@ -0,0 +1,10 @@ +'use strict'; + +define(['myApp'], function (app) { + + Umbraco.Sys.registerNamespace("MyPackage.PropertyEditors"); + + MyPackage.PropertyEditors.RegexEditor = function ($scope, $http, $filter) { + var asdf = ""; + }; +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/PostcodePropertyEditor.cs b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/PostcodePropertyEditor.cs new file mode 100644 index 0000000000..548dbcffc7 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/PostcodePropertyEditor.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Umbraco.Belle.System.PropertyEditors; + +namespace Umbraco.Belle.App_Plugins.MyPackage.PropertyEditors +{ + [PropertyEditor("E96E24E5-7124-4FA8-A7D7-C3D3695E100D", "Postal Code", + "~/App_Plugins/MyPackage/PropertyEditors/Views/PostcodeEditor.html")] + public class PostcodePropertyEditor : PropertyEditor + { + /// + /// Creates the value editor with custom validators + /// + /// + protected override ValueEditor CreateValueEditor() + { + var editor = base.CreateValueEditor(); + + editor.Validators = new List { new PostcodeValidator() }; + + return editor; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/PostcodeValidator.cs b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/PostcodeValidator.cs new file mode 100644 index 0000000000..aab3592757 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/PostcodeValidator.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.RegularExpressions; +using Newtonsoft.Json.Linq; +using Umbraco.Belle.System.PropertyEditors; +using Umbraco.Core; + +namespace Umbraco.Belle.App_Plugins.MyPackage.PropertyEditors +{ + /// + /// Validates a postcode + /// + internal class PostcodeValidator : ValidatorBase + { + public override IEnumerable Validate(object value, string preValues, PropertyEditor editor) + { + var stringVal = value.ToString(); + + if (preValues.IsNullOrWhiteSpace()) yield break; + var asJson = JObject.Parse(preValues); + if (asJson["country"] == null) yield break; + + if (asJson["country"].ToString() == "Australia") + { + if (!Regex.IsMatch(stringVal, "^\\d{4}$")) + { + yield return new ValidationResult("Australian postcodes must be a 4 digit number", + new[] + { + //we only store a single value for this editor so the 'member' or 'field' + // we'll associate this error with will simply be called 'value' + "value" + }); + } + } + else + { + yield return new ValidationResult("Only Australian postcodes are supported for this validator"); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/ServerInfoPropertyEditor.cs b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/ServerInfoPropertyEditor.cs new file mode 100644 index 0000000000..3b4dd3ed26 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/ServerInfoPropertyEditor.cs @@ -0,0 +1,29 @@ +using System; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using Umbraco.Belle.System.PropertyEditors; + +namespace Umbraco.Belle.App_Plugins.MyPackage.PropertyEditors +{ + [PropertyEditor("AD056473-492B-47F8-9613-5A4936666C67", "Server Info")] + public class ServerInfoPropertyEditor : PropertyEditor + { + protected override ValueEditor CreateValueEditor() + { + if (HttpContext.Current == null) + { + throw new InvalidOperationException("This property editor only works in a web context"); + } + + var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + + return new ValueEditor(urlHelper.Action("ServerEnvironment", "ServerSidePropertyEditors", new {area = "MyPackage"})); + } + + protected override PreValueEditor CreatePreValueEditor() + { + return new PreValueEditor(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/CsvEditor.html b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/CsvEditor.html new file mode 100644 index 0000000000..26cc63e9fa --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/CsvEditor.html @@ -0,0 +1,16 @@ +
+ +
+ + + + {{multiFormVal.myPackage_multiVal.errorMsg}} + +
+
+
+ + {{model.value}} +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/FileUploadEditor.html b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/FileUploadEditor.html new file mode 100644 index 0000000000..e4cdd234d4 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/FileUploadEditor.html @@ -0,0 +1,6 @@ +
+ +
    +
  • {{file.file.name}}
  • +
+
\ No newline at end of file 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 new file mode 100644 index 0000000000..9a5a5f05b8 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/PostcodeEditor.html @@ -0,0 +1,12 @@ +
+

+ Enter a postcode for country {{model.config.country}} +

+ + + {{propertyForm.myPackage_postcode.errorMsg}} + {{propertyForm.myPackage_postcode.errorMsg}} +
\ No newline at end of file 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 new file mode 100644 index 0000000000..ccbeebfb34 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Views/RegexEditor.html @@ -0,0 +1,15 @@ +
+

+ Value in the format of {{model.config}} +

+ + + + Required! + {{propertyForm.regex.errorMsg}} +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/System/MyStartupHandler.cs b/src/Umbraco.Web.UI/App_Plugins/MyPackage/System/MyStartupHandler.cs new file mode 100644 index 0000000000..ef6b7f159f --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/System/MyStartupHandler.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using Umbraco.Belle.System; +using Umbraco.Core; +using umbraco.businesslogic; + +namespace Umbraco.Belle.App_Plugins.MyPackage.System +{ + public class MyStartupHandler : ApplicationEventHandler + { + + //TODO: We will remove these when we move to Umbraco core. + protected override bool ExecuteWhenApplicationNotConfigured + { + get { return true; } + } + protected override bool ExecuteWhenDatabaseNotConfigured + { + get { return true; } + } + + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + base.ApplicationStarted(umbracoApplication, applicationContext); + + //create a custom server variable to be exposed in JS + ServerVariablesParser.Parsing += (sender, dictionary) => + { + + var httpContext = HttpContext.Current; + if (httpContext == null) return; + + var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(httpContext), new RouteData())); + + dictionary.Add("MyPackage", new Dictionary() + { + {"serverEnvironmentView", urlHelper.Action("ServerEnvironment", "ServerSidePropertyEditors", new {area = "MyPackage"})} + }); + }; + + //For testing for now we'll route to /Belle/Main + var route = RouteTable.Routes.MapRoute( + "umbraco-server-side-property-editors", + "Belle/PropertyEditors/{controller}/{action}/{id}", + new { controller = "ServerSidePropertyEditors", action = "Index", id = UrlParameter.Optional }); + //assign it to an area so that the plugin view engine looks for us + route.DataTokens.Add("area", "MyPackage"); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Trees/LegacyTestTree.cs b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Trees/LegacyTestTree.cs new file mode 100644 index 0000000000..e5d013d6a0 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Trees/LegacyTestTree.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using umbraco.cms.presentation.Trees; + +namespace Umbraco.Belle.App_Plugins.MyPackage.Trees +{ + public class LegacyTestTree : BaseTree + { + public LegacyTestTree(string application) : base(application) + { + } + + public override string TreeAlias + { + get { return "legacyTestTree"; } + } + + public override int StartNodeID + { + get { return -1; } + } + + public override void RenderJS(ref StringBuilder javascript) + { + } + + public override void Render(ref XmlTree tree) + { + for (var i = 0; i < 10; i++) + { + var node = XmlTreeNode.Create(this); + node.Text = "Node " + i; + tree.Add(node); + } + } + + protected override void CreateRootNode(ref XmlTreeNode rootNode) + { + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Views/ServerSidePropertyEditors/ServerEnvironment.cshtml b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Views/ServerSidePropertyEditors/ServerEnvironment.cshtml new file mode 100644 index 0000000000..f949999089 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Views/ServerSidePropertyEditors/ServerEnvironment.cshtml @@ -0,0 +1,9 @@ +@model dynamic + + +

+ Computer Name@Environment.MachineName +

+

+ Computer Time@DateTime.Now +

\ No newline at end of file diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Views/Web.config b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Views/Web.config new file mode 100644 index 0000000000..f179ab8806 --- /dev/null +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Views/Web.config @@ -0,0 +1,58 @@ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index f95e703d87..5472d1201a 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -283,6 +283,13 @@ Properties\SolutionInfo.cs + + + + + + + default.aspx ASPXCodeBehind @@ -545,6 +552,15 @@ umbraco.aspx + + + + + + + + + @@ -552,6 +568,9 @@ + + + 404handlers.config @@ -2527,13 +2546,13 @@ - + + - diff --git a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml index 6280ca4c64..23dbdb7a19 100644 --- a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml @@ -98,7 +98,8 @@
{{model}}
- + @**@ + diff --git a/src/Umbraco.Web.UI/umbraco/js/umbraco.app.js b/src/Umbraco.Web.UI/umbraco/js/umbraco.app.js index ce1c80e082..ba5b655f01 100644 --- a/src/Umbraco.Web.UI/umbraco/js/umbraco.app.js +++ b/src/Umbraco.Web.UI/umbraco/js/umbraco.app.js @@ -348,36 +348,36 @@ define(['angular', 'namespaceMgr'], function (angular) { //This directive will bubble up a notification via an emit event (upwards) // describing the state of the validation element. This is useful for // parent elements to know about child element validation state. -app.directive('valBubble', function (u$AngularHelper) { - return { - require: 'ngModel', - restrict: "A", - link: function (scope, element, attr, ctrl) { + app.directive('valBubble', function (u$AngularHelper) { + return { + require: 'ngModel', + restrict: "A", + link: function (scope, element, attr, ctrl) { - if (!attr.name) { - throw "valBubble must be set on an input element that has a 'name' attribute"; - } - - var currentForm = u$AngularHelper.getCurrentForm(scope); - if (!currentForm || !currentForm.$name) - throw "valBubble requires that a name is assigned to the ng-form containing the validated input"; - - //watch the current form's validation for the current field name - scope.$watch(currentForm.$name + "." + ctrl.$name + ".$valid", function (isValid, lastValue) { - if (isValid != undefined) { - //emit an event upwards - scope.$emit("valBubble", { - isValid: isValid, // if the field is valid - element: element, // the element that the validation applies to - expression: this.exp, // the expression that was watched to check validity - scope: scope, // the current scope - ctrl: ctrl // the current controller - }); + if (!attr.name) { + throw "valBubble must be set on an input element that has a 'name' attribute"; } - }); - } - }; -}); + + var currentForm = u$AngularHelper.getCurrentForm(scope); + if (!currentForm || !currentForm.$name) + throw "valBubble requires that a name is assigned to the ng-form containing the validated input"; + + //watch the current form's validation for the current field name + scope.$watch(currentForm.$name + "." + ctrl.$name + ".$valid", function (isValid, lastValue) { + if (isValid != undefined) { + //emit an event upwards + scope.$emit("valBubble", { + isValid: isValid, // if the field is valid + element: element, // the element that the validation applies to + expression: this.exp, // the expression that was watched to check validity + scope: scope, // the current scope + ctrl: ctrl // the current controller + }); + } + }); + } + }; + }); //This directive will display a validation summary for the current form based on the //content properties of the current content item. @@ -490,34 +490,34 @@ app.directive('valBubble', function (u$AngularHelper) { }); //This service is some helper methods for extending angular -contentHelpers.factory('u$AngularHelper', function() { - return { - getCurrentForm: function(scope) { - /// Returns the current form object applied to the scope or null if one is not found - //NOTE: There isn't a way in angular to get a reference to the current form object since the form object - // is just defined as a property of the scope when it is named but you'll always need to know the name which - // isn't very convenient. If we want to watch for validation changes we need to get a form reference. - // The way that we detect the form object is a bit hackerific in that we detect all of the required properties - // that exist on a form object. + contentHelpers.factory('u$AngularHelper', function() { + return { + getCurrentForm: function(scope) { + /// Returns the current form object applied to the scope or null if one is not found + //NOTE: There isn't a way in angular to get a reference to the current form object since the form object + // is just defined as a property of the scope when it is named but you'll always need to know the name which + // isn't very convenient. If we want to watch for validation changes we need to get a form reference. + // The way that we detect the form object is a bit hackerific in that we detect all of the required properties + // that exist on a form object. - var form = null; - var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$invalid", "$addControl", "$removeControl", "$setValidity", "$setDirty"]; - for (var p in scope) { - if (_.isObject(scope[p]) && !_.isFunction(scope[p]) && !_.isArray(scope[p]) && p.substr(0, 1) != "$") { - var props = _.keys(scope[p]); - if (props.length < requiredFormProps.length) continue; - if (_.every(requiredFormProps, function(item) { - return _.contains(props, item); - })) { - form = scope[p]; - break; + var form = null; + var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$invalid", "$addControl", "$removeControl", "$setValidity", "$setDirty"]; + for (var p in scope) { + if (_.isObject(scope[p]) && !_.isFunction(scope[p]) && !_.isArray(scope[p]) && p.substr(0, 1) != "$") { + var props = _.keys(scope[p]); + if (props.length < requiredFormProps.length) continue; + if (_.every(requiredFormProps, function(item) { + return _.contains(props, item); + })) { + form = scope[p]; + break; + } } } - } - return form; - } - }; -}); + return form; + } + }; + }); //This service is used to wire up all server-side valiation messages // back into the UI in a consistent format. @@ -630,7 +630,7 @@ contentHelpers.factory('u$AngularHelper', function() { $scope.serverErrors = u$ServerValidation; //the url to get the content from - var getContentUrl = Umbraco.Sys.ServerVariables.contentEditorApiBaseUrl + "GetContent?id=" + 1; + var getContentUrl = Umbraco.Sys.ServerVariables.contentEditorApiBaseUrl + "GetContent?id=" + 1166; var saveContentUrl = Umbraco.Sys.ServerVariables.contentEditorApiBaseUrl + "PostSaveContent"; //go get the content from the server @@ -670,7 +670,7 @@ contentHelpers.factory('u$AngularHelper', function() { transformRequest: function (data) { var formData = new FormData(); //need to convert our json object to a string version of json - formData.append("model", angular.toJson(u$ContentHelper.formatPostData(data.model))); + formData.append("contentItem", angular.toJson(u$ContentHelper.formatPostData(data.model))); //now add all of the assigned files for (var f in data.files) { //each item has a property id and the file object, we'll ensure that the id is suffixed to the key diff --git a/src/Umbraco.Web.UI/umbraco/lib/underscore/underscore-min.js b/src/Umbraco.Web.UI/umbraco/lib/underscore/underscore-min.js new file mode 100644 index 0000000000..83292f0902 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/lib/underscore/underscore-min.js @@ -0,0 +1,5 @@ +// Underscore.js 1.4.1 +// http://underscorejs.org +// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore may be freely distributed under the MIT license. +(function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.push,u=r.slice,a=r.concat,f=r.unshift,l=i.toString,c=i.hasOwnProperty,h=r.forEach,p=r.map,d=r.reduce,v=r.reduceRight,m=r.filter,g=r.every,y=r.some,b=r.indexOf,w=r.lastIndexOf,E=Array.isArray,S=Object.keys,x=s.bind,T=function(e){if(e instanceof T)return e;if(!(this instanceof T))return new T(e);this._wrapped=e};typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(exports=module.exports=T),exports._=T):e._=T,T.VERSION="1.4.1";var N=T.each=T.forEach=function(e,t,r){if(h&&e.forEach===h)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i2;if(d&&e.reduce===d)return r&&(t=T.bind(t,r)),i?e.reduce(t,n):e.reduce(t);N(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.reduceRight=T.foldr=function(e,t,n,r){var i=arguments.length>2;if(v&&e.reduceRight===v)return r&&(t=T.bind(t,r)),arguments.length>2?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=T.keys(e);s=o.length}N(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.find=T.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},T.filter=T.select=function(e,t,n){var r=[];return m&&e.filter===m?e.filter(t,n):(N(e,function(e,i,s){t.call(n,e,i,s)&&(r[r.length]=e)}),r)},T.reject=function(e,t,n){var r=[];return N(e,function(e,i,s){t.call(n,e,i,s)||(r[r.length]=e)}),r},T.every=T.all=function(e,t,r){t||(t=T.identity);var i=!0;return g&&e.every===g?e.every(t,r):(N(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=T.some=T.any=function(e,t,r){t||(t=T.identity);var i=!1;return y&&e.some===y?e.some(t,r):(N(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};T.contains=T.include=function(e,t){var n=!1;return b&&e.indexOf===b?e.indexOf(t)!=-1:(n=C(e,function(e){return e===t}),n)},T.invoke=function(e,t){var n=u.call(arguments,2);return T.map(e,function(e){return(T.isFunction(t)?t:e[t]).apply(e,n)})},T.pluck=function(e,t){return T.map(e,function(e){return e[t]})},T.where=function(e,t){return T.isEmpty(t)?[]:T.filter(e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},T.max=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&T.isEmpty(e))return-Infinity;var r={computed:-Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},T.min=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&T.isEmpty(e))return Infinity;var r={computed:Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;or||n===void 0)return 1;if(n>>1;n.call(r,e[u])=0})})},T.difference=function(e){var t=a.apply(r,u.call(arguments,1));return T.filter(e,function(e){return!T.contains(t,e)})},T.zip=function(){var e=u.call(arguments),t=T.max(T.pluck(e,"length")),n=new Array(t);for(var r=0;r=0;n--)t=[e[n].apply(this,t)];return t[0]}},T.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},T.keys=S||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)T.has(e,n)&&(t[t.length]=n);return t},T.values=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push(e[n]);return t},T.pairs=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push([n,e[n]]);return t},T.invert=function(e){var t={};for(var n in e)T.has(e,n)&&(t[e[n]]=n);return t},T.functions=T.methods=function(e){var t=[];for(var n in e)T.isFunction(e[n])&&t.push(n);return t.sort()},T.extend=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]=t[n]}),e},T.pick=function(e){var t={},n=a.apply(r,u.call(arguments,1));return N(n,function(n){n in e&&(t[n]=e[n])}),t},T.omit=function(e){var t={},n=a.apply(r,u.call(arguments,1));for(var i in e)T.contains(n,i)||(t[i]=e[i]);return t},T.defaults=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]==null&&(e[n]=t[n])}),e},T.clone=function(e){return T.isObject(e)?T.isArray(e)?e.slice():T.extend({},e):e},T.tap=function(e,t){return t(e),e};var M=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof T&&(e=e._wrapped),t instanceof T&&(t=t._wrapped);var i=l.call(e);if(i!=l.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=M(e[o],t[o],n,r)))break}else{var a=e.constructor,f=t.constructor;if(a!==f&&!(T.isFunction(a)&&a instanceof a&&T.isFunction(f)&&f instanceof f))return!1;for(var c in e)if(T.has(e,c)){o++;if(!(u=T.has(t,c)&&M(e[c],t[c],n,r)))break}if(u){for(c in t)if(T.has(t,c)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};T.isEqual=function(e,t){return M(e,t,[],[])},T.isEmpty=function(e){if(e==null)return!0;if(T.isArray(e)||T.isString(e))return e.length===0;for(var t in e)if(T.has(e,t))return!1;return!0},T.isElement=function(e){return!!e&&e.nodeType===1},T.isArray=E||function(e){return l.call(e)=="[object Array]"},T.isObject=function(e){return e===Object(e)},N(["Arguments","Function","String","Number","Date","RegExp"],function(e){T["is"+e]=function(t){return l.call(t)=="[object "+e+"]"}}),T.isArguments(arguments)||(T.isArguments=function(e){return!!e&&!!T.has(e,"callee")}),typeof /./!="function"&&(T.isFunction=function(e){return typeof e=="function"}),T.isFinite=function(e){return T.isNumber(e)&&isFinite(e)},T.isNaN=function(e){return T.isNumber(e)&&e!=+e},T.isBoolean=function(e){return e===!0||e===!1||l.call(e)=="[object Boolean]"},T.isNull=function(e){return e===null},T.isUndefined=function(e){return e===void 0},T.has=function(e,t){return c.call(e,t)},T.noConflict=function(){return e._=t,this},T.identity=function(e){return e},T.times=function(e,t,n){for(var r=0;r":">",'"':""","'":"'","/":"/"}};_.unescape=T.invert(_.escape);var D={escape:new RegExp("["+T.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+T.keys(_.unescape).join("|")+")","g")};T.each(["escape","unescape"],function(e){T[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),T.result=function(e,t){if(e==null)return null;var n=e[t];return T.isFunction(n)?n.call(e):n},T.mixin=function(e){N(T.functions(e),function(t){var n=T[t]=e[t];T.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(T,e))}})};var P=0;T.uniqueId=function(e){var t=P++;return e?e+t:t},T.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;T.template=function(e,t,n){n=T.defaults({},n,T.templateSettings);var r=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),i=0,s="__p+='";e.replace(r,function(t,n,r,o,u){s+=e.slice(i,u).replace(j,function(e){return"\\"+B[e]}),s+=n?"'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'":r?"'+\n((__t=("+r+"))==null?'':__t)+\n'":o?"';\n"+o+"\n__p+='":"",i=u+t.length}),s+="';\n",n.variable||(s="with(obj||{}){\n"+s+"}\n"),s="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+s+"return __p;\n";try{var o=new Function(n.variable||"obj","_",s)}catch(u){throw u.source=s,u}if(t)return o(t,T);var a=function(e){return o.call(this,e,T)};return a.source="function("+(n.variable||"obj")+"){\n"+s+"}",a},T.chain=function(e){return T(e).chain()};var F=function(e){return this._chain?T(e).chain():e};T.mixin(T),N(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];T.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),N(["concat","join","slice"],function(e){var t=r[e];T.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),T.extend(T.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs index 34a734aa54..d3aa1d5452 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing @@ -24,6 +25,6 @@ namespace Umbraco.Web.Models.ContentEditing public string View { get; set; } [DataMember(Name = "config")] - public string Config { get; set; } + public IEnumerable Config { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs index 2b453b6f6c..e3f8650689 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models; +using System.Collections.Generic; +using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { @@ -10,20 +11,6 @@ namespace Umbraco.Web.Models.ContentEditing public IDataTypeDefinition DataType { get; set; } public string Label { get; set; } public string Alias { get; set; } - public string Description { get; set; } - - public ContentPropertyDisplay ForDisplay(string getPreValue, string view) - { - return new ContentPropertyDisplay - { - Alias = Alias, - Id = Id, - View = view, - Config = getPreValue, - Description = Description, - Label = Label, - Value = Value - }; - } + public string Description { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 76f5222a5a..22567277a5 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.Models.Mapping Id = p.Id, Description = p.PropertyType.Description, Label = p.PropertyType.Name, - Config = _applicationContext.Services.DataTypeService.GetPreValueAsString(p.PropertyType.DataTypeDefinitionId), + Config = _applicationContext.Services.DataTypeService.GetPreValuesByDataTypeId(p.PropertyType.DataTypeDefinitionId), Value = p.Value.ToString(), View = editor.ValueEditor.View }; diff --git a/src/Umbraco.Web/UI/JavaScript/RequireJsConfig.js b/src/Umbraco.Web/UI/JavaScript/RequireJsConfig.js index 5be5db0bb9..cda72178ef 100644 --- a/src/Umbraco.Web/UI/JavaScript/RequireJsConfig.js +++ b/src/Umbraco.Web/UI/JavaScript/RequireJsConfig.js @@ -1,15 +1,15 @@ { waitSeconds: 120, paths: { - jquery: '../lib/jquery/jquery-1.8.2.min', - underscore: '../lib/underscore/underscore', - angular: '../lib/angular/angular', - angularResource: '../lib/angular/angular-resource', - statemanager: '../lib/angular/statemanager', - text: '../lib/require/text', - async: '../lib/require/async', - namespaceMgr: '../lib/Umbraco/NamespaceManager', - myApp: '../js/myApp' + jquery: 'lib/jquery/jquery-1.8.2.min', + underscore: 'lib/underscore/underscore-min', + angular: 'lib/angular/angular', + angularResource: 'lib/angular/angular-resource', + statemanager: 'lib/angular/statemanager', + text: 'lib/require/text', + async: 'lib/require/async', + namespaceMgr: 'lib/Umbraco/NamespaceManager', + myApp: 'js/umbraco.app' }, shim: { 'angular' : {'exports' : 'angular'},