SharePoint 2010 Default Theme Colors

Applies To: SharePoint

I’ve been messing with Branding quite a bit lately and have been trying to find a way to keep it as simple as possible. The easiest choice is to use themes. There are several out of the box and it’s easy enough to create your set of colors using PowerPoint or ThemeBuilder. On a publishing site you can even just specify them right on the site!

But every theme I tried just didn’t look right. I finally decided that I really liked the default theme except for a color or two. Unfortunately, I couldn’t just start with the default and change a couple of colors using the simple themes engine. So I cracked open the corev4.css to see if I could find the default colors. Sure enough, you can see where the Theme Replacement comments are right next to a specified color (default when no theme).

So I started just snooping through the CSS and found some really interesting things. First, the theming engine is really powerful. Not only does it replace element colors on the fly, it can tint images (nice png gradients), and for each color it will actually use shades for various elements allowing you to only have to specify a small number of colors to get a wide palette.

For much more detail about where and how theme colors are used in the CSS check out Eric Schrader’s blog post that includes a pretty awesome Excel file: http://eschrader.com/2010/09/23/sharepoint-2010-%E2%80%93-themes-and-corev4-css-comparison-technique-for-developers/

However, the most interesting thing I found was that when you don’t use a theme (Default) you get many more colors and so the page looks much better. What I mean is that in trying to find the theme color default, I found that each one had several variations which I’ve laid out in a chart below. I expected to find (except in the case of tinting and named variations (lighter, lightest, etc.)) a single color for each theme color. Instead, most had several variations – ALL of which get replaced by a single color. This results in the default theme having a much greater depth than any theme available.

For example, in the CSS file there are even single classes that specify different defaults for the same theme replacement (Lines 2302 & 2304 below):

.ms-siteactionsmenuinner{
/* [ReplaceColor(themeColor:"Dark2")] */ border-color:#21374C;
/* [ReplaceColor(themeColor:"Dark2")] */ border-top-color:#394f63;
/* [RecolorImage(themeColor:"Dark2-Lighter",method:"Filling",includeRectangle:{x:0,y:467,width:1,height:11})] */ background:url("/_layouts/images/bgximg.png") repeat-x -0px -467px;
/* [ReplaceColor(themeColor:"Dark2")] */ background-color:#21374c;
}

Notice the background-color and the border-top-color are defaulting to different colors but both are replaced with the Dark2 theme color.

So what does this mean? Well it means that the default theme (no theme) will always look better than any theme you choose. Looking through the chart below you may even be surprised to see that certain colors are reused in the default theme but are replaced by different theme colors! (The default Hyperlink color #0072BC for instance). If you’d like to achieve the same level of depth you’ll need to create your own CSS file based on corev4.css removing the theme comments and manually specifying your colors. If you’re very serious about Branding, you’re probably already doing this, but those of us looking for a quick solution, this is pretty frustrating.

The themeing engine is very powerful and is still a huge leg up from the themeing available in SharePoint 2007, but it surprises me that they didn’t extend it further to handle all the options they themselves obviously required.

Default Theme Colors

Theme Color Value Example of Color
Light1 #FFFFFF
Dark1 #000000
#003759
#676767
#525252
#6d6f72
#3b4f65
#0072bc
Light2 #f6f6f6
#efefef
#fcfcfc
#F5F6F7
#e7e7e8
#ffffff
#f9f9f9
Dark2 #204d89
#003759
#929fad
#65686b
#0072bc
#161d25
#3b4f65
#5d6878
#003759
#49617a
#21374C
#394f63
#23272c
#666666
#476382
#182738
Accent1 #44aff6
#529dcc
#0072bc
Accent2 #ff0000
#EC008C
#CA0078
Accent3 #003399
#0093CA
#00adee
Accent4 #fd9f08
#ffbb47
#FD9F08
Accent5 #058036
#36B000
Accent6 #FAE032
Hyperlink #0072BC
#0061a0
FollowedHyperlink #b10069

Show Term Store Manager on Site

Applies to: SharePoint

If you’ve ever used the Records Center or one of the other templates that has it, you might wonder how you can get the Term store management link to show up under the Site Administration section of your Site Settings page as shown below.

This is a hidden feature and so can’t be activated from the Site Features. From Powershell, use this command:

Enable-SPFeature -ID "73EF14B1-13A9-416b-A9B5-ECECA2B0604C" -url http://sharepointsite/sites/targetsite

Choose Which Content Database to Create Your Site Collection in

Applies to: SharePoint

If you want to choose which Content Database to use when creating a new Site Collection you will have to use Powershell. There are some crazy solutions out there that tell you to do things like detach all other content databases or adjust the site limits to force SharePoint to pick your Content Database, but in a production environment that is usually not possible – not to mention stupid.

You can do all of this with the stsadm tool in SharePoint 2007, but I won’t be covering that here. But a good reference to figure out what commands match the powershell ones can be found here: http://technet.microsoft.com/en-us/library/ff621081.aspx

If you already have a Content Database you’d like to use, then skip ahead. Otherwise you can create a new Content Database pretty easy in Powershell using this command:

New-SPContentDatabase -Name MyNewSite_Content -WebApplication http://mysharepointsite.com

One of the main irritations with creating a new Site Collection from Powershell rather than the GUI is, although you gain the ability to specify the Content Database, you have to specify the site template by internal name. To get the internal names of all the Site Templates available, use this command:

Get-SPWebTemplate

But for the lazy (me), I’ve put a quick list of most of the templates and their internal names (For English sites – 1033) at the end of this article.

To create the new Site Collection while specifying the Content Database use the following command:

New-SPSite http://mysharepointsite.com/sites/mynewsite -OwnerAlias "MyDomain\MyUser" -ContentDatabase MyNewSite_Content -Name "My New Site!" -Template "STS#0"

That’s it! There’s a bunch of other parameters for the New-SPSite command you can specify (Like Description), but that’s the basic syntax.

Default Installed Templates and their internal Names:

Internal Name Title
GLOBAL#0 Global template
STS#0 Team Site
STS#1 Blank Site
STS#2 Document Workspace
MPS#0 Basic Meeting Workspace
MPS#1 Blank Meeting Workspace
MPS#2 Decision Meeting Workspace
MPS#3 Social Meeting Workspace
MPS#4 Multipage Meeting Workspace
CENTRALADMIN#0 Central Admin Site
WIKI#0 Wiki Site
BLOG#0 Blog
SGS#0 Group Work Site
TENANTADMIN#0 Tenant Admin Site
ACCSRV#0 Access Services Site
ACCSRV#1 Assets Web Database
ACCSRV#3 Charitable Contributions Web Database
ACCSRV#4 Contacts Web Database
ACCSRV#6 Issues Web Database
ACCSRV#5 Projects Web Database
BDR#0 Document Center
BT#0 Bug Database
OFFILE#0 (obsolete) Records Center
OFFILE#1 Records Center
OSRV#0 Shared Services Administration Site
PPSMASite#0 PerformancePoint
BICenterSite#0 Business Intelligence Center
SPS#0 SharePoint Portal Server Site
SPSPERS#0 SharePoint Portal Server Personal Space
SPSMSITE#0 Personalization Site
SPSTOC#0 Contents area Template
SPSTOPIC#0 Topic area template
SPSNEWS#0 News Site
CMSPUBLISHING#0 Publishing Site
BLANKINTERNET#0 Publishing Site
BLANKINTERNET#1 Press Releases Site
BLANKINTERNET#2 Publishing Site with Workflow
SPSNHOME#0 News Site
SPSSITES#0 Site Directory
SPSCOMMU#0 Community area template
SPSREPORTCENTER#0 Report Center
SPSPORTAL#0 Collaboration Portal
SRCHCEN#0 Enterprise Search Center
PROFILES#0 Profiles
BLANKINTERNETCONTAINER#0 Publishing Portal
SPSMSITEHOST#0 My Site Host
ENTERWIKI#0 Enterprise Wiki
SRCHCENTERLITE#0 Basic Search Center
SRCHCENTERLITE#1 Basic Search Center
SRCHCENTERFAST#0 FAST Search Center
visprus#0 Visio Process Repository

Batch Updates with SharePoint Web Services

Applies To: SharePoint

One of the biggest irritations to me coming from a SQL background is trying to do SQL like things in SharePoint 2007. One of those irritations is performing a simple update through the web services.  In SQL, I could write something like:

UPDATE ListTable
    SET FieldName = FieldValue
    WHERE QueryFieldName = QueryFieldValue

In the generic T-SQL above, any row where the QueryFieldName column had a value equal to the QueryFieldValue would have it’s FieldName Column set to FieldValue. Pretty straightforward for anyone with a database background.

With the SharePoint Web Services, however, there isn’t a simple UPDATE command like this. Instead of the one simple command above, we have to make 2 calls to the Web Service and provide some complicated XML parameters. This can be a big hassle the first time you do this, so to hopefully save you some headaches, I’ll provide most of the code required below.

I won’t be showing you how to setup whatever project you are using or how to connect to the web service. The code is in C# and I will assume your service reference (Lists) is called myService.  So let’s get started!

Helper Functions:

I will be using a few helper functions to make generating the XML parameters easier, I’ll go ahead and list these here:

private void AddAttribute(XmlDocument x, ref XmlNode node,
          string attributename, string attributevalue,
          string AttributeNameSpace = "")
{
    XmlNode attnode =
              x.CreateNode(XmlNodeType.Attribute, attributename,
              AttributeNameSpace);
    attnode.Value = attributevalue;
    node.Attributes.Append(attnode);
}

private XmlNode CreateUpdateNode(XmlDocument x, int ID, string RowID)
{
    XmlNode node = x.CreateNode(XmlNodeType.Element, "Method", null);
    AddAttribute(x, node, "ID", ID);
    AddAttribute(x, node, "Cmd", "Update");
    node.AppendChild(CreateFieldNode(x, "ID", RowID));
    return node;
}

private XmlNode CreateFieldNode(XmlDocument x, string Name, string Value)
{
    XmlNode node = x.CreateNode(XmlNodeType.Element, "Field", null);
    AddAttribute(x, node, "Name", Name);
    node.InnerText = Value;
    return node;
}

Retrieve the Items to be Updated:

This is equivalent to the WHERE clause of the example SQL query. You will need to use the GetListItems method of the Lists service.

This method uses the List GUID to reference the target list, I’ll leave it to you to retrieve it and will assume it in the variable ListGUID. You will also need to replace the QueryFieldName, QueryFieldType, and QueryFieldValue variables with appropriate values.

XmlDocument doc = new XmlDocument();
XmlNode queryNode = doc.CreateNode(XmlNodeType.Element, "Query", null);
queryNode.InnerXml =
          string.Format("<Where><Eq><FieldRef Name='{0}' />
                         <Value Type='{1}'>{2}</Value></Eq></Where>",
          QueryFieldName, QueryFieldType, QueryFieldValue);

XmlNode viewNode = doc.CreateNode(XmlNodeType.Element, "ViewFields", null);
viewNode.InnerXml = "<FieldRef Name='ID'/>";

XmlNode resultsNode = myService.GetListItems(ListGUID, null, queryNode,
          viewNode, null,
          doc.CreateNode(XmlNodeType.Element, "QueryOptions", null), null);

 Parse the IDs from the Results:

XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("z", "#RowsetSchema");
XmlNodeList rowNodes = resultsNode.SelectNodes(".//z:row", nsmgr);
List<int> IDs = new List<int>();
foreach (XmlNode rowNode in rowNodes) {
    XmlAttribute result = rowNode.Attributes("ows_ID");
    IDs.Add(int.Parse(result.Value));
}

Perform the Update:

This is equivalent to the SET portion of the example SQL query. You will need to use the UpdateListItems method of the Lists service.

Again, this method uses the List GUID to reference the target list, I’ll leave it to you to retrieve it and will assume it in the variable ListGUID. You will also need to replace the FieldName and FieldValue variables with appropriate values.

<pre>XmlDocument doc2 = new XmlDocument();
XmlNode node = doc2.CreateNode(XmlNodeType.Element, "Batch", null);
AddAttribute(doc2, node, "OnError", "Continue");

int itemCount = 1;
foreach (int i in IDs) {
    XmlNode updateNode = CreateUpdateNode(doc2, itemCount, i);
    updateNode.AppendChild(CreateFieldNode(doc2, FieldName, FieldValue));
    //Append More children here to update multiple fields
    node.AppendChild(updateNode);
    itemCount += 1;
}

myService.UpdateListItems(ListGUID, node);

That’s it. To review, we created a CAML query and retrieved the IDs of the matching rows using the GetListItems method. We parsed the XML result and put all of those IDs in a List<int>. We then added each row ID to a batch update XML parameter which we used in the UpdateListItems method.

As you can see, this is way more complicated and not nearly as obvious as a SQL UPDATE command, but you can take the above code and create a pretty generic wrapper to make your web service updates nearly as easy. Let me know how it works out for you or if you have any questions!

Originally Published on WireBear.com/blog on February 11, 2011

WP7 Accent Shapes

Applies To: Windows Phone 7

The new Windows Phone 7 (WP7) introduces Microsoft’s Metro style (Okay, it’s not the first Microsoft product to use it but it will probably be the first time many users encounter it).  We’ve gone back and forth on it.  It seems most people (by people I mean developer’s and tech gurus) are convinced of it’s genius.

It is simple and there is a sort of beauty to it when done correctly.  However, it will be interesting to see how main stream consumers who are used to the styles introduced through the iOS and Android devices take to it.  As developers of multiple platforms we’ve struggled with it a bit.  It seems to fit some Apps remarkably well, but others not at all.

If  you have developed apps for other software platforms like iOS it can be tempting to try and port your apps to look and behave as it does on the other platforms.  Admittedly this was our first approach and we found it didn’t work too well.  Ultimately we’ve come to the conclusion that existing apps were made to match the OS they were created for and the same should be true of the various versions we create.

In other words, if the consumer wanted an iOS style app they would have bought an iOS device.  By purchasing a WP7 device the consumer has shown their preference for WP7 styles, which inevitably means Metro.

This isn’t always appropriate and should be decided on an App by App basis.  WP7 style is also a moving target and we shouldn’t hold back on innovation just to match existing patterns.  But there should be an evolution to our innovations so that they don’t clash with those patterns but rather enhance them.

</soapbox>

So now that you are all about Metro, here is at least one technique we use to take advantage of this design pattern. The SDK comes with a variety of StaticResources that map directly to the user’s theme settings. Ironically, these “Static” resources can be used to dynamically match a user’s design preferences.  Using these allows you to match a style your user obviously likes and allows their personalization to carry all the way through the phone into your apps.  This is a powerful and easy way to provide customization to your app for “Free”.

Note – By default your app will be doing this and if you wish to explicitly control your colors you will need to do so on each control.  Be sure to adjust your theme and accent color multiple times during the testing of your app to ensure your app remains readable and reasonably pretty.

This can be done from either ExpressionBlend or directly in the XAML.  The nice thing about doing it in ExpressionBlend is that it allows you to choose the resource from a list:

Doing this with text or built in shapes is pretty simple.  For instance, to create a textblock control that matches a user’s accent color (PhoneAccentBrush) you can just use this XAML:

<TextBlock Text="I'm so Pretty!"
 Foreground="{StaticResource PhoneAccentBrush}"/>

You could also create a Circle filled with the accent color using this XAML:

<Ellipse Fill="{StaticResource PhoneAccentBrush}" Width="25" Height="25"/>

There are some other basic shapes and a lot can be done with these.  But what do you do when you want something a little more complex?  You could draw it from shapes, include an image for each possible theme, or try this technique using an OpacityMask combined with an ImageBrush:

<Rectangle x:Name="Star"
 Fill="{StaticResource PhoneAccentBrush}"
 Width="15" Height="15">
  <Rectangle.OpacityMask>
    <ImageBrush ImageSource="/Images/Star.png"/>
  </Rectangle.OpacityMask>
</Rectangle>

Note – the image used above has it’s Build Action property set to Content.

This allows us to use an image with transparency to determine our shape.  The image itself doesn’t need to be any particular color it just needs to have an alpha layer.  This allows for complex shapes that match a user’s theme without a whole lot of work!

Both of the above screenshots use the same XAML:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Width="456">
    <TextBlock TextWrapping="Wrap" Text="PhoneForegroundBrush"
     Margin="0,0,-75,0" d:LayoutOverrides="Width"
     Foreground="{StaticResource PhoneForegroundBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneAccentBrush"
     Margin="0,0,15,0" d:LayoutOverrides="Width"
     Foreground="{StaticResource PhoneAccentBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneContrastBackgroundBrush"
     Foreground="{StaticResource PhoneContrastBackgroundBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneDisabledBrush"
     Foreground="{StaticResource PhoneDisabledBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneSubtleBrush"
     Foreground="{StaticResource PhoneSubtleBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneBorderBrush"
     Foreground="{StaticResource PhoneBorderBrush}"/>
    <TextBlock TextWrapping="Wrap" Text="PhoneChromeBrush"
     Foreground="{StaticResource PhoneChromeBrush}"/>
    <Line X2="400" StrokeThickness="4" Height="4"
     Stroke="{StaticResource PhoneAccentBrush}" Margin="0,50,0,0"
     HorizontalAlignment="Center"/>
    <Ellipse Fill="{StaticResource PhoneAccentBrush}"
     Width="25" Height="25" Margin="0,50,0,0" HorizontalAlignment="Center"/>
    <Rectangle x:Name="Star" Fill="{StaticResource PhoneAccentBrush}"
     Width="15" Height="15" VerticalAlignment="Center"
     HorizontalAlignment="Center" Margin="0,50,0,0">
      <Rectangle.OpacityMask>
        <ImageBrush ImageSource="/Images/Star.png"/>
      </Rectangle.OpacityMask>
    </Rectangle>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"
     Margin="0,50,0,0">
      <Image Source="/Images/Bear.png" Stretch="Fill"
       Width="63" Height="92" HorizontalAlignment="Center"/>
      <Rectangle x:Name="Bear" Fill="{StaticResource PhoneAccentBrush}"
       Width="63" Height="92" HorizontalAlignment="Center" Margin="50,0,0,0">
        <Rectangle.OpacityMask>
          <ImageBrush ImageSource="/Images/Bear.png"/>
        </Rectangle.OpacityMask>
      </Rectangle>
    </StackPanel>
  </StackPanel>
</Grid>

You can use this technique in all sorts of situations, but our favorite is to combine it with databinding and use a little icon to mark something about the entry (such as unread in an article reader).  This is an easy way to wow your users while maintaining consistency with the Metro style.

Originally Published on WireBear.com/blog on October 29, 2010

LiquidOffice Retry on Exception

Applies To: LiquidOffice

LiquidOffice tasks can fail for a number of reasons. By default, a failed task halts the entire process and you will have to manually skip the task, delete the process or take some other action using Management Console. Sometimes, however, wouldn’t it be nice if the failed task waited a certain amount of time then retried?

We have found this to be especially helpful for web service tasks. Calls to web services can fail if the server has gone offline for a minute, but could recover if tried in 10 minutes or so. So we have added an exception loop to each of our web service tasks that retries indefinitely.

Here is the process:

On this process you can see we have a Form task and then a script task called FailTask. The FailTask is pretty simple, it just throws an exception. Here is the code inside the FailTask:

void enteredActive(State state) {
 Log log = LogFactory.getLog();
 log.info("FAIL");
 thisProcess.setFieldValue("FakeField","panda");
}

So we write a message to the log then try to set the value on a field that doesn’t exist. FAIL! The next task, Other Stuff, will never be reached in this particular example, but is representative of the rest of your process.

The other four tasks are the interesting ones. First is an Exception task. An exception task is just a task that becomes active when the task it is connected to (with a red line) throws an exception – pretty straightforward. After the exception task we have placed a Delay task. This is the task that determines how long the process should wait before retrying.

The most important task is the Reset State script task. Here is the code inside:

void enteredActive(State state) {
  thisProcess.getTaskByName("FailTask").forceToState(State.READY);
}

(This Code can also be placed in the Delay task on the enteringDone event, we have separated it into a separate Script task just to make it easy to understand)

This code retrieves the failed task (whatever task you are attaching the exception loop too) and sets its’ state to READY. This command will fail if the task is not currently at state ABORT, so just make sure to only put this code inside your exception loop.

The last member of this loop is the strangest, the Loop task. The loop task is between the Delay task and the Exception task and does NOT link back to the main process (Fail Task). Resetting the state of the Failed task immediately activates it (which is why we reset AFTER the delay task). The loop is only there to reactivate the exception task so that if the Failed task fails again, the exception loop starts over.

In our example above the process will retry forever until it succeeds. You could easily add conditions to the loop task to determine how often it should run and this will be the equivalent of a retry count (since the exception task will never be reactivated).

Adding these types of loops to your processes can help them to be more robust and keep you from having to intervene on recoverable tasks.

UPDATE: Although the above guide can be greatly improve the reliability of your processes, LiquidOffice isn’t not a very reliable program. Depending on a variety of factors, Exception tasks themselves can have exceptions and there’s really no way to totally prevent these.

Originally published on wirebear.com/blog on January 18, 2011

Adding Color to Your Dynamic Tables in LiquidOffice

Applies To: LiquidOffice

In many of our LiquidOffice forms we use the dynamic table controls with row addition and removal turned on. You can see a sample on the left of a very simple form showing this behavior in Internet Explorer.  This works pretty well, but we recently had a seemingly simple request to change the color of the add (+) and remove (-) buttons.  There are several things you can customize in your form just by adjusting some properties, but the color of these buttons are not currently available as settings.

So what are our options?  We posed that question to Autonomy technical support and they came back with a couple of methods to get to these buttons in javascript and to set their color there.  We needed this behavior on multiple tables on the same form and on multiple forms so we put together a method using their help to come up with a pretty simple solution…

The heart of it is the following code:

//Sets the colors of the + and - buttons for dynamic tables
function ColorizeButtons(tableName){
  var tbl = CSForm.getTable(tableName);
  var addColor = "699A41";
  var remColor = "BF3834";
  if (tbl != null && tbl.isDynamic()) {
    if (tbl.isRowAdditionAllowed()) {
      var addButton = CSForm.getField("DFS__" + tableName + "_addRow");
      if (addButton != null) {
        addButton.setFillColor(addColor);
        addButton.setTextColor("FFFFFF");
      }
    }
    if (tbl.isRowRemovalAllowed()) {
      for(var i=0;i<=(tbl.getNumberOfRows()-1);i++) {
        var remButton = CSForm.getField("DFS__"+tableName+"_removeRow_"+ i);
        if (remButton != null) {
          remButton.setFillColor(remColor);
          remButton.setTextColor("FFFFFF");
        }
      }
    }
  }
}

To use this, open up the script editor in the Form Designer (Tools > Script Editor).  Expand your form and click on the <Client> node and paste the above code.  Then expand the<Client> node and then expand the CSForm node and double click the OnLoad() event to add the function.  In this event add a call to the above function for EACH dynamic table on your form where you want the buttons colored using the name of the table as the parameter (Our sample table is named GreatStuff).  For the test form we’re using our OnLoad event looks like this:

function CSForm_OnLoad()
{
  ColorizeButtons("GreatStuff");
}

Then for EACH dynamic table on your form find the table’s main node in the <Client> node on the right and expand it and double click the OnRowAdded() event and set the code like this:

function GreatStuff_OnRowAdded()
{
  ColorizeButtons("GreatStuff");
}

Your Script Editor should look similar to this:

Press F5 to save and compile.  That’s it.  Just test the form (File > Preview > As HTML) .  With most recent versions of Internet Explorer you will be prompted during the preview with a yellow message bar at the top of the page saying that it has restricted content from the page. This will only happen on your local machine and is a good, but annoying, security feature. Just click on the message and choose Allow Blocked Content… and all your scripts will run just fine.

Now your plus (+) buttons should be green and your minus (-) buttons should be red:

That’s all there is to it.  The buttons aren’t beautiful, but now they’re easier to see and the colors you choose can help indicate their function or match your logo.  To customize the colors used, just edit the ColorizeButtons function listed above with the hexadecimal color values in a string (without the #).  This is a pretty standard format found in HTML and you can easily find sites out there to help you with these.  A good and easy one is ColorPicker.com.  You can just adjust your colors right on the site and it gives you the hex value right at the top.  Just copy it and remove the #.

  var addColor = "699A41";
  var remColor = "BF3834";

To change the text color just edit the setTextColor(“”) calls using the same types of values (the code above uses white “FFFFFF”).

Let me know if you find this useful or if there are other tricks you use to help customize your forms.

Originally Published on WireBear.com/blog on November 26, 2010

LiquidOffice Suicide Loops (Timeouts)

Applies To: LiquidOffice

If you’ve ever created a semi-complex process in LiquidOffice you might have run into a common problem – zombie processes.  Zombie Processes are processes that never completed but they aren’t being actively used either. Generally these have to be manually killed using the Management Console.

This isn’t LiquidOffice’s fault, it’s doing what it’s supposed to, but depending on your environment this can be a big problem and a huge irritation.  Fortunately, we can use LiquidOffice to solve this problem quickly and simply with just a few additions to your process.

The most common cause of zombie processes is having a “choose your own adventure” type process with multiple forms.  Consider the following process:

In the above generic process we have two forms and two script tasks.  The script tasks are just there to serve as placeholders for whatever tasks you might be doing between and after forms.  Often in a more complex process you would have some sort of branch task that changed what the second form was or altered some fields depending on the result of the first form.  But to keep things simple this is our sample process.

Note – We have checked the “Automatically Display Next Form on Submit” property in the Form section of the first form task’s properties.  At this point our process also does not take advantage of the Initial Form property (we will cover that further down).

In our common BeanShell script area we have the following generic function:

//Writes a message to the Log
void writeToLog(String Message) {
  Log log = LogFactory.getLog();
  log.info(Message);
}

This is a simple helper function we use to save time when we write to the log.  Using this in the More Stuff task (final script task) we have:

void enteredActive(State state) {
  writeToLog("All forms processed!");
}

So if everything goes well, the final thing our process does is to write to the event log to let us know it’s all done.  But what happens if when a user gets to the second process they realize they messed up the first form so they close the window and start over?  A good solution is to provide a back button (See our Multiple Submit Buttons in LiquidOffice article).  However, not everyone will use a back button no matter how convenient and it still doesn’t account for people who either accidentally or purposely closed the window for whatever reason.

When this happens as far as the user is concerned the process is gone, but LiquidOffice holds onto the instance, all of it’s data, the versions of the form(s) and the process that were used, and the current state of the process.  All of this will need to be cleaned out manually using Management Console and in some versions of LiquidOffice the process isn’t fully cleaned up on manual deletion.

Fixed Lifespan:

The Fixed Lifespan solution is very easy to implement.  Here is our example from above with a Fixed Lifespan:

Only two controls were added to the process.  An Or block and a Delay task with no parent tasks (no preceding connections).  The Delay time you set is equal to the lifespan of the entire process.  The Or block needs to be the last item in your process and allows us to say,“however you get here, end”. If the tasks all complete before the delay expires then the process performs as expected and the delay task is ignored.  However, if the delay expires, the Or block at the end cancels out the rest of the process tree and the process gracefully dies.

This works because both Form 1 and the Delay task are started at the same time (process instantiation).  So it’s a race.

This makes for a very simple solution but should be used with extreme caution.  This is especially true for large processes which require approval, manual operations, or special routing.  You will need to take into account all reasonable time frames for each task to complete and make sure your Delay task is set much higher.

Estimating an appropriate lifespan and maintaining adjustments to that lifespan as the process changes can be difficult and error prone.  Many times you only want specific tasks to timeout and you’d like that timeout to be relative to each task.  For example, once a user has submitted the form(s) you probably don’t want an automatic timeout.  So what do you do when you only want to target certain tasks and you’d like those timeouts to be relative to the task they are timing out?

A Better Solution:

With the help of Autonomy tech support (Thanks Simon!) we have come up with a solution that fits that criteria and that we add to nearly every process we publish.  We call it a suicide loop because that sounds cool (right??), but that might lead you to believe that we are just forcing a delete on the process from within the process.  In actuality, we are still gracefully completing the process allowing for standard cleanup and for statistics to maintain their integrity.  In addition, you can add whatever code you want when this happens (send an email, write to the log, etc.).

The solution involves adding an Escalate task, some other task(s), and an Or block:

The escalation task needs to be attached to the form causing the problem (Form 2 above). Ensure your Delay until escalation property is set to a relatively high number to allow the user the chance to fill it out.  For testing we set ours to 1 minute (0:01:00.000), but a value of 24-72 hours is generally more appropriate.  To be clear, this is the amount of time the form (NOT the entire process) can be left open (zombie or not) before the process is sent to heaven – so you will need to pick it carefully.

If the form is submitted before the escalation delay then the process continues on as expected and the escalation task is cancelled.  However, if the escalate occurs, the Or block at the end cancels out the rest of the process tree and the process gracefully dies.

Wait a second, what about that extra script task between the escalate task and the Or block? This is REQUIRED.  This can be a blank script task (does nothing) or something else like an email task (or whatever other business logic you want to insert), but you must have something here.  This is because although the escalate task is cancelled, it “shows” as completed which signals the Or block to be followed – cancelling the process immediately.  Placing something here changes that.  In our process above, when the escalate task is cancelled the Script task is set to Passive which prevents it from executing and therefore stops the path.

Our script task does this:

void enteredActive(State state) {
  writeToLog("Goodbye Cruel World!");
}

If you run this process, close the window on (don’t submit) the second form and you’ll see the “Goodbye Cruel World!” message in the System:EventLog within the Management Console (after the escalation delay).  You’ll also find the instance is marked as complete.  The process can also be completed normally (all forms submitted) and the suicide loop will never be entered.

Congratulations, it was really that simple!  Unless of course, it wasn’t…

Multiple Suicide Loops:

In most cases the above solution should have taken care of your problem.  But if any of the following are true, you will need to add additional loops:

  • Multiple forms where a user could stall out
  • Using the process Initial Form setting set to Instantiate Process Before Display Form

Using our same process from above we have now set the process Initial Form property to Use Initial Form and set it to Instantiate Process Before Displaying Form:

This effectively means our process has multiple forms where a user could stall out.  So this means we need a way to kill the process on either form 1 or form 2 being abandoned.  This really just means we have to add another suicide loop (one for each stall point).  This isn’t that complicated but it’s a little annoying.

LiquidOffice doesn’t allow the escalate task to be attached to more than one task (the pink line).  This means you need a separate escalate task for each stall point.  This creates a new path for each stall point that can be joined back together for one final suicide task(s) as needed, but ultimately every path in your process needs to end in that final Or block.

Unfortunately, you can’t just have each escalate task point to the same inbetween task (the “Goodbye Cruel World” script in our example).  Because then the process will stall out at this task since it will be waiting for the other escalate task(s) to also reach that point before continuing.  You also can’t simply add an Or block before the imbetween task or you will have the same problem as before with the Or block being activated when the escalate task is cancelled.

So you need to add the “full” suicide loop for each stall point like this:

This is a pretty simple implementation.  But what if you wanted to send an email (or some other logic) every time a process stalled out?  You could just cut and paste and replace the imbetween script tasks with these tasks, but maintenance would be a nightmare and your process tree could quickly get huge.  So do something like this:

As you can see we still have a generic script task after our escalate tasks, but these do nothing.  We have moved our Finally logic between the Or blocks.

Although the suicide loop is more complex than the fixed lifespan, the benefits are worth it. Regardless of exact implementation, the prevention of zombie processes is an important part of your design for EACH process. These are our methods, what do you guys do?  Any other process maintenance tips? Let us know in the comments!

Originally Published on WireBear.com/blog on November 22, 2010

Multiple Submit Buttons in LiquidOffice

Applies To: LiquidOffice

LiquidOffice is an enterprise forms processing and workflow suite from Cardiff (Now Autonomy).  It does some things very well (like routing forms), but it can be an incredibly frustrating piece of software for developers and designers.

Recently a client wanted to use LiquidOffice (v6.2.1) alongside a web service to provide a list of locations using a zip code and mile radius.  The requirements included the ability to return to a previous form and to change the zip code and/or mile radius.  Doing something like this in ASP.NET would be very easy and there are a ton of examples out there.  Doing this in LiquidOffice, however, presents quite a few challenges:

  • The LiquidOffice Web Service task is just awful
  • Populating a table from the process isn’t straightforward
  • By default you can have only one submit button on a form (although there is a list of actions which can have various values)
  • Navigating between forms isn’t intuitive either

The good news is that we got it working.  To try and help others that may be struggling with how to do some of these things we will present our solutions.

The LiquidOffice Web Service Task

We could probably write an entire post just about this alone, but instead we’ll show you how to work around some of the difficulties and get it working.

A couple of warnings though:
(all of these issues have been confirmed with Autonomy technical support)

  • The WS Task does NOT do authentication of any kind
  • The WS Task will NOT create complex objects
  • Once you have set a WS location and saved it, you can NEVER change it without recreating the task.  You can change it, save it and it will be reflected in the task properties, but it will continue to point to the previous WSDL.

Fortunately, there are work arounds to all of the above.  We didn’t need authentication for this particular web service.  We’ve needed it before (like when connecting to SharePoint), and we hope to cover how we overcame that obstacle in a future post.

We did however need a complex object.  In this case our locations were being returned in an array of location objects.  To get around this we created our own intermediate web service sitting on the LiquidOffice Process Server.  Calling this web service called the location service and wrapped those objects into a single string (very similar to JSON).  Then using a Script Task we parse out the string (using the split command) into an array and use it as a complex object again.

Populating the table should be done on the Form task and accessed using thisTask.getTable(“TableName”);  If you use the thisProcess.getTable(“TableName”); command you will have to find the internal name of the table.  This name is actually your table’s name plus a random number (I’m sure it’s not random but tech support could not tell us where it came from or guarantee it wouldn’t change).

Multiple Submit Buttons

For our form we needed a button that would return the user to the previous form, a button that would reload the list of locations, and of course a submit button.  These could all be accomplished by using the default submitaction control and using the form task properties to customize the Submit Action List.  Unfortunately, the end result on the form is not very user friendly.  It puts a combo box with the submit actions next to a button and the user is expected to choose their action and then hit the button.

You can, however, hide this control and use javascript attached to other buttons to simulate selecting an action and pressing the submitaction button.  To do this you still need to setup your Submit Actions.  For this form our Submit Actions look like this:

On the form we create three buttons (Back, Reload, and Submit) and use client side code to hide the submitaction control.  Here is our Javascript:

function CSForm_OnLoad()
{
  document.getElementById("DFS__ActionList").style.display = 'none';
  document.getElementById("DFS__GO").style.display = 'none';
}

function ReloadButton_OnButtonClick()
{
  CSForm.getField("SearchZipCode").setRequired(true);
  SubmitForm("Reload Locations");
}

function LocationSelected() {
  var locations = CSForm.getTable("Locations");
  for (var i=0;i<locations.getNumberOfRows();i++) {
    if (locations.getAt(i,"Selected").getValue()=="1") {
      return true;
    }
  }
  return false;
}

function SubmitButton_OnButtonClick()
{
  CSForm.getField("SearchZipCode").setRequired(false);
  if (LocationSelected()) {
    SubmitForm("Submit Choices");
  } else {
    CSClient.alert("You must choose at least 1 location!",0,1);
  }
}

function BackButton_OnButtonClick()
{
  SubmitForm("Back");
}

function SubmitForm(result) {
  document.body.style.cursor = 'wait';
  var choices = CSForm.getField("DFS__ActionList").getChoices();
  for(var i=0;i<choices.getCount();i++) {
    if (choices.getAt(i,true)==result) {
      document.getElementById("DFS__ActionList").selectedIndex = i;
      document.getElementById("DFS__GO").onclick();
      break;
    }
  }
}

Then in our process we add two loop objects so that it looks like this:

In the loop tasks we edit the Loop properties to set the Follow Path Based On section toAnother Task.  The Location Search Loop task looks like this:

The Task Output should match the submit action value from above.  The Navigation Loop is exactly the same except it’s Task Output value is set to -1.

So, yes it’s possible to do some complicated navigation in your forms and, yes, you can have multiple submit buttons.  It just takes a little work!

Originally Published on WireBear.com/blog on October 25, 2010