Setting up a User's Profile
Once the PSML reader component was completed, the next step was to create a way to read this PSML and set the user preferences to a SESSION structure. This structure could then be used to output header and stylesheet information, tabs, and even portlets. For the structure SESSION.UserPrefs Victor outlined the following primary elements and what they would contain:
Controllers. All the information necessary for drawing portlets.
Header. The name of the header file to include. Footer. The name of the footer file to include. Style. The information from the skin registry.
StyleSheet. The name of the cascading stylesheet to include.
Tabs. Would contain an array of the names of the tabs.
All of these would need to be created in the Application.cfm for the default user (called "anon" for anonymous), and then rewritten when a user logged in. The basic information, such as header, footer, and style sheet would be stored in the database, whereas the others would require access to PSML information. The others would be written in two custom tags:
<CF_SKINWRITER> for style and <CF_CONTROLLERWRITER> for the others.
The Controller and Portlet Information
Creating the controller writer went quickly using the PSML reader component. Writing the tabs, for example, only took a couple of lines:
<CFINVOKE COMPONENT="portal.components.PSMLReader" METHOD="getTabs" USER="#SESSION.User#" RETURNVARIABLE="Tabs"> <CFSET SESSION.UserPrefs.Tabs = Tabs>
The getTabs() method already returned this array. The template simply assigned them to the SESSION.UserPrefs.Tabs structure. Setting the controller information required some looping:
<CFLOOP FROM="1" TO="#ArrayLen(SESSION.UserPrefs.Tabs)#" INDEX="i"> <CFINVOKE COMPONENT="portal.components.PSMLReader" METHOD="getPortlets" USER="#ATTRIBUTES.user#" TAB="#i#" RETURNVARIABLE="currPortalArray"> <CFINVOKE COMPONENT="portal.components.PSMLReader" METHOD="getController" USER="#SESSION.User#" TAB="#i#" RETURNVARIABLE="CurrController"> <CFSET ArrayAppend(TabPortals,currPortalArray)> <CFSET ArrayAppend(TabControllers,currController)> <CFSET TempArray = ArrayNew(1)> <CFLOOP FROM="1" TO="#ArrayLen(currPortalArray)#" INDEX="11"> <CFSET ArrayAppend(TempArray,"NORMAL")> </CFLOOP> <CFSET ArrayAppend(PortletsDisplay,TempArray)> </CFLOOP>
The tabs are looped through, and for each tab, the portlets, the controller (which will determine how the portlets are laid out), and the portlet state (set to NORMAL by default; the user may later change it to MINIMIZED, etc.) are set to arrays. Later in the template, these arrays are all set to the SESSION.UserPrefs structure. This information could be easily accessed for each user when the time comes.
The Style Information
The <CF_SKINWRITER> functions a bit differently, because the getUserSkin() method doesn't rely on the Jetspeed classes, but rather on the XML features in ColdFusion MX.
<CFINVOKE COMPONENT="portal.components.PSMLReader" METHOD="getUserPSML" USER="#ATTRIBUTES.User#" RETURNVARIABLE="myPSML"> <CFINVOKE COMPONENT="portal.components.PSMLReader" METHOD="getUserSkin" PSML="#myPSML#" RETURNVARIABLE="mySkin"> <!--- instantiate new array to put style stuff in ---> <CFSET StyleArray = ArrayNew(2)> <CFSET SESSION.UserPrefs.Style = StructNew()> <CFLOOP FROM=1 TO="#ArrayLen(mySkin.XmlChildren)#" INDEX="i"> <CFPARAM NAME="SESSION.UserPrefs.Style.#Replace(mySkin.XmlChildren [i].XmlAttributes.name,"-","","ALL")#" DEFAULT="#mySkin.XmlChildren[i].XmlAttributes.value#"> </CFLOOP>
First, the user's PSML is retrieved using the getUserPSML() method. This will return an XML document object representing the user's PSML file. The result is passed to the getUserSkin() method. That complex datatypes could be passed to components was an exciting discovery for the Project Omega team.
Next, the skin array that is returned (this contains styles and other information) is looped through, and using <CFPARAM>, a variable named by the name attribute of each (minus hyphens, which are removed using the Replace() function) is set to the corresponding value attribute.
The Application.cfm File
Finally, it is all put to work in the Application.cfm file:
<CFIF NOT IsDefined("SESSION.UserPrefs") <CFSET SESSION.UserPrefs = StructNew()> <CFPARAM NAME="SESSION.UserPrefs.Header" DEFAULT="default-header.cfm"> <CFPARAM NAME="SESSION.UserPrefs.Footer" DEFAULT="default-footer.cfm"> <CFPARAM NAME="SESSION.UserPrefs.StyleSheet" DEFAULT="default.css"> <CF_SKINWRITER USER="#Session.User#"> <CF_CONTROLLERWRITER USER="#Session.User#"> </CFIF>
Figure I-3.7 The SESSION.UserPrefs structure contains all of the information needed to lay out the portal for a user.