Creating a Gallery Component - Score 2.1

Step 1 - Downloading FlexSlider 2

For our exercise, we'll use a pre-built component called FlexSlider.  FlexSlider is a carousel JS component that can also be used to build a gallery with thumbnail support.

The package for FlexSlider can be downloaded at http://flexslider.woothemes.com/ . Because Score 2.1 use .scss instead of .less use this version with changed .less to .scss.  Download the package and open / unzip the file.

Investigating the package, you will find some things that we'll use:

itemDescription
flexslider.scssA .scss file - we can add this in to compile into our main.css
css/all the pieces that flexslider.scss is composed of
jquery.flexslider-min.jsA JS file that uses jQuery to drive the component - we'll add this into our layout documents
images/images that are used by the component
fonts/fonts that are used by the component

Step 2 - Adding in the flexslider.scss

First, copy the flexslider.scss file into your solution scss folder.

Next, copy all *.scss files from the css folder into your scss/flexslider:

Next, let's modify the flexslider.scss to properly refer to its pieces in our solution:

@import
    "flexslider/variables",
    "flexslider/mixins",
    "flexslider/font",
    "flexslider/resets",
    "flexslider/base",
    "flexslider/theme",
    "flexslider/responsive";


Finally, include the flexslider.scss file into your solution by modifying the main.scss file and adding the following on the end of file:

// Added in flexslider
"flexslider";


Step 3 - Adding in the fonts and images

Next, we'll simply copy the fonts and images folders from the FlexSlider directly into our solution - into our area:

Notice that the fonts folder merged with our Bootstrap fonts provided in the scaffolding.

Fix the .scss File Paths

Next, in order to use the fonts, we have to make a small modification to the .scss files included with flexslider to tell it where the fonts can be found. Find the scss/flexslider/variables.scss

Find the $font-path variable and change it to be:

$font-path:"../fonts";

Step 4 - Adding in the JS file

FlexSlider includes some JS that powers the component - we'll simply add this into our solution.

First, copy of the jquery.flexslider-min.js file into our area vendor js folder

Modifying the require.config.js

Now we need to specify where flexslider JS is stored. One line needs to added to require.config.js  

window.require.paths.flexslider = "/Areas/MySolution/js/Vendor/jquery.flexslider-min";

Step 5 - Study the Markup

We willl utilize the option for the FlexSlider titled Slider w/thumbnail controlNav Pattern

there are 2 pieces that are needed to support this functionality - first the markup

When viewing this markup, we can divide this into 2 pieces.  A Gallery, and a Gallery Pane.

First, the the Gallery - which will contain one or more Gallery Panes

<div class="flexslider">
   <ul class="slides">
      <!-- Gallery Panes will go here -->
   </ul>
</div>

Each Gallery Pane will render with the following markup:

<li data-thumb="slide1-thumb.jpg">
   <img src="slide1.jpg" />
</li>

Step 6 - Create the Gallery Rendering

Here's the source code for our Gallery rendering:

@using Score.UI.CCF
@model RenderingModel

@if (!Sitecore.Context.PageMode.IsExperienceEditor)
{
    using (Html.BeginUXModule("Components/Gallery"))
    {
        <div class="flexslider">
            <ul class="slides">
                @Html.Sitecore().DynamicPlaceholder("Gallery")
            </ul>
        </div>
    }
}
else
{
    <div class="editor-flexslider">
        @Html.Sitecore().DynamicPlaceholder("Gallery")
    </div>
}

Html.BeginUXModule is an HTML helper that connects C# component code with corresponding JS module. Hew we use the simplest version, check documentation for overloads.

This helper works as follows:

  • Load "Components/Gallery" JS module using JS AMD pattern
  • Sends scope object to JS Module so component execution can be limited to DOM of current component


Notice the 2 rendering modes used - rendering a <ul> with a placeholder in the page editor might cause some complications. For now, we'll avoid that.

Also notice the use of the placeholder - @Html.Sitecore().DynamicPlaceholder("Gallery") - Dynamic placeholders must be used when the placeholder can appear on the page more than once. So in this case, if 2 galleries were placed on a single page, which could happen.


We will place this rendering in the solution folder

MySolution.Web/Areas/MySolution/Views/Shared/Content/Collections/_Gallery.cshtml

Or, you may download the file
(lightbulb) _Gallery.cshtml

Creating the Rendering Definition Item

Now create a rendering definition item to tell Sitecore about the rendering you have just created

(click the image for a full size)

Step 7 - Create the Gallery Panel Rendering

Next, we will create the rendering for the Gallery Pane.  Once again, we'll customize the rendering a bit for the page editor experience:

@using Sitecore.Data.Fields
@model RenderingModel
           
@if (Sitecore.Context.PageMode.IsExperienceEditor)
{
    <div class="editor-gallery-item">
        <div class="editor-gallery-item-image">
            @Html.Sitecore().Field("Gallery Pane Image", new { @maxWidth = 400, @maxHeight = 400})    
        </div>
        <div class="editor-gallery-item-thumbnail">
            @Html.Sitecore().Field("Gallery Pane Thumbnail", new { @maxWidth = 100, @maxHeight = 100 })            
        </div>
    </div>
}
else
{
    ImageField thumbnail = Model.Item.Fields["Gallery Pane Thumbnail"];
    string thumbnailUrl = thumbnail.GetMediaUrl();
    <li data-thumb="@thumbnailUrl">
        @Html.Sitecore().Field("Gallery Pane Image")
    </li>
}

We will place this rendering in the solution folder

MySolution.Web/Areas/MySolution/Views/Shared/Content/Collections/_GalleryPane.cshtml

Or, you may download the file

Create the Rendering Definition Item

This rendering definition item will require a datasource location and a datasource template to be specified.

We will use a template built-in to SCORE(TM) for this purpose - GalleryPane:


/sitecore/templates/ScoreUI/Datasource Templates/Gallery Pane

First, the rendering definition item:


Then the datasource location and datasource template:




Step 8 - Placeholder Settings

The next step is to introduce our component into the tenant site by altering and adding some new placeholder settings.

Adding the Gallery Component to the Page

This is 2 distinct steps - first, adding the Gallery component to the page.  We'll keep this simple for now.  Let's allow the user to add the gallery component to the nested content areas, and also to build a snippet using the gallery component.

We will simply add our rendering to the placeholder settings for these 2 keys -


Then for the snippet placeholder

Next, the Gallery Pane

The gallery pane introduces a new Key - which we need to limit the renderings to only the specific rendering that is allowed to "nest" within the gallery.

Since this is a new key, we have to create a new placeholder settings item.  We'll call the settings item the same name as the key we need.

By default, Sitecore will populate the Placeholder Key with the name of the placeholder settings item you create. Make sure to blank this value out. Remember that we bind the placeholder key to the settings item within the page templates to localize the key to each tenant.

Now, assign the key to the page types for MySolution

Open each of your page type data templates, select Standard Values for the template, and open the presentation details.  Click edit to add the new key assignment.



When adding, use the key value Gallery and select the placeholder settings item you created.



Step 9 - Add JS Module to your component

Finally, you need to add some small block of code provided by the FlexSlider to your Component JS file. This is outlined already by FlexSlider

That code is scoped to whole page. That will work for slider but we will try something different. We want our JS to be executed per component so you can have multiple Components of the same type on the page and make sure that every Component can find own events and corresponding DOM objects.

We already build out C# part to sent scope into JS module as .scope property. Now we just need to use it in our Module.

Create new \Areas\MySolution\js\Components\Gallery.js file and copy-paste this code into it:

define([
    "jquery",
    "flexslider"
], function ($) {

    function Flexslider(scope) {

        $('.flexslider', scope).flexslider({
            animation: "slide",
            controlNav: "thumbnails",
            smoothHeight: true
        });
    };

    return function initFlexslider(args) {
        return new Flexslider(args.scope);
    };
});

What we are doing here:

  • First parameter of define() function describes dependencies required by this Module
  • We are not using any variables from flexslider so it is not sent to our anonymous function, although it still should be listed as dependency to being loaded
  • jquery is sent to our code as $ function parameter. That is the way to isolated our code from global $ variable
  • initFlexslider() function deals with scope 


Check Require JS documentation if you are not familiar with JS modularization and AMD

Step 10 - View the Result

Now, you can build your solution and build a gallery on your page (smile)


OPTIONAL

Step 11 - Optional Adding Rendering Parameters and a Custom Model to the Gallery Pane

One of our criteria is to allow the user to select a class for the gallery pane.  We'll start by adding in two things - a new selections folder data template, and a new rendering parameter template.

Add a new Selection Folder Template

To hold the CSS class selections for the gallery pane, we will create a new selections folder. We will start with a new template. Within the /sitecore/Templates/MySite I will add a folder called "Base". Within Base, I will add a new Template that is derived from Folder.

Add standard values to the new template.

Add the insert option to allow a List Item to be inserted (and nothing else) into the folder.

Create the new Selection Folder and Add A Selection

Add a Rendering Parameters Template

We will start by adding a new template that inherits from Base Component Rendering Parameters in SCORE UI templates.

Once we have the parameters template created, we need to add a drop down list selection to allow the user to "select" a Gallery Pane Style selection (List Item) from the current site Selections folder. To do this, we will add a field called "Gallery Pane Style" - and for the source we will use a relative query.

The source field (which you cannot read from the screen capture) will be something like the following:

query:#selections#*[@@templateid='{INSERT-YOUR-TEMPLATE-ID-HERE-FOR-GALLERY-PANE-STYLES}']/*

You will need to insert the item ID of your new folder template you createdNote - the ID of the TEMPLATE (not the Template ID of the Template)...

Add a Custom Model and a Datasource Item class

To make it easy to process this rendering parameter, and to be able to easily use the basic show/hide parameters, we will create a model and a class to wrap the datasource item.

First, we create a class to wrap the Datasource Item and also protect the view from a missing datasource:

The MySolution.MySite.Data needs a reference to Sitecore.Kernel.dll. Add it as a file reference to the Lib/Sitecore/Sitecore.Kernel.dll in your solution

GalleryPaneItem.cs
using Score.Data.Extensions;
using Score.UI.Data;
using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
namespace MySolution.MySite.Data.DatasourceItems
{
    public class GalleryPaneItem : CustomItem 
    {
        public static readonly ID GalleryPaneTemplateId = new ID("{ENTER-YOUR-GALLERY-PANE-TEMPLATE-ID}");


        public const string GalleryPaneThumbnailFieldName = "Gallery Pane Thumbnail";
        public const string GalleryPaneImageFieldName = "Gallery Pane Image";


        public ImageField GalleryPaneThumbnailField { get; private set; }
        public ImageField GalleryPaneImageField { get; private set; }


        public GalleryPaneItem(Item innerItem) : base(innerItem)
        {
            GalleryPaneThumbnailField = innerItem.Fields[GalleryPaneThumbnailFieldName];
            GalleryPaneImageField = innerItem.Fields[GalleryPaneImageFieldName];
        }
        public static bool TryParse(Item item, out GalleryPaneItem parsedItem)
        {
            parsedItem = item == null || !item.IsDerived(GalleryPaneTemplateId)
                ? null
                : new GalleryPaneItem(item);
            return parsedItem != null;
        }
        public static implicit operator GalleryPaneItem(Item innerItem)
        {
            return innerItem != null ? new GalleryPaneItem(innerItem) : null;
        }
        public static implicit operator Item(GalleryPaneItem customItem)
        {
            return customItem != null ? customItem.InnerItem : null;
        }
    }
}


Next what we should make is class Rendering Parameters

GalleryPaneParameters.cs
using System.Web;
using Score.UI.Data.RenderingParameters;
using Score.Data.Extensions;
using Sitecore.Mvc.Presentation;

namespace MySolution.MySite.Data.RenderingParameters
{
    public class GalleryPaneParameters : BaseComponentParameters
    {
        public const string GalleryPaneStyleParameterName = "Gallery Pane Style";
        public IHtmlString GalleryPaneClass { get; private set; }
        public override void LoadRenderingParameters(Rendering rendering)
        {
            base.LoadRenderingParameters(rendering);
            this.GalleryPaneClass = new HtmlString(rendering.Parameters.GetUserFriendlyValue(GalleryPaneStyleParameterName));
        }
    }
}


Next, a Rendering Model

GalleryPaneRenderingModel.cs
using Sitecore.Data.Items;
using Sitecore.Mvc.Presentation;
using MySolution.MySite.Data.DatasourceItems;
using MySolution.MySite.Data.RenderingParameters;
using Score.UI.Web.Areas.ScoreUI.Models;

namespace MySolution.MySite.Web.Areas.MySolution.MySite.Models
{
    public class GalleryPaneRenderingModel : RenderingModelBase<GalleryPaneParameters, GalleryPaneItem>
    {
        protected override GalleryPaneItem InitializeDatasource(Item item)
        {
          return  new GalleryPaneItem(item);
        }
        public override void Initialize(Rendering rendering)
        {
            base.Initialize(rendering);
            this.Classes.Add(this.RenderingParameters.GalleryPaneClass);
        }
    }
}

Next - we add a Sitecore item to represent the model

Bind the Model and Rendering Parameters Template to to the Rendering Item

Update the Gallery Pane Razor View

_GalleryPane.cshtml
@using GalleryPaneItem = MySolution.MySite.Data.DatasourceItems.GalleryPaneItem
@model MySolution.MySite.Web.Areas.MySite.Models.GalleryPaneRenderingModel

@{
    GalleryPaneItem ds;
    var hasDatasource = GalleryPaneItem.TryParse(Model.Item, out ds);
}
@if (Sitecore.Context.PageMode.IsExperienceEditor)
{
    <div class="editor-gallery-item">
        <div class="editor-gallery-item-image">
            @Html.Sitecore().Field(GalleryPaneItem.GalleryPaneImageFieldName, new { @maxWidth = 400, @maxHeight = 400 })
        </div>
        <div class="editor-gallery-item-thumbnail">
            @Html.Sitecore().Field(GalleryPaneItem.GalleryPaneThumbnailFieldName, new { @maxWidth = 100, @maxHeight = 100 })
        </div>
    </div>
}
else if (hasDatasource)
{
    <li data-thumb="@ds.GalleryPaneThumbnailField.GetMediaUrl()" class="@Model.Classes">
        @Html.Sitecore().Field(GalleryPaneItem.GalleryPaneImageFieldName)
    </li>
}


Build and Test