InfoDesktop

5

March 8, 2010 by Alistair Deneys

Back in January of this year Jens Mikkelsen (fellow MVP) published a great article about publishing strategies in Sitecore. To support his recommendation about using a scheduled publish Jens showed a countdown timer displaying in the Sitecore taskbar. This Sitecore UI tweak really inspired me and got me to thinking about what other kinds of information could be displayed in the desktop and in which other ways.

So I came up with the idea of putting this kind of information directly on the Sitecore desktop as part of the background. And with a bigger area to display information the more opportunity I’d have for the kinds of information I could display. Rather than just displaying a single figure, I could display an entire list of items.

So let’s get started on hacking our Sitecore desktop. The desktop uses Sitecore’s Sheer UI framework. First things first, let’s work out which XML file is handling the desktop. We can figure this out by looking at the request URL for the desktop. After logging in have a look at the URL you’re redirected to.

http://sitecore62.localhost/sitecore/shell/default.aspx

This aspx file looks for a query string key of xmlcontrol to work out which control to load. If the query string is omitted then the default shell control will be loaded, which is the desktop itself, which is called DucatShell. Ever had a permission error on the ducateshell.cs file? 🙂 Yeah, that’s it. The Sheer UI engine will create a class for each control it finds in an XML file which it generates into a C# file in the appropriate debug folder. If you’ve just setup Sitecore manually from the zip file and haven’t set the file permissions yet, then you would have run into a permission error on that file.

You’ll find the DucatShell.xml file in the /sitecore/shell/applications folder. The root element of a Sheer XAML file is the name of the control the file defines. If you open the DucatShell file you’ll see it only contains a single control “shell”. We’ll find that file inside the same folder. This is the desktop itself.

Now to work out the best positioning of this new information. I wanted to place it in the upper right of the desktop so I inserted a new Border control below the “Desktop” border control and above the Startbar control.

<FormPage Submittable="false" TrackModified="false">
  <img ID="Wallpaper" src="/sitecore/shell/Themes/Backgrounds/working.jpg"
    alt="" border="0"/>

  <Border KeyDown="ShowStartMenu" KeyFilter="c91">
    <GridPanel Class="scFill scFixed">

      <Border ID="Desktop" GridPanel.Class="scFill"
        ContextMenu="ShowContextMenu">
        <Border ID="Links" DblClick="Launch"/>
      </Border>


      <Border Class="infoTiles" ID="InfoTiles"/>

      <Startbar GridPanel.Class="scStartbar"/>

    </GridPanel>
  </Border> 

</FormPage>

To be able to start coding against this new UI we’ll need to create our own class which inherits the existing Sitecore.Shell.Applications.ShellForm class used by the shell control. We’ll also have to update the XAML file to use our new CodeBeside class instead of the existing one. Locate the CodeBeside element in the XAML file and change the type attribute to your new type.

To support the ability to be able to extend the information displayed on the desktop we’ll define an interface so we can dynamically load types at request time and pull data from them.

namespace InfoDesktop.Tiles
{
  public interface IInfoDesktopTile
  {
    string GetData();
  }
}

Now to write our new shell form.

using System;
using System.Text;
using System.Web;
using InfoDesktop.Tiles;
using Sitecore;
using Sitecore.Reflection;
using Sitecore.Web.UI.HtmlControls;

namespace InfoDesktop
{
  public class ShellForm : Sitecore.Shell.Applications.ShellForm
  {
    protected Border InfoTiles;

    protected override void OnLoad(EventArgs e)
    {
      base.OnLoad(e);

      InfoTiles.InnerHtml = PopulateTiles();
    }

    protected string PopulateTiles()
    {
      var markup = new StringBuilder();
      var db = Sitecore.Context.Database;
      var tileRoot = db.GetItem("/sitecore/system/Modules/Info Desktop/Tiles");
      var tiles = tileRoot.GetChildren(); 

      for (int i = 0; i < tiles.Count; i++)
      {
        var tileType = Type.GetType(tiles[i]["type"], false);
        if (tileType != null)
        {
          var iInfoTile = tileType.GetInterface("IInfoDesktopTile");
          if (iInfoTile != null)
          {
            var ob = (IInfoDesktopTile)ReflectionUtil.CreateObject(tileType);
            markup.Append(string.Format("<div class=\"title\">{0}</div><div>{1}</div>"
              ,tiles[i]["title"], ob.GetData()));
          }
          else
            markup.Append("<div>Failed to find IInfoDesktopTile interface</div>");
        }
        else
          markup.Append("<div>Failed to load type: " + tiles[i]["type"] + "</div>");
      }

      return markup.ToString();
    }
  }
}

You’ll note from the PopulateTiles method above that we’re reading each of the information “tiles” from the Sitecore core database. (Being that this code will run inside the Sitecore shell the context database will be the core database.) Then all we do is load the type, grab the IInfoDesktopTile interface, grab the output of the GetData method and stuff it into the rendered output.

Now we’ll need to create some info tiles to display on the desktop. Here’s the code for a tile which shows the current jobs of the system and what their status is.

using System.IO;
using System.Web.UI;
using Sitecore.Jobs;

namespace InfoDesktop.Tiles
{
  public class Jobs : IInfoDesktopTile
  {
    public string GetData()
    {
      var jobs = JobManager.GetJobs();

      var tw = new StringWriter();
      var htw = new HtmlTextWriter(tw);

      htw.RenderBeginTag(HtmlTextWriterTag.Table);

      for (int i = 0; i < jobs.Length; i++)
      {
        htw.RenderBeginTag(HtmlTextWriterTag.Tr);
        htw.RenderBeginTag(HtmlTextWriterTag.Td);
        htw.Write(jobs[i].Name + ":");
        htw.RenderEndTag();
        htw.RenderBeginTag(HtmlTextWriterTag.Td);
        htw.Write(jobs[i].Status.State);
        htw.RenderEndTag();
        htw.RenderEndTag();
      }

      htw.RenderEndTag();
      htw.Flush();

      return tw.GetStringBuilder().ToString();
    }
  }
}

This is all good, but the information will currently only display when the desktop is loaded, which includes whenever you click the background. But it would be better to have a more predictable update interval such as every 30 seconds. To do this we’ll need to use ajax. I did originally try using methods I found on the scForm object (postEvent, postMessage) but they caused active popups such as the Sitecore menu to disappear when the event ran. So instead we’ll write our own ajax from scratch.

Inside the shell XAML file add another script tag with the following javascript content.

var refreshRate = 5000;
window.onload = function() {
  refresh();
};

function refresh(){
  try {
    request = new XMLHttpRequest();
  }
  catch (e) {
    try {
      request = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch (e) {
      try {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      }
      catch (e) {
        request = null;
      }
    }
  }

  var url = window.location.href;
  var op = (url.indexOf("?") >= 0 ? "&" : "?");
  url += op + "infodesk=refresh";

  request.open("GET", url, false);

  request.onreadystatechange = function(){
    if (request.readyState == 4)
    {
      var border = document.getElementById("InfoTiles");
      border.innerHTML = request.responseText;
      setTimeout(refresh,refreshRate);
    }
  };

  request.send("");
}

In the code above we request the current URL (that of the shell form) with a query string containing “infodesk=refresh”. We’ll detect this in the form class and handle the output a little differently from a normal request. Insert the following code into the OnLoad method.

if (Context.ClientPage.ClientQueryString.Contains("infodesk=refresh"))
  Refresh();

And now for the Refresh method.

protected void Refresh()
{
  HttpContext.Current.Response.Clear();
  HttpContext.Current.Response.Write(PopulateTiles());
  HttpContext.Current.Response.Flush();
  HttpContext.Current.Response.End();
  return;
}

And once we have it all together (fill out the tiles a little more and add some CSS), this is what we get.

infodesktop

The Publish Queue tile above was simply to show that you can output any HTML from your tile.

Again, thanks to Jens for the inspiration to write this Sitecore UI tweak 🙂 .

Advertisements

5 thoughts on “InfoDesktop

  1. Paul says:

    Sweet! Nice job! I’m thinking another nice tile might be a “recent errors” tile to show the 5 most recently logged errors.

  2. Matt Hovany says:

    I really like this idea.

  3. Alex Shyba says:

    Hi Alistair, as always, you show the pinnacle of creativity! Are you considering share-source it?

    • Alistair Deneys says:

      Thanks Alex,
      I’d love to put this into shared source. The framework is pretty basic, but the library of tiles the community could create could be huge. For example, Paul’s idea above about the most recent errors logged. And being that each tile is defined in the content tree we can apply Sitecore security to them to filter out developer tiles from author tiles for different users.

      But I’d better finish off some of my other projects (including a shared source project) first before I get moving on this one.

  4. Great post Alistair! I’m definitely going to try this out

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

The views expressed on this blog are solely my own and do not necessarily reflect the views of my employer.
%d bloggers like this: