Training Site Template - Part 2: Workflows

Training Site Template - Part 2: Workflows

Rate This

Sean Hey there,

 It's Sean again with another installment of the template development walkthrough. Last time we covered the list schema for the Employee Training Scheduling and Materials template, one of the new Application Templates for Windows SharePoint Services 3.0. In this series I'm showing you how we built one of these templates from the ground up, and today we're going to author all of the training site's workflows using SharePoint Designer. With that done, you'll have built the whole back-end of the template, so in a future installment we'll get our hands dirty with SharePoint's views and forms.

Folder List (Training Site)Open up a fresh installation of the training site in SPD and you'll see the folder list pictured on the right - with the three workflows you'll be creating today in the Workflow Designer. They'll provide some of the rich behavior that you wouldn't see in a static web app, and you won't need to write any code to get it done. Let's get started!

Automatic Reminders

People are forgetful. Computers aren't. So when an instructor comes to your training site to create a course, why should they have to remember when it is? That's where you can come in with this workflow, which we'll set on the Courses list:

  1. In SPD, go to File > New > Workflow...
  2. Name it "Instructor reminder" and attach it to the SharePoint list "Courses"
  3. Check the box to Automatically start this workflow when a new item is created
  4. Click Next > to continue

From here you'll want to create 3 steps which will logically separate our workflow into sets of related actions. Click Add workflow step to move on to the next step, and in each case if you don't see the specific action listed, click the Actions button > More Actions... to see the full list. Additionally I'm using [brackets] to refer to what you need to do in each placeholder.

Also, fx just means the data binding button. You get the same dialog whenever you push Add Lookup to Body in the e-mail builder.

So before you start, first create two variables (click Variables... > Add... to make them), one called "Reminder Time" (Date/Time) and one called "Subject" (String). You'll need them once you get to the individual steps:

  1. First step: name it "Set reminder time"
    1. No conditions
    2. Actions > Add Time to Date - Add -24 hours to [fx :: Current Item, Start Time] (Output to Variable: Reminder Time)
    3. Actions > Build Dynamic String - Store "RE: [fx :: Current Item, Course Title]" in Variable: Subject
  2. Second step: name it "Send confirmation"
    1. No conditions
    2. Actions > Send an Email - Email [...]
      1. To: Workflow Lookup... :: Current Item, Instructor
      2. Subject: fx :: Current Item, Course Title
      3. Body: This is a confirmation that you are scheduled to teach [Add Lookup to Body :: Current Item, Course Title] on [Add Lookup to Body :: Current Item, Start Time] at [Add Lookup to Body :: Current Item, Location]. For more information, please visit the training site.
  3. Third step: name it "Send reminder about course"
    1. Conditions > Compare Courses field - If Created is less than [fx :: Workflow Data, Variable: Reminder Time]
    2. Actions > Pause Until Date - Pause until [fx :: Workflow Data, Variable: Reminder Time]
    3. Actions > Send an Email - Email [...]
      1. To: same as above
      2. Subject: fx :: Workflow Data, Variable: Subject
      3. Body: This is a reminder that you are scheduled to teach [Add Lookup to Body :: Current Item, Course Title] on [Add Lookup to Body :: Current Item, Start Time] at [Add Lookup to Body :: Current Item, Location] in 24 hours. For more information, please visit the training site.
  4. Click Finish

Make sense? We're sending two reminders to anyone who adds a new item to the Courses list (i.e. an instructor creating a course) - one right when they make it as a confirmation, and another 24 hours before the course is scheduled. And the first step was just to keep the variable assignment separate, there's nothing wrong with doing it in the other steps right before they are used.

We'll want to do a similar reminder for students, but we can do it at the same time that we enforce seating policy (each item in Courses has an Available Seats and Total Seats) since it only really makes sense to send reminders if you actually make it into a course.

Processing Student Registrations

If you try to add a new course by browsing to Courses > New..., you'll see this line in the new form:

And you'll also notice that there's no line for Available Seats or Filled Seats, even though we added those fields to the Courses schema in part one. I'll teach you the trick to hide form items next time, what's important here now is that 0 will be our sentinel value in the workflow to come.

Let's go through this one a bit faster by using some shorthand.

  • ... means I'm referring to a string (probably for those long auto-generated e-mails) that you can find the full text for by opening Attendee registration.xoml in SPD.
  • Courses* means you need to perform a lookup back into the Courses list. To do this, start by pushing fx and pick Source: Courses, at which point you'll see the "Find the List Item" section appear. In that section, match Field: Courses:ID to Value: Registrations:Course ID (use the nearby fx :: Current Item, Course ID).
  • Variable: = anything with a leading Variable: in front of it is a workflow variable that can be found in fx :: Workflow Data.

Start with a new workflow called "Attendee registration," attached to Registrations and triggered when an item is created. Create 7 variables before you begin, 4 of type String (Subject, Confirmation Body, Reminder Body, Reminder Body 2), 2 Date/Time (Reminder Time, Reminder Time 2), and 1 Number (New Filled Seats). From there build out the steps:

  1. "Cache all variables"
    1. No conditions
    2. Actions > Build Dynamic String (x4)
      1. Store ... in Variable: Confirmation Body
      2. Store ... in Variable: Reminder Body
      3. Store ... in Variable: Reminder Body 2
      4. Store ... in Variable: Subject
  2. "Enforce seating policy"
    1. Conditions > Compare any data source (x2)
      1. If [fx :: Courses*, Total Seats] equals 0
      2. or [fx :: Courses*, Filled Seats] is less than [fx :: Courses*, Total Seats]
    2. Actions > Set Workflow Variable (x2)
      1. Set Variable: Reminder Time to [fx :: Courses*, Start Time]
      2. Set Variable: Reminder Time 2 to [fx :: Courses*, End Time]
    3. Actions > Do Calculation - Calculate [fx :: Courses*, Filled Seats] plus 1 (Output to Variable: New Filled Seats)
    4. Actions > Update List Item
      1. List: Courses*
      2. Add... :: Set Filled Seats to Variable: New Filled Seats
    5. Click Add 'Else If' Conditional Branch
    6. No conditions
    7. Actions > Delete Item - Delete item in Registrations (Current Item)
    8. Actions > Stop Workflow - Stop the workflow and log "Course is already full."
  3. "Set reminder time"
    1. No conditions
    2. Actions > Add Time to Date - Add -24 hours to Variable: Reminder Time (Output to Variable: Reminder Time)
  4. "Send confirmation"
    1. No conditions
    2. Actions > Send an Email - Email ...
  5. "Send reminder about course"
    1. Conditions > Compare Registrations field - If Created is less than Variable: Reminder Time
    2. Actions > Pause Until Date - Pause until Variable: Reminder Time
    3. Actions > Send an Email - Email ...
  6. "Send reminder for feedback"
    1. Conditions > Compare Registrations field - If Created is less than Variable: Reminder Time 2
    2. Actions > Pause Until Date - Pause until Variable: Reminder Time 2
    3. Actions > Send an Email - Email ...
  7. "Move to Past Registrations"
    1. No conditions
    2. Actions > Copy List Item - Copy item in Registrations (Current Item) to Past Registrations
    3. Actions > Delete Item - Delete item in Registrations (Current Item)

Yikes, I know. But thankfully that's about as bad as it gets. Here's a brief explanation - a student will register for a course, but they won't get in right away (technically, they get in but they may be kicked out immediately if the seating logic fails). After setting all the variables we check if Total Seats is 0 (unlimited) or greater than Filled Seats (i.e. is there room in the course?). If not, we abort prematurely (delete/stop step two), otherwise we do the math and add 1 to the Filled Seats, send reminders, etc. The last step is the archival in the mirror list Past Registrations, something that will become more evident in part three when we build up the front end.

A Dirty Workflow Secret

That's all well and good, but what happens when someone wants to unregister from the course? We need to subtract 1 from the number of Filled Seats, then delete their registration from Registrations, but here's a problem - workflow cannot be triggered to run before an item is deleted. So here's how we'll fake it - since workflow can run when an item is changed, we'll set up another sentinel value for our workflow to be interested in. DELETE seems pretty self explanatory, right?

(Note that this workaround has some serious implications - we'll need to hide item deletion in Registrations and block all edits to our field so the user never trips the sentinel. Don't worry, I'll explain in due time...)

Our last workflow, "Attendee unregistration" (for lack of a better word), attached to Registrations, triggered when an item is changed. One variable before you start: a String called Log Message. Not even really necessary, just for posterity:

  1. "Unregister from course"
    1. Conditions > Compare Registrations field - If Title equals "DELETE"
    2. Actions > Delete Item - Delete item in Registrations (Current Item)
    3. Actions > Build Dynamic String - Store ... in Variable: Log Message
    4. Actions > Stop Workflow - Stop the workflow and log Variable: Log Message

And there you have it, dynamic behavior that takes a long time to spell out in a blog post, but goes a long way towards the richness and robustness of your SharePoint app. It's taken us a few key design decisions to get here, and so in part three we'll learn what consequences that holds for the front end system, which is driven by SharePoint views and forms. I'll see you then!


  • Thanks for the interest CQuick, I'm looking to get out the next installment by this week. It's probably the most in-depth article of the series, so it's been taking a while to write...



  • Great work with the articles Sean.  It really helps us see the power of the applications and how to design our own for clients and internal use.

    One thought, I know writing doc can take a long time, have you thought about producing screencasts instead of doc?  This might help in speeding up the time it takes to produce how-to's on application template designs.

    Great work,

    Ben McMann

  • Great Examples. I used this example to create a Workflow with a Reminder and an OverDue on a Project List. The only question I have is when the Workflow is paused waiting for a reminder date what if I want to stop the workflow if someone changes the status of an Item to "Completed" it doesn't seem to be able to unPause to stop the workflow.

  • Great example to get started!  I took your examples and be experimenting creating a shopping cart like application.  The problem I’m having is getting input in a dataview.  I understand how to put a label in a table and see it in workflow but, how a textbox?  When I put a textbox in the table, I do not see it as a form variable to assign in workflow.  Any thoughts?



  • Hi all,

    I want to build a new web part but I don't know how SP store this information in database. I know that there're some tables like AllLists, information 'b lists, webparts... But I don't know where SP stored all items in these lists?

    Please help me!

  • Its possible insert in the email a link to new item created in lists? how? (sorry for my english)

  • Hi all,

    I created a subsite using WSS 3.0 template “Employee Training Scheduling and Materials”.

    I gave some users Read permission and others Contribute permission.

    Users with Read permission cannot do their registration for any course. (Registration Fails)

    Users with Contribute permission can register fine, however they have more permissions that they should. For example, with Contribute permission they can add, edit or delete items, which they shouldn’t.

    I found that this is a permission related issue.

    My workaround was to give specific permissions for the courses list. I.e.:

    -I created a new permission level from a copy of the Contribute permission level. I uncheck the “Add Items” and “Delete Items” permission and leaved only the “Edit Items” permission.

    -Then on Courses permissions, I created a Group (named Training Users) and gave this group the permission level created.

    With this I was able to give users the ability to register to courses and at the same time deny them the ability to add new items and to delete existing items.

    Unfortunately this doesn’t answer to all my requirements because users can still edit items, i.e., they can change the name, time and available seats of any course.

    Note: The “edit items” permission in the courses list is needed because each time a user registers for a course the “Attendee registration” workflow starts using the credentials of the user that initiated the workflow, and if this user only has Read Permission the workflow fails. The user needs to have the ability to “edit items” in the courses list because one of the steps of the “Attendee registration” workflow is the “enforce seating policy” which updates an item in the courses list.

    In the registration list I gave the users the Contribute permission level because they need to add and delete items depending if they are registering or unregistering from a course.

    Resuming, I want users to be able to register to courses but at the same time I don’t want them to add, delete or edit items in the courses list.

    Can I run the workflows with elevated privileges independently of the users permission?

    I don’t know if I made myself clear. Any question please feel free to contact-me.

    Thanks in advance.

  • hey ...i used a workflow in my system..

    but everytime the condition is triggers the action more than once(around 10 times..)

    please share your views on this..

  • Part 3? I know you're busy, but it would be great to see Part 3!

  • Need some help.  I'm really interested in learning this but SPD gives me this ugly error:

    Errors were found when compiling the workflow.The workflow files were saved but cannot be run.

    Unexpected error on server associating the workflow

    Everything out there on this error points to VS.NET developed workflows, not SPD.  Help!?!

  • Well, I had the same error as David and my issue with SPD 2007.  Going through the logs (%systemdrive%\Program Files\Common Files\Microsoft Shared\web server extensions\12\LOGS) I found that the "temp" directory of the default NetworkService wasn't letting the SHAREPOINT\system user to compile the workflow.  Once I modified the directory permissions to let SHAREPOINT\system compile in that location, the errors David and I see went away and SPD would also compile and associate a custom workflow.

  • Wow folks... thanks for all the comments! I'm sorry I haven't been around much as of late, we've all been busy planning out all the awesome new SharePoint Designer features you'll see in the next wave of Office. But now that I have some time to catch my breath...

    Ben, CQuick: Part 3 is long overdue, I agree. Thanks for being patient - I hope you guys will find it useful in completing the picture here...

    Scott: after the pause action, you can create a new step with a condition to check status before doing anything else. It's not a synchronous check though, for that I would suggest looking into using modification forms in the WinWF.

    BBarthell: are you using custom form actions? You should be able to access any text box from the data form by pointing the lookup dialog (fx) to the "Form Fields" data source...

    Hoalio: writing your own web part isn't in the scope of this post, but you may want to try the SharePoint SDK for information on how backend data storage is handled.

    Pangolao: there isn't an easy way to do this today, but you can certainly handcode the path portion of the URL (http://server/site/List/DispForm.aspx?ID=) and then apply a lookup to the Current Item's ID, similarly to the examples in the post.

    Goncalo: SPD's workflows always impersonate the person who initiated them, so no there isn't a way to elevate without developing your own non-declarative workflow.

    NehaShina: did you check the start trigger for the workflow? It's possible to get into this situation if you set the workflow to start every time an item is changed, and the workflow itself does the change (thereby triggering an action several times).

    YokoGaijin: good catch and thanks for sharing.



  • Yes, I am using custom form actions ... my label shows up in the form fields (like your example) but, how can I put a text box in that is not related to the list?  For example, I want to show a list of products to order and ask for a Qty for each product.  The "Add to basket" is a custom form action (button).

    Products                     QTY

    Product A                _______    "Add to basket"

    Product B                _______    "Add to basket"



  • Sean, one request.  Can you tell me if the following even possible within the context of SPD (I know I can do it in VS... just didn't want to have to)?

    I added another list populated by an InfoPath form.  The "forced attendance" list allows HR and the supervisors in certain departments designate training as 'mandatory', 'highly suggested', and 'suggested'.  The difference being that the first is required, the other two are courses that directly apply to goals & objectives or identified on the job training needs.  The "forced attendance" list uses lookups from the "courses" list as defined in this example.

    My conundrum is how I can insert registrations using a workflow in SPD.  One of the fields in the "forced attendance" list is an attendees field populated with people picker and allows multiple entries.  Is there a way I can do a "do while" or "for each" using SPD without creating custom workflow features or resorting to VS?

  • Hi Sean,

    I surely adore the piece of work you are putting up here.

    This has definitely served me as the starting point and now to run forward with the SPD flag.

    3 Cheers to you!!

    Keeping my fingers crossed till you get us the next installment :-).

    Also, a small query...

    I am using the "Do Calculation" Action in my workflow in SPD.

    I am trying to use something like this:

    Calculate FileNumber:OldFileNo plus 1 (Output to Variable:NewNo)

    then use this variable "NewNo" to Update a column in myList which initially has a value "1".

    When this workflow runs, I see that the status is completed and also that the

    required list column is updated. But only problem is the updated value is incorrect.

    Instead of incrementing the "1" to "2", I find a value "11" in that column.

    This is wierd !!

    The calculation can't be a concatenation. I need it to add 1 to the older value.

    Am I missing on somethig.

    Thanks for any kinda help.


Page 2 of 7 (100 items) 12345»
Leave a Comment
  • Please add 1 and 4 and type the answer here:
  • Post