ColdFusion TIPS PLUS
Issue 00143 http://www.cftipsplus.com
I. My CommentsII. ColdFusion In Context: Meeting Schedule
By R. Martin Ladner
martin.ladner@charter.net
III. Identification Session Variables and Url Tokens: A Bad Mix
by Jonathan Schwarz
schwarz_jonathan@hotmail.com
I. Comments:
Just in case you did not see them I have Pictures up of my youngest son, Anthony when I had him with me at work. He likes to play on the XBox and he has Finished Star Wars Jedi Knight Jedi Academy.
Check out his photos
http://www.milforddel.com/photos/index.cfm
Also for those of you who live near me check out the Cake Website I made for my wife.
http://www.nsnd.com/cakes/
P.S. Send me an email of any websites you think show great Usability and still use graphics. What websites do you have that you think teach good web design.
Keep Coding,
Nathan Stanford
http://www.cftipsplus.com
If you have suggestions for articles send them to us.
If you would like to write for cftipsplus.com
send us an email to:
NathanS<at>nsnd.com
IF YOU WANT TO BE AN AUTHOR SEND IN YOUR COLDFUSION TIPS.
Remember this is a great way to get your name known in the
ColdFusion Community.
II. ColdFusion in Context: Meeting Schedule
By R. Martin Ladner
martin.ladner@charter.net
Suppose you have weekly meetings to which multiple people contribute in a structured manner. You could pass around a spreadsheet in E-mail. However, not everyone has the same version of the same spreadsheet program, and by the time you route a document through a dozen or more people, it's time for the next meeting. Here's a better way.
Meeting Structure
Toastmasters International has thousands of clubs worldwide. Club meetings have a predictable set of roles. Some of those roles are the meeting organizer known as the Toastmaster of the Day (TMOD or TM of Day for short), impromptu speaking organizer known as the Topics Master, and prepared speakers. Speakers deliver topics of their own choosing to meet objectives within a time limit (often 5-7 minutes) in various project manuals. To know how much time is available for speaking without any advance notice (an exercise known as Table Topics), we need to know how long the speeches are expected to take. Members need to be able to indicate when they will be away; so, they won't be scheduled to participate. Special events sometimes preempt or dictate speeches. Notes on future events sometimes encourage participation beyond the club level and provide ideas for speeches. This is the background for the example that follows.
Make a Table
Make a table named Meeting. Give it a MeetingID field and tell the database engine to automatically increment the field. Give it a MeetingDt field and give it a date type. Give it the following text fields; suggested lengths are shown in parentheses: Event (30), TMOD (15), TopicsMaster (15). Also Speaker1 (15), Length1 (8), Manual1 (30), Nr1 (5), Title (35). Repeat this group, adding Speaker2, Length2, Manual2, Nr2, and Title2. Because this is a lunchtime club, two speeches is about all you normally have time for. For an evening club, you would make provision for a third speech; a weekend club might provide even more opportunities for prepared speeches. Finally, add two more fields: Away (150) and Notes (150). (Don't call it Note; some engines are picky about this.)
Then use your database engine GUI to add six months or so. It's not a big deal. You only need to fill in the date. (You'll probably code a short page to do this after you've used this idea for a while.)
Overview
The user can see and update the entire schedule at once. If the form has been submitted, the page's first task is to loop through a list of record IDs provided in a single string in the form. For each ID, it will update the associated record. For example, it will set Speaker1 to the contents of a field named Speaker1_{ID number}. The thing that makes this possible is that for each ID, the code that builds the form adds the ID to a list of IDs and automatically creates all the fields needed to update the record identified by the ID.
Update
All code goes in meeting.cfm. Begin by checking to see if the user wants to exit the form. If so, change the location to a page of your choosing. If the user wants to save the form, begin to loop through a list of record IDs provided by the form. When creating field names on the fly, it's awkward to use them in the same tag that creates them. Therefore, for simplicity, create a structure whose keys are the column names for this table, assign the new field names as their values, and then use the evaluate function to get the real values. For example, set chg.Speaker1 to form.Speaker1_#ID#, then set chg.Speaker1 to evaluate(chg.Speaker1). Although you could use cfset for these assignments, it takes less typing for the programmer to use a cfscript tag.
It's tempting to delay the evaluate step until you're inside the query, but if you do this, the evaluate function will produce a raw string that ColdFusion will assign the string to the database "as is". We rely on ColdFusion to automatically escape single quotes when assigning variables to a database. So, do yourself a favor and create the variable first as shown here; so, ColdFusion can do the right thing during the query. (If you don't, the meeting schedule will break sometime around Secretaries' Day.) It's mechanical and therefore easy; just do it.
<cfparam name="form.Exit" default="">
<cfif len(trim(form.Exit))>
<cflocation url="menu.cfm">
<cfabort>
</cfif>
<cfparam name="form.Save" default="">
<cfif len(trim(form.Save))>
<cfset chg=structNew()>
<cfloop list="#form.IDList#" index="ID">
<cfscript>
chg.TMOD="form.TMOD_#ID#";
chg.TMOD=evaluate(chg.TMOD);
chg.Event="form.Event_#ID#";
chg.Event=evaluate(chg.Event);
chg.TopicsMaster="form.TopicsMaster_#ID#";
chg.TopicsMaster=evaluate(chg.TopicsMaster);
chg.Speaker1="form.Speaker1_#ID#";
chg.Speaker1=evaluate(chg.Speaker1);
chg.Length1="form.Length1_#ID#";
chg.Length1=evaluate(chg.Length1);
chg.Manual1="form.Manual1_#ID#";
chg.Manual1=evaluate(chg.Manual1);
chg.Nr1="form.Nr1_#ID#";
chg.Nr1=evaluate(chg.Nr1);
chg.Subject1="form.Subject1_#ID#";
chg.Subject1=evaluate(chg.Subject1);
chg.Speaker2="form.Speaker2_#ID#";
chg.Speaker2=evaluate(chg.Speaker2);
chg.Length2="form.Length2_#ID#";
chg.Length2=evaluate(chg.Length2);
chg.Manual2="form.Manual2_#ID#";
chg.Manual2=evaluate(chg.Manual2);
chg.Nr2="form.Nr2_#ID#";
chg.Nr2=evaluate(chg.Nr2);
chg.Subject2="form.Subject2_#ID#";
chg.Subject2=evaluate(chg.Subject2);
chg.Away="form.Away_#ID#";
chg.Away=evaluate(chg.Away);
chg.Notes="form.Notes_#ID#";
chg.Notes=evaluate(chg.Notes);
</cfscript>
When you have defined every column of the row to be updated, run the update query. Because the structure contains values, the update is easy. For example, set Speaker1 in the table to '#chg.Speaker1#'. When you have done this for all columns, clear the structure so it can be used again, end the loop, and end the cfif statement. The where statement deserves special mention. It's good practice to assign the value of numeric variables to the database so that specially hacked ID strings don't do more than you bargained for.
<cfquery name="updMeeting" datasource="ic">
update Meeting set
TMOD = '#chg.TMOD#',
Event = '#chg.Event#',
TopicsMaster = '#chg.TopicsMaster#',
Speaker1 = '#chg.Speaker1#',
Length1 = '#chg.Length1#',
Manual1 = '#chg.Manual1#',
Nr1 = '#chg.Nr1#',
Subject1 = '#chg.Subject1#',
Speaker2 = '#chg.Speaker2#',
Length2 = '#chg.Length2#',
Manual2 = '#chg.Manual2#',
Subject2 = '#chg.Subject2#',
Nr2 = '#chg.Nr2#',
Away = '#chg.Away#',
Notes = '#chg.Notes#'
where MeetingID = #val(ID)#
</cfquery>
<cfset dummy=structClear(chg)>
</cfloop>
</cfif>
Show and Get Entries
To fill the meeting schedule, query the table and create a form from the query. In the query, order by the meeting date (MeetingDt). Define the form action as this same page; set the method to post. Open a table, give it a border, reduce cell padding, and tell the table to use just 2/3 of the window width. Why? Because users will probably want to print the result (once anyway), and therefore it has to fit the printer. Empty an ID list (IDList). Begin to output the query.
<cfquery name="dayList" datasource="ic">
select * from Meeting
order by MeetingDt
</cfquery>
<form name="Meetings" action="meeting.cfm" method="post">
<table border="1" cellpadding="0" width="65%">
<cfset IDList="">
<cfoutput query="dayList">
Append the current MeetingID to IDList. Set a data cell to span the table. If this is the first week in the month (date less than 8), display the date followed by submit buttons: one to save the entire form and one to exit the form. If it is not the first week in the month, display just the date. Precede the the date with slashes slanting up and follow it with slashes slanting down to suggest a peak or a folder tab to get across the idea that associated data is below rather than above the date.
<cfset IDList=listAppend(IDList,MeetingID)>
<tr><td colspan="6">
// #dateFormat(MeetingDt,"d mmm yy")# \\
<cfif datePart("d", MeetingDt) lt 8>
<input type="submit" name="save"
value="Save entire schedule: refresh">
<input type="submit" name="exit"
value="Ignore unsaved changes: exit">
</cfif>
</td></tr>
Put TMOD, Event, and TopicsMaster fields in the same cell, spanning the table. Set the size of the fields to something appropriate based on anticipated font size, the length of the typical entries you expect, the appearance of the page, and the maximum length. Set the maximum length to match the column length (or less if you're still experimenting; you can fix the database later).
<tr><td colspan="6">
TMOD:
<input type="text" name="TMOD_#MeetingID#"
value="#TMOD#" size="13" maxlength="15">
Events:
<input type="text" name="Event_#MeetingID#"
value="#Event#" size="30" maxlength="30">
Topics Master:
<input type="text" name="TopicsMaster_#MeetingID#"
value="#TopicsMaster#" size="13" maxlength="15">
</td></tr>
Set a header and provide the speech information in tabular form. In the header, use two pound signs, not one, as the abbreviation for number. ColdFusion will convert this to a single pound sign for display. Again, set sizes for appearance (within the column length), and set maxlength to the length of the column.
<tr><td>..</td><td>SPEAKER</td><td>TIME</td>
<td>MANUAL</td><td>##</td><td>SUBJECT</td></tr>
<tr><td>1st:</td>
<td><input type="text" name="Speaker1_#MeetingID#"
value="#Speaker1#" size="13" maxlength="15"></td>
<td><input type="text" name="Length1_#MeetingID#"
value="#Length1#" size="4" maxlength="5"></td>
<td><input type="text" name="Manual1_#MeetingID#"
value="#Manual1#" size="26" maxlength="30"></td>
<td><input type="text" name="Nr1_#MeetingID#"
value="#Nr1#" size="1" maxlength="1"></td>
<td><input type="text" name="Subject1_#MeetingID#"
value="#Subject1#" size="37" maxlength="35"></td></tr>
<tr><td>2nd:</td>
<td><input type="text" name="Speaker2_#MeetingID#"
value="#Speaker2#" size="13" maxlength="15"></td>
<td><input type="text" name="Length2_#MeetingID#"
value="#Length2#" size="4" maxlength="5"></td>
<td><input type="text" name="Manual2_#MeetingID#"
value="#Manual2#" size="26" maxlength="30"></td>
<td><input type="text" name="Nr2_#MeetingID#"
value="#Nr2#" size="1" maxlength="1"></td><td><input type="text" name="Subject2_#MeetingID#"
value="#Subject2#" size="37" maxlength="35"></td></tr>
The Away field and Note textarea each get their own row, with the data spanning most of the table. "Note" in its singlar form tells the user to do something. Hence, it is used in the singular form as a label. However, the underlying column name is plural to avoid database engine conflicts. (The label and column should usually have the same basic name; this is a rare exception to this principle.) Set the size and maxlength as appropriate.
<tr><td>Away:</td>
<td colspan="5"><input type="text"
name="Away_#MeetingID#" value="#Away#" size="98" maxlength="110"></td></tr>
<tr><td>Note:</td>
<td colspan="5"><textarea name="Notes_#MeetingID#"
cols="75" rows="2" wrap="virtual">#Notes#</textarea>
</td></tr>
Wind down. Close the output. Close the table. Set a hidden field equal to the IDList that you've building while looping through the query for display. (Remember to surround the variable with cfoutput tags now that you're no longer within a pair of cfoutput tags.) Close the form.
</cfoutput>
</table>
<input type="hidden" name="IDList" value=<cfoutput>"#IDList#"</cfoutput>>
</form>
Review
Browse meeting.cfm and start planning your meetings. It's fast. It works. It's intuitive. Enjoy.
=Marty=
III. Identification Session Variables and Url Tokens: A Bad Mix
by Jonathan Schwarz
schwarz_jonathan@hotmail.com
I recently ran into a difficult problem. Some of my web application users were being recognized as other users. The problem was very intermittent and didn't seem to have a pattern. It only affected a few users, while leaving others completely fine. In addition, it didn't always affect the users, just sometimes.
The Setup
I created an application that would authenticate users if they didn't have a valid session variable. My validation scheme was simple. If the session variable wasn't there, it would create a string variable out of the attempted url and pass that variable to a template in an authenticated folder (NT/2000). Once the user was authenticated (username and password), the template would set the appropriate session variable and then use the string variable to cflocate the user back to their originally requested template.
The Technical Side
CFLOCATION has an attribute called "addtoken". The values allowed are "yes" or "no". The default is "yes". If you use tokens, the resulting url will contain a CFID and CFTOKEN variable. If you select "no" the server uses cookies to store the same values. These values are used by the server to distinguish between users (clients).
My Big Mistake
I neglected to set addtoken to "no" in the CFLOCATION tag after authenticating the user.
Things Are Not Always As They Seem
If you create a session variable (stored on server side), and you don't set addtoken to "no" in a following CFLOCATION tag, the server will look at the url variable to determine the appropriate set of session variables for your browser's session. That's fine unless you send your buddy a link to the page you are viewing. By doing so, you end up sending your CFID and CFTOKEN to him for your session. If your buddy clicks on the link while your session is still active the server sees your CFID and CFTOKEN and thinks he is you!
I also found out that even though the url variable is used as the primary source of these variables, and even if addtoken is set to "yes", the server still places cookies storing the CFID and CFTOKEN from the url variables. You can probably now see the danger here.
Solution Polution
Once I figured out that the CFID and CFTOKEN had been passed around via a shared hyperlink, I set addtoken to "no" on the authentication page. Prevailing logic stated that if I did so and communicated to the users to not use the hyperlink that was shared by another user, the system would not see a CFID or CFTOKEN and and send the users browser to the authentication template. The authentication template would then no longer use url variables for these values, relying cookies instead.
As you may expect, this failed. The users who had clicked on the shared hyperlink had unwittingly created cookies on their browser storing the shared CFID and CFTOKEN (even though addtoken was "yes"). As a result, if another session on the server with the same CFID/CFTOKEN was open and still active, they would not be sent to the authentication template and the server would use the current personal ID variable stored in the associated session variable. If, however, the session had expired (no other users using application with the same CFID/CFTOKEN), they would get sent for authentication. The server does not change the CFID or CFTOKEN value, but rather just updates the value of the session variable they pointed to.
How It Looked
User A:
- Has cookie and goes to the template without the url variables as directed
- Gets redirected and authenticated because it is a new session
- Session variable value updated
- User gets directed back to requested page (with no url variables)
- User closes browser
User B:
- Has cookie and goes to the template without the url variables as directed
- Gets redirected and authenticated because it is a new session
- Session variable value updated
- User gets directed back to requested page (with no url variables)
- User leaves browser open (session still open)
User A:
- Has cookie and goes to template without the url variables as directed
- System sees open session with same CFID and CFTOKEN and grabs current session variable which happens to belong to User B.
- User A calls me to complain that he is appearing to be User B.
What I Had To Do
The only way to solve this was to have all my users delete their cookies and start fresh using the revised authentication template that sets addtoken to "no".
Bottom Line
Addtoken="yes" and Identity Session variables are a bad mix and not very easy to troubleshoot or rectify. Make sure you turn off addtoken.
One other note from the Studio helpfiles:
Do not set a cookie variable on the same page that you use the cflocation tag. If you do, the cookie is never saved on the browser; therefore, it is of no value.
-Jon
Publisher and Creator:
Nathan Stanford,
NathanS<at>nsnd.com
http://www.cftipsplus.com
Macromedia and ColdFusion are U.S. registered trademarks.
Copyright (c) 2000 - 2004
CFTIPSPLUS.COM and NSND.COM
Permission is granted to circulate this publication via
MANUAL forwarding by email to friends provided that the text is
forwarded in its entirety and no fee is charged.