Implementing admin placement for content type editors

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros 2012-06-07 18:10:32 -07:00
parent 6d3bf98c8a
commit e519781033
21 changed files with 779 additions and 9 deletions

View File

@ -222,6 +222,7 @@
<Compile Include="Settings\Migrations.cs" />
<Compile Include="Settings\Drivers\SiteSettingsPartDriver.cs" />
<Compile Include="Settings\Routes.cs" />
<Compile Include="Settings\Tokens\SettingsTokens.cs" />
<Compile Include="Settings\ViewModels\SiteCulturesViewModel.cs" />
<Compile Include="Settings\Metadata\ContentDefinitionManager.cs" />
<Compile Include="Settings\Metadata\Records\ContentFieldDefinitionRecord.cs" />

View File

@ -1,14 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentTypes.Extensions;
using Orchard.ContentTypes.Services;
using Orchard.ContentTypes.Settings;
using Orchard.ContentTypes.ViewModels;
using Orchard.Core.Contents.Controllers;
using Orchard.Core.Contents.Settings;
using Orchard.Environment.Configuration;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.UI;
using Orchard.UI.Notify;
using Orchard.Utility.Extensions;
@ -16,17 +22,30 @@ namespace Orchard.ContentTypes.Controllers {
public class AdminController : Controller, IUpdateModel {
private readonly IContentDefinitionService _contentDefinitionService;
private readonly IContentDefinitionManager _contentDefinitionManager;
private readonly IPlacementService _placementService;
private readonly Lazy<IEnumerable<IShellSettingsManagerEventHandler>> _settingsManagerEventHandlers;
private readonly ShellSettings _settings;
public AdminController(IOrchardServices orchardServices, IContentDefinitionService contentDefinitionService, IContentDefinitionManager contentDefinitionManager) {
public AdminController(
IOrchardServices orchardServices,
IContentDefinitionService contentDefinitionService,
IContentDefinitionManager contentDefinitionManager,
IPlacementService placementService,
Lazy<IEnumerable<IShellSettingsManagerEventHandler>> settingsManagerEventHandlers,
ShellSettings settings
) {
Services = orchardServices;
_contentDefinitionService = contentDefinitionService;
_contentDefinitionManager = contentDefinitionManager;
_placementService = placementService;
_settingsManagerEventHandlers = settingsManagerEventHandlers;
_settings = settings;
T = NullLocalizer.Instance;
}
public IOrchardServices Services { get; private set; }
public Localizer T { get; set; }
public ILogger Logger { get; set; }
public ActionResult Index() { return List(); }
#region Types
@ -116,6 +135,89 @@ namespace Orchard.ContentTypes.Controllers {
return View(typeViewModel);
}
public ActionResult EditPlacement(string id) {
if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to edit a content type.")))
return new HttpUnauthorizedResult();
var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(id);
if (contentTypeDefinition == null)
return HttpNotFound();
var content = Services.ContentManager.New(id);
var shape = Services.ContentManager.BuildEditor(content);
var placementModel = new EditPlacementViewModel {
PlacementSettings = contentTypeDefinition.GetPlacement(PlacementType.Editor),
AllPlacements = _placementService.GetEditorPlacement(id).OrderBy(x => x.PlacementSettings.Position, new FlatPositionComparer()).ThenBy(x => x.PlacementSettings.ShapeType).ToList(),
ContentTypeDefinition = contentTypeDefinition,
};
return View(placementModel);
}
[HttpPost, ActionName("EditPlacement")]
[FormValueRequired("submit.Save")]
public ActionResult EditPlacementPost(string id, EditPlacementViewModel viewModel) {
if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to edit a content type.")))
return new HttpUnauthorizedResult();
var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(id);
if (contentTypeDefinition == null)
return HttpNotFound();
var allPlacements = _placementService.GetEditorPlacement(id).ToList();
var result = new List<PlacementSettings>(contentTypeDefinition.GetPlacement(PlacementType.Editor));
contentTypeDefinition.ResetPlacement(PlacementType.Editor);
foreach(var driverPlacement in viewModel.AllPlacements) {
// if the placement has changed, persist it
if (!allPlacements.Any(x => x.PlacementSettings.Equals(driverPlacement.PlacementSettings))) {
result = result.Where(x => !x.IsSameAs(driverPlacement.PlacementSettings)).ToList();
result.Add(driverPlacement.PlacementSettings);
}
}
foreach(var placementSetting in result) {
contentTypeDefinition.Placement(PlacementType.Editor,
placementSetting.ShapeType,
placementSetting.Differentiator,
placementSetting.Zone,
placementSetting.Position);
}
// persist changes
_contentDefinitionManager.StoreTypeDefinition(contentTypeDefinition);
_settingsManagerEventHandlers.Value.Invoke(x => x.Saved(_settings), Logger);
return RedirectToAction("EditPlacement", new {id});
}
[HttpPost, ActionName("EditPlacement")]
[FormValueRequired("submit.Restore")]
public ActionResult EditPlacementRestorePost(string id, EditPlacementViewModel viewModel) {
if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to edit a content type.")))
return new HttpUnauthorizedResult();
var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(id);
if (contentTypeDefinition == null)
return HttpNotFound();
contentTypeDefinition.ResetPlacement(PlacementType.Editor);
// persist changes
_contentDefinitionManager.StoreTypeDefinition(contentTypeDefinition);
_settingsManagerEventHandlers.Value.Invoke(x => x.Saved(_settings), Logger);
return RedirectToAction("EditPlacement", new { id });
}
[HttpPost, ActionName("Edit")]
[FormValueRequired("submit.Save")]
public ActionResult EditPOST(string id) {

View File

@ -0,0 +1,98 @@
using System;
using System.Linq;
using System.Web.Script.Serialization;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentTypes.Settings;
namespace Orchard.ContentTypes.Extensions {
public enum PlacementType {
Display,
Editor
}
public static class MetadataExtensions {
/// <summary>
/// Removes any custom placement for a specific shape
/// </summary>
public static ContentTypeDefinitionBuilder ResetPlacement(this ContentTypeDefinitionBuilder builder, PlacementType placementType, string shapeType, string differentiator) {
var serializer = new JavaScriptSerializer();
var placementSettings = GetPlacement(builder.Build(), placementType).ToList();
placementSettings = placementSettings.Where(x => x.ShapeType != shapeType && x.Differentiator != differentiator).ToList();
var placement = serializer.Serialize(placementSettings.ToArray());
return builder.WithSetting("ContentTypeSettings.Placement." + placementType, placement);
}
/// <summary>
/// Removes any custom placement
/// </summary>
public static ContentTypeDefinitionBuilder ResetPlacement(this ContentTypeDefinitionBuilder builder, PlacementType placementType) {
return builder.WithSetting("ContentTypeSettings.Placement." + placementType, String.Empty);
}
/// <summary>
/// Defines a custom placement
/// </summary>
public static ContentTypeDefinitionBuilder Placement(this ContentTypeDefinitionBuilder builder, PlacementType placementType, string shapeType, string differentiator, string zone, string position) {
var serializer = new JavaScriptSerializer();
var placementSettings = GetPlacement(builder.Build(), placementType).ToList();
placementSettings = placementSettings.Where(x => x.ShapeType != shapeType && x.Differentiator != differentiator).ToList();
placementSettings.Add(new PlacementSettings {
ShapeType = shapeType,
Differentiator = differentiator,
Zone = zone,
Position = position
});
var placement = serializer.Serialize(placementSettings.ToArray());
return builder.WithSetting("ContentTypeSettings.Placement." + placementType, placement);
}
/// <summary>
/// Adds a placement the string representation of a placement
/// </summary>
public static ContentTypeDefinition Placement(this ContentTypeDefinition builder, PlacementType placementType, string shapeType, string differentiator, string zone, string position) {
var serializer = new JavaScriptSerializer();
var placementSettings = GetPlacement(builder, placementType).ToList();
placementSettings = placementSettings.Where(x => !x.IsSameAs(new PlacementSettings { ShapeType = shapeType, Differentiator = differentiator })).ToList();
placementSettings.Add(new PlacementSettings {
ShapeType = shapeType,
Differentiator = differentiator,
Zone = zone,
Position = position
});
var placement = serializer.Serialize(placementSettings.ToArray());
builder.Settings["ContentTypeSettings.Placement." + placementType] = placement;
return builder;
}
/// <summary>
/// Adds a placement the string representation of a placement
/// </summary>
public static ContentTypeDefinition ResetPlacement(this ContentTypeDefinition builder, PlacementType placementType) {
builder.Settings["ContentTypeSettings.Placement." + placementType] = String.Empty;
return builder;
}
public static PlacementSettings[] GetPlacement(this ContentTypeDefinition contentTypeDefinition, PlacementType placementType) {
var currentSettings = contentTypeDefinition.Settings;
string placement;
var serializer = new JavaScriptSerializer();
currentSettings.TryGetValue("ContentTypeSettings.Placement." + placementType, out placement);
return String.IsNullOrEmpty(placement) ? new PlacementSettings[0] : serializer.Deserialize<PlacementSettings[]>(placement);
}
}
}

View File

@ -7,3 +7,4 @@ OrchardVersion: 1.4.1
Description: ContentTypes modules enables the creation and alteration of content types not based on code.
Dependencies: Contents
Category: Content
Priority: -10

View File

@ -35,12 +35,14 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="ClaySharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath>
@ -49,9 +51,14 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Extensions\MetadataExtensions.cs" />
<Compile Include="ResourceManifest.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Services\ContentTypePlacementStrategy.cs" />
<Compile Include="Services\IPlacementService.cs" />
<Compile Include="Services\PlacementService.cs" />
<Compile Include="Settings\EditorEvents.cs" />
<Compile Include="Settings\PlacementSettings.cs" />
<Compile Include="ViewModels\AddPartsViewModel.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Permissions.cs" />
@ -65,6 +72,7 @@
<Compile Include="ViewModels\EditFieldViewModel.cs" />
<Compile Include="ViewModels\EditPartFieldViewModel.cs" />
<Compile Include="ViewModels\EditPartViewModel.cs" />
<Compile Include="ViewModels\EditPlacementViewModel.cs" />
<Compile Include="ViewModels\EditTypePartViewModel.cs" />
<Compile Include="ViewModels\ListContentPartsViewModel.cs" />
<Compile Include="ViewModels\RemoveFieldViewModel.cs" />
@ -76,6 +84,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="Module.txt" />
<Content Include="Styles\Images\move.gif" />
<Content Include="Styles\orchard-contenttypes-admin.css" />
<Content Include="Views\Admin\AddFieldTo.cshtml" />
<Content Include="Views\Admin\AddPartsTo.cshtml" />
@ -115,6 +124,10 @@
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Themes\Orchard.Themes.csproj">
<Project>{CDE24A24-01D3-403C-84B9-37722E18DFB7}</Project>
<Name>Orchard.Themes</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="web.config" />
@ -125,6 +138,9 @@
<ItemGroup>
<Content Include="Views\DefinitionTemplates\ContentTypeSettingsViewModel.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Admin\EditPlacement.cshtml" />
</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,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentTypes.Settings;
using Orchard.DisplayManagement.Descriptors;
using Orchard.ContentTypes.Extensions;
using Orchard.Environment;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions.Models;
namespace Orchard.ContentTypes.Services {
public class TypePlacement {
public PlacementSettings Placement { get; set; }
public string ContentType { get; set; }
}
public class ContentTypePlacementStrategy : IShapeTableEventHandler {
private readonly Work<IContentDefinitionManager> _contentDefinitionManager;
public ContentTypePlacementStrategy(Work<IContentDefinitionManager> contentDefinitionManager) {
_contentDefinitionManager = contentDefinitionManager;
}
public virtual Feature Feature { get; set; }
public void ShapeTableCreated(ShapeTable shapeTable) {
var typeDefinitions = _contentDefinitionManager.Value.ListTypeDefinitions();
var allPlacements = typeDefinitions.SelectMany(td => td.GetPlacement(PlacementType.Editor).Select(p => new TypePlacement { Placement = p, ContentType = td.Name }) );
// group all placement settings by shape type
var shapePlacements = allPlacements.GroupBy(x => x.Placement.ShapeType).ToDictionary(x => x.Key, y=> y.ToList());
// create a new predicate in a ShapeTableDescriptor has a custom placement
foreach(var shapeType in shapeTable.Descriptors.Keys) {
List<TypePlacement> customPlacements;
if(shapePlacements.TryGetValue(shapeType, out customPlacements)) {
var descriptor = shapeTable.Descriptors[shapeType];
// there are some custom placements, build a predicate
var placement = descriptor.Placement;
foreach(var customPlacement in customPlacements) {
// create local variables to prevent LINQ issues
var placementShapeType = customPlacement.Placement.ShapeType;
var type = customPlacement.ContentType;
var differentiator = customPlacement.Placement.Differentiator;
var location = customPlacement.Placement.Zone;
if(!String.IsNullOrEmpty(customPlacement.Placement.Position)) {
location = String.Concat(location, ":", customPlacement.Placement.Position);
}
descriptor.Placement = ctx => {
var nextPlacement = placement(ctx);
if(ctx.DisplayType == null && ((ctx.Differentiator ?? "") == (differentiator ?? "")) && ctx.ContentType == type) {
nextPlacement.Location = location;
}
return nextPlacement;
};
}
}
}
}
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Orchard.ContentTypes.Settings;
namespace Orchard.ContentTypes.Services {
public interface IPlacementService : IDependency {
IEnumerable<DriverResultPlacement> GetDisplayPlacement(string contentType);
IEnumerable<DriverResultPlacement> GetEditorPlacement(string contentType);
IEnumerable<string> GetZones();
}
}

View File

@ -0,0 +1,254 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
using ClaySharp.Implementation;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers;
using Orchard.ContentTypes.Settings;
using Orchard.DisplayManagement;
using Orchard.DisplayManagement.Descriptors;
using Orchard.Environment.Extensions;
using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
using Orchard.Themes.Services;
using Orchard.UI.Zones;
namespace Orchard.ContentTypes.Services {
public class DriverResultPlacement {
public PlacementSettings PlacementSettings { get; set; }
public DriverResult ShapeResult { get; set; }
public dynamic Shape { get; set; }
}
public class PlacementService : IPlacementService {
private readonly IContentManager _contentManager;
private readonly ISiteThemeService _siteThemeService;
private readonly IExtensionManager _extensionManager;
private readonly IShapeFactory _shapeFactory;
private readonly IShapeTableLocator _shapeTableLocator;
private readonly RequestContext _requestContext;
private readonly IEnumerable<IContentPartDriver> _contentPartDrivers;
private readonly IEnumerable<IContentFieldDriver> _contentFieldDrivers;
private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IWorkContextAccessor _workContextAccessor;
public PlacementService(
IContentManager contentManager,
ISiteThemeService siteThemeService,
IExtensionManager extensionManager,
IShapeFactory shapeFactory,
IShapeTableLocator shapeTableLocator,
RequestContext requestContext,
IEnumerable<IContentPartDriver> contentPartDrivers,
IEnumerable<IContentFieldDriver> contentFieldDrivers,
IVirtualPathProvider virtualPathProvider,
IWorkContextAccessor workContextAccessor
)
{
_contentManager = contentManager;
_siteThemeService = siteThemeService;
_extensionManager = extensionManager;
_shapeFactory = shapeFactory;
_shapeTableLocator = shapeTableLocator;
_requestContext = requestContext;
_contentPartDrivers = contentPartDrivers;
_contentFieldDrivers = contentFieldDrivers;
_virtualPathProvider = virtualPathProvider;
_workContextAccessor = workContextAccessor;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public IEnumerable<DriverResultPlacement> GetDisplayPlacement(string contentType) {
var content = _contentManager.New(contentType);
const string actualDisplayType = "Detail";
dynamic itemShape = CreateItemShape("Content");
itemShape.ContentItem = content;
itemShape.Metadata.DisplayType = actualDisplayType;
var context = new BuildDisplayContext(itemShape, content, actualDisplayType, String.Empty, _shapeFactory);
BindPlacement(context, actualDisplayType, "Content");
var placementSettings = new List<DriverResultPlacement>();
_contentPartDrivers.Invoke(driver => {
var result = driver.BuildDisplay(context);
if (result != null) {
placementSettings.AddRange(ExtractPlacement(result, context));
}
}, Logger);
_contentFieldDrivers.Invoke(driver => {
var result = driver.BuildDisplayShape(context);
if (result != null) {
placementSettings.AddRange(ExtractPlacement(result, context));
}
}, Logger);
foreach (var placementSetting in placementSettings) {
yield return placementSetting;
}
}
public IEnumerable<DriverResultPlacement> GetEditorPlacement(string contentType) {
var content = _contentManager.New(contentType);
dynamic itemShape = CreateItemShape("Content_Edit");
itemShape.ContentItem = content;
var context = new BuildEditorContext(itemShape, content, String.Empty, _shapeFactory);
BindPlacement(context, null, "Content");
var placementSettings = new List<DriverResultPlacement>();
_contentPartDrivers.Invoke(driver => {
var result = driver.BuildEditor(context);
if (result != null) {
placementSettings.AddRange(ExtractPlacement(result, context));
}
}, Logger);
_contentFieldDrivers.Invoke(driver => {
var result = driver.BuildEditorShape(context);
if (result != null) {
placementSettings.AddRange(ExtractPlacement(result, context));
}
}, Logger);
foreach (var placementSetting in placementSettings) {
yield return placementSetting;
}
}
public IEnumerable<string> GetZones() {
var theme = _siteThemeService.GetSiteTheme();
IEnumerable<string> zones = new List<string>();
// get the zones for this theme
if (theme.Zones != null)
zones = theme.Zones.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.Distinct()
.ToList();
// if this theme has no zones defined then walk the BaseTheme chain until we hit a theme which defines zones
while (!zones.Any() && theme != null && !string.IsNullOrWhiteSpace(theme.BaseTheme)) {
string baseTheme = theme.BaseTheme;
theme = _extensionManager.GetExtension(baseTheme);
if (theme != null && theme.Zones != null)
zones = theme.Zones.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.Distinct()
.ToList();
}
return zones;
}
private IEnumerable<DriverResultPlacement> ExtractPlacement(DriverResult result, BuildShapeContext context) {
if (result is CombinedResult) {
foreach (var subResult in ((CombinedResult) result).GetResults()) {
foreach (var placement in ExtractPlacement(subResult, context)) {
yield return placement;
}
}
}
else if (result is ContentShapeResult) {
var contentShapeResult = (ContentShapeResult) result;
var placement = context.FindPlacement(
contentShapeResult.GetShapeType(),
contentShapeResult.GetDifferentiator(),
contentShapeResult.GetLocation()
);
string zone = placement.Location;
string position = String.Empty;
// if no placement is found, it's hidden, e.g., no placement was found for the specific ContentType/DisplayType
if (placement.Location != null) {
var delimiterIndex = placement.Location.IndexOf(':');
if (delimiterIndex >= 0) {
zone = placement.Location.Substring(0, delimiterIndex);
position = placement.Location.Substring(delimiterIndex + 1);
}
}
var content = _contentManager.New(context.ContentItem.ContentType);
dynamic itemShape = CreateItemShape("Content_Edit");
itemShape.ContentItem = content;
if(context is BuildDisplayContext) {
var newContext = new BuildDisplayContext(itemShape, content, "Detail", "", context.New);
BindPlacement(newContext, "Detail", "Content");
contentShapeResult.Apply(newContext);
}
else {
var newContext = new BuildEditorContext(itemShape, content, "", context.New);
BindPlacement(newContext, null, "Content");
contentShapeResult.Apply(newContext);
}
yield return new DriverResultPlacement {
Shape = itemShape.Content,
ShapeResult = contentShapeResult,
PlacementSettings = new PlacementSettings {
ShapeType = contentShapeResult.GetShapeType(),
Zone = zone,
Position = position,
Differentiator = contentShapeResult.GetDifferentiator() ?? String.Empty
}
};
}
}
private dynamic CreateItemShape(string actualShapeType) {
var zoneHoldingBehavior = new ZoneHoldingBehavior((Func<dynamic>)(() => _shapeFactory.Create("ContentZone", Arguments.Empty())), _workContextAccessor.GetContext().Layout);
return _shapeFactory.Create(actualShapeType, Arguments.Empty(), new[] { zoneHoldingBehavior });
}
private void BindPlacement(BuildShapeContext context, string displayType, string stereotype) {
context.FindPlacement = (partShapeType, differentiator, defaultLocation) => {
var theme = _siteThemeService.GetSiteTheme();
var shapeTable = _shapeTableLocator.Lookup(theme.Id);
var request = _requestContext.HttpContext.Request;
ShapeDescriptor descriptor;
if (shapeTable.Descriptors.TryGetValue(partShapeType, out descriptor)) {
var placementContext = new ShapePlacementContext {
ContentType = context.ContentItem.ContentType,
Stereotype = stereotype,
DisplayType = displayType,
Differentiator = differentiator,
Path = VirtualPathUtility.AppendTrailingSlash(_virtualPathProvider.ToAppRelative(request.Path)) // get the current app-relative path, i.e. ~/my-blog/foo
};
// define which location should be used if none placement is hit
descriptor.DefaultPlacement = defaultLocation;
var placement = descriptor.Placement(placementContext);
if (placement != null) {
placement.Source = placementContext.Source;
return placement;
}
}
return new PlacementInfo {
Location = defaultLocation,
Source = String.Empty
};
};
}
}
}

View File

@ -0,0 +1,45 @@
using System;
namespace Orchard.ContentTypes.Settings {
public class PlacementSettings : IEquatable<PlacementSettings> {
/// <summary>
/// e.g., Parts_Title_Summary
/// </summary>
public string ShapeType { get; set; }
/// <summary>
/// e.g., Header, /Navigation
/// </summary>
public string Zone { get; set; }
/// <summary>
/// e.g, 5, after.7
/// </summary>
public string Position { get; set; }
/// <summary>
/// e.g, 5, MyTextField
/// </summary>
public string Differentiator { get; set; }
public bool IsSameAs(PlacementSettings other) {
return (ShapeType ?? String.Empty) == (other.ShapeType ?? String.Empty)
&& (Differentiator ?? String.Empty) == (other.Differentiator ?? String.Empty);
}
public bool Equals(PlacementSettings other) {
if(other == this) {
return true;
}
if(other == null) {
return false;
}
return (ShapeType ?? String.Empty) == (other.ShapeType ?? String.Empty)
&& (Zone ?? String.Empty) == (other.Zone ?? String.Empty)
&& (Position ?? String.Empty) == (other.Position ?? String.Empty)
&& (Differentiator ?? String.Empty) == (other.Differentiator ?? String.Empty);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

View File

@ -109,4 +109,40 @@ fieldset.action {
.manage-part dd dd {
float:left;
padding-left:.5em;
}
/* PLACEMENT EDITOR */
#placement li {
margin-bottom: 10px;
padding: 0px;
border: 1px solid #eee;
}
#placement li .shape-type {
cursor: move;
background-color: #eee;
background: #EEE url(images/move.gif) no-repeat 10px 15px;
height: 30px;
padding: 10px 0px 0px 30px;
}
#placement li .shape-editor {
padding: 10px;
}
#save-message {
display: none;
margin-bottom: 10px;
}
.overlay{
background:red repeat top left;
position:fixed;
top:0px;
bottom:0px;
left:0px;
right:0px;
z-index:100;
}

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentTypes.Services;
using Orchard.ContentTypes.Settings;
namespace Orchard.ContentTypes.ViewModels {
public class EditPlacementViewModel {
public ContentTypeDefinition ContentTypeDefinition { get; set; }
public PlacementSettings[] PlacementSettings { get; set; }
public List<DriverResultPlacement> AllPlacements { get; set; }
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentManagement.ViewModels;

View File

@ -2,12 +2,15 @@
@{
Style.Require("ContentTypesAdmin");
Script.Require("jQuery");
Layout.Title = T("Edit Content Type").ToString();
Layout.Title = T("Edit Content Type - {0}", Model.DisplayName).ToString();
}
@using (Html.BeginFormAntiForgeryPost()) {
// todo: come up with real itemtype definitions and locations for said definitions
<div itemscope="itemscope" itemid="@Model.Name" itemtype="http://orchardproject.net/data/ContentType">
<div class="manage add-to-type">
@Html.ActionLink(T("Edit Placement").Text, "EditPlacement", new { area = "Orchard.ContentTypes", id = Model.Name }, new { @class = "button" })
</div>
@Html.ValidationSummary()
<fieldset>
<label for="DisplayName">@T("Display Name")</label>
@ -19,7 +22,7 @@
<meta itemprop="Id" content="@Model.Name" />
@Html.HiddenFor(m => m.Name)
</fieldset>
@{ Html.RenderTemplates(Model.Templates); }
@{ Html.RenderTemplates(Model.Templates); }
<div class="manage-type">
<h2>@T("Fields")</h2>
<div class="manage add-to-type">
@ -31,6 +34,8 @@
@Html.ActionLink(T("Add Parts").Text, "AddPartsTo", new { area = "Orchard.ContentTypes", id = Model.Name }, new { @class = "button" })
</div>
@Html.EditorFor(m => m.Parts, "TypeParts", "")
<h2>@T("Placement")</h2>
@Html.EditorFor(m => m.Settings, "Placement", "")
</div>
<fieldset class="action">
<button class="primaryAction" type="submit" name="submit.Save" value="Save">@T("Save")</button>

View File

@ -0,0 +1,92 @@
@model Orchard.ContentTypes.ViewModels.EditPlacementViewModel
@{
Style.Require("ContentTypesAdmin");
Script.Require("jQueryUI_Sortable");
Layout.Title = T("Edit Placement - {0}", Model.ContentTypeDefinition.DisplayName).ToString();
var hiddenShapes = Model.AllPlacements.Where(x => String.IsNullOrEmpty(x.PlacementSettings.Zone) && (String.IsNullOrWhiteSpace(x.PlacementSettings.Position) || x.PlacementSettings.Position == "-"));
}
<div id="save-message" class="message message-Warning">@T("You need to hit \"Save\" in order to save your changes.")</div>
@using (Html.BeginFormAntiForgeryPost()) {
@Html.ValidationSummary()
<ul id="placement">
@for (int i = 0; i < Model.AllPlacements.Count; i++ ) {
var placement = Model.AllPlacements[i].PlacementSettings;
<li data-shape-type="@placement.ShapeType" data-shape-differentiator="@placement.Differentiator" data-shape-zone="Content" data-shape-position="@placement.Position">
<div class="shape-type"><h3>@placement.ShapeType @placement.Differentiator</h3></div>
<div class="shape-editor">@Display(Model.AllPlacements[i].Shape)</div>
@* @shape.Position @(Model.PlacementSettings.Any(x => x.Equals(shape)))*@
@Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.ShapeType, new { @class = "type" })
@Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Differentiator, new { @class = "differentiator" })
@Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Zone, new { @class = "zone" })
@Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Position, new { @class = "position" })
</li>
}
</ul>
<fieldset class="action">
<button class="primaryAction" type="submit" name="submit.Save" value="Save">@T("Save")</button>
<button class="primaryAction" type="submit" name="submit.Restore" value="Restore" itemprop="RemoveUrl">@T("Restore")</button>
</fieldset>
}
@using (Script.Foot()) {
<script type="text/javascript">
//<![CDATA[
(function ($) {
var startPos;
$('#placement').sortable({
start: function (event, ui) {
var self = $(ui.item);
startPos = self.prevAll().size();
},
stop: function (event, ui) {
var self = $(ui.item);
var stopPos = self.prevAll().size();
var begin = Math.min(startPos, stopPos);
var end = Math.max(startPos, stopPos);
// get siblings ( .siblings() doesn't include itself)
var siblings = self.parent().children();
for (var i = begin; i < end; i++) {
// get both positions
var a = $(siblings[i]).find('.position').val();
var b = $(siblings[i + 1]).find('.position').val();
// swap values
var temp = a;
$(siblings[i]).find('.position').val(b);
$(siblings[i + 1]).find('.position').val(temp);
}
// ensure all successive values are different
for (var i = 0; i < siblings.size() - 1; i++) {
// get both positions
a = $(siblings[i]).find('.position').val();
b = $(siblings[i + 1]).find('.position').val();
if (a == b) {
$(siblings[i + 1]).find('.position').val(a + '.5');
}
}
$('#save-message').show();
}
});
$("#placement").disableSelection();
})(jQuery);
//]]>
</script>
}

View File

@ -47,5 +47,9 @@ namespace Orchard.ContentManagement.Drivers {
result.Apply(context);
}
}
public IEnumerable<DriverResult> GetResults() {
return _results;
}
}
}

View File

@ -89,5 +89,21 @@ namespace Orchard.ContentManagement.Drivers {
_groupId=groupId;
return this;
}
public string GetDifferentiator() {
return _differentiator;
}
public string GetGroup() {
return _groupId;
}
public string GetLocation() {
return _defaultLocation;
}
public string GetShapeType() {
return _shapeType;
}
}
}

View File

@ -4,7 +4,7 @@ namespace Orchard.ContentManagement.Drivers {
public class DriverResult {
public virtual void Apply(BuildDisplayContext context) { }
public virtual void Apply(BuildEditorContext context) { }
public ContentPart ContentPart { get; set; }
public ContentField ContentField { get; set; }
}

View File

@ -16,15 +16,19 @@ namespace Orchard.DisplayManagement.Descriptors {
private readonly IExtensionManager _extensionManager;
private readonly ICacheManager _cacheManager;
private readonly IParallelCacheContext _parallelCacheContext;
private readonly IEnumerable<IShapeTableEventHandler> _shapeTableEventHandlers;
public DefaultShapeTableManager(
IEnumerable<Meta<IShapeTableProvider>> bindingStrategies,
IExtensionManager extensionManager,
ICacheManager cacheManager,
IParallelCacheContext parallelCacheContext) {
IParallelCacheContext parallelCacheContext,
IEnumerable<IShapeTableEventHandler> shapeTableEventHandlers
) {
_extensionManager = extensionManager;
_cacheManager = cacheManager;
_parallelCacheContext = parallelCacheContext;
_shapeTableEventHandlers = shapeTableEventHandlers;
_bindingStrategies = bindingStrategies;
Logger = NullLogger.Instance;
}
@ -72,6 +76,8 @@ namespace Orchard.DisplayManagement.Descriptors {
Bindings = descriptors.SelectMany(sd => sd.Bindings).ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.OrdinalIgnoreCase),
};
_shapeTableEventHandlers.Invoke(ctx => ctx.ShapeTableCreated(result), Logger);
Logger.Information("Done building shape table");
return result;
});

View File

@ -1,4 +1,6 @@
namespace Orchard.DisplayManagement.Descriptors {
using Orchard.Events;
namespace Orchard.DisplayManagement.Descriptors {
public interface IShapeTableManager : ISingletonDependency {
ShapeTable GetShapeTable(string themeName);
@ -7,4 +9,9 @@
public interface IShapeTableProvider : IDependency {
void Discover(ShapeTableBuilder builder);
}
public interface IShapeTableEventHandler : IEventHandler {
void ShapeTableCreated(ShapeTable shapeTable);
}
}

View File

@ -103,7 +103,7 @@ namespace Orchard.DisplayManagement.Descriptors {
descriptor.Placement = ctx => predicate(ctx) ? location : next(ctx);
});
}
public ShapeAlteration Build() {
return new ShapeAlteration(_shapeType, _feature, _configurations.ToArray());
}