Stump the Panel » Business Problem Statements: SharePoint Designer Workflow Solutions

Calculated challenge of the day- any brave souls up for it?

(37 posts)
  • Started 2 weeks ago by NancyCentury
  • Latest reply from Nelson
  1. NancyCentury
    Member

    Thank you, intrepid Panel Stumpers.

    I actually lost sleep over this, and it's driving me insane. I apologize for the long post, but please help if you can!

    Scenario:

    My project list users want to see an easy, at-a-glance indicator (colored dot) on the project list when their project is getting close to the due date.

    They call the "close to the due date" period the "critical" period, and its length depends on the length of the project period. If it's a standard project, the critical period is 3 days long; for rush projects it's all considered "critical."

    The length of the project period is represented in a calculated column called "Target Days" that subtracts due date from start date to display the number of days allotted for the project. For the 2 project types- RUSH and STANDARD- the Project type is based off the Target Days value as follows:

    If Target days is less than 4, Type = RUSH ; Else Type = STANDARD.

    The Type value is achieved via calculated column, which looks like this:
    =IF([Target days]>3,"Standard","Rush")

    Other calculated columns have already produced other values based off the due date:
    Critical1 = due date -1 day (the date of the day before the project is due)
    Critical3 = due date -3 (the date of the day three days before the due date)

    The goal of all this is:

    A colored indicator shows green, yellow or red depending on whether the project is in the "critical period." The indicator is driven by a column called "Timing," a choice column whose values are:
    (1) Late
    (2) Critical
    (3) On Time

    My problems:

    1) Allowing for changes to the due date.
    Initally, I set up a workflow to run on creation that set the Target Days and related Project Type, and used a series of "Pause" actions that changed the Timing value accordingly to maintain the color of the Indicator in real time.

    For example, on creation, if Project type = Standard, the Timing value = On time (Indicator clor = green). Due Date = July 14, therefore Critical3 = July 11th.

    Action: Pause until Critical3; Action: Set Field in Current Item = change the "Timing"value to "Critical." The Indicator color will change accordingly to a yellow dot.

    Action: Pause until Due date. If the status not equals Completed: Action: Set Field in Current Item to change Timing value to "Late." Indicator color changes to a red dot.

    (The process is the same if the Project type = Rush, except the workflow first pauses til Critical1, then pauses til Due Date, then checks status.)

    This works fine EXCEPT- if, at any time, a user changes the Due date- it all breaks.

    I can't prohibit them from changing due dates, since that happens a lot if the project scope changes. The calculated columns will all reset off the new due date, BUT the running workflow will not update itself. It will continue pausing according to the original set of dates. So the users are not seeing updated Indicator colors.

    I set up an "On Change" workflow to evaluate whether the due date had changed and to re-initiate the pausing action, but it simply ran parallel to the original workflow and resulted in 2 sets of actions and much confusion.

    I though perhaps if the Timing column's value could somehow be calculated by comparing the current date to the Critical and/or due dates.... that might take care of it. But I could not make that work, despite my prayers to Dessie's altar.

    If any of this has made sense to you- I am amazed. If you have a better way in mind for me to achieve this process, PLEASE let me know. I'm going nuts.

    Posted 2 weeks ago #
  2. I understand your problem, it's the dreaded pause until a certain time then do something scenario. It works all well and good until the time changes. The other problem is you can't have one workflow stop or directly influence another one so doing anything on change doesn't solve the initial problem.

    Just thinking out loud here, but what if you stored the initial due date in a workflow variable. You pause the workflow until this date. The next step in the workflow looks up the current due date, if that equals the stored value, continue on with your work flow. If it is different, update the variable to this new due date and pause until then.

    Posted 2 weeks ago #
  3. NancyCentury
    Member

    I do have a column capturing the original due date- it's called "copy due date" and is populated in an action during the "on create" workflow. I was using in an "on change" workflow that only ran if the due date did not equal the copy due date (indicating that the due date had changed).

    I understand what you are saying, though- to create that copied date in a WF variable, vs in a site column- if I then build in a condition to check the current due date against that WF variable every time I reach an action that needs to update one of the status/timing fields, and stop or skip a step IF those values did not match... or add a new due date variable that captures the new due date each time it changes... hmmm.

    Argh.

    Which is the most likely scenario- 1) that an item's due date will remain unchanged OR 2) that an item's due date will change?

    If I know which scenario will be most likely to occur, I should build workflows that will best accomodate THAT scenario.

    I started with the premise that I should build for Scenario 1. That meant- an "on create" workflow that followed the example in my original post, assuming that the only steps needed were ones that ssumed an unchanged due date ALONG WITH Then an "on change" workflow that checked for a change in the due date and responded accordingly (with the resulting problems I described). Something tells me that this model will only result in more problems, since it's not very flexible. (The "responded accordingly" part was what I can't make work.)

    If instead I start from the premise that scenario 2 is most likely/most realistic, and use a more complex series of steps that use your idea- building in conditions that check the current due date against a variable that tells me if it's changed since the pause period began- it could work.

    I just have a hesitancy about relying on pauses, especially if the project period is long. That requires the workflow to be rock-solid in terms of what can/can't affect the fields driving the establishment of the pauses and their resulting actions. PRESSURE!! :-)

    The workflow would have a "primary track" whose steps assume the original scenario of no changes. But hte alternate conditions/steps must allow for multiple due date changes (because there's no way to force a limit on how often they could change it).

    What about an "on change" secondary WF that fires if the original WF stops (triggered by one of the due date change conditions being true)? I have a field called "Processed" that could be used to set a status of yes, no, etc. in this case. The appropriate value which could then trigger the secondary WF to start?

    My mind, she is blown.

    Posted 2 weeks ago #
  4. Nancy,
    I think we can do this without any workflows by just using calculated columns and a bit of javascript. Let me play with it a little and see if I can come up with a simple approach.

    Can you post back with the schema of your list so I can duplicate it? (List out each column with its type and return value type, along with any calculated columns and formulas)

    Thanks,

    - Dessie

    Posted 2 weeks ago #
  5. NancyCentury
    Member

    There are so many hidden fields and some of the formulas are very long... I will have to find time to work on that.

    Posted 2 weeks ago #
  6. No problem - whenever you get a chance (anything you can provide would be helpful in coming up with a solution).

    - Dessie

    Posted 2 weeks ago #
  7. NancyCentury
    Member

    I have been testing various workflow scenarios all day and nothing is working.

    The premise from which I am operating requires real-time evaluation of one or more date fields against the current date, and the only way I know how is to use the action "pause until [date]" and then a set of conditions/actions to check if the current date matches the date field in my item.

    BUT- if the due date changes while the item is paused, that change (and the corresponding calculated column date-field changes) are not being reflected correctly in the item.

    I remain convinced that the only way to make this model work is to somehow be able to evaluate whether the due date has changed; and if so, to terminate the currently running workflow so a new workflow can start which will re-set all the date values and start the pausing sequence over again.

    However, short of deploying custom actions, I do not know how to terminate one workflow from within another workflow.

    Posted 2 weeks ago #
  8. NancyCentury
    Member

    Dessie,
    I had some inspiration on this today.

    I have changed several list columns whose values were either choice options or workflow-driven values to calculated columns. This has eliminated the need to monitor the list items for changes and reflect them in the list, eliminating the pause actions and the concern over "what to do if a user updates an item while it's paused?"

    So that is a good thing!

    Here is the remaining issue...

    The Indicator column (calculated column) pulls a value from the Timing column.

    If Timing = On Time, the Indicator produces a green dot; if timing = Critical, a yellow dot; if Timing = Late, a red dot.

    Timing is now a calculated column that compares Today's date to one of several calculated date columns that relate to the Due Date value, as well as Due Date itself:

    Critical1 = due date minus 1 day (the date of the day before the due date)
    Critical2 = due date minus 2 days (the date two days before the due date)
    Critical3 = due date minus 3 days (the date three days before the due date)

    So, all these calculated columns now feed off each other, for example:

    • If Today = Critical1, Critical2 or Critical3, Timing = Critical and Indicator displays yellow dot
    • If Today= Due Date, Timing = Late and Indicator displays a red dot
    • If Today = any other date, Timing = On time and Indicator displays a green dot

    so...

    THE CATCH part 1-

    [Today] is a list column that gets its value from me running this manual workflow:

    Action: Create a date/time variable called [variable:today].
    Action: set [variable today] to current date.
    Action: Set field in Current Item: Set [Today] to [Variable:today]

    THE CATCH part 2-
    I can't possibly run a manual workflow on every single list item EVERY DAY to update the value in the Today field. !!!!

    How can I get this field to update itself every day?

    (idea courtesy of this question: http://www.endusersharepoint.com/STP/topic/current-date-field)

    Posted 2 weeks ago #
  9. Nancy,
    Try this...

    Using Javascript, you can get the current date and set a calculated column to render that value, then use it in your calculations in other columns.

    The idea is essentially to create a calculated column containing the formula:


    ="<DIV id='Today'></DIV>"

    This will render an empty "div" tag with a fixed ID of "Today".

    We'll then use Christophe's "HTML Calculated Column" script to render the contents of that calculated column as html on the page (add to a CEWP):


    <script type="text/javascript">
    //Christophe's Render HTML script - stripped down to minimum needed
    var theTDs = document.getElementsByTagName("TD");
    var i=0;
    var TDContent = " ";
    while (i < theTDs.length)
    {
    try
    {
    TDContent = theTDs[i].innerText || theTDs[i].textContent;
    if ((TDContent.indexOf("<DIV") == 0) && (TDContent.indexOf("</DIV>") >= 0))
    {
    theTDs[i].innerHTML = TDContent;
    }
    }
    catch(err){}
    i=i+1;
    }
    </script>

    Next, we'll add the script to get the current date and write it to the empty "div" tag (again, add to the CEWP):


    <script type="text/javascript">
    var d = new Date();
    var myDate = parseInt(d.getMonth()+1).toString()+"/"+d.getDate().toString()+"/"+d.getFullYear().toString();
    var tbls = document.getElementsByTagName('table');
    for(var i=0; i<tbls.length; i++)
    {
    if(tbls[i].id != "")
    {
    if(tbls[i].getAttribute('summary') != "")
    {
    var divs = tbls[i].getElementsByTagName('div');
    for(var j=0; j<divs.length; j++)
    {
    if(divs[j].id == "Today")
    {
    divs[j].innerHTML = myDate;
    }
    }
    }
    }
    }
    </script>

    Finally, we can reference the calculated column containing the "div" tag in other formulas in order to compare them to the current date (the "Today" date).

    Let me know if this works for you or not (I had this running in test list awhile back and it updated each day).

    - Dessie

    Posted 2 weeks ago #
  10. NancyCentury
    Member

    So if I read you correctly- this only works if the ="<DIV id='Today'></DIV>"
    field is present on a page which also holds a CEWP containing the script?

    Posted 2 weeks ago #
  11. Yes - the script embedded in the CEWP will cause the contents of the calculated column (the "div" stuff) to be rendered on the page as actual html (which will then be populated with the current date), so if you plan on using this on multiple "View" pages, you'll need to add the CEWP and script to each one.

    - Dessie

    Posted 2 weeks ago #
  12. NancyCentury
    Member

    Will the "Today" value then be available in workflows etc?

    Posted 2 weeks ago #
  13. Yes - you should see your calculated column show up in an SPD workflow.

    - Dessie

    Posted 2 weeks ago #
  14. NancyCentury
    Member

    I tried this and ran into some questions:

    Are the 2 pieces of code you provide intended to be used in 2 different CEWPs? I am not clear on that.

    Also, should the calculated column that contains ="<DIV id='Today'></DIV>" be a date/time field or a single line of text?

    Posted 2 weeks ago #
  15. Nancy,
    The calculated column containing the "div" html is just a "Single line of text" column (nothing special) since we want the script to render out its contents as plain html (think of it as a "placeholder" for a date value which we'll write into it later using script).

    Both of the scripts can go in the same CEWP (both in the same "script" tag block if you'd like, I had just separated them at the time I wrote them out).

    Does this help?

    - Dessie

    Posted 2 weeks ago #
  16. NancyCentury
    Member

    Worked perfectly. My column using this method is called "Today2".

    Now- I need to make sure I undrestandhow this will work in relation to my other calculated columns.

    Right now, my "Timer" column formula (the one that displays the indicator color for hte item,you helped me on it yesterday) is:

    =IF([Due Date]=Today,"(1) Late",IF(OR(Critical1=Today,Critical2=Today,Critical3=Today),"(2) Critical","(3) On Time"))

    The "today" value used in that formula comes from another date/time column that I am entering the current date into manually.

    How do I now use the new column's value in that formula? Can I just change each reference to "today" to "today2"?

    Is is a problem if the "today" column is a date/time type and "today2" is a lne of text?

    Posted 2 weeks ago #
  17. Nancy,

    Your best bet is to just create a new "test" column and enter in the same formula, but reference "today2" instead of "today" - this way you can see if it will work without breaking anything existing.

    Realistically, you shouldn't have to do anything other than just update the reference (the system is pretty good at recognizing if a string value is a date and using it as such in formulas), but the test column should indicate this for you. If you need to convert it to an actual date value, there are functions to do this for you that shouldn't be to difficult to implement (just "string formatting" stuff really).

    Give it a shot and post back with the results (this sounds like a really interesting project you're working on).

    - Dessie

    Posted 2 weeks ago #
  18. NancyCentury
    Member

    Originally, the Timer column referenced the Today column. So I created a new column called Timer2 whose formula references Today2.

    The result of that calculation must then be picked up by a third calculated colun, called "Indicator," whose formula produces the corresponding colored shape. I changed this formula to reference Timer2 instead of Timer.

    ="<img style='float:left;' src='/_layouts/images/KPIDefault-"&(3-RIGHT(LEFT(Timer2,2),1))&".gif'/>"

    My result was all green dots, meaning the formula in Timer2 was producing the default result in every case.

    It seems like the actual date value being produced in Today2 (via the script) is not being recognized as a date within the list, in these other formulas?

    Posted 2 weeks ago #
  19. Hmm...I think the problem lies in that the value being displayed for the "Today" date is just in the browser (due to manipulation of the field with the script), but the actual value of the column is still just the empty "DIV" tag, which is being used in the calcluations instead of the "rendered" date (the calculations are being accomplished in the backend using the "actual" value, not the "displayed" value represented in the front-end).

    So...the only "real" (as far as I can tell) to make this work, would be to move the calculations to the script as well, then just render out the final html displaying the colors.

    Does this sound like a workable approach to you as well?

    - Dessie

    Posted 1 week ago #
  20. NancyCentury
    Member

    I think I understand what you are saying... I just don't know how that would be rendered.

    I also wondered if it would be possible to add another calc. column whose default value would be the value of the Today2 column... but if the true value recognized by the system is the div tag tex, not the date itself, that still will not work.

    It's frustrating to have a way capture of the current date (at least "visually"), yet ot able to do anything with it. :-(

    Posted 1 week ago #
  21. I'm playing with another script that will (hopefully) get you what you need...I'll post it up once I get the bugs worked out.

    - Dessie

    Posted 1 week ago #
  22. NancyCentury
    Member

    Thank you!!!

    Posted 1 week ago #
  23. Nancy,
    Ok, I have a working model that seems to do what you're looking for.
    You'll have to adapt it for your list, but as a test to see how it works you can do the following:

    Create a new custom list (this is just to test the functionality).
    On the list, create the following three columns:

    1. "Due Date"
    Date and Time column set for "Date only".

    2. "Today's Date"
    Calculated column with a return type of "Single line of text".
    Enter in the formula:

    ="<DIV class='"&TEXT([Due Date],"MM/DD/YYYY")&"Today'> </DIV>"

    3. "Indicator"
    Calculated column with a return type of "Single line of text".
    Enter in the formula:

    ="<DIV id='"&TEXT([Due Date],"MM/DD/YYYY")&"'> </DIV>"

    Next, add a CEWP to the default list page and add in the following script:


    <script type="text/javascript">
    //--Christophe's Render HTML script-->
    var theTDs = document.getElementsByTagName("TD");
    var i=0;
    var TDContent = " ";
    while (i < theTDs.length)
    {
    try
    {
    TDContent = theTDs[i].innerText || theTDs[i].textContent;
    if ((TDContent.indexOf("<DIV") == 0) && (TDContent.indexOf("</DIV>") >= 0))
    {
    theTDs[i].innerHTML = TDContent;
    }
    }
    catch(err){}
    i=i+1;
    }
    //-->

    //--Working with Today's Date-->
    var d = new Date();
    //Today's date (current date)
    var todaysDate = parseInt(d.getMonth()+1).toString()+"/"+d.getDate().toString()+"/"+d.getFullYear().toString();
    var tbls = document.getElementsByTagName('table'); //All tables

    for(var i=0; i<tbls.length; i++) //Loop through all tables
    {
    if(tbls[i].id != "") //Only look at tables with ID's
    {
    if(tbls[i].getAttribute('summary') != "") //The table we want will have a "Summary" attribute
    {
    var nobrs = tbls[i].getElementsByTagName('nobr'); //All Date fields (Due Date column) in our table
    for(var x=0; x<nobrs.length; x++) //Loop through date fields
    {
    var divs = tbls[i].getElementsByTagName('div'); //All divs in our table
    for(var z=0; z<divs.length; z++) //Loop through all divs
    {
    //Compare "Due Date" value with DIV "Class" name (looking at "Today's Date" column)
    if("0"+nobrs[x].innerText+"Today" == divs[z].className || "0"+nobrs[x].textContent+"Today" == divs[z].className)
    {
    //Set DIV value to current date
    divs[z].innerText = todaysDate;
    divs[z].textContent = todaysDate;
    }
    //Compare "DIV" id with "Due Date" column (this will be the "Indicator" column)
    else if(divs[z].id == "0"+nobrs[x].innerText || divs[z].id == "0"+nobrs[x].textContent)
    {
    var tDate = new Date(todaysDate); //Current Date
    var temp = nobrs[x].innerText || nobrs[x].textContent;
    var dDate = new Date(temp); //Due Date

    //Logic to set 2-digit month with leading zero if needed
    var mVal="";
    if((dDate.getMonth()+1)<10)
    {
    mVal="0";
    }
    var dVal="";
    if(dDate.getDate()<10)
    {
    dVal="0";
    }

    var one_day=1000*60*60*24 //Millisecond value for date comparison
    var dateCalc = Math.ceil((tDate.getTime() - dDate.getTime())/one_day); // date difference

    if(dateCalc > 0) //Due date is in the past
    {
    document.getElementById(divs[z].id).parentNode.innerHTML = "<DIV style='font-weight:bold; font-size:24px; color:red;'>•</DIV>";
    }
    else if(dateCalc == 0) //Due date is today
    {
    document.getElementById(divs[z].id).parentNode.innerHTML = "<DIV style='font-weight:bold; font-size:24px; color:red;'>•</DIV>";
    }
    else if(dateCalc == -1) //Due date is tomorrow
    {
    document.getElementById(divs[z].id).parentNode.innerHTML = "<DIV style='font-weight:bold; font-size:24px; color:yellow;'>•</DIV>";
    }
    else if(dateCalc == -2) //Due date is in 2 days
    {
    document.getElementById(divs[z].id).parentNode.innerHTML = "<DIV style='font-weight:bold; font-size:24px; color:yellow;'>•</DIV>";
    }
    else if(dateCalc == -3) //Due date is in 3 days
    {
    document.getElementById(divs[z].id).parentNode.innerHTML = "<DIV style='font-weight:bold; font-size:24px; color:yellow;'>•</DIV>";
    }
    else //Due date is in the future and is not critical (more than 3 days out)
    {
    document.getElementById(divs[z].id).parentNode.innerHTML = "<DIV style='font-weight:bold; font-size:24px; color:green;'>•</DIV>";
    }
    }
    }
    }
    }
    }
    }
    </script>

    I've included some comments in the script to help explain things, but in a essentially it performs the following tasks:

    1. Christophe's "Render HTML" script is added to render the values of the calculated columns to the page.
    2. Create a date variable equal to the current date (this will update each day automatically).
    3. Find the table on the page that we want to work with.
    4. Find all of the "Due Date" column values.
    5. For each one, loop through the div tags and find the ones that have a matching "Class Name" (the class name will equal the "Due Date" value plus the literal text "Today").
    6. For each one found, update its (the DIV) value to today's date.
    7. For each of the other divs (the ones that dont match the "Class Name" check), compare the div's "ID" to the "Due Date" value (they should match).
    8. If they match, perform the date comparison logic and display the colored indicator based on the result.

    It does seem to work fine, but there are a couple caveats to how it works. Right now, it does need to have the "Today's Date" column visible on the page, but could be modified to skip this since the value is persistent throughout the code. Also, (and this is one of the concerns when moving your logic and calculations to JavaScript) since we're performing all of the comparison logic in the script itself, the values we arrive at wont really be able to be used with other calculated columns since its all being performed at the client-side instead of at the server-side (as I mentioned in a revious comment).

    Play with it some and let me know if this will work for your needs (I may/should write this up into a blog article if it does).

    - Dessie

    *Edited post to include cross-browser version of the script - pasted in wrong one initially.

    Posted 1 week ago #
  24. NancyCentury
    Member

    I plan to tackle this tomorrow- thank you so much for such a lot of help.!!!

    Posted 1 week ago #
  25. NancyCentury
    Member

    Your solution worked perfectly with my test list/test page.

    Some thoughts:
    In my original list, I needed calcuated columns to calculate the dates that constitute the "critical" period (1-3 days prior to the due date) as columns in the list itself, then have other calculated columns evaluate those values and reflect their values via formulas and workflows.

    Your solution only requires one date field- due date- and the script present in the page does all the work of evaluating the "critical period" and presenting the indicator accordingly, as long as the due date field, the indicator column and the script are present in any page or list view of my Project List.

    I don't need to worry about the 3 "critical date" calculated columns, the "timing" calculated column, or my "indicator" calculated column... or the need to ensure that "today" as an actual date value is present. I also will not need any workflows to set variables or pause til a particular date.

    While it doesn't solve the one issue- how to get TODAY as an actual date value into the list (which I still really want to be able to do)... it IS a solution that is a more than acceptable trade-off to the need to visit my list every date and manually change "today" to the current date, which is what I have been doing.

    I only have one further request....

    Depending on the value present in the Timer column, my original Indicator formula returned the traditional KPI-style indicators (green circle, red diamond, yellow triangle):

    = "<img style='float:left;' src='/_layouts/images/KPIDefault-"&(3-RIGHT(LEFT(Timer,2),1))&".gif'/>;

    Is there any way to incorporate that indicator style into your script vs. the basic 24pt colored dot?

    THANK YOU!!!

    Posted 5 days ago #

RSS feed for this topic

Reply »

You must log in to post.