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