Introduction to Gadgets

Product version:

EPiServer CMS 6 CTP2

Document version:

1.0

Document last saved:

10/16/2014 11:19:01 AM

The dashboard is one of the new features of EPiServer 6 which is also a plug-in area open to partners. Usages include presenting high-level information or provide quick access to common tasks. This document is a tutorial to creating gadgets on the dashboard.

While this document highlights aspects related to gadget development with code examples keep these external resources handy for in depth information:

  • EPiServer Shell Extras, C# project: RSS ”best practice” gadget (install this from deployment center’s “Install Compressed Module”)
  • QuickChat project, Zipped C# project: Quick and dirty output from this tutorial
  • Dynamic DataStore SDK: Documentation and examples on the data store
  • Up-to-date info on ASP.NET MVC

Contents

Setup

Starting out with gadgets

Gadget Development, step 1: Showing Information

Gadget Development, step 2: Server Interaction

Gadget Development, step 3: Client side development

Gadget development, step 4: Styles and CSS

Checklist: Playing nice on the dashboard

Setup

Start by installing an EPiServer CMS 6 web site using the standard installer. Using the default options you will end up with a web site in c:\EPiServer\Sites\[MySiteName]. In the example below this site was installed in c:\EPiServer\Sites\ExampleEPiServerSite4. While it’s not required for normal operation this tutorial assumes you have installed ASP.NET MVC 1.0.

Develop gadgets in separate project

The module is created as a separate project below the “modules” folder of the CTP 2 EPiServer site.

And don’t forget unit tests, you’ll need them soon:

As you can see in the screen shot when enabling “Show all files” the quick chat module is placed below the development site.

Modifications to default

Some modifications are recommended to the default MVC application.

1.      Remove default controllers and views

Remove the default controllers and views (HomeController.cs, /Views/Home, etc.) according to the image below. You can always use another project to play with ASP.NET MVC.

2.      Clean up web.config

The web configuration is inherited from the public templates site but if you leave some settings in the module’s web.config you won’t kill intellisense. You can copy this file from the QuickChat and RSS gadget examples.

3.      Change output path

Open the QuickChat project's properties and change the default build output path of the quick chat project to “..\..\bin\”

4.      Add references

Add references to EPiServer.Shell and EPiServer.Data. You’ll need these later on. You can find a copy of these in the public template’s bin folder.

5.      Register module in web.config

Open to the public templates’ web.config file and find the episerver.shell configuration section. Add the quick chat module to the /configuration/episerver.shell/modules/ path. This is an excerpt of the added configuration:

<episerver.shell>
     <modules autoDiscovery="Minimal">
          <!-- other modules -->
               <add name="QuickChat">
                    <assemblies>
                         <add assembly="QuickChat"/>
                    </assemblies>
               </add>
          </modules>

 

Develop gadgets in public templates project

While creating gadgets in a separate project as described in the “Create gadgets in separate project” section often is a good approach it’s also possible create gadgets in the PublicTemplates.csproj project by doing some modifications to configuration and the csproj file.

1.      Configuration

The Public Templates needs to be registered in the configuration/episerver.shell/modules configuration section. The resourcePath attribute tells the system to look for views (which are described later in this document) in the folder /Views/[ControllerName]/[ActionName].ascx instead of the default location below the modules folder. Also update the add assembly section to any new name chosen for the public templates application.

<episerver.shell>
     <modules autoDiscovery="Minimal">
          <add name="Public" resourcePath="~/">
               <assemblies>
                    <add assembly="EPiServer.Templates.Public" />
               </assemblies>
          </add>

Add {603c0e0b-db56-11dc-be95-000d561079b0}; to the list of existing project type guids. Now save, right click “Reload Project”.

2.      References

Add references to “System.Web.Mvc”, “EPiServer.Shell” and “EPiServer.Data”.

3.      Create Folders

The ASP.NET MVC framework suggests you organize the code in below three folders: “Controllers”, “Models” and “Views”. Add these folders to your project.

4.      Visual Studio Integration

ASP.NET MVC includes some wizards to help out in the task of creating controllers and views. These are not enabled in the PublicTemplates.csproj installed with EPiServer CMS but they can be with some manual tweaking of the file. Right-click on the project and “Unload”, then “Edit …” it.

<Project>
     <PropertyGroup>
          <ProjectTypeGuids>{603c0e0b-db56-11dc-be95-000d561079b0};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

 

Starting out with gadgets

It’s time to create a gadget. Head to the QuickChat project, right click on the “Controllers” directory and “Add Controller”.

Add the gadget attribute to the controller template created for you. To reduce Time-to-dashboard we change the return statement to return html content directly (we’ll change this soon).

using System.Web.Mvc;
using EPiServer.Shell.Gadgets;

namespace QuickChat.Controllers
{
     [Gadget]
     public class QuickChatController : Controller
     {
          public ActionResult Index()
          {
               return Content("<strong>Some</strong>thing!");
          }
     }
}

Now compile and switch over to the dashboard. Click on the (+) symbol of QuickChat in “Add Gadgets…” to add this new gadget. Now that we’ve got “Something” out on the dashboard we can move on to more serious business.

A tiny bit of background

Dashboard gadgets are developed using the MVC model. From the point of view of processing a server request this model can be described like this:

Incoming Request » Controller Action » View » Outgoing Response

The “Controller Action” and “View” represents code that is developed to provide functionality. The arrows (à) represent pluggable points provided by the ASP.NET MVC framework.

In a typical ASP.NET MVC application the flow spans over a complete page request. However, in the case of our dashboard gadgets this is slightly different:

Incoming Request » Dashboard Controller Action » Dashboard View
     foreach (gadget on userDashboard)
     { 
          Partial Request » Controller Action » View 
     } » Outgoing Response

Dashboard gadgets uses the same principles as a typical MVC application but only renders a portion of the dashboard.

 

Gadget Development, step 1: Showing Information

With something out on the dashboard and a little background behind us we can let the coding begin.

Models

Add a class Message to the Models directory of the quick chat project.

using System;
namespace QuickChat.Models
{
     public class Message
     {
          public string Text { get; set; }
          public string From { get; set; }
          public DateTime Sent { get; set; }
     }
}
The model represents another pillar of the MVC architecture. This describes the data we interact with using the controller and show in our views.

Controller

Going back to the QuickChatController and use EPiServer.Data to retrieve messages from the database (take a look at the Dynamic DataStore SDK for in-depth information about the data store):

[Gadget]
public class QuickChatController : Controller
{
     // since the controller is created for each request
     // we can assign a store as a variable
     DynamicDataStore<Message> store =
     DynamicDataStore<Message>.CreateStore(false);

     public ActionResult Index()
     {
          // select all messages for the last 5 minutes
          var fiveMinutesAgo = DateTime.Now.AddMinutes(-5);
          var messages = from m in store
               where m.Sent > fiveMinutesAgo
               select m;

          // pass the messages to the view
          return View(messages.ToList());
     }
}

The code will select all recent messages and pass them to the “Index” view for display.

Views

Create an MVC partial view in the quick chat project.

The wizard can be conjured by right-clicking in the controller action and choosing “Add view…” (requires ASP.NET MVC 1.0 to be installed).

When the view is located in Views/QuickChat/Index.ascx in the quick chat project it will be automatically selected to show the messages.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IList<QuickChat.Models.Message>>" %>
<%@ Import Namespace="QuickChat.Models" %>
<div class="epi-defaultPadding">
     <ul>
     <% foreach (Message m in Model) { %>
          <li><%= m.From %>: <%= m.Text %></li>
     <% } %>
     </ul>
     Number of messages: <%= Model.Count %>
</div>

The view shows messages passed to it by the controller which is just about enough when adhering to the MVC pattern.

 

Gadget Development, step 2: Server Interaction

So far we have created a gadget that shows messages in the database but there is no way to put the messages in. That is precisely what is going to be fixed.

Posting messages to the controller

The most convenient option for posting information from the dashboard is using AJAX. The CTP includes an extension method that helps out doing this:

<%@ Import Namespace="EPiServer.Shell.Extensions" %>
<div class="epi-defaultPadding">...</div>
<hr />
<% Html.BeginGadgetForm("Save"); %>
     <input name="message" />
     <%= Html.AcceptButton() %>
<% Html.EndForm(); %>

The new improvement of the view renders a form with a text input field and a submit button. Since we used a “gadget form” the form will be serialized and posted using jQuery.ajax. The results of the “Save” action will replace any existing content in the gadget.

Taking care of posted messages in the controller

The controller accepts the posted message and stores it in the store. The view that is returned will take the place of the existing view in the gadget area. The mapping of the posted message string to input parameters is a feature of ASP.NET MVC. Refer to the blogosphere for documentation on form posting scenarios.

public ActionResult Save(string message)
{
     // create a new message and store it
     var newMessage = new Message();
     newMessage.From = User.Identity.Name;
     newMessage.Sent = DateTime.Now;
     newMessage.Text = message;

     store.Save(newMessage);

     // pass recent messages to the view
     var recentMessages = GetRecentMessages();
     return View("Index", recentMessages);
}

IList<Message> GetRecentMessages()
{
     // select all messages for the last 5 minutes
     var fiveMinutesAgo = DateTime.Now.AddMinutes(-5);
     var messages = from m in store
          where m.Sent > fiveMinutesAgo
          orderby m.Sent
          select m;

     return messages.ToList();
}

 

Adding options in the gadget menu

Dashboard gadgets support actions accessed through the gadget menu on each gadget. This example retrieved from the RSS gadget exemplifies how an action is hooked into the gadget menu:

/// <summary>
/// Returns the configuration view for the Gadget.
/// </summary>
/// <param name="gadgetId">The gadget id.</param>
[GadgetAction(ActionType.Menu, TextResourceKey = "Edit", ResourceType = typeof(EPiServer.Shell.UI.Views.Shared.SharedResources))]
public ActionResult Configure(Guid gadgetId)
{
     Settings rssSettings = LoadSettings(gadgetId);
     return View(rssSettings);
}

 

Testing

Most of the time it’s a good idea to unit test. Even the most trivial of tests can expose the nastiest of errors. These tests were made before the controller logic they test.

Test project setup

These are the steps for getting a test project up and running (you can copy paste from the example project):

  1. Create the project (if not already there)
  2. Modify app.config
         a. Add a connection string to an EPiServer CMS test database
         b. Add an episerver.dataStore section
  3. Add references to EPiServer.Shell and EPiServer.Data (enable the copy local on the references’ properties)
  4. Add test class and start writing a test

The test

The actual test ensure that the save action returns messages including the newly posted message and that the posted message is in the database.

[TestMethod]
public void Save_creates_message_in_store_and_shows_default_view()
{
     var viewResult = (ViewResultBase)controller.Save("Howdy!");
     
     var messages = (IList<Message>)viewResult.ViewData.Model;

     Assert.AreEqual("Index",
          viewResult.ViewName, "Should show default");
     Assert.AreEqual(1, messages.Count,
          "Didn't pass saved message");

     Message storedMessage = 
          store.Where(m => m.From == "Joe").ToList()[0];
     Assert.AreEqual("Howdy!", storedMessage.Text);
}

The test assumes some shared initialization has been performed:

DynamicDataStore<Message> store;
QuickChatController controller;

[TestInitialize]
public void TestInitialize()
{
     // creates or uses an existing store 
     // deleting all messages in the store
     store = DynamicDataStore<Message>.CreateStore(false);
     store.DeleteAll();

     controller = new QuickChatController();
     var user = new GenericPrincipal
          (new GenericIdentity("Joe"),
          new string[0]);
     var context = new FakeHttpContext {User = user};
     controller.ControllerContext = 
          new ControllerContext(context, new RouteData(), controller);
}
 

Multiple languages

When it comes to translating the UI screens of your gadgets you are free to use the technology you prefer. The EPiServer CMS lang model or ASP.NET local resources are two options.

For translating the gadget names you will need to instruct the dashboard on how to retrieve the localized text. This is done in the gadget attribute:

[Gadget(Name = "RSS Feed Reader", ClientScriptInitMethod = "epi.rssReader.init", ResourceType = typeof(RssResources), NameResourceKey = "Title")]

This will instruct the dashboard to look for a static property named Title on the RssResources class. One way to generate this property is using the resx designer in Visual Studio.

 

Gadget Development, step 3: Client side development

While server development is great it’s what you do on the client that makes your projects shine.

You may have noticed that the chat gadget at this point still isn’t very interactive. While you get updates when refreshing the page, or posting messages it’s just sits there in between.

Defining resources

First of all is getting out on the dashboard with all the other gadgets. The recommended way of doing this is using an attribute on the controller, like so:

[Gadget]
[EPiServer.Shell.Web.ScriptResource("Content/QuickChat.js")]
public class QuickChatController : Controller
{
}
This will declare a java script file located in the Content directory of the quick chat module which will be loaded with the dashboard. The script is loaded every time the dashboard is displayed, regardless of there being any quick chat gadgets or not, so takes care to put stable code in it.

While the script is executed each time the dashboard loads the way to associate with a gadget instance are client script init methods:

[Gadget(ClientScriptInitMethod = "quickchat.init")]
[EPiServer.Shell.Web.ScriptResource("Content/QuickChat.js")]
public class QuickChatController : Controller

 

Client scripts

The script resource attribute assumes a JavaScript method “quickchat.init” is present on the client. Let’s take a look on this method in the quickchat.js file:

(function($) {
     // using this pattern helps keeping your privates private
     quickchat = {};
     quickchat.init = function(e, gadgetContext) {
          setInterval(function() {
               // while this reloads the view on an interval
               // it might cause problems to people actually
               // wanting to chat since it will clear
               // the text field
               gadgetContext.instance.loadView("Index");
          }, 5000);
     };
})(epiJQuery);

The init method is invoked by the dashboard and starts updating the gadget every five seconds. While this represents an easy way to update the gadget regularly it’s not particularly suited for a chat since it will steal focus from the text box.

Other JavaScript options

A slightly longer alternative is to construct the Ajax request manually and update just the messages portion:

(function($) {
     quickchat = {};
     quickchat.init = function(e, gadgetContext) {
          // We will be using the gadget instance a lot
          var gadgetInstance = gadgetContext.instance;

          // Reloads the gadget if it is visible
          var reload = function() {
               if (isVisible()) {
                    // Using the gadet's ajax handler brings
                    // features such as the ajax loader 
                    // and error handling
                    var routeValues = { action: "Messages" }
                    var actionUrl = gadgetInstance.getActionPath(routeValues);
                    gadgetInstance.ajax({
                         url: actionUrl,
                         dataType: "html",
                         success: successHandler
                    });
               }
          };

          // Updates the messages div with new content retrieved
          // by the ajax request
          var successHandler = function(data) {
               var chatMessages = $(".messages", gadgetInstance.element);
               chatMessages.html(data);
          };

          // This is a way to detect whether 
          // the gadget is visible
          var isVisible = function() {
               return $(gadgetInstance.element).filter("*:visible").length > 0;
          }

          // start reloading the messages at an interval
          setInterval(reload, 5000);
     };
})(epiJQuery);

 

Gadget development, step 4: Styles and CSS

The CSS styles used on the Dashboard resets many of the default styling of elements added by the browser and in some cases adds an own default style. This styling may however not be enough (it’s rather none styled) so to the rescue comes some additional convenient classes.

  • epi-contentArea, set on a container element, any type, for content you want to be affected.
  • epi-formArea, set on a container element of any type, does not only apply to form element.

Registering styles on the dashboard

The best way to get your style sheets out on the dashboard is defining them for your gadget as in this snippet:

[Gadget(ClientScriptInitMethod = "quickchat.init")]
[EPiServer.Shell.Web.CssResource("Content/QuickChat.css")]
public class QuickChatController : Controller

epi-contentArea

At the moment the epi-contentArea class gives you a “Dashboard default” look on tables, lists and heading and link colors, but will probably affect more in the final release. An example:

<div>
     <h2>Two handy CSS classes</h2>
     <ul>
          <li>epi-contentArea</li>
          <li>epi-contentForm</li>
     </ul>
     <table>
          <caption>Table caption</caption>
          <thead>
               <tr>
                    <th scope="col">Heading One</th>
                    <th scope="col">Heading Two</th>
                    <th scope="col">Heading Three</th>
               </tr>
          </thead>
          <tbody>
               <tr>
                    <td>Data A</td>
                    <td>Lorem ipsum</td>
                    <td>Dolor sit amet</td>
               </tr>
               <tr>
                    <td>Data B</td>
                    <td>Lorem Ipsum</td>
                    <td>Dolor sit amet</td>
               </tr>
          </tbody>
     </table> 
</div>

The above code would look like this without the use of epi-contentArea class:

Adding the epi-contentArea class to the container div will change the appearance.

<div class="epi-contentArea">

 

In most cases epi-contentArea will suit fine, but in some scenarios you might want one table to look like above without affecting any other child elements. In such a case you could use epi-default or epi-simple (no borders or margins) classes directly on the table element like:

<table class="epi-default">

Forms and epi-formArea

Form elements like fieldset, legend, input, textarea, etc will by default have a very minimalistic styling.

<form action="#" onsubmit="return false;">
     <fieldset>
          <legend>Styling forms</legend>
          <p>The parent child relation of labels and inputs is discussed below.</p>
          <fieldset>
               <legend>Love CSS?</legend>
               <label>
                    <input type="radio" name="radio1" 
                         checked="checked" value="1" />Yes
               </label>
               <label>
                    <input type="radio" 
                         name="radio2" value="2" />No
               </label>
          </fieldset>
          <div>
               <label>
                    <span>Please explain why</span>
               <input type="text" value="" />
               </label>
          </div>
     </fieldset>
</form>

 The above code will show up as:

Setting the epi-formArea class on the form element gives this:

There are also some sizing classes that could be used individually on inputs, selects, labels or containers for labels.

  • epi-size3 (Not on labels or containers for labels)
  • epi-size10
  • epi-size15
  • epi-size20
  • epi-size25
  • epi-size30 (Not on labels or containers for labels)

Note: Does not work on select element in IE at the moment. They will still take width of content.

By setting an input element inside a label the label automatically will be associated with the input without having to set the “id” attribute on the input (and the “for” attribute on the label). This is perfectly valid HTML and we easily avoid having the same id twice in a page, which the use of two gadgets of the same kind in a page would result in.

The parent child relation also results in some nifty alignment possibilities of labels and form fields:

<form action="#" onsubmit="return false;" class="epi-formArea">
     <fieldset>
          <legend>Styling form</legend>
          <div class="epi-size10">
               <label>
                    <span>Label one</span>
                    <input class="epi-size3" type="text" />
               </label>
          </div>
          <div class="epi-size10">
               <label>
                    <span>Label two</span>
                    <input type="text" class="epi-size15" />
               </label>
          </div>
          <div class="epi-size10">
               <label>
                    <span>A really long label which will break row</span>
                    <select class="epi-size15">
                         <option>Option One</option>
                         <option>Option Two</option>
                    </select>
               </label>
          </div>
          <div class="epi-size10">
               <label>
                    <span>Label four</span>
                    <input type="text" class="epi-size30" />
               </label>
          </div>
     </fieldset>
</form>

You may have noticed that the QuickChat gadget uses the CSS class “epi-defaultPadding”. This particular class gives padding consistent with other gadgets on the dashboard.

EPiServer currently also promotes the following CSS classes for usage on the dashboard:

Paddings etc:

  • epi-defaultPadding
  • epi-defaultPaddingHorizontal
  • epi-defaultPaddingVertical
  • epi-smallPadding
  • epi-smallPaddingHorizontal
  • epi-smallPaddingVertical
  • epi-noDisplay
  • epi-noBorder
  • epi-noMargin
  • epi-buttonRow

Checklist: Playing nice on the dashboard

Making a gadget means sharing a common workspace with others. Take a moment to review this list before publishing your gadget to a wider audience:

  • “Namespace” your CSS classes
  • Namespace and encapsulate your JavaScript methods to avoid polluting global namespace
  • Don’t override other gadget’s styles
  • Don’t assume other gadgets will stay the same
  • Never assume there is only one gadget of your kind (affects element id:s)
  • Prefer documented CSS classes for a consistent look and feel over time
  • Avoid long-running operation (such as reports or RSS operations) from your default action

FURTHER INFORMATION

» Introducing Site Center 

» Introduction to Gadgets 

» EPiServer CMS Mirroring 2.0

» Monitoring in Mirroring 2.0

» Configuring Content Guides


 

NOTE

This CTP version has been built as an "Enterprise version" of EPiServer CMS. Some features will therefore only be available in Enterprise and not Professional.