From dc61c92f317b2834e3aab2401cfd5a6c0c4d7229 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Thu, 7 Oct 2010 13:08:31 -0700 Subject: [PATCH] Introducing display-time events to shape rendering Shape specific events can be hooked with ShapeDescriptor Displaying and Displayed delegate arrays Events for every shape can be hooked with IShapeDisplayEvents Displaying and Displayed methods --HG-- branch : dev --- .../DefaultDisplayManagerTests.cs | 122 ++++++++++++++++-- .../Descriptors/ShapeDescriptor.cs | 5 + .../Implementation/DefaultDisplayManager.cs | 68 +++++++--- .../Implementation/IShapeDisplayEvents.cs | 26 ++++ src/Orchard/Orchard.Framework.csproj | 1 + 5 files changed, 195 insertions(+), 27 deletions(-) create mode 100644 src/Orchard/DisplayManagement/Implementation/IShapeDisplayEvents.cs diff --git a/src/Orchard.Tests/DisplayManagement/DefaultDisplayManagerTests.cs b/src/Orchard.Tests/DisplayManagement/DefaultDisplayManagerTests.cs index 031a042f9..15b40ee50 100644 --- a/src/Orchard.Tests/DisplayManagement/DefaultDisplayManagerTests.cs +++ b/src/Orchard.Tests/DisplayManagement/DefaultDisplayManagerTests.cs @@ -32,10 +32,22 @@ namespace Orchard.Tests.DisplayManagement { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As() + .As() + .InstancePerLifetimeScope(); + builder.Register(ctx => _defaultShapeTable); builder.Register(ctx => _workContext); } + class TestDisplayEvents : IShapeDisplayEvents { + public Action Displaying = ctx => { }; + public Action Displayed = ctx => { }; + + void IShapeDisplayEvents.Displaying(ShapeDisplayingContext context) { Displaying(context); } + void IShapeDisplayEvents.Displayed(ShapeDisplayedContext context) { Displayed(context); } + } + public class Theme : ITheme { public string ThemeName { get; set; } public string DisplayName { get; set; } @@ -209,22 +221,108 @@ namespace Orchard.Tests.DisplayManagement { var descriptor = new ShapeDescriptor { ShapeType = "Foo", }; - descriptor.Bindings["Foo"] = new ShapeBinding { - BindingName = "Foo", - Binding = ctx => new HtmlString("Hi there!"), - }; - descriptor.Bindings["Foo__1"] = new ShapeBinding { - BindingName = "Foo__1", - Binding = ctx => new HtmlString("Hello (1)!"), - }; - descriptor.Bindings["Foo__2"] = new ShapeBinding { - BindingName = "Foo__2", - Binding = ctx => new HtmlString("Hello (2)!"), - }; + AddBinding(descriptor, "Foo", ctx => new HtmlString("Hi there!")); + AddBinding(descriptor, "Foo__1", ctx => new HtmlString("Hello (1)!")); + AddBinding(descriptor, "Foo__2", ctx => new HtmlString("Hello (2)!")); AddShapeDescriptor(descriptor); var result = displayManager.Execute(CreateDisplayContext(shape)); Assert.That(result.ToString(), Is.EqualTo("Hello (2)!")); } + + private static void AddBinding(ShapeDescriptor descriptor, string bindingName, Func binding) { + descriptor.Bindings[bindingName] = new ShapeBinding { + BindingName = bindingName, + Binding = binding, + }; + } + + + [Test] + public void IShapeDisplayEventsIsCalled() { + var displayManager = _container.Resolve(); + + var shape = new Shape { + Metadata = new ShapeMetadata { + Type = "Foo" + } + }; + + var descriptor = new ShapeDescriptor { + ShapeType = "Foo", + }; + AddBinding(descriptor, "Foo", ctx => new HtmlString("yarg")); + AddShapeDescriptor(descriptor); + + var displayingEventCount = 0; + var displayedEventCount = 0; + _container.Resolve().Displaying = ctx => { ++displayingEventCount; }; + _container.Resolve().Displayed = ctx => { ++displayedEventCount; ctx.ChildContent = new HtmlString("[" + ctx.ChildContent.ToHtmlString() + "]"); }; + + var result = displayManager.Execute(CreateDisplayContext(shape)); + + Assert.That(displayingEventCount, Is.EqualTo(1)); + Assert.That(displayedEventCount, Is.EqualTo(1)); + Assert.That(result.ToString(), Is.EqualTo("[yarg]")); + } + + + [Test] + public void ShapeDescriptorDisplayingAndDisplayedAreCalled() { + var displayManager = _container.Resolve(); + + var shape = new Shape { + Metadata = new ShapeMetadata { + Type = "Foo" + } + }; + + var descriptor = new ShapeDescriptor { + ShapeType = "Foo", + }; + AddBinding(descriptor, "Foo", ctx => new HtmlString("yarg")); + AddShapeDescriptor(descriptor); + + var displayingEventCount = 0; + var displayedEventCount = 0; + descriptor.Displaying = new Action[] { ctx => { ++displayingEventCount; } }; + descriptor.Displayed = new Action[] { ctx => { ++displayedEventCount; ctx.ChildContent = new HtmlString("[" + ctx.ChildContent.ToHtmlString() + "]"); } }; + + var result = displayManager.Execute(CreateDisplayContext(shape)); + + Assert.That(displayingEventCount, Is.EqualTo(1)); + Assert.That(displayedEventCount, Is.EqualTo(1)); + Assert.That(result.ToString(), Is.EqualTo("[yarg]")); + } + + [Test] + public void DisplayingEventFiresEarlyEnoughToAddAlternateShapeBindingNames() { + var displayManager = _container.Resolve(); + + var shapeFoo = new Shape { + Metadata = new ShapeMetadata { + Type = "Foo" + } + }; + var descriptorFoo = new ShapeDescriptor { + ShapeType = "Foo", + }; + AddBinding(descriptorFoo, "Foo", ctx => new HtmlString("alpha")); + AddShapeDescriptor(descriptorFoo); + + var descriptorBar = new ShapeDescriptor { + ShapeType = "Bar", + }; + AddBinding(descriptorBar, "Bar", ctx => new HtmlString("beta")); + AddShapeDescriptor(descriptorBar); + + + var resultNormally = displayManager.Execute(CreateDisplayContext(shapeFoo)); + descriptorFoo.Displaying = new Action[] { ctx => ctx.ShapeMetadata.Alternates.Add("Bar") }; + var resultWithOverride = displayManager.Execute(CreateDisplayContext(shapeFoo)); + + Assert.That(resultNormally.ToString(), Is.EqualTo("alpha")); + Assert.That(resultWithOverride.ToString(), Is.EqualTo("beta")); + } } } diff --git a/src/Orchard/DisplayManagement/Descriptors/ShapeDescriptor.cs b/src/Orchard/DisplayManagement/Descriptors/ShapeDescriptor.cs index b2ba34581..28ef54af0 100644 --- a/src/Orchard/DisplayManagement/Descriptors/ShapeDescriptor.cs +++ b/src/Orchard/DisplayManagement/Descriptors/ShapeDescriptor.cs @@ -9,6 +9,8 @@ namespace Orchard.DisplayManagement.Descriptors { public ShapeDescriptor() { Creating = Enumerable.Empty>(); Created = Enumerable.Empty>(); + Displaying = Enumerable.Empty>(); + Displayed = Enumerable.Empty>(); Wrappers = new List(); Bindings = new Dictionary(); } @@ -36,6 +38,9 @@ namespace Orchard.DisplayManagement.Descriptors { public IEnumerable> Creating { get; set; } public IEnumerable> Created { get; set; } + public IEnumerable> Displaying { get; set; } + public IEnumerable> Displayed { get; set; } + public IList Wrappers { get; set; } } diff --git a/src/Orchard/DisplayManagement/Implementation/DefaultDisplayManager.cs b/src/Orchard/DisplayManagement/Implementation/DefaultDisplayManager.cs index 257f14bb1..4ca7fba14 100644 --- a/src/Orchard/DisplayManagement/Implementation/DefaultDisplayManager.cs +++ b/src/Orchard/DisplayManagement/Implementation/DefaultDisplayManager.cs @@ -9,11 +9,13 @@ using Microsoft.CSharp.RuntimeBinder; using Orchard.DisplayManagement.Descriptors; using Orchard.DisplayManagement.Shapes; using Orchard.Localization; +using Orchard.Logging; namespace Orchard.DisplayManagement.Implementation { public class DefaultDisplayManager : IDisplayManager { private readonly IShapeTableManager _shapeTableManager; private readonly IWorkContextAccessor _workContextAccessor; + private readonly IEnumerable _shapeDisplayEvents; // this need to be Shape instead of IShape - cast to interface throws error on clr types like HtmlString private static readonly CallSite> _convertAsShapeCallsite = CallSite>.Create( @@ -25,13 +27,17 @@ namespace Orchard.DisplayManagement.Implementation { public DefaultDisplayManager( IShapeTableManager shapeTableManager, - IWorkContextAccessor workContextAccessor) { + IWorkContextAccessor workContextAccessor, + IEnumerable shapeDisplayEvents) { _shapeTableManager = shapeTableManager; _workContextAccessor = workContextAccessor; + _shapeDisplayEvents = shapeDisplayEvents; T = NullLocalizer.Instance; + Logger = NullLogger.Instance; } public Localizer T { get; set; } + public ILogger Logger { get; set; } public IHtmlString Execute(DisplayContext context) { @@ -48,36 +54,70 @@ namespace Orchard.DisplayManagement.Implementation { var workContext = _workContextAccessor.GetContext(context.ViewContext); var shapeTable = _shapeTableManager.GetShapeTable(workContext.CurrentTheme.ThemeName); - // preproc loop / event (alter shape, swapping type) - ShapeDescriptor shapeDescriptor; + var displayingContext = new ShapeDisplayingContext { + Shape = shape, + ShapeMetadata = shapeMetadata + }; + _shapeDisplayEvents.Invoke(sde => sde.Displaying(displayingContext), Logger); + + // find base shape association using only the fundamental shape type. + // alternates that may already be registered do not affect the "displaying" event calls ShapeBinding shapeBinding; - if (TryGetDescriptorBinding(shapeMetadata.Type, shapeMetadata.Alternates, shapeTable, out shapeDescriptor, out shapeBinding)) { - shape.Metadata.ChildContent = Process(shapeDescriptor, shapeBinding, shape, context); + if (TryGetDescriptorBinding(shapeMetadata.Type, Enumerable.Empty(), shapeTable, out shapeBinding)) { + shapeBinding.ShapeDescriptor.Displaying.Invoke(action => action(displayingContext), Logger); + } + + // now find the actual binding to render, taking alternates into account + ShapeBinding actualBinding; + if (TryGetDescriptorBinding(shapeMetadata.Type, shapeMetadata.Alternates, shapeTable, out actualBinding)) { + shape.Metadata.ChildContent = Process(actualBinding, shape, context); } else { throw new OrchardException(T("Shape type {0} not found", shapeMetadata.Type)); } foreach (var frameType in shape.Metadata.Wrappers) { - ShapeDescriptor frameDescriptor; ShapeBinding frameBinding; - if (TryGetDescriptorBinding(frameType, Enumerable.Empty(), shapeTable, out frameDescriptor, out frameBinding)) { - shape.Metadata.ChildContent = Process(frameDescriptor, frameBinding, shape, context); + if (TryGetDescriptorBinding(frameType, Enumerable.Empty(), shapeTable, out frameBinding)) { + shape.Metadata.ChildContent = Process(frameBinding, shape, context); } } + var displayedContext = new ShapeDisplayedContext { + Shape = shape, + ShapeMetadata = shape.Metadata, + ChildContent = shape.Metadata.ChildContent, + }; + + _shapeDisplayEvents.Invoke(sde => { + var prior = displayedContext.ChildContent = displayedContext.ShapeMetadata.ChildContent; + sde.Displayed(displayedContext); + // update the child content if the context variable has been reassigned + if (prior != displayedContext.ChildContent) + displayedContext.ShapeMetadata.ChildContent = displayedContext.ChildContent; + }, Logger); + + if (shapeBinding != null) { + shapeBinding.ShapeDescriptor.Displayed.Invoke(action => { + var prior = displayedContext.ChildContent = displayedContext.ShapeMetadata.ChildContent; + action(displayedContext); + // update the child content if the context variable has been reassigned + if (prior != displayedContext.ChildContent) + displayedContext.ShapeMetadata.ChildContent = displayedContext.ChildContent; + }, Logger); + } + return shape.Metadata.ChildContent; } - static bool TryGetDescriptorBinding(string shapeType, IEnumerable shapeAlternates, ShapeTable shapeTable, out ShapeDescriptor shapeDescriptor, out ShapeBinding shapeBinding) { + static bool TryGetDescriptorBinding(string shapeType, IEnumerable shapeAlternates, ShapeTable shapeTable, out ShapeBinding shapeBinding) { // shape alternates are optional, fully qualified binding names // the earliest added alternates have the lowest priority // the descriptor returned is based on the binding that is matched, so it may be an entirely // different descriptor if the alternate has a different base name foreach (var shapeAlternate in shapeAlternates.Reverse()) { if (shapeTable.Bindings.TryGetValue(shapeAlternate, out shapeBinding)) { - shapeDescriptor = shapeBinding.ShapeDescriptor; return true; } } @@ -88,14 +128,12 @@ namespace Orchard.DisplayManagement.Implementation { var shapeTypeScan = shapeType; for (; ; ) { if (shapeTable.Bindings.TryGetValue(shapeTypeScan, out shapeBinding)) { - shapeDescriptor = shapeBinding.ShapeDescriptor; return true; } var delimiterIndex = shapeTypeScan.LastIndexOf("__"); if (delimiterIndex < 0) { shapeBinding = null; - shapeDescriptor = null; return false; } @@ -114,9 +152,9 @@ namespace Orchard.DisplayManagement.Implementation { return new HtmlString(HttpUtility.HtmlEncode(value)); } - static IHtmlString Process(ShapeDescriptor shapeDescriptor, ShapeBinding shapeBinding, IShape shape, DisplayContext context) { + static IHtmlString Process(ShapeBinding shapeBinding, IShape shape, DisplayContext context) { - if (shapeDescriptor == null || shapeBinding == null || shapeBinding.Binding == null) { + if (shapeBinding == null || shapeBinding.Binding == null) { // todo: create result from all child shapes return shape.Metadata.ChildContent ?? new HtmlString(""); } @@ -148,4 +186,4 @@ namespace Orchard.DisplayManagement.Implementation { } } } -} \ No newline at end of file +} diff --git a/src/Orchard/DisplayManagement/Implementation/IShapeDisplayEvents.cs b/src/Orchard/DisplayManagement/Implementation/IShapeDisplayEvents.cs new file mode 100644 index 000000000..d34092bfd --- /dev/null +++ b/src/Orchard/DisplayManagement/Implementation/IShapeDisplayEvents.cs @@ -0,0 +1,26 @@ +using System; +using System.Web; +using Orchard.DisplayManagement.Shapes; + +namespace Orchard.DisplayManagement.Implementation { + public interface IShapeDisplayEvents : IDependency { + void Displaying(ShapeDisplayingContext context); + void Displayed(ShapeDisplayedContext context); + } + + public class ShapeDisplayingContext { + public dynamic Shape { get; set; } + public ShapeMetadata ShapeMetadata { get; set; } + } + + public class ShapeDisplayedContext { + public dynamic Shape { get; set; } + public ShapeMetadata ShapeMetadata { get; set; } + public IHtmlString ChildContent { get; set; } + } + + public abstract class ShapeDisplayEvents : IShapeDisplayEvents { + public virtual void Displaying(ShapeDisplayingContext context) { } + public virtual void Displayed(ShapeDisplayedContext context) { } + } +} diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 1dc640f51..125226bd3 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -162,6 +162,7 @@ +