Automating Pivot Trade Card Creation

UPDATE: since I can’t figure out how to attach the template file discussed here, I’ve uploaded it to my bitbucket samples repository – you can see the code here.

Sorry for the delay in this part in my series documenting how I created the MSDN Magazine Pivot collection.  Between the holidays and a bunch of other stuff that’s been happening at work (more on that later), I let this slip through the cracks.  Anyways, my gut feel is that for those of you who are interested in creating data-driven Pivot collections, this is likely the installment that you’ll be most interested in.

In Pivot, the trade card is the name given to a single visual unit of data.  For example, in the MSDN Magazine collection, a single article is represented by a single trade card as shown below.

single-trade-card

Everything you see here with the one exception being the 2 action buttons in the upper right hand corner is part of the trade card.  There’s additional metadata that drives the filter and info panes, but that stuff is defined in your cxml which we covered in the previous post.  The 2 action buttons are going to be defined in your Silverlight xap – we’ll cover that in a future post.

So when you take a look at all of the different elements on the trade card, you should see that these are simply the data elements that you’ve defined in your cxml (or they are visual elements whose display is conditional on those data elements – the example here being the code icon in the upper right).  The would make this a simple data binding problem, right?  Well, perhaps in some future version, but for now, that’s not quite how things work.

Pivot viewer leverages Deep Zoom image compositions to do its magic.  This means that all of those various data elements and imagery spread across the trade card are actually a single bitmap which is fed into the Deep Zoom composer to produce a whole bunch of additional bitmaps for different zoom levels.

When Pivot was first available for experimentation, you basically had a couple choices for creating collections.  There was an Excel based tool which was capable of generating both cxml and Deep Zoom collections from your data.  However, the tool did not give you the ability to do anything sophisticated with the trade card image (rather, there was a single column for ‘image’).  For data-driven collections such as the MSDN Magazine collection, the alternative was to figure out how to create the image you wanted for your trade card and then feed the set of trade cards into the Deep Zoom composer software to generate all of the different images for all of the different zoom levels.

For a while, the solution du jour for creating the trade card images was to use a technology like Windows Forms or WPF (where you had a data binding infrastructure), iterating through the source data, binding the data to the form, and then saving a snapshot of the form as the trade card image.  That set could then be fed into the Deep Zoom composer and the outputted composition (dzc file) could be handed to the Pivot Viewer control (along with the cxml) which could then display the collection.

Fortunately, not too long after the Pivot viewer was made available, the Pivot team released a command line utility called pauthor.exe.  This tool allowed you to (among other things) feed in a cxml file and an HTML template file.  It would then follow the same basic steps I outlined above:

  1. Bind a cxml item to the HTML template
  2. Take a snapshot of the bound template
  3. Call the Deep Zoom composition API for the set of all captured snapshots

This was a hugely helpful tool for a couple of reasons.  Firstly, it was less code that I had to maintain and evolve.  Secondly, pauthor had built-in multi-threading and parallelization, which made it run a great deal faster than performing these steps manually.  And while I could have evolved my custom image generator to have the same great capabilities, I’ll fall back to the first point and say that this was code I didn’t have to write.

So, if you recall the SSIS package that I was using to stitch everything together, you’ll see that the last control flow task is to shell out to pauthor using the following command:

pauthor.exe /source cxml “Working/msdnmagazine.cxml” /target deepzoom “Output/msdnmagazine.cxml” /html-template “MSDNMagazineCollectionTemplate2.htm”

The Template

So you’ve seen how the cxml gets generated, you’ve seen how it is used to create the Deep Zoom image compositions – the only thing you haven’t seen is the template that is used to create the individual trade cards, so here it is.

 
<!-- size: 1200, 1200 --> 
<html> 
<head>
   <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
   <script type="text/javascript">
      var popGroup = "{Popularity}".toLowerCase();

      switch (popGroup) {
         case "viewed often":
            popGroup = "top";
            break;
         case "viewed occasionally":
            popGroup = "middle";
            break;
         default:
            popGroup = "bottom";
      }

      function GetNthPosition(str, c, occurrence) {
         var lastoccurrence = 0;
         for (var i = 0; i < occurrence; i++) {
            var t = str.indexOf(c, lastoccurrence + 1);
            if (t == lastoccurrence)
               return lastoccurrence;
            else if (t == -1)
               return -1;
            lastoccurrence = t;
         }
         return lastoccurrence;
      }
   </script>
</head>
<body>
   <div id="tradeCard">
      <div id="top">
         <div id="codeIndicator">
            <script type="text/javascript">
                    var hasCode = "{Has Code}";

                    if (hasCode == "Has Code") {
                        if (popGroup == "bottom")
                            document.write('<img alt="" src="Images/hasCode_black.png" />');
                        else
                            document.write('<img alt="" src="Images/hasCode_white.png" />');
                    }
                </script>
             </div>
             <div id="headline">
                 <span id="subhead">{Subhead}</span>: <br />
                 <span id="title">
                     <script type="text/javascript">
                        var titleStr = "{Title}";
                        if (titleStr == "(no data)")
                            document.write("");
                        else
                            document.write(titleStr);
                    </script>
                 </span>
             </div>
             <div id="authors">{Authors}</div>
         </div>
         <div id="bottom">
             <div id="description">
                 <script type="text/javascript">
                    var desc = "{description}";

                    if (desc) {
                        var fifthSpacePos = GetNthPosition(desc, ' ', 5);

                        document.write('<span id="lead">' + desc.substring(0, fifthSpacePos) + ' </span>');

                        var detailStr = desc.substring(fifthSpacePos + 1, desc.length);
                        if (detailStr.length > 400) {
                            detailStr = detailStr.substring(0, detailStr.indexOf(" ", 400)) + "...";
                        }

                        document.write('<span id="detail">' + detailStr + '</span>');
                    }

                </script>
             </div>
             <div id="popularity">
                 <div id="popularityGroup">{Popularity}</div>
                 <div id="pageViewsSnapshotDates">{Page Views} Page Views in November</div>
             </div>
             <div id="issue"><span class="verticalRule">&nbsp;</span><span>{Issue}</span></div>
             <div id="topics">
                 <script type="text/javascript">
                    var allTopicsStr = "{Topic:join: | }";

                    if (allTopicsStr.indexOf("Topic:join:", 0) > -1 || "{Issue}".length > 20)
                        document.write("");
                    else {
                        var secondPipePos = GetNthPosition(allTopicsStr, " | ", 2);
                        if (secondPipePos > -1)
                            allTopicsStr = allTopicsStr.substring(0, secondPipePos);

                        if (allTopicsStr.length > 33) //second pass to ensure still not too long
                            allTopicsStr = allTopicsStr.substring(0, allTopicsStr.lastIndexOf(" | "));

                        document.write(allTopicsStr);
                    }
                </script>
             </div>
         </div>
     </div>
      <script type="text/javascript">
        document.getElementById("tradeCard").className = popGroup;
    </script>
 </body>
 </html>

A couple quick notes on this code – and some stuff to be aware of should you go down this path as well.

  • Make sure you include that size comment at the top of the file – this is needed by pauthor.exe so that it knows the appropriate region of which to take a snapshot post data binding.
  • As you’ve probably already noticed, the data binding syntax is simply the name of the facet enclosed in curly braces.
  • I’ve commented out the CSS that defines all the layout and background images – I’ve done this only for space.  I’ll attach the HTML template file to this post for a more thorough view.
  • While unobtrusive JavaScript is the right way to build Web pages, it’s not always the right way to build pauthor templates.  This is because as pauthor iterates, it only allows for a certain amount of time for the data binding operation.  If the operation goes past this time quantum, the item is skipped by pauthor.  Initially, my template did all of its work in an unobtrusive manner – and the result was a bunch of blank squares in the generated collection.  As such, you can see that I’m doing a bunch of inline logic and document.write code.
  • Testing is a bit of a pain, but it’s doable (and should be done).  Download the whole template file to see some of the test code that I have in there.

I hope this gives you a good sense of how the trade cards were created for the MSDN Magazine Pivot collection.  I’ll follow up with the Silverlight bits and then the Azure bits.  Hopefully, I’ll let less time go by this time.

About Howard Dierking

I like technology...a lot...
This entry was posted in MSDN Magazine, Pivot. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • John Tomaselli

    Thanks so much for this article, it’s truly amazing and a hugh time saver.