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
This commit is contained in:
Louis DeJardin
2010-10-07 13:08:31 -07:00
parent 48a615e597
commit dc61c92f31
5 changed files with 195 additions and 27 deletions

View File

@@ -32,10 +32,22 @@ namespace Orchard.Tests.DisplayManagement {
builder.RegisterType<DefaultDisplayManager>().As<IDisplayManager>();
builder.RegisterType<TestShapeTableManager>().As<IShapeTableManager>();
builder.RegisterType<TestWorkContextAccessor>().As<IWorkContextAccessor>();
builder.RegisterType<TestDisplayEvents>().As<IShapeDisplayEvents>()
.As<TestDisplayEvents>()
.InstancePerLifetimeScope();
builder.Register(ctx => _defaultShapeTable);
builder.Register(ctx => _workContext);
}
class TestDisplayEvents : IShapeDisplayEvents {
public Action<ShapeDisplayingContext> Displaying = ctx => { };
public Action<ShapeDisplayedContext> 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<DisplayContext, IHtmlString> binding) {
descriptor.Bindings[bindingName] = new ShapeBinding {
BindingName = bindingName,
Binding = binding,
};
}
[Test]
public void IShapeDisplayEventsIsCalled() {
var displayManager = _container.Resolve<IDisplayManager>();
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<TestDisplayEvents>().Displaying = ctx => { ++displayingEventCount; };
_container.Resolve<TestDisplayEvents>().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<IDisplayManager>();
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<ShapeDisplayingContext>[] { ctx => { ++displayingEventCount; } };
descriptor.Displayed = new Action<ShapeDisplayedContext>[] { 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<IDisplayManager>();
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<ShapeDisplayingContext>[] { 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"));
}
}
}

View File

@@ -9,6 +9,8 @@ namespace Orchard.DisplayManagement.Descriptors {
public ShapeDescriptor() {
Creating = Enumerable.Empty<Action<ShapeCreatingContext>>();
Created = Enumerable.Empty<Action<ShapeCreatedContext>>();
Displaying = Enumerable.Empty<Action<ShapeDisplayingContext>>();
Displayed = Enumerable.Empty<Action<ShapeDisplayedContext>>();
Wrappers = new List<string>();
Bindings = new Dictionary<string, ShapeBinding>();
}
@@ -36,6 +38,9 @@ namespace Orchard.DisplayManagement.Descriptors {
public IEnumerable<Action<ShapeCreatingContext>> Creating { get; set; }
public IEnumerable<Action<ShapeCreatedContext>> Created { get; set; }
public IEnumerable<Action<ShapeDisplayingContext>> Displaying { get; set; }
public IEnumerable<Action<ShapeDisplayedContext>> Displayed { get; set; }
public IList<string> Wrappers { get; set; }
}

View File

@@ -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<IShapeDisplayEvents> _shapeDisplayEvents;
// this need to be Shape instead of IShape - cast to interface throws error on clr types like HtmlString
private static readonly CallSite<Func<CallSite, object, Shape>> _convertAsShapeCallsite = CallSite<Func<CallSite, object, Shape>>.Create(
@@ -25,13 +27,17 @@ namespace Orchard.DisplayManagement.Implementation {
public DefaultDisplayManager(
IShapeTableManager shapeTableManager,
IWorkContextAccessor workContextAccessor) {
IWorkContextAccessor workContextAccessor,
IEnumerable<IShapeDisplayEvents> 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<string>(), 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<string>(), shapeTable, out frameDescriptor, out frameBinding)) {
shape.Metadata.ChildContent = Process(frameDescriptor, frameBinding, shape, context);
if (TryGetDescriptorBinding(frameType, Enumerable.Empty<string>(), 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<string> shapeAlternates, ShapeTable shapeTable, out ShapeDescriptor shapeDescriptor, out ShapeBinding shapeBinding) {
static bool TryGetDescriptorBinding(string shapeType, IEnumerable<string> 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 {
}
}
}
}
}

View File

@@ -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) { }
}
}

View File

@@ -162,6 +162,7 @@
<Compile Include="DisplayManagement\Descriptors\ShapeAlterationBuilder.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapeTable.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapeTableBuilder.cs" />
<Compile Include="DisplayManagement\Implementation\IShapeDisplayEvents.cs" />
<Compile Include="DisplayManagement\Implementation\IShapeFactoryEvents.cs" />
<Compile Include="DisplayManagement\Shapes\ITagBuilderFactory.cs" />
<Compile Include="Environment\Extensions\Loaders\RawThemeExtensionLoader.cs" />