From 50e10155e76faca27f7f89f08b640615d72e4d44 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Nov 2013 14:27:58 +1100 Subject: [PATCH] Completes: U4-3712 Allow c# property editors to have custom js injected into the js initialization block so they can load in angular controllers --- .../config/ClientDependency.Release.config | 29 +----- .../config/ClientDependency.config | 25 ----- .../Editors/BackOfficeController.cs | 6 +- .../PropertyEditors/DropDownPropertyEditor.cs | 4 +- .../PropertyEditorAssetAttribute.cs | 40 ++++++++ .../UI/JavaScript/AssetInitialization.cs | 99 +++++++++++++++++-- .../UI/JavaScript/CssInitialization.cs | 10 +- .../UI/JavaScript/JsInitialization.cs | 8 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/WebBootManager.cs | 10 +- 10 files changed, 162 insertions(+), 70 deletions(-) create mode 100644 src/Umbraco.Web/PropertyEditors/PropertyEditorAssetAttribute.cs diff --git a/src/Umbraco.Web.UI/config/ClientDependency.Release.config b/src/Umbraco.Web.UI/config/ClientDependency.Release.config index 1cce8a4204..0efd128202 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.Release.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.Release.config @@ -17,12 +17,12 @@ NOTES: The LoaderControlProvider is set to default, the javascriptPlaceHolderId, cssPlaceHolderId attributes are optional and default to what is listed below. If using this provider, then you must specify both PlaceHolder controls on your page in order to render the JS/CSS. --> - + - + @@ -62,30 +62,5 @@ NOTES: mapPath="~/App_Data/TEMP/ClientDependency" /> - - - - - - - - - - - diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config index 11f7a71ea9..889f02f6ed 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.config @@ -52,31 +52,6 @@ NOTES: - - - - - - - - - - - diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 82cced0131..fd299e2abf 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -51,9 +51,9 @@ namespace Umbraco.Web.Editors //get the legacy ActionJs file references to append as well var legacyActionJsRef = new JArray(GetLegacyActionJs(LegacyJsActionType.JsUrl)); - - var result = initJs.GetJavascriptInitialization(JsInitialization.GetDefaultInitialization(), legacyActionJsRef); - result += initCss.GetStylesheetInitialization(); + + var result = initJs.GetJavascriptInitialization(HttpContext, JsInitialization.GetDefaultInitialization(), legacyActionJsRef); + result += initCss.GetStylesheetInitialization(HttpContext); return JavaScript(result); } diff --git a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs index d9c8ee6d49..4cc8f068cf 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs @@ -1,9 +1,10 @@ -using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.PropertyEditors; using umbraco; +using ClientDependency.Core; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.PropertyEditors { @@ -18,7 +19,6 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.DropDownListAlias, "Dropdown list", "dropdown", ValueType = "STRING")] public class DropDownPropertyEditor : DropDownWithKeysPropertyEditor { - /// /// We need to override the value editor so that we can ensure the string value is published in cache and not the integer ID value. /// diff --git a/src/Umbraco.Web/PropertyEditors/PropertyEditorAssetAttribute.cs b/src/Umbraco.Web/PropertyEditors/PropertyEditorAssetAttribute.cs new file mode 100644 index 0000000000..db4b37a066 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/PropertyEditorAssetAttribute.cs @@ -0,0 +1,40 @@ +using System; +using ClientDependency.Core; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Indicates that the property editor requires this asset be loaded when the back office is loaded + /// + /// + /// This wraps a CDF asset + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class PropertyEditorAssetAttribute : Attribute + { + public ClientDependencyType AssetType { get; private set; } + public string FilePath { get; private set; } + public int Priority { get; set; } + + /// + /// Returns a CDF file reference + /// + public IClientDependencyFile DependencyFile + { + get + { + return Priority == int.MinValue + ? new BasicFile(AssetType) {FilePath = FilePath} + : new BasicFile(AssetType) {FilePath = FilePath, Priority = Priority}; + + } + } + + public PropertyEditorAssetAttribute(ClientDependencyType assetType, string filePath) + { + AssetType = assetType; + FilePath = filePath; + Priority = int.MinValue; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs b/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs index 1ea3c56b5b..7b5716bb20 100644 --- a/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs @@ -1,25 +1,109 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Text; using System.Web; using ClientDependency.Core; using ClientDependency.Core.Config; +using ClientDependency.Core.FileRegistration.Providers; using Newtonsoft.Json.Linq; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Web.UI.JavaScript { + /// + /// A custom renderer that only outputs a dependency path instead of script tags - for use with the js loader with yepnope + /// + public class DependencyPathRenderer : StandardRenderer + { + public override string Name + { + get { return "Umbraco.DependencyPathRenderer"; } + } + + /// + /// Used to delimit each dependency so we can split later + /// + public const string Delimiter = "||||"; + + /// + /// Override because the StandardRenderer replaces & with & but we don't want that so we'll reverse it + /// + /// + /// + /// + /// + /// + public override void RegisterDependencies(List allDependencies, HashSet paths, out string jsOutput, out string cssOutput, HttpContextBase http) + { + base.RegisterDependencies(allDependencies, paths, out jsOutput, out cssOutput, http); + + jsOutput = jsOutput.Replace("&", "&"); + cssOutput = cssOutput.Replace("&", "&"); + } + + protected override string RenderSingleJsFile(string js, IDictionary htmlAttributes) + { + return js + Delimiter; + } + + protected override string RenderSingleCssFile(string css, IDictionary htmlAttributes) + { + return css + Delimiter; + } + + } + internal abstract class AssetInitialization { + /// + /// Get all dependencies declared on property editors + /// + /// + /// + /// + protected JArray ScanPropertyEditors(ClientDependencyType cdfType, HttpContextBase httpContext) + { + if (httpContext == null) throw new ArgumentNullException("httpContext"); + var cdfAttributes = + PropertyEditorResolver.Current.PropertyEditors + .SelectMany(x => x.GetType().GetCustomAttributes(false)) + .Where(x => x.AssetType == cdfType) + .Select(x => x.DependencyFile) + .ToList(); + + string jsOut; + string cssOut; + var renderer = ClientDependencySettings.Instance.MvcRendererCollection["Umbraco.DependencyPathRenderer"]; + renderer.RegisterDependencies(cdfAttributes, new HashSet(), out jsOut, out cssOut, httpContext); + + var toParse = cdfType == ClientDependencyType.Javascript ? jsOut : cssOut; + + var result = new JArray(); + //split the result by the delimiter and add to the array + foreach (var u in toParse.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries)) + { + result.Add(u); + } + return result; + } + /// /// This will check if we're in release mode, if so it will create a CDF URL to load them all in at once /// /// /// + /// /// - protected JArray CheckIfReleaseAndOptimized(JArray fileRefs, ClientDependencyType cdfType) + protected JArray CheckIfReleaseAndOptimized(JArray fileRefs, ClientDependencyType cdfType, HttpContextBase httpContext) { - if (HttpContext.Current != null && HttpContext.Current.IsDebuggingEnabled == false) + if (httpContext == null) throw new ArgumentNullException("httpContext"); + + if (httpContext.IsDebuggingEnabled == false) { - return GetOptimized(fileRefs, cdfType); + return GetOptimized(fileRefs, cdfType, httpContext); } return fileRefs; } @@ -29,8 +113,9 @@ namespace Umbraco.Web.UI.JavaScript /// /// /// + /// /// - protected JArray GetOptimized(JArray fileRefs, ClientDependencyType cdfType) + protected JArray GetOptimized(JArray fileRefs, ClientDependencyType cdfType, HttpContextBase httpContext) { var depenencies = fileRefs.Select(x => { @@ -39,7 +124,7 @@ namespace Umbraco.Web.UI.JavaScript { if (Uri.IsWellFormedUriString(asString, UriKind.Relative)) { - var absolute = new Uri(HttpContext.Current.Request.Url, asString); + var absolute = new Uri(httpContext.Request.Url, asString); return new BasicFile(cdfType) { FilePath = absolute.AbsolutePath }; } return null; @@ -49,8 +134,8 @@ namespace Umbraco.Web.UI.JavaScript var urls = ClientDependencySettings.Instance.DefaultCompositeFileProcessingProvider.ProcessCompositeList( depenencies, - cdfType, - new HttpContextWrapper(HttpContext.Current)); + cdfType, + httpContext); var result = new JArray(); foreach (var u in urls) diff --git a/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs b/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs index be92605cda..a483d7f776 100644 --- a/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs @@ -1,4 +1,5 @@ -using ClientDependency.Core; +using System.Web; +using ClientDependency.Core; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -22,7 +23,7 @@ namespace Umbraco.Web.UI.JavaScript /// /// Processes all found manifest files and outputs yepnope.injectcss calls for all css files found in all manifests /// - public string GetStylesheetInitialization() + public string GetStylesheetInitialization(HttpContextBase httpContext) { var merged = new JArray(); foreach (var m in _parser.GetManifests()) @@ -31,7 +32,10 @@ namespace Umbraco.Web.UI.JavaScript } //now we can optimize if in release mode - merged = CheckIfReleaseAndOptimized(merged, ClientDependencyType.Css); + merged = CheckIfReleaseAndOptimized(merged, ClientDependencyType.Css, httpContext); + + //now we need to merge in any found cdf declarations on property editors + ManifestParser.MergeJArrays(merged, ScanPropertyEditors(ClientDependencyType.Css, httpContext)); return ParseMain(merged); } diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs b/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs index 389f50be00..e78a329db0 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; +using System.Web; using ClientDependency.Core; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -37,7 +38,7 @@ namespace Umbraco.Web.UI.JavaScript /// /// Processes all found manifest files and outputs the main.js file containing all plugin manifests /// - public string GetJavascriptInitialization(JArray umbracoInit, JArray additionalJsFiles = null) + public string GetJavascriptInitialization(HttpContextBase httpContext, JArray umbracoInit, JArray additionalJsFiles = null) { foreach (var m in _parser.GetManifests()) { @@ -51,7 +52,10 @@ namespace Umbraco.Web.UI.JavaScript } //now we can optimize if in release mode - umbracoInit = CheckIfReleaseAndOptimized(umbracoInit, ClientDependencyType.Javascript); + umbracoInit = CheckIfReleaseAndOptimized(umbracoInit, ClientDependencyType.Javascript, httpContext); + + //now we need to merge in any found cdf declarations on property editors + ManifestParser.MergeJArrays(umbracoInit, ScanPropertyEditors(ClientDependencyType.Javascript, httpContext)); return ParseMain( umbracoInit.ToString(), diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0be01640cc..b4e6208f70 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -359,6 +359,7 @@ + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index d2cd4e7438..1c15ecf3a1 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; +using ClientDependency.Core.Config; using StackExchange.Profiling.MVCHelpers; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -27,6 +29,7 @@ using Umbraco.Web.PropertyEditors.ValueConverters; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; +using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; @@ -84,7 +87,12 @@ namespace Umbraco.Web //add the profiling action filter GlobalFilters.Filters.Add(new ProfilingActionFilter()); - + + //Register a custom renderer - used to process property editor dependencies + var renderer = new DependencyPathRenderer(); + renderer.Initialize("Umbraco.DependencyPathRenderer", new NameValueCollection { { "compositeFileHandlerPath", "~/DependencyHandler.axd" } }); + ClientDependencySettings.Instance.MvcRendererCollection.Add(renderer); + return this; }