Tales from the Evil Empire

Bertrand Le Roy's blog

News


Bertrand Le Roy

BoudinFatal's Gamercard

Tales from the Evil Empire - Blogged

Blogs I read

My other stuff

Archives

July 2011 - Posts

So you don't want to use placement.info?

(c) Bertrand Le Roy 2010In Orchard, the UI gets composed from many independent parts. We wrote a lot of code to handle that fact without friction. It is easy to add a new part or remove an existing one without breaking anything. One ingredient in this is the placement.info file.

The role of placement is to dispatch the shapes that represent content types to local zones and to specify in what order they should appear. it separates the templates and their orchestration, implementing some healthy separation of concerns.

It is quite powerful and flexible, but it represents a sort of inversion of control that can be quite puzzling to designers: we are used, in other systems, to the layout pulling the different "includes" that constitute it. With placement, the "includes" are getting pushed into zones according to placement. This is similar to master pages in spirit, a concept we walked away from for similar reasons.

There is always a little pause in the learning of Orchard when people have to discover placement. In this post, I'm going to show how to do layout more explicitly, without placement. Whether it is a good idea or not, the possibility is there and will be easier and more familiar to some.

On my personal blog, the summary of a post is rendered by the regular ~/Core/Contents/Views/Content.cshtml template:

<article class="content-item @contentTypeClassName">
    <header>
        @Display(Model.Header)
        @if (Model.Meta != null) {
        <div class="metadata">
            @Display(Model.Meta)
        </div>
        }
    </header>
    @Display(Model.Content)
    @if(Model.Footer != null) {
    <footer>
        @Display(Model.Footer)
    </footer>
    }
</article>

This is not doing much except defining Header, Meta, Content and Footer local zones where placement can inject the shapes for the various parts forming the content item. We want to do without placement so these zones are not going to help us.

Here is the placement for summaries:

<Placement>
    <Match DisplayType="Summary">
        <Place Parts_RoutableTitle_Summary="Header:5"/>
        <Place Parts_Common_Body_Summary="Content:5"/>
        <Place Parts_Tags_ShowTags="Footer:0" />
        <Place Parts_Common_Metadata_Summary="Footer:1"/>
        <Place Parts_Comments_Count="Footer:2" />
    </Match>
</Placement>

We can see here where each shape is supposed to go: title in the header; text in content; tags, date and comment count in the footer. We'll now reproduce the exact same markup, but without placement.

The first thing to do is to override the Content.cshtml template in our theme with a more specialized alternate, Content-BlogPost.Summary.cshtml. This alternate will be used when rendering a blog post in summary form.

As always when writing templates, Shape Tracing is our best friend.Shape tracing

On the left side of the shape tracing tool, we can see the parts that entered the composition of the content summary rendering, confirming what placement showed.The shapes

On the right side, the Model tab enables us to drill into the shape used to represent Content, which is what the "Model" will represent from our template.Getting the title

For example, we can see that the title can be rendered with @Model.Title. We can drill further into the content item to find the body:Getting to the body text

Similarly we can find the tags, creation date and comments. Some of those are less trivial to render than the title, and we'll have to copy and adapt from the existing templates for each of the shapes.

The following template will render the markup that we want:

@using Orchard.ContentManagement
@{
    if (Model.Title != null) {
        Layout.Title = Model.Title;
    }
    var bodyHtml = Model.ContentItem.BodyPart.Text;
    var more = bodyHtml.IndexOf("<!--more-->");
    if (more != -1) {
        bodyHtml = bodyHtml.Substring(0, more);
    }
    else {
        var firstP = bodyHtml.IndexOf("<p>");
        var firstSlashP = bodyHtml.IndexOf("</p>");
        if (firstP >=0 && firstSlashP > firstP) {
            bodyHtml = bodyHtml.Substring(firstP,
firstSlashP + 4 - firstP); } } var body = new HtmlString(bodyHtml); } <article class="content-item blog-post"> <header><h1>
<a href="@Model.Path">@Model.Title</a>
</h1></header> <p>@body</p> <p>@Html.ItemDisplayLink(T("Read more...").ToString(),
(ContentItem)Model.ContentItem)</p> <footer> @{ var tagsHtml = new List<IHtmlString>(); foreach(var t in Model.ContentItem.TagsPart.CurrentTags) { if (tagsHtml.Any()) { tagsHtml.Add(new HtmlString(", ")); } tagsHtml.Add(Html.ActionLink(
(string)t.TagName,
"Search",
"Home",
new {
area = "Orchard.Tags",
tagName = (string)t.TagName
},
new { })); } } @if (tagsHtml.Any()) { <p class="tags"> <span>@T("Tags:")</span> @foreach(var htmlString in tagsHtml) { @htmlString } </p> } <div class="published">
@Model.ContentItem.CommonPart.CreatedUtc.ToString(
"MMM dd yyyy hh:mm tt")
</div> <span class="commentcount">
@T.Plural("1 Comment", "{0} Comments",
(int)Model.ContentItem.CommentsPart.Comments.Count)
</span> </footer> </article>

The problem with this approach is that although we did without placement and still got the same rendering, there is a lot of repetition here, notably from part templates. This is inconvenient because if we start applying that sort of method everywhere, we are going to repeat ourselves a lot, which will eventually become a maintenance nightmare.

What we really want to do is to render the same shapes as before, and to let their templates do their job. These shapes do still exist but are not obvious to find.

As part of its job preparing the shape tree, the controller action that was responsible for handling the current request will have made a number of calls into ContentManager.BuildDisplay. Some of those calls were for the summaries of blog posts. BuildDispay calls into the drivers for each of the parts forming the content item. This is how the shapes for each part get created. It will then use placement to dispatch those shapes into zones. Those zones have not yet been rendered, and in our case never will, but the shapes are there, ready to be used. Shape tracing is not showing them, but they are under one Model.NameOfTheZone or another. Of course, we don't know what zone they are in, or at what index, so to find them we have to scan the model for zones and the zones for shapes.

I wrote a little helper to make that easier and added it to my theme's project file:

using System;
using System.Collections.Generic;
using System.Linq;
using ClaySharp;
using Orchard.DisplayManagement;

namespace Util {
    public static class ShapeHelper {
        public static dynamic Find(IShape model, string name) {
            var zones = new Dictionary<string, object>();
            ((IClayBehaviorProvider)model).Behavior
.GetMembers(_nullFunc, model, zones); foreach (var key in zones.Keys
.Where(key => !key.StartsWith("_"))) {
var zone = zones[key] as IShape; if (zone == null ||
zone.Metadata.Type != "ContentZone") continue; foreach (IShape shape in ((dynamic)zone).Items) { if (shape.Metadata.Type == name) return shape; } } return null; } private static readonly Func<object> _nullFunc = () => null; } }

This is using some Clay wizardry to enumerate zones and then the shapes within them to find one with the specified name. Now with this helper, we can replace most of the code in the template and get something fairly clean and easy to understand:

@using Util
<article class="content-item blog-post">
    <header><h1><a href="@Model.Path">@Model.Title</a></h1></header>
    @Display(ShapeHelper.Find(Model, "Parts_Common_Body_Summary"))
    <footer>
    @Display(ShapeHelper.Find(Model, "Parts_Tags_ShowTags"))
    @Display(ShapeHelper.Find(Model, "Parts_Common_Metadata_Summary"))
    @Display(ShapeHelper.Find(Model, "Parts_Comments_Count"))
    </footer>
</article>

And this is it, we now have a very explicit layout for our blog post summaries, we are getting the same markup without placement, and the code is still nicely factored and maintainable. Of course, if you go that route, you will have to modify your templates every time you add a new part or field instead of relying on placement, but then again that's what you wanted…

Future Orchard Part 3: Autoroute

The way URLs work in Orchard today is fine for the simplest sites but it's not very customizable and comes with a number of challenges. Let's look at how it works today. Let's start with a plain page:image

Once you're done typing a title, when you tab out of the title field, an ajax request is fired that asks the server to do a fake publication of the page and get a slug out of the title. This happens in the Routable part, and it is extensible by implementing ISlugEventHandler. There are a few problems with this.

First, the fake publication is creating some noise for subscribers to the publication event. It would be better unfortunately to do away with the nice ajax behavior.

Second, it becomes really hairy once you introduce containers, such as lists or blogs. Should the path of the contained object begin with the path of the container? Should the path recursively include the slugs of all parent? When the path of the parent changes, does that mean the paths of all children change?image

We had to come up with answers to all those questions and more, but the problem is that the answers we came up with are fixed and there will always be people who disagree and they would be right.

This is why we are revising the whole URL infrastructure to make it highly configurable, a feature that we call Autoroute. Mockups here are far from final by the way, so don't focus on UI or wording.

There will be a new setting on the Routable part type, so that you can go to the content type editor and choose which pattern to use for each routable content type:image

Once you've configured your content type to use a specific pattern, the content editor will have something like this:image

If you leave the checkbox checked and save, the pattern will get applied and you will get something like this:image

Now if you uncheck the checkbox, then the path becomes editable and you can set it to just about anything.

The set of patterns that you can use is configured on a separate settings screen. There, you can create named patterns using Tokens:image

Finally, there is a third screen where you can find the full set of all paths that were generated.image

This will enable you to recalculate paths in bulk. This is necessary because we made the choice this time around that once a pattern has been applied to generate the path for a content item, that path is fixed. If the path to the container changes for example, and the pattern you used depended on the container slug, then the contained items won't change unless you make the conscious decision to reapply the pattern.

Of course, this is all built on top of highly extensible APIs that can enable replacement of part or all of the UI and behavior above, but this is what the feature should look like to users out of the box.

Future Orchard Part 2: more Tokens

(c) Bertrand Le Roy 2011This is part 2 for this post

Before I show some more advanced features of Tokens, I should probably say a word about why exactly we think we need this feature. In a CMS, there are many places where you need to build strings from some static but configurable part and a number of values that come from the environment. In the first post in this series, I used the rather silly example of Mad Libs because I thought it was a fun and light-hearted way of explaining the technology. But obviously we are not building the feature to play silly word games, we are building it because we need it to build other cool stuff. Real applications include:

  • e-mail formatting: this does not require full templating but always involves inserting values from various sources (sender, comment URL and text, site name, etc.). Message text should be easy to change by the site administrator.
  • Content post-processing: one can imagine a lightweight post-processing phase on content item bodies, where tokens get dynamically replaced by their values. Having an extensible set of tokens enables modules to add their own stuff.
    UPDATE: I forgot to mention that, but the way we insert images into body from the media picker could use a token that specifies the path of the image in a way that better survives content deployment to another server.
  • URL patterns: this is the Autoroute feature that I will detail in the next post. The idea is that the site administrator can specify an arbitrary pattern of URLs for a content type, without having to touch the code for the affected content types. For example, if you like the style of URLs on this blog, you could specify a pattern such as "/{Content.Container.Owner}/Blog/{Content.Date.Year}/{Content.Date.Month}/{Content.Date.Day}/{Content.Slug}" from the admin and get post URLs like: "/bleroy/Blog/2011/7/26/more-tokens".
  • Many, many others, including your own.

In the URL example above, you may have noticed some deep tokens such as BlogPost.Date.Year. Those can be easily implemented using token chains:

context.For<IContent>("Content")
    .Token("Date",
        content => content.As<ICommonPart>().CreatedUtc.ToShortDateString())
    .Chain("Date", "DateTime",
        content => content.As<ICommonPart>().CreatedUtc)

The date token itself is defined for any content item. If the token chain stops there (Content.Date), the short date string for the creation time of the content item is displayed. If there is more behind it (Content.Date.Year), then the creation date object is going to get fed again into token providers as a DateTime token for further resolution. This could work like this:

context.For<DateTime>("DateTime", () => DateTime.Now)
    .Token("Year", d => d.Year)
    .Token("Month", d => d.Month)
    .Token("Day", d => d.Day)
    /// ... and more

We have effectively described how to resolve the year, month and day tokens, not just the sub-tokens of Content.Date. This means that if I have a comment modification date token for example, as long as it targets "DateTime", this will be re-used.

Also note the default date value of Now if no DateTime exists on the context. This would enable me to do "The current year is {Date.Year}."

But what if I don't know the full list of token names people could use? What if, for example, I want the sub-token after a date to be the format to use on the date object? Well, you don't have to use the syntactic sugar for static tokens that I've used so far, and instead go dynamic:

context.For("DateTime", () => DateTime.Now)
       .Token((token, v) => v.ToString(token));

This opens the door for pretty much any custom dynamic tokens you may dream of. We can now rewrite our original example of a URL pattern like so:

/{Content.Container.Owner}/Blog/{Content.Date.yyyy/MM/dd}/{Content.Slug}

You may be wondering what characters are legal in token names. How would you include a dot or curly braces? Well, just double them.

To conclude this post, I'd like to show one more non-trivial example of token implementation. Here is how we'll surface tokens for content item fields:

if (context.Target == "Content") {
  var forContent = context.For<IContent>("Content");
  foreach (var typePart in
forContent.Data.ContentItem.TypeDefinition.Parts) {
foreach (var partField in typePart.PartDefinition.Fields) { var tokenName = "Fields." + typePart.PartDefinition.Name
+ "." + partField.Name; forContent.Token( tokenName, content => LookupField(content,
typePart.PartDefinition.Name,
partField.Name)
.Storage.Get<string>()); forContent.Chain( tokenName, partField.FieldDefinition.Name, content => LookupField(content,
typePart.PartDefinition.Name,
partField.Name)); } } }

With this provider, you can access fields with tokens looking like {Content.Fields.MyPart.MyField}. If the field itself implements its own tokens, you could do crazy things like {Content.Fields.MyPart.MyField.Some.Crazy.Thing}.

That's it for tokens. Next post, I'll show the preliminary design of Autoroutes, which enables the specification of custom URL patterns such as the one I've used as an example in this post.

Future Orchard Part 1: Introducing Tokens

After a long phase of cleanup on the new Orchard 2.0, we are now busy designing new features. We are focusing on a few foundational pieces, and on enabling e-commerce on top of the platform. In this post, I'm going to expose the basics of the preliminary design for one new foundational piece: Tokens.

You could technically confuse Tokens with a fancy String.Format, or with a very lightweight templating solution, but you'd be slightly wrong in both cases. Its usage is what sets it apart from both. You will use tokens whenever you need to build a string by inserting named environmental variables into a string that has placeholders. That is it. No code, no loops, no ifs, just formatted substitution, but really the keyword here is environmental, and by the end of this post it should be clearer what we mean by that.

Let's start with trying to implement Mad Libs. If you have the following Mad Lib:

_____________! he said ________ as he
  exclamation            adverb

jumped into his convertible ______ and
                              noun

drove off with his ___________ wife.
                     adjective

Somebody could pick at random: "Praise the Flying Spaghetti Monster", "vigorously", "left nipple" and "smelly", and we would get the following weird sentence: "Praise the Flying Spaghetti Monster! he said vigorously as he jumped into his convertible left nipple and drove off with his smelly wife."

What's fun and creative about it is that the "format string" and the tokens that go into the placeholders come from different persons, and none of them know in advance what the other will write.

Similarly, Tokens in Orchard 2.0 can come from many sources, and the list is fully extensible. The way you use the API is by first injecting an ITokenizer dependency. Once you've done that, you can call Replace to start formatting strings:

_tokens.Replace(
  @"{MadLib.Exclamation}! he said {MadLib.Adverb} as he
    jumped into his convertible {MadLib.Noun} and
    drove off with his {MadLib.Adjective} wife.",
    null);

Without doing anything else, this will only yield a rather meaningless "! he said  as he jumped into his convertible  and drove off with his  wife." Being used to String.Format as you are, you may be a little frustrated by this and be tempted to directly inject the values for the tokens as an additional parameter to Replace. After all, that null does look a little suspicious. Well, if that is what you want, you should probably continue to use String.Format. But this is not what we're trying to do here. What we want is for whatever other player is available to provide us with values to inject. This is where the second part of the puzzle appears: token providers.

public class MadLibTokenProvider : ITokenProvider {
  public void Describe(DescribeContext context) {
    context.For("MadLib", T("Mad Lib"), T("Random words for Mad Libs."))
      .Token("Exclamation", T("Exclamation"), T("A random exclamation."))
      .Token("Adverb", T("Adverb"), T("A random adverb."))
      .Token("Noun", T("Noun"), T("A random noun."))
      .Token("Adjective", T("Adjective"), T("A random adjective."));
  }

  public void Evaluate(EvaluateContext context) {
    context.For<IMadLib>("MadLib", () => new MadLib())
      .Token("Exclamation", madLib => madLib.GetExclamation())
      .Token("Adverb", madLib => madLib.GetAdverb())
      .Token("Noun", madLib => madLib.GetNoun())
      .Token("Adjective", madLib => madLib.GetAdjective());
  }
}

public class MadLib : IMadLib {
  public string GetExclamation() {
    return PickOneOf(
        "Praise the Flying Spaghetti Monster",
        "Holy rusted metal, Batman",
        "!@#$%");
  }

  public string GetAdverb() {
    return PickOneOf(
        "vigorously",
        "cheerfully",
        "alarmingly");
  }

  public string GetNoun() {
    return PickOneOf(
        "left nipple",
        "rocket",
        "jacket");
  }

  public string GetAdjective() {
    return PickOneOf(
        "smelly",
        "pink",
        "absurd");
  }

  private string PickOneOf(params string[] strings) {
    return strings[(new Random().Next(strings.Length))];
  }
}

public interface IMadLib {
  string GetExclamation();
  string GetAdverb();
  string GetNoun();
  string GetAdjective();
}

The Describe part is pure metadata that will be used to provide users hints on what tokens can be used. The second part, Evaluate, is doing the actual evaluation of the tokens. It's accepting tokens of type "MadLib" and of names "Exclamation", "Adverb", "Noun" or "Adjective". The specific implementation should be fairly simple to understand as it's just picking at random in lists of strings.

Because we now have a token providers that knows how to handle all four of our Mad Lib tokens, and because our provider is picking them at random, we have recreated the fun of Mad Libs.

Decoupling the token values from the formatting site is more than a cosmetic improvement. Now our tokens are available everywhere. I can reuse the MadLibTokenProvider for other Mad Libs of course:

_tokens.Replace(
  @"My {MadLib.Noun} is {MadLib.Adverb} {MadLib.Adjective}.",
null);

Or I can reuse them to add some fun to an e-mail notification, although the notification module knows nothing of Mad Libs (note that the string wouldn't be hard-coded but rather come from configuration):

_tokens.Replace(
  @"{MadLib.Exclamation}, {Site.Admin.FullName},

A new {MadLib.Adjective} comment has been published on
{Site.Name} by {Comment.Author}: {Comment.Text} <a href="
"{Comment.AdminUrl}"">Click here to moderate</a>.", new {Comment: newComment});

In addition to this mix of tokens from various providers, something interesting and new is happening here. We have a number of tokens that are just environmental, such as Mad Libs, the site name, and the full name of the site administrator, but the comment object is provided as local context at the formatting site.

You may have noticed how in our Mad Lib token provider, we provided an expression that creates a new MadLib object when evaluating token values:

context.For<IMadLib>("MadLib", () => new MadLib())

This defines the default, or global, or environmental MadLib to use, but if you needed an IMadLib implementation that was choosing in a different list of words, for example words that actually make some kind of sense in this context, all you would have to do is override the default by just providing it in your context object:

_tokens.Replace("...my Mad Lib...",
  new {MadLib: mySpecialMadLibVocabulary});

This new special object would become the argument to the Lambda expressions for the token evaluation, and it will just work, without a change in the token provider.

In summary, token providers are exposing a vocabulary of tokens that can be used everywhere with the guarantee that any given token is always going to work, even though the values themselves can be global or contextual. The feature effectively gives users a predictable and stable vocabulary that they can use in dynamically populated strings.

Next time, I'll show some more subtle features of the new Tokens API, such as how to build deep graphs of tokens.

More Posts