Preparing to put a content part placement subsystem in effect

ContentShapeResult.Apply calls through the context.FindPlacement to acquire placement location
DefaultContentDisplay component wires the FindPlacement onto the ShapeDescriptor.Placement
ShapeDescriptor.Placement func contains the chained per-theme rule stack for part shape placement
Next up - simple Placement.txt parser to convert rules into Placement chain

--HG--
branch : composition
This commit is contained in:
Louis DeJardin
2010-10-12 01:02:18 -07:00
parent 0d27bc794c
commit 0ce0dcb69b
13 changed files with 293 additions and 89 deletions

View File

@@ -79,5 +79,67 @@ namespace Orchard.Tests.DisplayManagement.Descriptors {
Assert.That(foo.Displaying.Single(), Is.SameAs(cb3));
Assert.That(foo.Displayed.Single(), Is.SameAs(cb4));
}
[Test]
public void DefaultPlacementIsReturnedByDefault() {
var manager = _container.Resolve<IShapeTableManager>();
var hello = manager.GetShapeTable(null).Descriptors["Hello"];
hello.DefaultPlacement = "Header:5";
var result = hello.Placement(null);
Assert.That(result, Is.EqualTo("Header:5"));
}
[Test]
public void DescribedPlacementIsReturnedIfNotNull() {
_container.Resolve<TestShapeProvider>().Discover =
builder => builder.Describe("Hello")
.Placement(ctx => ctx.DisplayType == "Detail" ? "Main" : null)
.Placement(ctx => ctx.DisplayType == "Summary" ? "" : null);
var manager = _container.Resolve<IShapeTableManager>();
var hello = manager.GetShapeTable(null).Descriptors["Hello"];
var result1 = hello.Placement(new ShapePlacementContext { DisplayType = "Detail" });
var result2 = hello.Placement(new ShapePlacementContext { DisplayType = "Summary" });
var result3 = hello.Placement(new ShapePlacementContext { DisplayType = "Tile" });
hello.DefaultPlacement = "Header:5";
var result4 = hello.Placement(new ShapePlacementContext { DisplayType = "Detail" });
var result5 = hello.Placement(new ShapePlacementContext { DisplayType = "Summary" });
var result6 = hello.Placement(new ShapePlacementContext { DisplayType = "Tile" });
Assert.That(result1, Is.EqualTo("Main"));
Assert.That(result2, Is.EqualTo(""));
Assert.That(result3, Is.Null);
Assert.That(result4, Is.EqualTo("Main"));
Assert.That(result5, Is.EqualTo(""));
Assert.That(result6, Is.EqualTo("Header:5"));
}
[Test]
public void TwoArgumentVariationDoesSameThing() {
_container.Resolve<TestShapeProvider>().Discover =
builder => builder.Describe("Hello")
.Placement(ctx => ctx.DisplayType == "Detail", "Main")
.Placement(ctx => ctx.DisplayType == "Summary", "");
var manager = _container.Resolve<IShapeTableManager>();
var hello = manager.GetShapeTable(null).Descriptors["Hello"];
var result1 = hello.Placement(new ShapePlacementContext { DisplayType = "Detail" });
var result2 = hello.Placement(new ShapePlacementContext { DisplayType = "Summary" });
var result3 = hello.Placement(new ShapePlacementContext { DisplayType = "Tile" });
hello.DefaultPlacement = "Header:5";
var result4 = hello.Placement(new ShapePlacementContext { DisplayType = "Detail" });
var result5 = hello.Placement(new ShapePlacementContext { DisplayType = "Summary" });
var result6 = hello.Placement(new ShapePlacementContext { DisplayType = "Tile" });
Assert.That(result1, Is.EqualTo("Main"));
Assert.That(result2, Is.EqualTo(""));
Assert.That(result3, Is.Null);
Assert.That(result4, Is.EqualTo("Main"));
Assert.That(result5, Is.EqualTo(""));
Assert.That(result6, Is.EqualTo("Header:5"));
}
}
}

View File

@@ -34,19 +34,17 @@ namespace Orchard.Core.Common.Drivers {
}
protected override DriverResult Display(BodyPart part, string displayType, dynamic shapeHelper) {
var bodyText = _htmlFilters.Aggregate(part.Text, (text, filter) => filter.ProcessContent(text));
var body = shapeHelper.Parts_Common_Body(ContentPart: part, Html: new HtmlString(bodyText));
if (!string.IsNullOrWhiteSpace(displayType))
body.Metadata.Type = string.Format("{0}.{1}", body.Metadata.Type, displayType);
var location = part.GetLocation(displayType);
//return Combined(
// Services.Authorizer.Authorize(Permissions.ChangeOwner) ? ContentPartTemplate(model, "Parts/Common.Body.ManageWrapperPre").LongestMatch(displayType, "SummaryAdmin").Location(location) : null,
// Services.Authorizer.Authorize(Permissions.ChangeOwner) ? ContentPartTemplate(model, "Parts/Common.Body.Manage").LongestMatch(displayType, "SummaryAdmin").Location(location) : null,
// ContentPartTemplate(model, TemplateName, Prefix).LongestMatch(displayType, "Summary", "SummaryAdmin").Location(location),
// Services.Authorizer.Authorize(Permissions.ChangeOwner) ? ContentPartTemplate(model, "Parts/Common.Body.ManageWrapperPost").LongestMatch(displayType, "SummaryAdmin").Location(location) : null);
return ContentShape(body).Location(location);
return Combined(
ContentShape("Parts_Common_Body", displayType == "Detail" ? "Content" : null, () => {
var bodyText = _htmlFilters.Aggregate(part.Text, (text, filter) => filter.ProcessContent(text));
return shapeHelper.Parts_Common_Body(ContentPart: part, Html: new HtmlString(bodyText));
}),
ContentShape("Parts_Common_Body_Summary", displayType == "Summary" ? "Content" : null, () => {
var bodyText = _htmlFilters.Aggregate(part.Text, (text, filter) => filter.ProcessContent(text));
return shapeHelper.Parts_Common_Body_Summary(ContentPart: part, Html: new HtmlString(bodyText));
})
);
}
protected override DriverResult Editor(BodyPart part, dynamic shapeHelper) {

View File

@@ -3,6 +3,6 @@
@Display(Model.Header)
</header>
<section>
@Display(Model.Primary)
@Display(Model.Content)
</section>
</article>

View File

@@ -45,11 +45,7 @@ namespace Orchard.Core.Routable.Drivers {
}
protected override DriverResult Display(RoutePart part, string displayType, dynamic shapeHelper) {
var routePart = shapeHelper.Parts_RoutableTitle(ContentPart: part, Title: part.Title);
if (!string.IsNullOrWhiteSpace(displayType))
routePart.Metadata.Type = string.Format("{0}.{1}", routePart.Metadata.Type, displayType);
var location = part.GetLocation(displayType, "Header", "5");
return ContentShape(routePart).Location(location);
return ContentShape("Parts_RoutableTitle", "Header:5", () => shapeHelper.Parts_RoutableTitle(ContentPart: part, Title: part.Title));
}
protected override DriverResult Editor(RoutePart part, dynamic shapeHelper) {
@@ -80,7 +76,7 @@ namespace Orchard.Core.Routable.Drivers {
}
protected override DriverResult Editor(RoutePart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new RoutableEditorViewModel();
updater.TryUpdateModel(model, Prefix, null, null);
part.Title = model.Title;

View File

@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Routing;
using Microsoft.CSharp.RuntimeBinder;
using Orchard.ContentManagement.Handlers;
using Orchard.DisplayManagement;
using Orchard.DisplayManagement.Descriptors;
using Orchard.Logging;
using Orchard.Mvc;
using Orchard.Themes;
namespace Orchard.ContentManagement {
public class DefaultContentDisplay : IContentDisplay {
private readonly Lazy<IEnumerable<IContentHandler>> _handlers;
private readonly IShapeHelperFactory _shapeHelperFactory;
private readonly IShapeTableManager _shapeTableManager;
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly Lazy<IThemeService> _themeService;
private readonly RequestContext _requestContext;
public DefaultContentDisplay(
Lazy<IEnumerable<IContentHandler>> handlers,
IShapeHelperFactory shapeHelperFactory,
IShapeTableManager shapeTableManager,
IWorkContextAccessor workContextAccessor,
IHttpContextAccessor httpContextAccessor,
Lazy<IThemeService> themeService,
RequestContext requestContext) {
_handlers = handlers;
_shapeHelperFactory = shapeHelperFactory;
_shapeTableManager = shapeTableManager;
_workContextAccessor = workContextAccessor;
_httpContextAccessor = httpContextAccessor;
_themeService = themeService;
_requestContext = requestContext;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
static readonly CallSiteCollection _shapeHelperCalls = new CallSiteCollection(shapeTypeName => Binder.InvokeMember(
CSharpBinderFlags.None,
shapeTypeName,
Enumerable.Empty<Type>(),
null,
new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
public dynamic BuildDisplay(IContent content, string displayType) {
var contentTypeDefinition = content.ContentItem.TypeDefinition;
string stereotype;
if (!contentTypeDefinition.Settings.TryGetValue("Stereotype", out stereotype))
stereotype = "Content";
var shapeTypeName = "Items_" + stereotype;
var shapeDisplayType = string.IsNullOrWhiteSpace(displayType) ? "Detail" : displayType;
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = _shapeHelperCalls.Invoke(shapeHelper, shapeTypeName);
itemShape.ContentItem = content.ContentItem;
itemShape.Metadata.DisplayType = shapeDisplayType;
var context = new BuildDisplayContext(itemShape, content, shapeDisplayType, _shapeHelperFactory);
BindPlacement(context, displayType);
_handlers.Value.Invoke(handler => handler.BuildDisplay(context), Logger);
return context.Shape;
}
public dynamic BuildEditor(IContent content) {
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = shapeHelper.Items_Content_Edit();
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var context = new BuildEditorContext(itemShape, content, _shapeHelperFactory);
BindPlacement(context, null);
_handlers.Value.Invoke(handler => handler.BuildEditor(context), Logger);
return context.Shape;
}
public dynamic UpdateEditor(IContent content, IUpdateModel updater) {
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = shapeHelper.Items_Content_Edit();
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var context = new UpdateEditorContext(itemShape, content, updater, _shapeHelperFactory);
BindPlacement(context, null);
_handlers.Value.Invoke(handler => handler.UpdateEditor(context), Logger);
return context.Shape;
}
private void BindPlacement(BuildShapeContext context, string displayType) {
context.FindPlacement = (partShapeType, defaultLocation) => {
//var workContext = _workContextAccessor.GetContext();
//var theme = workContext.CurrentTheme;
var theme = _themeService.Value.GetRequestTheme(_requestContext);
var shapeTable = _shapeTableManager.GetShapeTable(theme.ThemeName);
ShapeDescriptor descriptor;
if (shapeTable.Descriptors.TryGetValue(partShapeType, out descriptor)) {
var placementContext = new ShapePlacementContext {
ContentType = context.ContentItem.ContentType,
DisplayType = displayType
};
var location = descriptor.Placement(placementContext);
return location ?? defaultLocation;
}
return defaultLocation;
};
}
}
}

View File

@@ -4,14 +4,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Autofac;
using Microsoft.CSharp.RuntimeBinder;
using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentManagement.Records;
using Orchard.Data;
using Orchard.DisplayManagement;
using Orchard.Indexing;
using Orchard.Logging;
@@ -23,7 +21,7 @@ namespace Orchard.ContentManagement {
private readonly IRepository<ContentItemVersionRecord> _contentItemVersionRepository;
private readonly IContentDefinitionManager _contentDefinitionManager;
private readonly Func<IContentManagerSession> _contentManagerSession;
private readonly IShapeHelperFactory _shapeHelperFactory;
private readonly IContentDisplay _contentDisplay;
public DefaultContentManager(
IComponentContext context,
@@ -32,14 +30,14 @@ namespace Orchard.ContentManagement {
IRepository<ContentItemVersionRecord> contentItemVersionRepository,
IContentDefinitionManager contentDefinitionManager,
Func<IContentManagerSession> contentManagerSession,
IShapeHelperFactory shapeHelperFactory) {
IContentDisplay contentDisplay) {
_context = context;
_contentTypeRepository = contentTypeRepository;
_contentItemRepository = contentItemRepository;
_contentItemVersionRepository = contentItemVersionRepository;
_contentDefinitionManager = contentDefinitionManager;
_contentManagerSession = contentManagerSession;
_shapeHelperFactory = shapeHelperFactory;
_contentDisplay = contentDisplay;
Logger = NullLogger.Instance;
}
@@ -370,59 +368,17 @@ namespace Orchard.ContentManagement {
return context.Metadata;
}
static readonly CallSiteCollection _shapeHelperCalls = new CallSiteCollection(shapeTypeName => Binder.InvokeMember(
CSharpBinderFlags.None,
shapeTypeName,
Enumerable.Empty<Type>(),
null,
new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
public dynamic BuildDisplay(IContent content, string displayType = "") {
var contentTypeDefinition = content.ContentItem.TypeDefinition;
string stereotype;
if (!contentTypeDefinition.Settings.TryGetValue("Stereotype", out stereotype))
stereotype = "Content";
var shapeTypeName = "Items_" + stereotype;
var shapeDisplayType = string.IsNullOrWhiteSpace(displayType) ? "Detail" : displayType;
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = _shapeHelperCalls.Invoke(shapeHelper, shapeTypeName);
itemShape.ContentItem = content.ContentItem;
itemShape.Metadata.DisplayType = shapeDisplayType;
var context = new BuildDisplayContext(itemShape, content, shapeDisplayType, _shapeHelperFactory);
Handlers.Invoke(handler => handler.BuildDisplay(context), Logger);
return context.Shape;
return _contentDisplay.BuildDisplay(content, displayType);
}
public dynamic BuildEditor(IContent content) {
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = shapeHelper.Items_Content_Edit();
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var context = new BuildEditorContext(itemShape, content, _shapeHelperFactory);
Handlers.Invoke(handler => handler.BuildEditor(context), Logger);
return context.Shape;
return _contentDisplay.BuildEditor(content);
}
public dynamic UpdateEditor(IContent content, IUpdateModel updater) {
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = shapeHelper.Items_Content_Edit();
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var context = new UpdateEditorContext(itemShape, content, updater, _shapeHelperFactory);
Handlers.Invoke(handler => handler.UpdateEditor(context), Logger);
return context.Shape;
return _contentDisplay.UpdateEditor(content, updater);
}
public IContentQuery<ContentItem> Query() {

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement.MetaData;
using Orchard.DisplayManagement;
namespace Orchard.ContentManagement.Drivers {
public abstract class ContentPartDriver<TContent> : IContentPartDriver where TContent : ContentPart, new() {
@@ -27,8 +28,21 @@ namespace Orchard.ContentManagement.Drivers {
protected virtual DriverResult Editor(TContent part, dynamic shapeHelper) { return null; }
protected virtual DriverResult Editor(TContent part, IUpdateModel updater, dynamic shapeHelper) { return null; }
public ContentShapeResult ContentShape(dynamic shape) {
return new ContentShapeResult(shape, Prefix).Location(Zone);
[Obsolete("Provided while transitioning to factory variations")]
public ContentShapeResult ContentShape(IShape shape) {
return ContentShapeImplementation(shape.Metadata.Type, Zone, () => shape);
}
public ContentShapeResult ContentShape(string shapeType, Func<dynamic> factory) {
return ContentShapeImplementation(shapeType, null, factory);
}
public ContentShapeResult ContentShape(string shapeType, string defaultLocation, Func<dynamic> factory) {
return ContentShapeImplementation(shapeType, defaultLocation, factory);
}
private ContentShapeResult ContentShapeImplementation(string shapeType, string defaultLocation, Func<object> factory) {
return new ContentShapeResult(shapeType, Prefix, factory).Location(defaultLocation);
}
[Obsolete]

View File

@@ -1,37 +1,57 @@
using Orchard.ContentManagement.Handlers;
using System;
using Orchard.ContentManagement.Handlers;
using Orchard.DisplayManagement;
using Orchard.DisplayManagement.Shapes;
namespace Orchard.ContentManagement.Drivers {
public class ContentShapeResult : DriverResult {
public dynamic Shape { get; set; }
public string Prefix { get; set; }
public string Zone { get; set; }
public string Position { get; set; }
private string _defaultLocation;
private readonly string _shapeType;
private readonly string _prefix;
private readonly Func<dynamic> _shapeBuilder;
public ContentShapeResult(dynamic shape, string prefix) {
Shape = shape;
Prefix = prefix;
public ContentShapeResult(string shapeType, string prefix, Func<dynamic> shapeBuilder) {
_shapeType = shapeType;
_prefix = prefix;
_shapeBuilder = shapeBuilder;
}
public override void Apply(BuildDisplayContext context) {
context.Shape.Zones[Zone].Add(Shape, Position);
ApplyImplementation(context, context.DisplayType);
}
public override void Apply(BuildEditorContext context) {
IShape iShape = Shape;
if (iShape != null )
Shape.Metadata.Prefix = Prefix;
context.Shape.Zones[Zone].Add(Shape, Position);
ApplyImplementation(context, null);
}
private void ApplyImplementation(BuildShapeContext context, string displayType) {
var location = context.FindPlacement(_shapeType, _defaultLocation);
if (string.IsNullOrEmpty(location) || location == "-")
return;
dynamic parentShape = context.Shape;
IShape contentShape = _shapeBuilder();
contentShape.Metadata.Prefix = _prefix;
contentShape.Metadata.DisplayType = displayType;
var delimiterIndex = location.IndexOf(':');
if (delimiterIndex < 0) {
parentShape.Zones[location].Add(contentShape);
}
else {
var zoneName = location.Substring(0, delimiterIndex);
var position = location.Substring(delimiterIndex + 1);
parentShape.Zones[zoneName].Add(contentShape, position);
}
}
public ContentShapeResult Location(string zone) {
Zone = zone;
_defaultLocation = zone;
return this;
}
public ContentShapeResult Location(string zone, string position) {
Zone = zone;
Position = position;
_defaultLocation = zone + ":" + position;
return this;
}

View File

@@ -1,3 +1,4 @@
using System;
using Orchard.DisplayManagement;
namespace Orchard.ContentManagement.Handlers {
@@ -6,10 +7,13 @@ namespace Orchard.ContentManagement.Handlers {
Shape = shape;
ContentItem = content.ContentItem;
New = shapeHelperFactory.CreateHelper();
FindPlacement = (partType, defaultLocation) => defaultLocation;
}
public dynamic Shape { get; private set; }
public ContentItem ContentItem { get; private set; }
public dynamic New { get; private set; }
public Func<string, string, string> FindPlacement { get; set; }
}
}

View File

@@ -31,6 +31,12 @@ namespace Orchard.ContentManagement {
dynamic UpdateEditor(IContent content, IUpdateModel updater);
}
public interface IContentDisplay : IDependency {
dynamic BuildDisplay(IContent content, string displayType = "");
dynamic BuildEditor(IContent content);
dynamic UpdateEditor(IContent content, IUpdateModel updater);
}
public class VersionOptions {
public static VersionOptions Latest { get { return new VersionOptions { IsLatest = true }; } }
public static VersionOptions Published { get { return new VersionOptions { IsPublished = true }; } }

View File

@@ -73,7 +73,7 @@ namespace Orchard.DisplayManagement.Descriptors {
descriptor.Created = existing.Concat(new[] { action });
});
}
public ShapeAlterationBuilder OnDisplaying(Action<ShapeDisplayingContext> action) {
return Configure(descriptor => {
var existing = descriptor.Displaying ?? Enumerable.Empty<Action<ShapeDisplayingContext>>();
@@ -88,8 +88,28 @@ namespace Orchard.DisplayManagement.Descriptors {
});
}
public ShapeAlterationBuilder Placement(Func<ShapePlacementContext, string> action) {
return Configure(descriptor => {
var next = descriptor.Placement;
descriptor.Placement = ctx => action(ctx) ?? next(ctx);
});
}
public ShapeAlterationBuilder Placement(Func<ShapePlacementContext, bool> predicate, string location) {
return Configure(descriptor => {
var next = descriptor.Placement;
descriptor.Placement = ctx => predicate(ctx) ? location : next(ctx);
});
}
public ShapeAlteration Build() {
return new ShapeAlteration(_shapeType, _feature, _configurations.ToArray());
}
}
}
public class ShapePlacementContext {
public string ContentType { get; set; }
public string DisplayType { get; set; }
}
}

View File

@@ -13,6 +13,7 @@ namespace Orchard.DisplayManagement.Descriptors {
Displayed = Enumerable.Empty<Action<ShapeDisplayedContext>>();
Wrappers = new List<string>();
Bindings = new Dictionary<string, ShapeBinding>();
Placement = ctx => DefaultPlacement;
}
public string ShapeType { get; set; }
@@ -41,6 +42,9 @@ namespace Orchard.DisplayManagement.Descriptors {
public IEnumerable<Action<ShapeDisplayingContext>> Displaying { get; set; }
public IEnumerable<Action<ShapeDisplayedContext>> Displayed { get; set; }
public Func<ShapePlacementContext, string> Placement { get; set; }
public string DefaultPlacement { get; set; }
public IList<string> Wrappers { get; set; }
}

View File

@@ -159,6 +159,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ContentManagement\DefaultContentDisplay.cs" />
<Compile Include="ContentManagement\Drivers\ContentShapeResult.cs" />
<Compile Include="ContentManagement\Handlers\BuildShapeContext.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapeDescriptor.cs" />