Auto-Refresh An ASPX Page Programmatically

I wanted to be able to allow a user of one of my applications to specify an auto-refresh rate for a summary page. There are a whole bunch of ways of doing this – some discussed below – each having their own strengths and weaknesses as techniques. Given my experience, I thought I would share a) another way of doing it and b) some of the challenges I came across when trying to use other techniques.

Introduction

Before I begin I think it would help to show, in a little more detail, what I was trying to accomplish. The screenshot below is from my TechMagic project that essentially allows users a readonly view of a bunch of common tables in Service Desk Express as a series of datagrids housed in an accordian control.

I wanted users to be able to set their own refresh rates that persisted sessions without storing anything in a database and as such chose to make use of cookies. That was the easy bit – automatically refreshing the page became a little more challenging.

Standard Methods of Auto-Refresh

HTTP-EQUIV Method

The HTTP-EQUIV method of auto-refreshing a page is very simple. Essentially between the <head></head> tags of the HTML you simply include a line: <meta http-equiv=”refresh” content=”5″ />. This would cause the page to refresh every 5 seconds. To make this dynamic such that a user can enter a value all we do is change the line slightly to allow access to it programatically:

<meta id="autoRefresh" runat="server" >

Now in the code-behind we can right some code in the Page_Load event that looks something like:

autoRefresh.HttpEquiv = "refresh";
autoRefresh.Content = txtRefreshRate.Text;

JavaScript Method

The JavaScript method is also very easy to implement. We just create a function that puts a timeout on the page:

<head>
<script type="text/javascript" language="javascript">
function refreshPage()
{
window.location = unescape(window.location);
}
function setTimeOut()
{
window.setTimeout(refreshPage, document.getElementById("txtRefreshRate").value * 1000);
}
</script>
</head>

<body onload="setTimeOut()">

A slightly different approach

Both these methods essentially do the same thing and work perfectly well. The problem is that if my users had applied sorting to any of the datagrids or collapsed/expanded different panels, all these settings were forgotten after the page refreshed. I rapidly realised what I ACTUALLY wanted to do was a programatic postback rather than a refresh. So here is how I did it.

Essentially, I used the JavaScript method above but instead of setting the window.location in the refreshPage function, I simply click the Update button that you can see in the screenshot:

function refreshPage()
{
var btn = document.getElementById("btnUpdate");
if (btn) btn.click();
}

This works perfectly but seems, at least to me, a very clumsy approach so as always, I welcome any feedback (positive or negative).

How To Make Sure Two Divs Are The Same Height

I recently needed to make sure that two DIV tags always stay the same height even though both have variable content, driven from a database. I thought I would share the solution I used although would welcome any alternative ways of doing the same thing.

HTML

The basic HTML I started with is shown below:

<html>
<head>
<title>Maintaining DIV Heights</title>
<style type="text/css">
#divLeft
{
float:left;
width:49%;
border:1px solid #000;
}
#divRight
{
float:right;
width:49%;
border:1px solid #000;
}
</style>
</head>
<body>
<div id="divLeft">
<p>
This is some text - this is more text - this is even more text...
This is some text - this is more text - this is even more text...
</p>
</div>
<div id="divRight">
<p>
This is some text - this is more text - this is even more text...
This is some text - this is more text - this is even more text...
This is some text - this is more text - this is even more text...
This is some text - this is more text - this is even more text...
</p>
</div>
</body>
</html>

So it’s a rather basic file and the idea is that no matter how much text you type into either column, the heights will always remain the same.

Solution

The solution I used involved a little JavaScript that essentially:

  1. Finds the height of the two columns and stores those values in two variables. I have had to check for IE as IE and Firefox/Safari interpret offsetHeight and clientHeight differently.
  2. Decides which is taller – the left or the right.
  3. Makes the shorter column the same height as the taller column.

So I added the function below into the <head></head> area…

<script type="text/javascript" language="javascript">
function setDivHeights() {
if (document.all) {
var iLeftHeight = document.getElementById("divLeft").offsetHeight;
var iRightHeight = document.getElementById("divRight").offsetHeight;
}
else {
var iLeftHeight = document.getElementById("divLeft").clientHeight;
var iRightHeight = document.getElementById("divRight").clientHeight;
}
if (iLeftHeight > iRightHeight) {
document.getElementById("divRight").style.height = iLeftHeight + "px";
}
else {
document.getElementById("divLeft").style.height = iRightHeight + "px";
}
}
</script>

…and then added this piece of JavaScript to call the function in the <body> tag…

onLoad="setDivHeights()"

…such that the end result looked like:

<html>
<head>
<title>Maintaining DIV Heights</title>
<style type="text/css">
#divLeft
{
float:left;
width:49%;
border:1px solid #000;
}
#divRight
{
float:right;
width:49%;
border:1px solid #000;
}
</style>
<script type="text/javascript" language="javascript">
function setDivHeights() {
if (document.all) {
var iLeftHeight = document.getElementById("divLeft").offsetHeight;
var iRightHeight = document.getElementById("divRight").offsetHeight;
}
else {
var iLeftHeight = document.getElementById("divLeft").clientHeight;
var iRightHeight = document.getElementById("divRight").clientHeight;
}
if (iLeftHeight > iRightHeight) {
document.getElementById("divRight").style.height = iLeftHeight + "px";
}
else {
document.getElementById("divLeft").style.height = iRightHeight + "px";
}
}
</script>
</head>
<body onLoad="setDivHeights()">
<div id="divLeft">
<p>
This is some text - this is more text - this is even more text...
This is some text - this is more text - this is even more text...
</p>
</div>
<div id="divRight">
<p>
This is some text - this is more text - this is even more text...
This is some text - this is more text - this is even more text...
This is some text - this is more text - this is even more text...
This is some text - this is more text - this is even more text...
</p>
</div>
</body>
</html>

An example of the solution can be found here. If you have padding as part of the divs then you need to do some jiggerypokery to get the heights to balance again by subtracting the top and bottom padding from the Firefox/Safari side.

For example:

If the top and bottom padding were 10px you would need to subtract 20.

var iLeftHeight = document.getElementById(“divLeft”).clientHeight – 20;
var iRightHeight = document.getElementById(“divRight”).clientHeight – 20;

If anyone has any better solutions than this then I would be very open to it. Anyway, hope this helps. As always, any feedback (positive or negative) is always appreciated.

Default Groups when Logging Into Service Desk Express 9.6

Network Associates/BMC have never supported the concept of users having a default group when they log into Magic/Service Desk Express. Judging by the comments in the forums lately I would suggest that this could be a feature for a future release. There were some rather elegant solutions posted in the forums that I think would do rather well, but in the meantime I set about finding a “workaround” to the issue. What I propose to discuss here is an alternative solution that you can implement yourself that will “remember” the user’s last logged on group between browser sessions by storing a cookie on the user’s machine.

PLEASE TAKE A BACKUP COPY OF THE TWO FILES BEFORE MODIFYING THEM AS THIS IS AN UNSUPPORTED HACK!

Also please be careful that when cutting and pasting from this blog to notepad that “” are not accidentally replaced with a different type of quotes.

Storing the Cookie

So the first step is modifying a file that will allow us to store which group the user has logged on as. There are a bunch of files you could do this but I choose options_nailogo.aspx using a little JavaScript. Open options_nailogo.aspx (C:Program FilesBMCService Desk ExpressApplication Server by default) in Notepad or, more preferably, Notepad ++ (an absolutely brilliant editor that I now use for pretty much everything). Near the bottom of the file you will find code that looks like:

<script Language="JavaScript">
//alert(""+strBaseURL);
window.status =  sUserName + "; " + sGroupName;
window.defaultStatus = sUserName + "; " + sGroupName;
document.all("marqWBNotice").trueSpeed=true
</script>
<script language="JavaScript">clmGetText(document)</script>

We are going to insert a little function between these two functions that stores the cookie on the user’s machine such that code looks like:

<script Language="JavaScript">
//alert(""+strBaseURL);
window.status =  sUserName + "; " + sGroupName;
window.defaultStatus = sUserName + "; " + sGroupName;
document.all("marqWBNotice").trueSpeed=true
</script>
<script Language="JavaScript">
var expiry = new Date();
expiry.setTime(exp.getTime() + (1000 * 60 * 60 * 24 * 30));
document.cookie = "SelectedGroup=" + escape(sGroupName) + "; expires=" + expiry.toGMTString() + "; path=/";
</script>
<script language="JavaScript">clmGetText(document)</script>

Save the file.

Retrieving the Cookie

When a user tabs out of the UserName textbox of the Login screen of Service Desk Express a C# function is called that dynamically builds the HTML of the dropdown list based on the list of groups that the user is a member of. So essentially all we are going to do is modify this function to grab the cookie we placed in the function above (if it exists) and use it to set the selected option of the Group downdown list. This function lives in Login.cs. Open Login.cs (C:Program FilesBMCService Desk ExpressApplication Serverincludes by default) in any text editor and replace the function GetUserGroups with the function below:

string GetUserGroups(string sUsrName)
{
string strGRPName, strGRPSeq, strOutPut;
strOutPut = "";
string strDefaultGRPName = "";
HttpCookie cookie = Request.Cookies["SelectedGroup"];
if (null != cookie)
{
strDefaultGRPName = cookie.Value.ToString();
strDefaultGRPName = strDefaultGRPName.Replace("%20", " ");
}
if (MetaData != null)
{
System.Diagnostics.Trace.WriteLine("[Login] Metadata available.");
NAMMETADATALib.IMUser objUser = (MetaData.Users as NAMMETADATALib.IMUsers).GetUserByName(sUsrName) as NAMMETADATALib.IMUser;
if (objUser != null)
{
for (int i = 0; i < objUser.GroupCount; i++)
{
NAMMETADATALib.IMGroup objGroup = objUser.GetGroupByIndex(i) as NAMMETADATALib.IMGroup;
if (objGroup.IsActive == true)
{
strGRPName = objGroup.name;
strGRPSeq = objGroup.Sequence.ToString();
if (strDefaultGRPName != "")
{
if (strGRPName == strDefaultGRPName)
{
strOutPut = strOutPut + "<OPTION ID=" + strGRPSeq + " SELECTED="True">" + strGRPName + "</OPTION>";
}
else
{
strOutPut = strOutPut + "<OPTION ID=" + strGRPSeq + ">" + strGRPName + "</OPTION>";
}
}
else
{
strOutPut = strOutPut + "<OPTION ID=" + strGRPSeq + ">" + strGRPName + "</OPTION>";
}
}
}
}
else
{
strOutPut = "NOTVALIDUSER";
}
}
else
{
System.Collections.SortedList oUsers=null;
try
{
oUsers = (System.Collections.SortedList)FeatureManager.GetUserGroups(sUsrName);
}
catch(Exception e)
{
string strResponse="<DATA>";
strResponse=strResponse+ "<ERROR>"+ e.Message +"</ERROR>";
strResponse=strResponse+"</DATA>";
strOutPut=strResponse;
return strOutPut;
}
if (oUsers != null)
{
if (oUsers.Count == 0)
{
strOutPut = "NOTVALIDUSER";
}
for (int i = 0; i < oUsers.Count; i++)
{
strGRPSeq = oUsers.GetKey(i).ToString();
strGRPName = oUsers.GetByIndex(i).ToString();
if (strDefaultGRPName != "")
{
if (strGRPName == strDefaultGRPName)
{
strOutPut = strOutPut + "<OPTION ID=" + strGRPSeq + " SELECTED="True">" + strGRPName + "</OPTION>";
}
else
{
strOutPut = strOutPut + "<OPTION ID=" + strGRPSeq + ">" + strGRPName + "</OPTION>";
}
}
else
{
strOutPut = strOutPut + "<OPTION ID=" + strGRPSeq + ">" + strGRPName + "</OPTION>";
}
}
}
}
return strOutPut;
}

Save the file and that’s it. You may need to clear your browser’s cache and it is probably worth an IISRESET as well.

As always, hope this helps, and any feedback (positive or negative) is always appreciated.

Navigator Bar – Minimised by Default

Awhile ago I posted something in MagicSolutions forum about minimising the navigator bar by default. As I use my blog here as a repository for tricks and tips etc. I thought it would be worth just documenting it here. So, if you would like the navigator bar minimised by default for ALL your users:

Open C:Program FilesBMCService Desk ExpressApplication Serveroptions_nailogo.asp in Notepad.

Find:

<body style="background-image:url(images/header_main_bkgd.gif); background-repeat:repeat-x;padding:0px; margin:0px;margin:0;border:0;height:100%;overflow:auto" onload="WindowLoad()" onunload="WinUnload()">

Replace With:

<body style="background-image:url(images/header_main_bkgd.gif); background-repeat:repeat-x;padding:0px; margin:0px;margin:0;border:0;height:100%;overflow:auto" onload="WindowLoad();hideNavigation()" onunload="WinUnload()">

Hope this helps.

Allowing Remote Assistance to accept the machine name as a parameter

In a previous post I talked about how the Program Builder functionality of Service Desk Express allowed me to launch the Microsoft Remote Assistance tool. Whilst this was neat, what I really needed was to be able to prefill the machine name on the remote assistance page with a machine name as shown below:

Remote Assistance Screen

Remote Assistance Screen

The first thing we needed to do was to hack the Remote Assistance tool to accept a parameter. To do this you need to open

C:WINDOWSpchealthhelpctrVendorsCN=Microsoft Corporation,L=Redmond,S=Washington,C=USRemote AssistanceEscalationUnsolicitedUnSolicitedRCUI.htm

using, for example Notepad. Near the top of the file there is a function called OnLoad that looks like:

function onLoad()
{
;
;
try
{
setTimeout("idComputerName.focus()",250);
g_oSAFRemoteDesktopConnection = oSAFClassFactory.CreateObject_RemoteDesktopConnection();
}
catch(error)
{
FatalError( L_RCCTL_Text, error );
}
;
return;
}

All we need to do is add a couple of lines of Javascript at the top of this function that basically says, if the QueryString of the passed URL (the bit after the ? in a URL e.g. p=51 in the case of http://www.joatit.com/wordpress/?p=51 ) is not equal to the word “machinename”, prefill the machine name in the idComputerName textbox. So after editing, the function should look like:

function onLoad()
{
;
;
try
{
if (window.location.search.substring(1)!= "machinename")
{
idComputerName.value = window.location.search.substring(1);
}
setTimeout("idComputerName.focus()",250);
g_oSAFRemoteDesktopConnection = oSAFClassFactory.CreateObject_RemoteDesktopConnection();
}
catch(error)
{
FatalError( L_RCCTL_Text, error );
}
;
return;
}

Ok, so save that and now we are ready to launch it from Service Desk Express. So as per the previous post, log onto the Service Desk Express application server and run builder.exe that lives in C:Program FilesBMCService Desk ExpressTools. You should be greeted with a screen that looks like the example below:

Program Launcher

Program Launcher

In the Filename textbox enter: RemoteAssistance.asp

In the Command line textbox enter:

C:WINDOWSPCHEALTHHELPCTRBinariesHelpCtr.exe -url hcp://CN=Microsoft%20Corporation,L=Redmond,S=Washington,C=US/ Remote%20Assistance/Escalation/Unsolicited/ UnsolicitedRCui.htm?machinename"

and click Build Launch File.

Now using Form Customisation you can add a Customised Toolbar Button of type link to, for example, the Machine Name field in the Inventory Item module, by specifying a URL of:

http://<SDEServerName>/SDE/RemoteAssistance.asp?machinename=$64$

So, the obvious question is, where does the $64$ come from? The answer lies by running the following SQL statement:

SELECT * FROM SMSYSDBINFO WHERE View_Name = 'Inventory Items'

If you find the Machine Name field in the ALIAS column and look across to the INTERNAL_COL_ID for that field, you will find 64.

The final “icing on the cake” comes by using the DB Admin Tool to expose that Machine Name field to the Incident module by adding a new virtual field (let’s say called ‘Asset Machine Name’) through the existing Seq.Inv.Item foreign key. Run the following SQL statement…

SELECT INTERNAL_COL_ID FROM SMSYSDBINFO WHERE View_Name = 'Incident' AND ALIAS = 'Asset Machine Name'

and then using Form Customisation you can add a Customised Toolbar Button of type link on the Incident form to that newly exposed field. Now you can launch the Remote Assistance tool from the incident form, populating the machine name direct from the selected asset!

Hope it helps – as always all comments very welcome!