So I was using the cflayout tag to create a tab navigation system for reporting module. Each tab had a different report based on live data. The problem is that if you are on any tab that is not the default tab and you click refresh or press the F5 key to reload the data, then you get sent back to the default tab.
I needed to find a way to make it so that whenever a user clicked on a tab, their selected tab was saved in the session scope so that if they happen to use their browsers refresh links instead of using the refresh links I provided for them, they didn't loose which tab they were on.
To begin with set up a basic cflayout tab system like this:
<html>
<head>
<title>Tab Example</title>
</head>
<body>
<table width="95%" align="center"><tr><td>
<cflayout type="tab" name="MyTabLayout">
<cflayoutarea name="tab1" title="Tab 1" style="height:100%">
This is Tab 1
</cflayoutarea>
<cflayoutarea name="tab2" title="Tab 2" style="height:100%">
This is Tab 2
</cflayoutarea>
<cflayoutarea name="tab3" title="Tab 3" style="height:100%">
This is Tab 3
</cflayoutarea>
<cflayoutarea name="tab4" title="Tab 4" style="height:100%">
This is Tab 4
</cflayoutarea>
</cflayout>
</td></tr></table>
</body>
</html>
Try it here, you will see the 4 tabs and when you select tab 2, 3, or 4 then click your browsers refresh button (or F5) you get sent back to tab 1.
Now cflayoutarea has a selected attribute that takes a boolean value to flag a tab as being selected before you load the page. The problem is that there is no obvious way from the documentation to know how to save the selected tab to the session when a user selects it. To do this, we will need to make sure that session management is turned on in our application.cfc and we will need to set up a default for our session variable that will hold the active tab value. Here is my very basic application.cfc:
<cfcomponent displayname="Application" output="true" hint="Handle the application.">
<!--- Set up the application. --->
<cfset THIS.Name = "coldFusionSamples">
<cfset THIS.ApplicationTimeout = CreateTimeSpan( 1, 0, 0, 0 )>
<cfset THIS.SessionManagement = true>
<cfset THIS.SetClientCookies = true>
<cfset THIS.SetDomainCookies = false>
<cfset THIS.ClientManagement = false>
<cfset THIS.ScriptProtect = true>
<cffunction name="OnSessionStart"
hint="Fires when the session is first created."
access="public"
returntype="void"
output="false">
<cfset session.ActiveTab = "tab1">
<cfreturn>
</cffunction>
<cffunction name="OnSessionEnd"
hint="Fires when the session is terminated."
access="public"
returntype="void"
output="false">
<cfargument
name="SessionScope"
type="struct"
required="true">
<cfargument
name="ApplicationScope"
type="struct"
required="false"
default="#StructNew()#">
<cfset structclear(sessionscope)>
<cfreturn>
</cffunction>
</cfcomponent>
Then we will need to set up a CFC to save the active tab name to the session scope, so I have created this basic CFC which can be called via AJAX to set and get session variables and named it session.cfc:
<cfcomponent name="session" hint="Performs Session Functions">
<cffunction name="SetSessionVar" access="remote" output="false" returntype="Boolean">
<cfargument name="Name" type="string" required="true">
<cfargument name="Value" type="string" required="true">
<cfset session[arguments.Name] = arguments.Value>
<cfreturn true>
</cffunction>
<cffunction name="GetSessionVar" access="remote" output="true" returntype="String">
<cfargument name="Name" type="string" required="true">
<cfreturn session[arguments.Name]>
</cffunction>
</cfcomponent>
Next we need to set up a listener so that whenever a tab is clicked, it calls a javascript function to save the selected tab into the session scope.
<html>
<head>
<title>Save Active Tab Example</title>
<!--- cfajaxproxy is used here to create the js class for connecting
to the session.cfc we created --->
<cfajaxproxy cfc="session" jsclassname="CFCs.session">
<script language="JavaScript">
//this is the function that adds the listener to
//the tabchange event in the tab object
addTabChangeListener = function(){
myTabs = ColdFusion.Layout.getTabLayout("MyTabLayout");
myTabs.on('tabchange',SaveActiveTab);
}
//this is the listener that will call the session.cfc
//everytime a tab is selected
SaveActiveTab = function(){
myTabs = ColdFusion.Layout.getTabLayout("MyTabLayout");
actTab = myTabs.getActiveTab();
var SessionObj = new CFCs.session();
SessionObj.setErrorHandler(errorHandler);
SessionObj.SetSessionVar("ActiveTab", actTab.id);
}
//this is an error handler used when calling the cfc
errorHandler = function(statusCode,statusMsg) {
alert(statusCode+': '+statusMsg)
}
</script>
<!--- the AjaxOnLoad() function tells ColdFusion to
Call the specified function once it is done loading
all the AJAX objects on the page. I am using it here
to call the function I made to create a listener on the Tabs
--->
<cfset AjaxOnLoad("addTabChangeListener")>
</head>
<body>
<table width="95%" align="center"><tr><td>
<cflayout type="tab" name="MyTabLayout">
<!--- for each of the tabs I have added code to check the session scope and
set the boolean value to be used for the selected attribute in the
cflayoutarea tag --->
<cfif session.activetab eq "tab1">
<cfset isSelected="true">
<cfelse>
<cfset isSelected="false">
</cfif>
<cflayoutarea name="tab1" selected="#isSelected#" title="Tab 1" style="height:100%">
This is Tab 1
</cflayoutarea>
<cfif session.activetab eq "tab2">
<cfset isSelected="true">
<cfelse>
<cfset isSelected="false">
</cfif>
<cflayoutarea name="tab2" selected="#isSelected#" title="Tab 2" style="height:100%">
This is Tab 2
</cflayoutarea>
<cfif session.activetab eq "tab3">
<cfset isSelected="true">
<cfelse>
<cfset isSelected="false">
</cfif>
<cflayoutarea name="tab3" selected="#isSelected#" title="Tab 3" style="height:100%">
This is Tab 3
</cflayoutarea>
<cfif session.activetab eq "tab4">
<cfset isSelected="true">
<cfelse>
<cfset isSelected="false">
</cfif>
<cflayoutarea name="tab4" selected="#isSelected#" title="Tab 4" style="height:100%">
This is Tab 4
</cflayoutarea>
</cflayout>
</td></tr></table>
</body>
</html>
Try it out here and you will see that no matter what tab you are on, it will now stay on that tab if you refresh your browser.
-Happy Coding!
Thanks for posting this. It's really useful!
e
Great work!
If one was still stuck with application.cfm, how would you amend this to make it work.
Thanks,
Noel
You would make sure that session variables are enabled in your cfapplication tag and add some code to set the default value of the session variable. Something like:
<cfapplication
name="MyAppName"
sessionManagement="true">
<cflock scope="SESSION" type="EXCLUSIVE" timeout="1">
<cfif not isdefined("session.ActiveTab")>
<cfset session.ActiveTab = "tab1">
</cfif>
</cflock>
I use a default.cfm master page with cfinclude and under this framework the tab state does not save. I've confirmed that the code is working by creating a page outside of this framework.
As an example I use http://applicationname/pages/default.cfm?Title=Man...
Do you have any suggestions?
Thanks again,
Noel
It should still work fine with your framework. You just need to make sure that your CFC for saving the session variable is mapped correctly. Since your default.cfm file is including the Candidates/ShowCandidates.cfm file you will need to make sure that in ShowCandidates.cfm where you are doing the equivalent of:
<cfajaxproxy cfc="session" jsclassname="CFCs.session">
to set up the javascript proxy to the CFC that is saving the state to your session scope, you need specify a dot-delimited path to the CFC. The path can be an absolute filepath, or relative to location of the default.cfm file (not the ShowCandidates.cfm file).
Great code; 99% works a treat for me.
But i`m struggling with the java script; i am using 3 layouts on 1 page so i need it to moniter 3 different sets of possible selected tabs.
I can get it all work apart from the javascript! for example i am using the tab names "options", "contacts" & "business".
Can you give me a hint what the javascript would look like if it were moniter more than one layout for tab changes at a time.
I started to write a comment to answer your question, but It was quite lengthy so I added a new blog entry instead.
http://www.coldfusionguy.com/ColdFusion/blog/index...
http://www.coldfusionguy.com/ColdFusion/blog/index...
addTabChangeListener is not defined
I loaded the demo on our servers and it works fine, the only differences I notice in the source is that AjaxOnLoad gets pushed up higher in the page on my website -not the demo-.
I assume that cfpod or cfwindow breaks the listener
Thanks for the insight, I had been looking all around for how to add listeners for cflayout tabbing. They seem to ignore this totally on the livedocs.
You can use javascript to determine which tab is active by using the getActiveTab function like this:
AlertActiveTab = function(){
myTabs = ColdFusion.Layout.getTabLayout("MyTabLayout");
actTab = myTabs.getActiveTab();
alert(actTab.id);
}
I'm gonna try and see if I can get the contents of the active tab to open in a new window so that I can get a full print out of the page.
The section below the scroll bar in the cflayout tab display does not normally get printed. The browser just prints the visible area of the page.
I have had no luck with the event listener actually updating the session.ActiveTab. I am using CF8.01 Enterprise Edition.
The code is correct and I tested it, but it simply does not remember the tab I selected after I refresh. Is this a CFAJAXPROXY bug for me?
I know you don't have much to go on here but I need some kind of answer here.
Thanks.
Anyway, why not just set a session variable equal to the tab when the tab is clicked on instead of going through the whole proxy, cfc route?
You can not just set a session variable equal to the tab when the tab is clicked on because the tabs switch on the client side (in the browser) without sending a request to the server. Since session variables are maintained on the server side, it is necessary to create a javascript listener that sends an AJAX request to your server when a tab is clicked.
I have a issue though, not sure if you have an experience of handling it. I am trying to do paging in side each tab of cflayoutarea. I couldnot figure out how to maintain the tab in the main page.
I basically have three tabs each dispays reasult of a query base on the filter of a user form.
if I use Previous and Next by url parameter, I will simply navigate out of the main page, instead of doing pagination with in the tab. Do you have any advice as to how to tackle this situation. Appreciate your time and help.
<cflayout type="tab">
<cflayoutarea title="cat1" source="cat1.cfm" refreshonactivate="true" />
<cflayoutarea title="cat2" source="cat2.cfm" refreshonactivate="true" />
<cflayoutarea title="cat3" source="cat3.cfm" refreshonactivate="true" />
</cflayout>
You can accomplish this by using the AJAXLink() function.
http://livedocs.adobe.com/coldfusion/8/htmldocs/fu...
I am trying to save the active tab to session variable. However, my form is part of .cfm form , that gets included in a template.
So I don't actually have a <head></head> tag to work with, unless I add it to the template.
Is there a work around to execute javascript?
<cfsavecontent variable="HTMLHead">
<!--- put the HTML/JavaScript you need in the header here --->
</cfsavecontent>
<cfhtmlhead text = "#HTMLHead#">
Awesome find! I've been struggling with the issue of not being able to
maintain the active tab state for awhile now. I have cfm pages that
have 2 tabs, when the user hits a submit button, the page goes
back to showing the 1st tab instead of the one that the user actually
hit the submit button. I'll give this a whirl and see if it works.
One question though: there is a bunch of whitespace that is being
loaded at the top of the content. I tried several measures but none
worked. Could this be due to hidden variables inside the cflayoutarea
generating whitespace?
Are you referring to whitespace in the HTML source, or are you seing whitespace in the redering of the page in your browser?
The HTML source would be handled serverside with things like using <cfsetting enablecfoutputonly="true"></cfsetting>, or <cfsilent></cfsilent>, or <cfprocessingdirective supresswhitespace="true"></cfprocessingdirective> depending on the situation.
If you are just seeing some extra space when you view the page in the browser, you may have to add some CSS to your page to modify margins and stuff like that. A common thing that I find is form/cfform tags will add a bottom margin in some browsers, which can be fixed by setting form { margin: 0 } in your stylesheet.
as the submit button is hit and the page is refreshed, it goes back
to the 1st tab when in fact the submit was done on the 2nd tab.
Also, in IE 7 the tab image is suddenly gone, disappeared. In FF
it shows fine.
I've got a lot going on in the include files. Could that be a reason
from this not working in IE? Is there someway I could check to
see what's causing the issue? Thanks
as Scott mentioned the order of including the js files. I've got a
shinola load of include files so I included these js files in the first
include's html head section.
Thanks again for sharing this Scott!
have 3 tabs, the are contains links to reports.
when I go to tab2 and select link, it will open pdf report then
when I hit Back button it returne me to the tab1, and open tab2
when I refresh page.