So I was asked recently how to stop people leaving a web page without hitting the submit button on the form they were filling in.

It turns out people were registering for an event but the hastily put together form had little sense checking and a number of people had filled in the (very long) form but hadn't hit the submit button. They thought they were registered for the event but their details weren't saved.

In general, individuals may be smart but sure as hell, people are dumb dumb dumb. If there's a chance someone will do something weird with a form, you can bet they will.

Luckily, warning them when they try to navigate away from the page their on is pretty easy.

Step 1

Stick this in the head of your page:

<script language="javascript">
function unloadMsg(){
    msg = "You haven't finished filling in your form."
    return msg;
}
function setUnload(on){
    window.onbeforeunload = (on) ? unloadMsg : null;
}
setUnload(true);
</script>

Step 2

Any button or link that should actually let them leave the page without warning should have this added to it:

onclick="window.onbeforeunload = null;"

Job done!

Example

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Please don't leave!</title>
<script language="javascript">
function unloadMsg(){
    msg = "You haven't finished filling in your form."
    return msg;
}
function setUnload(on){
    window.onbeforeunload = (on) ? unloadMsg : null;
}
setUnload(true);
</script>
</head>
<body>
<h1>Please Don't Go Test</h1>
<p>To test:
<ul>
<li>Try closing this page</li>
<li>Navigate to <a href="http://www.google.com">Google</a> with warning still turned on</li>
<li>Turn off warning and navigate to <a href="http://www.google.com" onclick="window.onbeforeunload = null;">Google</a></li>
</ul>
</p>
</body>
</html>

Don't feel unloved if you haven't received an email in the last 20 minutes. Sometimes they may just be stuck somewhere!

I've used the tu-berlin echo service for years now as a very simple test before I do further investigation but I'm constantly surprised that few have heard of it.

Just send an email to echo@tu-berlin.de and they'll send one straight back to you.

Recently I've been playing around with some new low cost monitoring services.
For a long while I used to use Monitorus on an ad-hoc basis but there's some newer ones that are well worth  looking at.
G introduced me to CopperEgg which seems particularly interesting. With the free version you can install server monitoring agents on up to 2 servers and monitor uptime/response times of up to 30 websites.
Perfect for SMEs and charities.
I'm just putting the finishing touches to a Cookie Acceptance Plug-in for both Joomla 1.5 & 2.5.
I should have it finished and published on  Joomla extensions in the next week if all the testing goes to plan.
The plugin stops Joomla creating any cookies unless the user has accepted the site's T&Cs.
The acceptance bar css can be different for each template (useful if you have mobile versions or you allow users to switch templates from the front end). It works with language files, allowing different text to be displayed depending if you are reading in English, German, etc.

Watch this space.
I was recently working on a single domain which contained a lot of nested security groups (ie one group was a member of another, which in turn was a member of another etc.).
This kind of model can be useful to place a user account in one security group and automatically belong to many. However this can be a nightmare to diagnose if something goes wrong!
So here's a script I've had in my box of tricks for a while that should help. It outputs a list of users and all their group memberships (including the nested ones).
'==== SETTINGS ====================

' start ad path for user list
strOU = "OU=Site01,OU=Staff"


'==================================
If Right(LCase(WScript.FullName), 11) = "wscript.exe" Then
      Set objShell = CreateObject("WScript.Shell")
      objShell.Run "cscript """ & WScript.ScriptFullName & """", 1, False
      Set objShell = Nothing
      WScript.Quit
End If



Set adoCommand = CreateObject("ADODB.Command")
Set adoConnection = CreateObject("ADODB.Connection")
adoConnection.Provider = "ADsDSOObject"
adoConnection.Open "Active Directory Provider"
adoCommand.ActiveConnection = adoConnection

 ' Search entire Active Directory domain.
Set objRootDSE = GetObject("LDAP://RootDSE")
strDNSDomain = objRootDSE.Get("defaultNamingContext")

If Right(strOU, 1) <> "," Then strOU = strOU & ","
strBase = ""

strFilter = "(&(objectCategory=person)(objectClass=user))"

' Comma delimited list of attribute values to retrieve.
strAttributes = "ADsPath"

' Construct the LDAP syntax query.
strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
adoCommand.CommandText = strQuery
adoCommand.Properties("Page Size") = 100
adoCommand.Properties("Timeout") = 30
adoCommand.Properties("Cache Results") = False

' Run the query.
Set adoRecordset = adoCommand.Execute


' Enumerate the resulting recordset.
Do Until adoRecordset.EOF
      WScript.Echo ""
      WScript.Echo "Proccessing: " & adoRecordset.Fields("ADsPath").Value

    ' Retrieve values and display.
      Set objUser = GetObject(adoRecordset.Fields("ADsPath").Value)

      strResults = ""
      strGroups = ""
     
      intLevel = 0
     
      GetMemberOfNames objUser, intLevel
     
      strResults = Replace(objUser.Name, "CN=", "") & " is a member of: "
      arrGroups = Split(strGroups, VbCrLf)
      For intCount = LBound(arrGroups) To UBound(arrGroups)
            If strResults = "" Then
                  strResults = arrGroups(intCount)
            Else
                  strResults = strResults & VbCrLf & arrGroups(intCount)
            End If
      Next

      Set objUser = Nothing

      WScript.Echo ""
      WScript.Echo strResults

      adoRecordset.MoveNext
Loop

' Clean up.
adoRecordset.Close
Set adoRecordset = Nothing

adoConnection.Close

MsgBox "Done. Please see text file " & strOutputFile


'==== Sub's and Functions =========

Sub GetMemberOfNames(objObjectToCheck, intLevel)
      ' This function can get caught in a loop if there is a circular
      ' group membership.  There is a method of using a Dictionary object
      ' here: http://www.rlmueller.net/MemberOf.htm
      ' which checks if the group has been used before.
     
      intLevel = intLevel + 1
      ' Retrieve ALL of the user groups that a user is a member of
      On Error Resume Next
      objMemberOf = objObjectToCheck.GetEx("MemberOf")
      If Err.Number = 0 Then
            On Error GoTo 0
            For Each objGroup in objMemberOf
                  strGroupName = Left(Mid(objGroup, InStr(objGroup, "CN=") + 3),InStr(Mid(objGroup, InStr(objGroup, "CN=") + 3), ",") - 1)
                  If strGroups = "" Then
                        strGroups = String(intLevel, chr(9)) & strGroupName
                  Else
                        strGroups = strGroups & VbCrLf & String(intLevel, chr(9)) & "o " & strGroupName
                  End If
                  Set objNextGroup = GetObject("LDAP://" & objGroup)
                  GetMemberOfNames objNextGroup, intLevel
            Next
            intLevel = intLevel - 1
      Else
            intLevel = intLevel - 1
            Err.Clear
            On Error GoTo 0
      End If
End Sub

NB: Usually I stick some comments at the top of code so I know the source. I've had this so long that really can't remember if I wrote this or modified someone else's code. So if the above looks familiar, drop me an email and I'll give you credit!


Sitting on an old Exchange 2003 server the other day, I needed a quick list of all the email addresses (including aliases). Being a bit old school the easiest thing for me to do was slap together a batch file to give me the answer. So to save me (and anyone else) the effort if I ever have to do it again, here it is:


@echo Off
ldifde -f %TEMP%\ldifde-dump.txt -l proxyaddresses
find "proxyAddresses: " < %TEMP%\ldifde-dump.txt > %TEMP%\ldifde-dump.filter1.txt
find "@" < %TEMP%\ldifde-dump.filter1.txt > %TEMP%\email-addresses.txt
del %TEMP%\ldifde-dump.txt
del %TEMP%\ldifde-dump.filter1.txt
notepad %TEMP%\email-addresses.txt

Sometimes, in SQL group queries, you need totals over a series(e.g. Month numbers 1 to 12). What do you do if some parts of that range have no data but you still want the number in the series displayed?

For instance in our example you have sales data for Month 1,2,3,5 & 6 but there isn't anything for month number 4. You still want Month 4 displayed as a row, just with a null value.

The easiest way to do this is have a small table with numbers 1 to 12 that you can LEFT JOIN on to.

This function will output a table just like that.

CREATE FUNCTION dbo.fnNumberList
(
 @iStart int,
 @iEnd int,
 @iStep int = 1
)  
RETURNS @RtnValue table 
(
 ircNum int
) 

/*
 Returns a table with column name "ircNum" numbers from iStart to iEnd incrementing/decrementing by iStep
 Example:
 select * from dbo.NumberList(4,10,2);
 
 ircNum
 ------
 4
 6
 8
 10
*/

AS  
BEGIN 
 Declare @Cnt int
 Set @Cnt = @iStart

 if @iStep>0 
 While (@Cnt<=@iEnd)
 Begin
  Insert Into @RtnValue (ircNum) VALUES (@Cnt)
  Set @Cnt = @Cnt + @iStep
 End

 if @iStep<0 
 While (@Cnt>=@iEnd)
 Begin
  Insert Into @RtnValue (ircNum) VALUES (@Cnt)
  Set @Cnt = @Cnt + @iStep
 End
 
 Return
END