Orchard CMS: building a slider with Projection

We will be using the Projector module to create a slider/carousel widget, without using third party modules. We love Bootstrap, so we will be using its responsive carousel implementation.

First of all, I hope that you are working with Orchard using WebMatrix. Start by creating a free website on Windows Azure, picking Orchard from the gallery. After the initial setup, you’ll be running a latest Orchard website on Azure with zero cost. When you are ready with your Orchard Azure deployment, go back to your azure dashboard. Having selected your brand new website click on the webmatrix icon (only works on IE). A couple of clicks later you ‘ll have a local deployment of your orchard website.

On to the slider. Our carousel will slide “testimonials” around. A testimonial consists of a title, a body and a Testimonial Name. We will use the Projector module, now part of Orchard core. We will create a query to filter for testimonials, and then present this data using our own custom Layout.

The query

Go to queries, add new query. Define you filter as “Content with type Testimonial”, and you sort criteria as “Random order”.

The layout

The Projector core layouts, namely Grid and list, don’t suit our case. We need to construct our HTML the way the carousel javascript expects it, which is something like this. The js initialization code should also follow the html. Let’s create our own carousel layout: start by opening the .csproject file located in Modules\Orchard.Projections with Visual Studio. Go to Providers/Layouts and add a new file named CarouselLayout.cs with the following code.

using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.DisplayManagement;
using Orchard.Localization;
using Orchard.Projections.Descriptors.Layout;
using Orchard.Projections.Models;
using Orchard.Projections.Services;

namespace Orchard.Projections.Providers.Layouts
{
    public class CarouselLayout : ILayoutProvider
    {
        private readonly IContentManager _contentManager;
        protected dynamic Shape { get; set; }

        public CarouselLayout(IShapeFactory shapeFactory, IContentManager contentManager) {
            _contentManager = contentManager;
            Shape = shapeFactory;
            T = NullLocalizer.Instance;
        }

        public Localizer T { get; set; }

        public void Describe(DescribeLayoutContext describe) {
            describe.For("Html", T("Html"), T("Html Layouts"))
                .Element("Carousel", T("Carousel"), T("Organizes content items in a carousel."),
                    DisplayLayout,
                    RenderLayout,
                    "CarouselLayout"
                );
        }

        public LocalizedString DisplayLayout(LayoutContext context) {
            return T("{0} carousel layout");
        }

        public dynamic RenderLayout(LayoutContext context, IEnumerable layoutComponentResults) {

            string outerDivClass = context.State.OuterDivClass;
            string outerDivId = context.State.OuterDivId;
            string innerDivClass = context.State.InnerDivClass;
            string firstItemClass = context.State.FirstItemClass;
            string itemClass = context.State.ItemClass;

            IEnumerable shapes =
               context.LayoutRecord.Display == (int)LayoutRecord.Displays.Content
                   ? layoutComponentResults.Select(x => _contentManager.BuildDisplay(x.ContentItem, context.LayoutRecord.DisplayType))
                   : layoutComponentResults.Select(x => x.Properties);

            return Shape.Carousel(Id: outerDivId, Items: shapes, OuterClasses: new[] { outerDivClass },
                    InnerClasses: new[] { innerDivClass }, FirstItemClasses: new[] { firstItemClass }, ItemClasses: new[] { itemClass });
        }
    }
}

Add a file named CarouselLayoutForm.cs.

using System;
using Orchard.DisplayManagement;
using Orchard.Forms.Services;
using Orchard.Localization;

namespace Orchard.Projections.Providers.Layouts
{

    public class CarouselLayoutForms : IFormProvider
    {
        protected dynamic Shape { get; set; }
        public Localizer T { get; set; }

        public CarouselLayoutForms(
            IShapeFactory shapeFactory) {
            Shape = shapeFactory;
            T = NullLocalizer.Instance;
        }

        public void Describe(DescribeContext context) {
            Func form =
                shape => {

                    var f = Shape.Form(
                        Id: "CarouselLayout",
                        _HtmlProperties: Shape.Fieldset(
                            Title: T("Html properties"),
                            _ListId: Shape.TextBox(
                                Id: "outer-div-id", Name: "OuterDivId",
                                Title: T("Outer div id"),
                                Description: T("The id to provide on the div element."),
                                Classes: new[] { "textMedium", "tokenized" }
                                ),
                            _ListClass: Shape.TextBox(
                                Id: "outer-div-class", Name: "OuterDivClass",
                                Title: T("Outer div class"),
                                Description: T("The class to provide on the div element."),
                                Classes: new[] { "textMedium", "tokenized" }
                                ),
                            _InnerClass: Shape.TextBox(
                                Id: "inner-div-class", Name: "InnerDivClass",
                                Title: T("Inner div class"),
                                Description: T("The class to provide on the inner div element."),
                                Classes: new[] { "textMedium", "tokenized" }
                                ),
                             _FirstItemClass: Shape.TextBox(
                                Id: "first-item-class", Name: "FirstItemClass",
                                Title: T("First item class"),
                                Description: T("The class to provide on the first item element."),
                                Classes: new[] { "textMedium", "tokenized" }
                                ),
                            _ItemClass: Shape.TextBox(
                                Id: "item-class", Name: "ItemClass",
                                Title: T("Item class"),
                                Description: T("The class to provide on the item element."),
                                Classes: new[] { "textMedium", "tokenized" }
                                )
                            )
                        );

                    return f;
                };

            context.Form("CarouselLayout", form);

        }
    }
}

Finally edit LayoutShapes.cs in order to add the new Carousel shape.

[Shape]
public void Carousel(dynamic Display, TextWriter Output, HtmlHelper Html, string Id, IEnumerable Items,
                    IEnumerable OuterClasses, IDictionary OuterAttributes,
                    IEnumerable InnerClasses, IDictionary InnerAttributes,
                    IEnumerable FirstItemClasses, IDictionary FirstItemAttributes, IEnumerable ItemClasses, IDictionary ItemAttributes) {
    if (Items == null) return;

    var items = Items.ToList();
    var itemsCount = items.Count;

    if (itemsCount < 1) return;

    var outerDivTag = GetTagBuilder("div", Id, OuterClasses, OuterAttributes);
    var innerDivTag = GetTagBuilder("div", string.Empty, InnerClasses, InnerAttributes);
    var firstItemTag = GetTagBuilder("div", string.Empty, FirstItemClasses, FirstItemAttributes);
    var itemTag = GetTagBuilder("div", string.Empty, ItemClasses, ItemAttributes);

    Output.Write(outerDivTag.ToString(TagRenderMode.StartTag));
    Output.Write(innerDivTag.ToString(TagRenderMode.StartTag));

    int i = 0;

    foreach (var item in items) {
        if (i == 0)
            Output.Write(firstItemTag.ToString(TagRenderMode.StartTag));
        else
            Output.Write(itemTag.ToString(TagRenderMode.StartTag));

        Output.Write(Display(item));
        Output.Write(itemTag.ToString(TagRenderMode.EndTag));
        i++;
    }

    Output.Write(innerDivTag.ToString(TagRenderMode.EndTag));

    Output.Write("", Id);

    Output.Write(outerDivTag.ToString(TagRenderMode.EndTag));

    Output.Write("");

}

Fix dependencies for the Projector project and build the source code. Restart your website. You should now be a proud owner of a Carousel layout. You can use the Carousel form ui to set values for slide css classes.

Any advice on pushing this code as a separate module instead of changes to Projector is welcome.

Attachment :