Updating for part placement

Correcting several issues
Providing default placement info for body and tags
Fixing predicate logic

--HG--
branch : dev
This commit is contained in:
Louis DeJardin
2010-10-12 21:08:27 -07:00
parent f7748c3590
commit 945e41de1e
19 changed files with 155 additions and 97 deletions

View File

@@ -54,7 +54,6 @@ namespace Orchard.Tests.DisplayManagement.Descriptors {
VerifyShapeType("Views/Items", "Content.Edit", "Items_Content_Edit");
}
[Test]
public void ExplicitSpecializationMixedWithDisplayTypes() {
VerifyShapeType("Views/Items", "Content-MyType", "Items_Content__MyType");
@@ -62,12 +61,18 @@ namespace Orchard.Tests.DisplayManagement.Descriptors {
VerifyShapeType("Views/Items", "Content-MyType.Edit", "Items_Content_Edit__MyType");
}
[Test]
public void ImplicitSpecializationMixedWithDisplayTypes() {
VerifyShapeType("Views/Items", "MyType", "Items_Content__MyType");
VerifyShapeType("Views/Items", "MyType.Summary", "Items_Content_Summary__MyType");
VerifyShapeType("Views/Items", "MyType.Edit", "Items_Content_Edit__MyType");
}
[Test]
public void MultipleDotsAreNormalizedToUnderscore() {
VerifyShapeType("Views/Parts", "Common.Body", "Parts_Common_Body");
VerifyShapeType("Views/Parts", "Common.Body.Summary", "Parts_Common_Body_Summary");
VerifyShapeType("Views/Parts", "Localization.ContentTranslations.Summary", "Parts_Localization_ContentTranslations_Summary");
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using JetBrains.Annotations;
@@ -36,33 +37,35 @@ namespace Orchard.Core.Common.Drivers {
protected override DriverResult Display(BodyPart part, string displayType, dynamic shapeHelper) {
return Combined(
ContentShape("Parts_Common_Body", displayType == "Detail" ? "Content" : null, () => {
ContentShape("Parts_Common_Body",
() => {
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, () => {
ContentShape("Parts_Common_Body_Summary",
() => {
var bodyText = _htmlFilters.Aggregate(part.Text, (text, filter) => filter.ProcessContent(text));
return shapeHelper.Parts_Common_Body_Summary(ContentPart: part, Html: new HtmlString(bodyText));
})
);
}
private string IfThen(bool predicate, string value) {
return predicate ? value : null;
}
protected override DriverResult Editor(BodyPart part, dynamic shapeHelper) {
var model = BuildEditorViewModel(part);
var location = part.GetLocation("Editor");
return ContentPartTemplate(model, TemplateName, Prefix).Location(location);
return ContentShape("Parts_Common_Body_Editor",
() => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: model, Prefix: Prefix));
}
protected override DriverResult Editor(BodyPart part, IUpdateModel updater, dynamic shapeHelper) {
var model = BuildEditorViewModel(part);
updater.TryUpdateModel(model, Prefix, null, null);
// only set the format if it has not yet been set to preserve the initial format type - might want to change this later to support changing body formats but...later
if (string.IsNullOrWhiteSpace(model.Format))
model.Format = GetFlavor(part);
var location = part.GetLocation("Editor");
return ContentPartTemplate(model, TemplateName, Prefix).Location(location);
return ContentShape("Parts_Common_Body_Editor",
() => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: model, Prefix: Prefix));
}
private static BodyEditorViewModel BuildEditorViewModel(BodyPart part) {

View File

@@ -0,0 +1,13 @@
<Placement>
<Place Parts_Common_Body_Editor="Primary:2"/>
<!-- show summary for all DisplayType by default -->
<Place Parts_Common_Body_Summary="Content:5"/>
<Match DisplayType="Detail">
<!-- hide summary, show full content, for Detail -->
<Place Parts_Common_Body_Summary="-"
Parts_Common_Body="Content:5" />
</Match>
</Placement>

View File

@@ -1,9 +1,9 @@
@model BodyDisplayViewModel
@using Orchard.Core.Common.ViewModels;
@*doing excerpt generation on the way out for now so we don't stick ourselves with needing to regen excerpts for existing data
@*doing excerpt generation on the way out for now so we don't stick ourselves with needing to regen excerpts for existing data
also, doing this here, inline, until we have a pluggable processing model (both in and out)
also, ...this is ugly *@
@{
var body = new HtmlString(Html.Excerpt(Model.Html.ToString(), 200).ToString().Replace(Environment.NewLine, "</p>" + Environment.NewLine + "<p>"));
Orchard.ContentManagement.ContentItem contentItem = Model.ContentPart.ContentItem;
string bodyHtml = Model.Html.ToString();
var body = new HtmlString(Html.Excerpt(bodyHtml, 200).ToString().Replace(Environment.NewLine, "</p>" + Environment.NewLine + "<p>"));
}
<p>@body @Html.ItemDisplayLink(T("[more]").ToString(), Model.BodyPart.ContentItem)</p>
<p>@body @Html.ItemDisplayLink(T("[more]").ToString(), contentItem)</p>

View File

@@ -8,7 +8,6 @@ namespace Orchard.Core.Contents {
public class Shapes : IShapeTableProvider {
public void Discover(ShapeTableBuilder builder) {
builder.Describe("Items_Content")
.OnCreating(creating => creating.Behaviors.Add(new ZoneHoldingBehavior(name => ContentZone(creating, name))))
.OnDisplaying(displaying => {
ContentItem contentItem = displaying.Shape.ContentItem;
if (contentItem != null) {
@@ -16,12 +15,14 @@ namespace Orchard.Core.Contents {
displaying.ShapeMetadata.Alternates.Add("Items_Content__" + contentItem.Id);
}
});
}
private static object ContentZone(ShapeCreatingContext creating, string name) {
var zone = creating.New.ContentZone();
zone.ZoneName = name;
return zone;
builder.Describe("Items_Content_Editor")
.OnDisplaying(displaying => {
ContentItem contentItem = displaying.Shape.ContentItem;
if (contentItem != null) {
displaying.ShapeMetadata.Alternates.Add("Items_Content_Editor__" + contentItem.ContentType);
}
});
}
}
}

View File

@@ -381,7 +381,7 @@
<Content Include="ContentsLocation\Views\Web.config" />
<Content Include="Messaging\Views\Web.config" />
<Content Include="Contents\Views\Items\Content.cshtml" />
<Content Include="Contents\Views\Items\Content.Edit.cshtml" />
<Content Include="Contents\Views\Items\Content.Editor.cshtml" />
<Content Include="Contents\Views\Items\Content.Summary.cshtml" />
<Content Include="Contents\Views\Items\Content.SummaryAdmin.cshtml" />
<Content Include="Shapes\Views\Document.cshtml" />
@@ -392,6 +392,7 @@
<Content Include="Shapes\Views\MenuItem.cshtml" />
<Content Include="Shapes\Views\Web.config" />
<Content Include="Dashboard\Views\Helper\Index.cshtml" />
<None Include="Common\Placement.info" />
<None Include="Routable\Views\Parts\RoutableTitle.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

View File

@@ -31,7 +31,7 @@ namespace Orchard.Core.Shapes {
// and has an automatic zone creating behavior
builder.Describe("Layout")
.Configure(descriptor => descriptor.Wrappers.Add("Document"))
.OnCreating(creating => creating.Behaviors.Add(new ZoneHoldingBehavior(name => CreateZone(creating))))
.OnCreating(creating => creating.Behaviors.Add(new ZoneHoldingBehavior(() => creating.New.Zone())))
.OnCreated(created => {
var layout = created.Shape;
layout.Head = created.New.DocumentZone(ZoneName: "Head");
@@ -81,9 +81,6 @@ namespace Orchard.Core.Shapes {
});
}
static object CreateZone(ShapeCreatingContext context) {
return context.New.Zone();
}
static TagBuilder GetTagBuilder(string tagName, string id, IEnumerable<string> classes, IDictionary<string, string> attributes) {
var tagBuilder = new TagBuilder(tagName);
@@ -271,8 +268,8 @@ namespace Orchard.Core.Shapes {
}
[Shape]
public void Partial(HtmlHelper Html, TextWriter Output, string TemplateName, object Model) {
RenderInternal(Html, Output, TemplateName, Model, null);
public void Partial(HtmlHelper Html, TextWriter Output, string TemplateName, object Model, string Prefix) {
RenderInternal(Html, Output, TemplateName, Model, Prefix);
}
[Shape]

View File

@@ -24,11 +24,8 @@ namespace Orchard.Tags.Drivers {
public virtual IUser CurrentUser { get; set; }
protected override DriverResult Display(TagsPart part, string displayType, dynamic shapeHelper) {
var showTags = shapeHelper.Parts_Tags_ShowTags(ContentPart: part, Tags: part.CurrentTags);
if (!string.IsNullOrWhiteSpace(displayType))
showTags.Metadata.Type = string.Format("{0}.{1}", showTags.Metadata.Type, displayType);
var location = part.GetLocation(displayType);
return ContentShape(showTags).Location(location);
return ContentShape("Parts_Tags_ShowTags",
() => shapeHelper.Parts_Tags_ShowTags(ContentPart: part, Tags: part.CurrentTags));
}
protected override DriverResult Editor(TagsPart part, dynamic shapeHelper) {

View File

@@ -112,6 +112,9 @@
<Name>Orchard.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="Placement.info" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@@ -0,0 +1,3 @@
<Placement>
<Place Parts_Tags_ShowTags="Header:after.7"/>
</Placement>

View File

@@ -12,10 +12,5 @@
</Match>
-->
<Match ContentType="Page" DisplayType="Detail">
<Place
Parts_RoutableTitle="Content:5.b"
Parts_Common_Body="Header:after" />
</Match>
</Placement>

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Routing;
using ClaySharp;
using ClaySharp.Implementation;
using Microsoft.CSharp.RuntimeBinder;
using Orchard.ContentManagement.Handlers;
using Orchard.DisplayManagement;
@@ -10,11 +12,13 @@ using Orchard.DisplayManagement.Descriptors;
using Orchard.Logging;
using Orchard.Mvc;
using Orchard.Themes;
using Orchard.UI.Zones;
namespace Orchard.ContentManagement {
public class DefaultContentDisplay : IContentDisplay {
private readonly Lazy<IEnumerable<IContentHandler>> _handlers;
private readonly IShapeHelperFactory _shapeHelperFactory;
private readonly IShapeFactory _shapeFactory;
private readonly IShapeTableManager _shapeTableManager;
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
@@ -24,6 +28,7 @@ namespace Orchard.ContentManagement {
public DefaultContentDisplay(
Lazy<IEnumerable<IContentHandler>> handlers,
IShapeHelperFactory shapeHelperFactory,
IShapeFactory shapeFactory,
IShapeTableManager shapeTableManager,
IWorkContextAccessor workContextAccessor,
IHttpContextAccessor httpContextAccessor,
@@ -31,6 +36,7 @@ namespace Orchard.ContentManagement {
RequestContext requestContext) {
_handlers = handlers;
_shapeHelperFactory = shapeHelperFactory;
_shapeFactory = shapeFactory;
_shapeTableManager = shapeTableManager;
_workContextAccessor = workContextAccessor;
_httpContextAccessor = httpContextAccessor;
@@ -55,29 +61,30 @@ namespace Orchard.ContentManagement {
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);
var actualShapeType = "Items_" + stereotype;
var actualDisplayType = string.IsNullOrWhiteSpace(displayType) ? "Detail" : displayType;
dynamic itemShape = CreateItemShape(actualShapeType);
itemShape.ContentItem = content.ContentItem;
itemShape.Metadata.DisplayType = shapeDisplayType;
itemShape.Metadata.DisplayType = actualDisplayType;
var context = new BuildDisplayContext(itemShape, content, shapeDisplayType, _shapeHelperFactory);
BindPlacement(context, displayType);
var context = new BuildDisplayContext(itemShape, content, actualDisplayType, _shapeHelperFactory);
BindPlacement(context, actualDisplayType);
_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();
var contentTypeDefinition = content.ContentItem.TypeDefinition;
string stereotype;
if (!contentTypeDefinition.Settings.TryGetValue("Stereotype", out stereotype))
stereotype = "Content";
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var actualShapeType = "Items_" + stereotype + "_Editor";
dynamic itemShape = CreateItemShape(actualShapeType);
itemShape.ContentItem = content.ContentItem;
var context = new BuildEditorContext(itemShape, content, _shapeHelperFactory);
BindPlacement(context, null);
@@ -87,12 +94,15 @@ namespace Orchard.ContentManagement {
}
public dynamic UpdateEditor(IContent content, IUpdateModel updater) {
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = shapeHelper.Items_Content_Edit();
var contentTypeDefinition = content.ContentItem.TypeDefinition;
string stereotype;
if (!contentTypeDefinition.Settings.TryGetValue("Stereotype", out stereotype))
stereotype = "Content";
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var actualShapeType = "Items_" + stereotype + "_Editor";
dynamic itemShape = CreateItemShape(actualShapeType);
itemShape.ContentItem = content.ContentItem;
var context = new UpdateEditorContext(itemShape, content, updater, _shapeHelperFactory);
BindPlacement(context, null);
@@ -101,6 +111,11 @@ namespace Orchard.ContentManagement {
return context.Shape;
}
private dynamic CreateItemShape(string actualShapeType) {
var zoneHoldingBehavior = new ZoneHoldingBehavior(() => _shapeFactory.Create("ContentZone", Arguments.Empty()));
return _shapeFactory.Create(actualShapeType, Arguments.Empty(), new[] { zoneHoldingBehavior });
}
private void BindPlacement(BuildShapeContext context, string displayType) {
context.FindPlacement = (partShapeType, defaultLocation) => {
//var workContext = _workContextAccessor.GetContext();

View File

@@ -24,7 +24,8 @@ namespace Orchard.DisplayManagement.Descriptors {
/// </summary>
public string BindingSource {
get {
return Bindings[ShapeType].BindingSource;
ShapeBinding binding;
return Bindings.TryGetValue(ShapeType, out binding) ? binding.BindingSource : null;
}
}

View File

@@ -31,7 +31,7 @@ namespace Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy {
var activeExtensions = Once(activeFeatures);
foreach (var extensionDescriptor in activeExtensions) {
foreach (var featureDescriptor in extensionDescriptor.Features.Where(fd=>fd.Name == fd.Extension.Name)) {
foreach (var featureDescriptor in extensionDescriptor.Features.Where(fd => fd.Name == fd.Extension.Name)) {
ProcessFeatureDescriptor(builder, featureDescriptor);
}
}
@@ -46,7 +46,7 @@ namespace Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy {
}
private void ProcessPlacementFile(ShapeTableBuilder builder, FeatureDescriptor featureDescriptor, PlacementFile placementFile) {
var feature = new Feature {Descriptor = featureDescriptor};
var feature = new Feature { Descriptor = featureDescriptor };
// invert the tree into a list of leaves and the stack
var entries = DrillDownShapeLocations(placementFile.Nodes, Enumerable.Empty<PlacementMatch>());
@@ -55,7 +55,9 @@ namespace Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy {
var matches = entry.Item2;
Func<ShapePlacementContext, bool> predicate = ctx => true;
predicate = matches.SelectMany(match=>match.Terms).Aggregate(predicate, BuildPredicate);
if (matches.Any()) {
predicate = matches.SelectMany(match => match.Terms).Aggregate(predicate, BuildPredicate);
}
builder.Describe(shapeLocation.ShapeType)
.From(feature)
@@ -65,11 +67,19 @@ namespace Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy {
private Func<ShapePlacementContext, bool> BuildPredicate(Func<ShapePlacementContext, bool> predicate, KeyValuePair<string, string> term) {
var expression = term.Value;
switch(term.Key) {
switch (term.Key) {
case "ContentType":
return ctx=>ctx.ContentType == expression ? true : predicate(ctx);
if (expression.EndsWith("*")) {
var prefix = expression.Substring(0, expression.Length - 1);
return ctx => (ctx.ContentType ?? "").StartsWith(prefix) && predicate(ctx);
}
return ctx => (ctx.ContentType == expression) && predicate(ctx);
case "DisplayType":
return ctx=>ctx.DisplayType == expression ? true : predicate(ctx);
if (expression.EndsWith("*")) {
var prefix = expression.Substring(0, expression.Length - 1);
return ctx => (ctx.DisplayType ?? "").StartsWith(prefix) && predicate(ctx);
}
return ctx => (ctx.DisplayType == expression) && predicate(ctx);
}
return predicate;
}
@@ -85,7 +95,7 @@ namespace Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy {
}
// recurse down into match nodes
foreach (var placementMatch in nodes.OfType<PlacementMatch>()) {
foreach (var findShapeLocation in DrillDownShapeLocations(placementMatch.Nodes, path.Concat(new[] {placementMatch}))) {
foreach (var findShapeLocation in DrillDownShapeLocations(placementMatch.Nodes, path.Concat(new[] { placementMatch }))) {
yield return findShapeLocation;
}
}

View File

@@ -26,7 +26,7 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy {
}
public IEnumerable<HarvestShapeHit> HarvestShape(HarvestShapeInfo info) {
var lastDot = info.FileName.IndexOf('.');
var lastDot = info.FileName.LastIndexOf('.');
if (lastDot <= 0) {
yield return new HarvestShapeHit {
ShapeType = Adjust(info.SubPath, info.FileName, null)

View File

@@ -1,4 +1,5 @@
using ClaySharp;
using System.Collections.Generic;
using ClaySharp;
using ClaySharp.Implementation;
namespace Orchard.DisplayManagement {
@@ -8,6 +9,7 @@ namespace Orchard.DisplayManagement {
/// </summary>
public interface IShapeFactory : IDependency {
IShape Create(string shapeType, INamedEnumerable<object> parameters);
IShape Create(string shapeType, INamedEnumerable<object> parameters, IEnumerable<IClayBehavior> behaviors);
}
public static class ShapeFactoryExtensions {
@@ -16,3 +18,4 @@ namespace Orchard.DisplayManagement {
}
}
}

View File

@@ -25,13 +25,17 @@ namespace Orchard.DisplayManagement.Implementation {
}
public IShape Create(string shapeType, INamedEnumerable<object> parameters) {
return Create(shapeType, parameters, Enumerable.Empty<IClayBehavior>());
}
public IShape Create(string shapeType, INamedEnumerable<object> parameters, IEnumerable<IClayBehavior> behaviors) {
var defaultShapeTable = _shapeTableManager.GetShapeTable(null);
ShapeDescriptor shapeDescriptor;
defaultShapeTable.Descriptors.TryGetValue(shapeType, out shapeDescriptor);
var creatingContext = new ShapeCreatingContext {
New = _shapeHelperFactory.Value.CreateHelper(),
ShapeFactory=this,
ShapeFactory = this,
ShapeType = shapeType,
OnCreated = new List<Action<ShapeCreatedContext>>()
};
@@ -65,7 +69,12 @@ namespace Orchard.DisplayManagement.Implementation {
};
}
// "creating" events may add behaviors and alter base type
if (behaviors != null && behaviors.Any()) {
// include behaviors passed in by caller, if any
creatingContext.Behaviors = creatingContext.Behaviors.Concat(behaviors).ToList();
}
// "creating" events may add behaviors and alter base type)
foreach (var ev in _events) {
ev.Value.Creating(creatingContext);
}
@@ -117,6 +126,8 @@ namespace Orchard.DisplayManagement.Implementation {
return createdContext.Shape;
}
}

View File

@@ -20,9 +20,9 @@ namespace Orchard.UI.Zones {
///
/// </summary>
public class ZoneHoldingBehavior : ClayBehavior {
private readonly Func<string, dynamic> _zoneFactory;
private readonly Func<dynamic> _zoneFactory;
public ZoneHoldingBehavior(Func<string, dynamic> zoneFactory) {
public ZoneHoldingBehavior(Func<dynamic> zoneFactory) {
_zoneFactory = zoneFactory;
}
@@ -50,10 +50,10 @@ namespace Orchard.UI.Zones {
}
public class ZonesBehavior : ClayBehavior {
private readonly Func<string, dynamic> _zoneFactory;
private readonly Func<dynamic> _zoneFactory;
private readonly object _parent;
public ZonesBehavior(Func<string, dynamic> zoneFactory, object parent) {
public ZonesBehavior(Func<dynamic> zoneFactory, object parent) {
_zoneFactory = zoneFactory;
_parent = parent;
}
@@ -78,11 +78,11 @@ namespace Orchard.UI.Zones {
}
public class ZoneOnDemandBehavior : ClayBehavior {
private readonly Func<string, dynamic> _zoneFactory;
private readonly Func<dynamic> _zoneFactory;
private readonly object _parent;
private readonly string _potentialZoneName;
public ZoneOnDemandBehavior(Func<string, dynamic> zoneFactory, object parent, string potentialZoneName) {
public ZoneOnDemandBehavior(Func<dynamic> zoneFactory, object parent, string potentialZoneName) {
_zoneFactory = zoneFactory;
_parent = parent;
_potentialZoneName = potentialZoneName;
@@ -93,7 +93,7 @@ namespace Orchard.UI.Zones {
if (name == "Add" && (argsCount == 1 || argsCount == 2)) {
dynamic parent = _parent;
dynamic zone = _zoneFactory(_potentialZoneName);
dynamic zone = _zoneFactory();
zone.Parent = _parent;
zone.ZoneName = _potentialZoneName;
parent[_potentialZoneName] = zone;