codeflood logo

InfoDesktop

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 :) .

Comments

Paul

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

Matt Hovany

I really like this idea.

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

Great post Alistair! I'm definitely going to try this out

Alistair Deneys

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.

Leave a comment

All fields are required.