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.
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!
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:
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:
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.
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:
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:
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!
In the first workflow, Instructor Reminder, you set a variable for the subject and then never use it. However, looking at the template, it appears you actually use that variable in the third step, Send reminder about course. Just thought I'd point that out...
Another great tutorial... looking forward to the next one!
Also, in the second workflow you never set the value for the variable Reminder Date 2. Shouldn't this be set to the course start time + 24 hours?
Great tutorial Sean!!!
One complaint I've had on the applications released in the past, is the lack of documentation on what all they do. Without your explanation I would've never even downloaded the application, but now that I know all that I can do with it I see the enormous value of the application, thanks for creating it and thanks for sharing with us how you did it.
One question, do you have any suggestions on how to add on-demand courses to the Courses Schedule?
Thanks in advance,
Well, the workflow designing seems to be nice and easy, but in my first attempts to realize some new ones to train myself on Sharepoint Designer and WSS 3.0 I suddenly came to a stop.
It happens on two points on Calendar events creation on which i'd like to get some help:
1) I'm trying to set the lasting of an event after i created it to a precise amount of time (e.g. 3 days). After making "Actions > Add Time to Date - Add 72 hours to [fx :: Current Item, Start Time] (Output to Variable: Reminder Time)" and "Actions > Set Field to Value - Set [fx :: Current Item, End Time] to [fx :: Workflow Data, Variable: Reminder Time]" i get an error from the WSS 3.0 installation.
2) I'd like to automatically create a Workspace related to a Calendar event the moment a certain user creates such event. How should i do?
Thanks for your help
CQuick: thanks for the catch in the "Instructor reminder" workflow, I'll add the correction (meant to use the "RE:" string in the second e-mail). As for "Attendee registration" Reminder Time 2 gets set to the related course's End Time in step 2.2.2. We use it later in e-mail for step 6.
Gus: appreciate the feedback! You could try setting a Start Time far into the past, and End Time far into the future - you'd then see the course span every calendar cell, so it would seem "dateless." Admittedly this is going to be clunky... the real problem is that Start and End Time are both required with the Calendar content type so you can't alter that at the app level. You may just want to make a separate list (i.e. On-Demand Courses) for those special cases, and adapt the dashboard views to include it (we'll get to the dashboard in a future installment).
Paolo: with your workflow, it sounds like what you're doing is correct but if you're still getting errors, try setting the End Time separately (similarly to the template, do all the date math in one step and then actually set the list item's fields in another step). As for Meeting Workspaces, I'll cover view customizations in the next article - the one you'll be interested in is Courses/NewForm.aspx, and setting a default value on the checkbox that we've hidden away. Stay tuned...
Thanks for this tutorial Sean, its given me the kick start I have been looking for (and has saved me buying webparts that I thought I required). I think that only were scratching on the surface of a very powerful tool. Keep at it, looking forward to the next tutorial.
Questions (hope theyre not too stupid):
What happens to the workflows that are "paused" (e.g. the reminder) if I have to re-boot the server?
The Action "Calculate ..." allows for very simple formulas (plus, minus etc.) Is there a way to perform more complex calculations using, for example, the formuals available on the WSS site. A simple example would be to calculate the working days between two dates.
Thanks in advance
Tubs: definitely good questions to ask. When a workflow is paused, it gets pushed down into the database (in this sleeping state, we often call the workflow "dehydrated") so a server reboot will not cause the workflow to lose its state. If the wake-up time passes while the server is still in the process of rebooting, the engine will kick start the workflow once the machine goes up again.
You're right in that our support for date math within workflow today is limited. You'd be best served by expressing the calculation you want in WSS (e.g. create a new column in your list, a calculated field type that does End Time - Start Time) and then using that column wherever you need the calculated value within your workflow.
I really like this template and I've implemented it at our College for employee training purposes. We have a department dedicated to Faculty & Staff training and they have many events "Courses" running every day. This used to cause a massive administrative burden for their secretary -- and with your magic I was able to reduce their workload immensely AND provide a central place for course documents an awesome archive for them. Many thanks!
I'm POSITIVE that I have permission settings wrong and this is causing workflows (triggered by registrations and UNregistrations) to fail. I see this message: "Failed on start (retrying)" for all registrations (except for users who have "Design" permission throughout the app). The description of the error is "Error updated a list item"; the outcome "Access Denied"; and the User ID is "System Account". (But this seems misleading...how can the system account be denied access, right?)
I've posted two messages in the Sharepoint newsgroups about this problem but haven't seen any responses:
My permissions break down like this:
- The "Employee Training Scheduling and Materials" template was created as a subsite and all our Active Directory users have "Read" permission throughout that subsite. (These registrations fail.)
- A small group of people who manage the training have "Design" permission. (These registrations succeed.)
- All site content inherits those permission settings except:
-- "Course Surveys" allows all users to "Contribute" in addition to "Read".
-- For "Registrations" and "Past Registrations" and "Tasks" I've given all users "Design" permission in addition to "Read" -- (I did this hoping that the workflows wouldn't fail, but they still fail.)
It would appear that the "Courses" list also requires 'more than Read' permission for all users, but how then can I prevent 'just any employee' adding an event to this calendar?
I realize this isn't a "forum" for questions, but perhaps in your next article you might address the matter of "Permissions" and how to configure the tools in this template to ensure that the workflow works properly.
Again...thanks for a great app -- and I'm totally enjoying WSS.3
It seems it doesn't want to work... :(
Even if i separate in different steps the "+3 days" calculation from the variable to field assignment it keeps giving me errors.
I tried it on different installations but the problems keeps happening.
I even tried to set the value to a string field. Then i found the value in "dd/mm/yyyy hh.mm.ss" format instead of "dd/mm/yyyy hh.mm" but i don't think this should be a problem. Or is it?
Thanks again for your help
David: it's great to hear the template site is working out for you overall. I apologize that we didn't get to your newsgroup posts right away, commenting on the blog is a better venue for drawing the attention of the product team...
One of our limitations today is that SPD workflows always run in the context of the initiator, so although the workflow engine itself has a system account we lock it down based on the perms of who triggered the workflow to start. So yes, you'll get errors if people don't have perms to any list that "Attendee registration" touches along the way, because there is an action that updates the seating count in Courses.
So once you open the floodgates, how do you stop Joe Schmoe from adding his own courses? My suggestion would be to add a whitelist step to the existing Courses workflow - basically, before you do anything else, check Created By == GoodUser1, GoodUser2... and if the check fails, delete the current list item. You may be able to do something similar with Modified By to prevent drive-by edits, but realistically you should just turn on versioning to leave a "paper trail" on the list.
Paolo: sorry you're still having trouble. Our support folks will probably be more helpful than me at this point - the blog is not the best place for in-depth technical support. It sounds the problem you're hitting is rather specific.
Is it possible to have an action copy an item from one list to another located in a sub-site? If this is not a native capability, has anyone seen any extensions that might allow it?
now how the hell do u subtract days from time in the workflow, i see that there is add time to days but no subtract or is there a workaround. I require to workout a start date and an end date from a final date.
thsi is what im trying to achive and was hoping i could do it with the workflow designer.
Im a newbie to sharepoint and am in desperate need of help! My project is to dev an asp.net page that uses a final date date picker in order to assign a start date and an end date to a group of users. When the final date is picked; each user in the list must have their start and end date changes or updaated based on the final date and the amount of days they each have allocated to do the task.
So if X's allocated days = 5 and the final date picked = 2007/04/28, his start date must be assigned as the 2007/04/23 and end date will = the final date.
The next step is to assign the next user's start and end date based on the first users start date. ie: the second user has been allocated 7 days to complete task therefore would need to take x's start date - 7 days: so must change or update the second users start date to the 2007/04/16 and then add or update the second users end date to 2007/04/16 + 7 = 2007/04/23
The start and end date apply only to the users, the final date is global. The total of days allocated for all users should = 12 days at this point between the final date and the last start date
Rcrouch: currently, SPD workflows can only operate within the context of your current site. To do something cross-web you'll need to develop your workflow in Visual Studio.
Troyson: the Add Time to Date action accepts negative values (like step 1.1 of "Instructor reminder" in the walkthrough). If you need additional assistance, try soliciting the SharePoint newsgroups on microsoft.com for advice specific to your scenario. The Design and Customization forum would be a good place to start.
Solved by updating (or setting) the [fx :: Current Item, Start Time] to itself after setting [fx :: Current Item, End Time] to the variable.
This has no explanation to me, but it worked, in some ways... ;)
When is the next installment? I'm really interested in how you changed the associated forms for each list since they all render XSLT transform errors and it is difficult to understand how these things are being handled.