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!


  • Hey I figured that out !!

    Was really silly though..... :-) Sorry guys if anybody out there got disturbed due to this.

    It was the variable data type which should be "Number" instead of "String".

    Thanx Sean, I again appreciate your works.


  • Hi Sean,

       Thanks very much for your example.  I am trying to send a reminder before a Task Due Eate.  But I have a problem with a "Pause Until .. " activity.  After the pause I want to check a condition by adding a new Workflow step.  But after the pause is completed, the workflow is marked as Completed and never executed the next Workflow step.  I think there is a bug in the WF.  I also updated the patch according to the KB932394.   But it still doesn't work.

    Thanks in advance for your help.


  • Hi Sean,

    Is there to assign courses to specific person rather that ask the person to sign for a course.

    We have training courses for new employees, so what I am wondering is if there is a way to allow HR people to assign those courses direct to the new employees.



  • Any news on the next installment?

    Both training site templates tutorials have been extremely useful. Thank you for putting them together as it reaaly helps in the understanding of how sharepoint works.

  • Í have a leasing-list with 100 items. Every item has a date in a colum named "end-date". I need a workflow with checks every week all these 100 items and if the end-date is nearer then 1 month, the workflow should send an email to an defined e-mail-adress. I don't know how to handle this problem... I only can start the workflow for several items, but ot for the hole list. Can somebody help me?

  • Okay... I'm now officially frustrated because this article is taking so long to publish. I need some good examples in SPD, and this was a good start. Now that we're looking to the actual functinality -- I find myself in the middle of a desert with no water... please please please!

  • I'm unable to get a recurrance event at the new form to appear. After re-enabling the contents managment types , it still did not appear.

    Thanks In Advance for the assistance.


  • Hi Sean,

    i just read your examples. great idea do show this piece of work in detail.

    do you have any ideas when you can finish part 3 ?

    i'm really looking foward  to it !!!

    greets marco

  • Really when can we expect part 3?  This walk through has been very helpful up to this point.  Thanks for the help in getting started with Designer because before I was just basically using it as an editing tool.   Now I can see where I can build an actual application.

  • Hi Sean,

    Thank you for writing this Training Site Template part I and II, I found the articles very helpful.  I am desperateley looking for the same material concerning the Server Admin Application Template HelpDesk.wsp in order to understand how this helpdesk template is built.  I need to make some customization on the Helpdesk template, in particular, I need to modify the Service Request Form to add new fields.  I cannot find any guidance in a step-by-step manner as your "Training Site Template part I and II".  Could you please give me some hints to customize the Helpdesk.wsp and ProjectTracking.wsp?

    Thank you in advance.

  • Thanks for an excellent tutorial.  Now all we need is part 3 to glue it all together.  We know you are busy, but you should always finish what you start, right???  ;)

  • Thanks for part I and II Sean. We are looking forward to part III. We find however that we are having issues with the workflow correctly managing the seat enforcement. We have tried this several times but the workflow never seems to increment/decrement even though the students are registered. Any ideas?

  • Trying to do this ... either in the string or variable set ...

    Set a variable (e.g. YEAR([%customlist:DATEFIELD%]) to get a string of the year

    so I can later use it in a Dynamic string ...

    Why can't I do this ???

  • Odd problem here...

    Regardless of how I try this...whether following your steps to create everything, or using the default template that includes all these workflows...I run into the following problem:

    A user registers for a course (with no problems) and the workflow subtracts 1 from the total seats available.

    If the user then unregisters, the workflow seems to fire off and actually remove them from the registrants listing, but the total number of available seats does not increment back up.

    For example, I have a course with 5 seats available.  User1 registers for the course and the count decrements by one (now shows 4 seats available).  User2 registers for the course and the same process occurs (as it should) which decrements the count again by one (count now shows 3 seats available).  If User1 wants to unregister, they follow through the steps to confirm they want to unregister and it removes them...but the count will stay at 3 for the available seats, even though now only one person is showing as registered.

    No mattter what I try, each time the count will never go back up.  The only way I've been able to get things to work is to go back in and manually add one more seat in the edit item screen...which in effect make the count of available seats accurate again, but in reality is actually off by one (I'd have to do this for each user that unregisters).

    Has anyone ran into this...and if so, how did you get around it?  Seems like a pretty simple thing to me...register and the count goes down by one, unregister and it goes up by one.

    I've also monitored the workflows and none of them appear to be hanging or throwing any errors.

    Any ideas?

  • I have aquestion with this part of the workFlow that i hope youcan help me with.

    you did a new work flow with the information =

    Start with a new workflow called "Attendee registration," attached to Registrations

    in your second workflow step

    2)"Enforce seating policy"

      1. Conditions > Compare any data source (x2)

            1. If [fx :: Courses*, Total Seats] equals 0

    My question is when i use the  "Compare any data source" what information should i put into the fields.

    Example , say i attach my workflow to workDocuments. in the first step i assign a form to someone to review the document (approve/reject) ( i know sharepoint has its own Approve/reject for this examples lets stay with the form)

    in step 2 i want it to  be

    condition  > if Approve/reject equals  reject

    heres where my question comes in. to do this i cannot use "Compare workDocuments field " i have to use "Compare any data source". so i use this and it Brings up "Define Workflow Lookup" so i  select Source>Tasks  Field> Approve/Reject   then under Find the List Item  i select Field>Approve/Reject then there is a "Value" drop down box. i can leave blank or put in Approve , Pending or Reject. (Should i fill in a value and what does this do?) pressing the OK button bring up the Unique value warning press yes and next to condition it shows "If Taks:Aprrove/Reject equals value". Now i fill in Reject for Value and i get "If Tasks:Aprrove/Reject equals Reject" which seems like what i wanted originally, but it never seems to work out and i think it is from the question of the "Value" drop down box in the Define workflow Lookup.

    So could you help me with my example and could you give some incite as to what happens if i fill in different Values in the Value box of the Define Workflow Lookup.

    Desperate in need of help

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