Skip to content

Commit

Permalink
Add ability to generate DisplayText with a Pattern (#4464)
Browse files Browse the repository at this point in the history
  • Loading branch information
jptissot authored and sebastienros committed Oct 10, 2019
1 parent 2067a6e commit 85f2b58
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<div class="col-lg">
<label asp-for="Pattern">@T["Pattern"]</label>
<textarea asp-for="Pattern" rows="5" class="form-control"></textarea>
<span class="hint">@T["The pattern used to render the custom url of this content type."]</span>
<span class="hint">@T["The pattern used to render the custom url of this content type. With Liquid support."]</span>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using OrchardCore.ContentManagement.Display.ContentDisplay;
using OrchardCore.ContentManagement.Metadata;
using OrchardCore.DisplayManagement.ModelBinding;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Title.Models;
Expand All @@ -9,6 +12,11 @@ namespace OrchardCore.Title.Drivers
{
public class TitlePartDisplay : ContentPartDisplayDriver<TitlePart>
{
private readonly IContentDefinitionManager _contentDefinitionManager;
public TitlePartDisplay(IContentDefinitionManager contentDefinitionManager)
{
_contentDefinitionManager = contentDefinitionManager;
}
public override IDisplayResult Display(TitlePart titlePart)
{
return Initialize<TitlePartViewModel>("TitlePart", model =>
Expand All @@ -26,8 +34,7 @@ public override IDisplayResult Edit(TitlePart titlePart)
{
model.Title = titlePart.ContentItem.DisplayText;
model.TitlePart = titlePart;
return new ValueTask();
model.Settings = GetSettings(titlePart);
});
}

Expand All @@ -39,5 +46,14 @@ public override async Task<IDisplayResult> UpdateAsync(TitlePart model, IUpdateM

return Edit(model);
}


private TitlePartSettings GetSettings(TitlePart titlePart)
{
var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(titlePart.ContentItem.ContentType);
var contentTypePartDefinition = contentTypeDefinition.Parts.FirstOrDefault(x => string.Equals(x.PartDefinition.Name, nameof(TitlePart), StringComparison.Ordinal));
return contentTypePartDefinition?.GetSettings<TitlePartSettings>();
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Fluid;
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Handlers;
using OrchardCore.ContentManagement.Metadata;
using OrchardCore.Liquid;
using OrchardCore.Title.Models;

namespace OrchardCore.Title.Handlers
{
public class TitlePartHandler : ContentPartHandler<TitlePart>
{
private readonly ILiquidTemplateManager _liquidTemplateManager;
private readonly IContentDefinitionManager _contentDefinitionManager;

public TitlePartHandler(
ILiquidTemplateManager liquidTemplateManager,
IContentDefinitionManager contentDefinitionManager)
{
_liquidTemplateManager = liquidTemplateManager;
_contentDefinitionManager = contentDefinitionManager;
}


public override async Task UpdatedAsync(UpdateContentContext context, TitlePart part)
{
var settings = GetSettings(part);
// Do not compute the title if the user can modify it and the text is already set.
if (settings.Options == TitlePartOptions.Editable && !String.IsNullOrWhiteSpace(part.ContentItem.DisplayText))
{
return;
}

if (!String.IsNullOrEmpty(settings.Pattern))
{
var templateContext = new TemplateContext();
templateContext.SetValue("ContentItem", part.ContentItem);

var title = await _liquidTemplateManager.RenderAsync(settings.Pattern, NullEncoder.Default, templateContext);
title = title.Replace("\r", String.Empty).Replace("\n", String.Empty);

part.Title = title;
part.ContentItem.DisplayText = title;
part.Apply();
}
}

private TitlePartSettings GetSettings(TitlePart part)
{
var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(part.ContentItem.ContentType);
var contentTypePartDefinition = contentTypeDefinition.Parts.FirstOrDefault(x => String.Equals(x.PartDefinition.Name, nameof(TitlePart), StringComparison.Ordinal));
return contentTypePartDefinition.GetSettings<TitlePartSettings>();
}
}
}
2 changes: 0 additions & 2 deletions src/OrchardCore.Modules/OrchardCore.Title/Models/TitlePart.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using OrchardCore.ContentManagement;
using System.ComponentModel.DataAnnotations;

namespace OrchardCore.Title.Models
{
public class TitlePart : ContentPart
{
[Required]
public string Title { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.ComponentModel;

namespace OrchardCore.Title.Models
{
public enum TitlePartOptions
{
Editable,
GeneratedDisabled,
GeneratedHidden,
}
public class TitlePartSettings
{
/// <summary>
/// Gets or sets whether a user can define a custom title
/// </summary>
[DefaultValue(TitlePartOptions.Editable)]
public TitlePartOptions Options { get; set; } = TitlePartOptions.Editable;

/// <summary>
/// The pattern used to build the Title.
/// </summary>
public string Pattern { get; set; }
}
}
37 changes: 27 additions & 10 deletions src/OrchardCore.Modules/OrchardCore.Title/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
# Title (`OrchardCore.Title`)

The `Title` module provides a **Title Part** that lets user define a title for a content item.
It also defines the `DisplayText` property of the `ContentItemMetadata` aspect.
The `Title` module provides a **Title Part** that lets you customize the DisplayText property of a content item.
The DisplayText property is used throughout the Admin interface to help you recognize your content items.

## TitlePart

Attach this part to your content items to customize the DisplayText property of a ContentItem.

### TitlePart Settings

By default, attaching the TitlePart will allow content editors to manually edit the DisplayText(title) of a ContentItem.

You can also generate the Title by specifying a pattern using a Liquid expression.

The Pattern has access the current ContentItem and is executed on ContentItem update.
For example, fields can be used to generate the pattern. The following example uses a __Text field__ named `Name`, on a `Product` content type.

```liquid
{{ ContentItem.Content.Product.Name.Text }}
```

## Theming

The following shapes are rendered when the **Title Part** is attached to a content type.

| Name | Display Type | Default Location | Model Type |
| ------| ------------ |----------------- | ---------- |
| `TitlePart` | `Detail` | `Header:5` | `TitlePartViewModel` |
| `TitlePart` | `Summary` | `Header:10` | `TitlePartViewModel` |
| Name | Display Type | Default Location | Model Type |
| ----------- | ------------ | ---------------- | -------------------- |
| `TitlePart` | `Detail` | `Header:5` | `TitlePartViewModel` |
| `TitlePart` | `Summary` | `Header:10` | `TitlePartViewModel` |

### View Model

The following properties are available in the `TitlePartViewModel` class.

| Name | Type | Description |
| -----| ---- |------------ |
| `Title` | `string` | The title property of the part. |
| `TitlePart` | `TitlePart` | The `TitlePart` instance. |
| Name | Type | Description |
| ----------- | ----------- | ------------------------------- |
| `Title` | `string` | The title property of the part. |
| `TitlePart` | `TitlePart` | The `TitlePart` instance. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using OrchardCore.Title.Models;
using OrchardCore.Title.ViewModels;
using OrchardCore.ContentManagement.Metadata.Models;
using OrchardCore.ContentTypes.Editors;
using OrchardCore.DisplayManagement.ModelBinding;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Liquid;

namespace OrchardCore.Title.Settings
{
public class TitlePartSettingsDisplayDriver : ContentTypePartDefinitionDisplayDriver
{
private readonly ILiquidTemplateManager _templateManager;

public TitlePartSettingsDisplayDriver(ILiquidTemplateManager templateManager, IStringLocalizer<TitlePartSettingsDisplayDriver> localizer)
{
_templateManager = templateManager;
T = localizer;
}

public IStringLocalizer T { get; private set; }

public override IDisplayResult Edit(ContentTypePartDefinition contentTypePartDefinition, IUpdateModel updater)
{
if (!String.Equals(nameof(TitlePart), contentTypePartDefinition.PartDefinition.Name, StringComparison.Ordinal))
{
return null;
}

return Initialize<TitlePartSettingsViewModel>("TitlePartSettings_Edit", model =>
{
var settings = contentTypePartDefinition.GetSettings<TitlePartSettings>();
model.Options = settings.Options;
model.Pattern = settings.Pattern;
model.TitlePartSettings = settings;
}).Location("Content");
}

public override async Task<IDisplayResult> UpdateAsync(ContentTypePartDefinition contentTypePartDefinition, UpdateTypePartEditorContext context)
{
if (!String.Equals(nameof(TitlePart), contentTypePartDefinition.PartDefinition.Name, StringComparison.Ordinal))
{
return null;
}

var model = new TitlePartSettingsViewModel();

await context.Updater.TryUpdateModelAsync(model, Prefix,
m => m.Pattern,
m => m.Options);

if (!string.IsNullOrEmpty(model.Pattern) && !_templateManager.Validate(model.Pattern, out var errors))
{
context.Updater.ModelState.AddModelError(nameof(model.Pattern), T["Pattern doesn't contain a valid Liquid expression. Details: {0}", string.Join(" ", errors)]);
}
else
{
context.Builder.WithSettings(new TitlePartSettings {Pattern = model.Pattern, Options = model.Options,});
}

return Edit(contentTypePartDefinition, context.Updater);
}
}
}
7 changes: 7 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Title/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Display.ContentDisplay;
using OrchardCore.ContentManagement.Handlers;
using OrchardCore.ContentTypes.Editors;
using OrchardCore.Data.Migration;
using OrchardCore.Indexing;
using OrchardCore.Modules;
using OrchardCore.Title.Drivers;
using OrchardCore.Title.Handlers;
using OrchardCore.Title.Indexing;
using OrchardCore.Title.Models;
using OrchardCore.Title.Settings;
using OrchardCore.Title.ViewModels;

namespace OrchardCore.Title
Expand All @@ -17,6 +21,7 @@ public class Startup : StartupBase
static Startup()
{
TemplateContext.GlobalMemberAccessStrategy.Register<TitlePartViewModel>();
TemplateContext.GlobalMemberAccessStrategy.Register<TitlePartSettingsViewModel>();
}

public override void ConfigureServices(IServiceCollection services)
Expand All @@ -25,6 +30,8 @@ public override void ConfigureServices(IServiceCollection services)
services.AddScoped<IContentPartDisplayDriver, TitlePartDisplay>();
services.AddContentPart<TitlePart>();
services.AddScoped<IContentPartIndexHandler, TitlePartIndexHandler>();
services.AddScoped<IContentPartHandler, TitlePartHandler>();
services.AddScoped<IContentTypePartDefinitionDisplayDriver, TitlePartSettingsDisplayDriver>();

services.AddScoped<IDataMigration, Migrations>();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using OrchardCore.Title.Models;

namespace OrchardCore.Title.ViewModels
{
public class TitlePartSettingsViewModel
{
public TitlePartOptions Options { get; set; }
public string Pattern { get; set; }

[BindNever]
public TitlePartSettings TitlePartSettings { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using OrchardCore.Title.Models;

namespace OrchardCore.Title.ViewModels
Expand All @@ -9,5 +9,7 @@ public class TitlePartViewModel

[BindNever]
public TitlePart TitlePart { get; set; }
[BindNever]
public TitlePartSettings Settings { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
@model TitlePartViewModel
@using OrchardCore.ContentLocalization
@using OrchardCore.Localization
@using OrchardCore.Title.Models
@using OrchardCore.Title.ViewModels;

@{
var culture = await Orchard.GetContentCultureAsync(Model.TitlePart.ContentItem);
}

<fieldset class="form-group" asp-validation-class-for="Title">
<label asp-for="Title">@T["Title"]</label>
<input asp-for="Title" class="form-control content-preview-text content-caption-text" autofocus="autofocus" dir="@culture.GetLanguageDirection()" />
<span asp-validation-for="Title"></span>
<span class="hint">@T["The title of the content item."]</span>
</fieldset>
@if (Model.Settings?.Options != TitlePartOptions.GeneratedHidden)
{
<fieldset class="form-group" asp-validation-class-for="Title">
<label asp-for="Title">@T["Title"]</label>
<input asp-for="Title" class="form-control content-preview-text content-caption-text" disabled="@(Model.Settings?.Options == TitlePartOptions.GeneratedDisabled)" autofocus="autofocus" dir="@culture.GetLanguageDirection()"/>
<span asp-validation-for="Title"></span>
@if (String.IsNullOrWhiteSpace(Model.Settings?.Pattern) && Model.Settings?.Options == TitlePartOptions.Editable)
{
<span class="hint">@T["The title of the content item. Set empty to generate it using the pattern."]</span>
}
else
{
<span class="hint">@T["The title of the content item. It will be automatically generated."]</span>
}
</fieldset>
}

0 comments on commit 85f2b58

Please sign in to comment.