/**
  ______________________________________________________________________________
 |
 | @project     Peopleforms
 | @purpose     Utility scripts
 | @author      William Bronsema (bill@bronsema.ca)
 | @copyright   Copyright (c) 2007-2008 PeopleGIS Inc
 |              All rights reserved
 |______________________________________________________________________________
  ______________________________________________________________________________
 |
 |  Define attribute and option objects
 |______________________________________________________________________________

 **/

pfForm = function ()
{
    this.nId = -1;
    this.szTitle = '';
    this.anFormOwnerIds=[];
    this.szFormOwnerName='';
    this.nPasswordProtected = 0;
    this.szPassword = '';
    this.szTable = '';
    this.nEnabled = 1;
    this.szFormType = '';
    this.nLocked = 0;
    this.nRecEdit = 0;
    this.szRecEditPwd = '';
    this.szCreated = 'Unknown';
    this.nRecords = 0;
    this.szTitleCols = '';
    this.aAttributes = new Array();
    this.szEmailList = '';
    this.szThanks = '';
    this.szMapfile = '';
    this.szMapSymbol = '';
    this.szMapSymbolURL = '';
    this.nDefaultScale = -1;
    this.nPDF = '';
    this.szDescription = '';
    this.szSortFields = '';
	this.oMapOptions = {};
	
	this.events = {};
	this.events.onReset = new YAHOO.util.CustomEvent("PEOPLEGIS.form.onReset", this);
	this.events.onLoadValue = new YAHOO.util.CustomEvent("PEOPLEGIS.form.onLoadValue",this);
	this.events.onFormLoaded = new YAHOO.util.CustomEvent("PEOPLEGIS.form.onFormLoaded",this);
	this.events.onMapLoaded = new YAHOO.util.CustomEvent("PEOPLEGIS.form.onMapLoaded",this);
};

pfForm.prototype.toString = function()
{
	var str = "[pfForm";
	str += "[nId="+this.nId+"]";
	str += "[szTitle="+this.szTitle+"]";
	str += this.aAttributes;
	str += "]";
	return str;
};

var pfAttribute = function ()
{
    this.nId = -1;
    this.nIndex = -1;
    this.szTitle = '';
    this.szText = '';
    this.szType = '';
    this.szMetaContent = '';
    this.nRequired = 0;
    this.nDeleted = 0;
	this.nPrivate = 0;
    this.aOptions = new Array();
}

pfAttribute.prototype.toString = function()
{
	var str = "[pfAttribute"
	str += "[nId="+this.nId+"]"
	str += "[nIndex="+this.nIndex+"]"
	str += "[szTitle="+this.szTitle+"]"
	str += "[szText="+this.szText+"]"
	str += "[szType="+this.szType+"]"
	str += "[szMetaContent="+this.szMetaContent+"]"
	str += "[nRequired="+this.nRequired+"]"
	str += "[nDeleted="+this.nDeleted+"]"
	str += "[nPrivate="+this.nPrivate+"]"
	str += this.aOptions
	str += "]"
	return str
}

var pfOption = function ()
{
    this.nId = -1;
    this.szText = '';
    this.szValue = '';
    this.nDefault = 0;
    this.nDeleted = 0;
}

pfOption.prototype.toString = function()
{
	var str = "[pfOption"
	str += "[nId="+this.nId+"]"
	str += "[szText="+this.szText+"]"
	str += "[szValue="+this.szValue+"]"
	str += "[nDefault="+this.nDefault+"]"
	str += "[nDeleted="+this.nDeleted+"]"
	str += "]"
	return str
}

var pfUser = function ()
{
    this.nId = -1;
    this.szUser = '';
    this.szPassword = '';
    this.szFName = '';
    this.szLName = '';
    this.szEmail = '';
    this.szClientName = '';
    this.nDeleted = 0;
}

var pfClient = function ()
{
    this.nId = -1;
    this.szName = '';
    this.szURL = '';
    this.szDIR = '';
    this.nMAT = 0;
};


var getAttribute = function( nId, oForm )
{
    for( var i=0; i<oForm.aAttributes.length; i++ )
    {
        if ( oForm.aAttributes[i].nId == nId )
        {
            return oForm.aAttributes[i];
        }
    }
};

var getAttributeByTitle = function( szTitle, oForm )
{
    for( var i=0; i<oForm.aAttributes.length; i++ )
    {
        if ( oForm.aAttributes[i].szTitle == szTitle ||
             oForm.aAttributes[i].szTitle + '_num' == szTitle ||
             oForm.aAttributes[i].szTitle + '_name' == szTitle ||
             oForm.aAttributes[i].szTitle + '_unit' == szTitle ||
             oForm.aAttributes[i].szTitle + '_x_coord' == szTitle ||
             oForm.aAttributes[i].szTitle + '_y_coord' == szTitle )
        {
            return oForm.aAttributes[i];
        }
    }
};

var getAttributeIndex = function( nId, oForm )
{
    for( var i=0; i<oForm.aAttributes.length; i++ )
    {
        if ( oForm.aAttributes[i].nId == nId )
        {
            return i;
        }
    }
};

/**
  ______________________________________________________________________________
 |
 |  XML parsing functions
 |______________________________________________________________________________

 **/
var xml2pfForm = function( xmlResults )
{
    // create new form array object
    var aoForms = new Array();

    // get root node
    var nNodeIndex = findNodeByName( xmlResults.responseXML,'response' );
    if ( nNodeIndex == -1 )
    {
        alert( 'Invalid response from server.' );
        return aoForms;
    }
    var oResultsNode = xmlResults.responseXML.childNodes[nNodeIndex];

    // check for errors
    nNodeIndex = findNodeByName( oResultsNode,'error' );
    if( nNodeIndex != -1 )
    {
        // give error message
        alert( getNodeValue( oResultsNode.childNodes[nNodeIndex] ) );
        return aoForms;
    }

    // process the results
    var nFormsIndex = findNodeByName( oResultsNode,'forms' );
    if( nFormsIndex >= 0 )
    {
        // get the forms node
        var oFormsNode = oResultsNode.childNodes[nFormsIndex];

        // loop through all the rows and process
        var nNumRows = oFormsNode.childNodes.length;
        for ( var i=0; i<nNumRows; i++ )
        {
            // make sure that it is a form tag
            if ( oFormsNode.childNodes[i].nodeName != 'form' )
            {
                continue;
            }

            // process all cell nodes for this form
            var nNumCells = oFormsNode.childNodes[i].childNodes.length;
            var nId = -1;
            var szTitle = '';
            var anFormOwnerIds = [];
            var szFormOwnerName = '';
            var nPasswordProtected = 0;
            var nLocked = 0;
            var nRecEdit = 0;
            var nEnabled = 1;
            var szFormType = '';
            var szTable = '';
            var szDateCreated = '';
            var nRecords = 0;
            var szTitleCols = '';
            var szEmailList = '';
            var szThanks = '';
            var szDescription = '';
            var szSortFields = '';
            var szMapfile = '';
            var szMapSymbol = '';
            var szMapSymbolURL = '';
            var nDefaultScale = -1;
            var nPDF = 0;
			var oMapOptions = {};
            for ( var j=0; j<nNumCells; j++ )
            {
                // init
                var szName = oFormsNode.childNodes[i].childNodes[j].nodeName;
                var szValue = oFormsNode.childNodes[i].childNodes[j].childNodes.length > 0? oFormsNode.childNodes[i].childNodes[j].childNodes[0].nodeValue:'';

                // process each type
                if ( szValue != '' )
                {
                    switch( szName )
                    {
                        case 'id':
                            nId = parseInt( szValue );
                            break;
                        case 'title':
                            szTitle = szValue;
                            break;
                        case 'form_owner_id':
                        	anFormOwnerIds = szValue.split(",");
                            break;
						case 'form_owner_name':
							szFormOwnerName=szValue;
							break;
                        case 'password_protected':
                            nPasswordProtected = parseInt( szValue );
                            break;
                        case 'locked':
                            nLocked = parseInt( szValue );
                            break;
                        case 'rec_edit':
                            nRecEdit = parseInt( szValue );
                            break;
                        case 'enabled':
                            nEnabled = parseInt( szValue );
                            break;
                        case 'form_type':
                            szFormType = szValue;
                            break;
                        case 'table':
                            szTable = szValue;
                            break;
                         case 'created':
                            szDateCreated = szValue;
                            break;
                         case 'records':
                            nRecords = parseInt( szValue );
                            break;
                        case 'title_cols':
                            szTitleCols = szValue;
                            break;
                        case 'email_list':
                            szEmailList = szValue;
                            break;
                        case 'thanks':
                            szThanks = szValue;
                            break;
                        case 'description':
                            szDescription = szValue;
                            break;
                        case 'sortfields':
                            szSortFields = szValue;
                            break;
                        case 'mapfile':
                            szMapfile = szValue;
                            break;
                        case 'mapsymbol':
                            szMapSymbol = szValue;
                            break;
                        case 'mapsymbolurl':
                            szMapSymbolURL = szValue;
                            break;
                        case 'defaultscale':
                            nDefaultScale = parseInt( szValue );
                        case 'pdf':
                            nPDF = parseInt( szValue );
                            break;
						case 'map_options':
							if (szValue && szValue.length > 0) oMapOptions = eval("(" + szValue + ")");
							break;
                    }
                }
            }

            // add the values to the object
            var oForm = new pfForm();
            oForm.nId = nId;
            oForm.szTitle = szTitle;
            oForm.anFormOwnerIds = anFormOwnerIds;
            oForm.szFormOwnerName = szFormOwnerName;
            oForm.nPasswordProtected = nPasswordProtected;
            oForm.szTable = szTable;
            oForm.nEnabled = nEnabled;
            oForm.szFormType = szFormType;
            oForm.nLocked = nLocked;
            oForm.nRecEdit = nRecEdit;
            oForm.szCreated = szDateCreated;
            oForm.nRecords = nRecords;
            oForm.szTitleCols = szTitleCols;
            oForm.szEmailList = szEmailList;
            oForm.szThanks = szThanks;
            oForm.szDescription = szDescription;
            oForm.szSortFields = szSortFields;
            oForm.szMapfile = szMapfile;
            oForm.szMapSymbol = szMapSymbol;
            oForm.szMapSymbolURL = szMapSymbolURL;
            oForm.nDefaultScale = nDefaultScale;
            oForm.nPDF = nPDF;
			oForm.oMapOptions = oMapOptions;

            // add it to the array of forms
            aoForms.push( oForm );
        }
    }

    // return array
    return aoForms;
}

var xml2pfAtt = function( xmlResults )
{
    // init
    var aoAttributes = new Array();

    // get root node
    var nNodeIndex = findNodeByName( xmlResults.responseXML,'response' );
    if ( nNodeIndex == -1 )
    {
        alert( 'Invalid response from server.' );
        return aoAttributes;
    }
    var oResultsNode = xmlResults.responseXML.childNodes[nNodeIndex];

    // check for errors
    nNodeIndex = findNodeByName( oResultsNode,'error' );
    if( nNodeIndex != -1 )
    {
        // give error message
        alert( getNodeValue( oResultsNode.childNodes[nNodeIndex] ) );
        return aoAttributes;
    }

    // process the results
    var nAttIndex = findNodeByName( oResultsNode,'attributes' );
    if( nAttIndex >= 0 )
    {
        // get the attributes node
        var oAttNode = oResultsNode.childNodes[nAttIndex];

        // loop through all the rows and process
        var nNumRows = oAttNode.childNodes.length;
        for ( var i=0; i<nNumRows; i++ )
        {
            // make sure that it is a attribute tag
            if ( oAttNode.childNodes[i].nodeName != 'attribute' )
            {
                continue;
            }

            // process all cell nodes for this form
            var nNumCells = oAttNode.childNodes[i].childNodes.length;
            var nId = -1;
            var nIndex = -1;
            var szTitle = '';
            var szText = '';
            var szType = '';
            var nRequired = 0;
            var szMetaContent = '';
			var nPrivate = 0;
            var aOptions = new Array();
            for ( var j=0; j<nNumCells; j++ )
            {
                // init
                var szName = oAttNode.childNodes[i].childNodes[j].nodeName;
                var szValue = oAttNode.childNodes[i].childNodes[j].childNodes.length > 0? oAttNode.childNodes[i].childNodes[j].childNodes[0].nodeValue:'';

                // process each type
                if ( szValue != '' )
                {
                    switch( szName )
                    {
                        case 'id':
                            nId = parseInt( szValue );
                            break;
                        case 'index':
                            nIndex = parseInt( szValue );
                            break;
                        case 'title':
                            szTitle = szValue;
                            break;
                        case 'text':
                            szText = szValue;
                            break;
                        case 'type':
                            szType = szValue;
                            break;
                        case 'required':
                            nRequired = parseInt( szValue );
                            break;
                        case 'metacontent':
                            szMetaContent = xmlUnSafe( szValue );
                            break;
						case 'private':
							nPrivate = parseInt( szValue );
                        case 'options':
                            aOptions = parseAttOptions( oAttNode.childNodes[i].childNodes[j] );
                            break;
                    }
                }
            }

            // create new pfAttributes object
            var oNewAtt = new pfAttribute();
            oNewAtt.nId = nId
            oNewAtt.nIndex = nIndex;
            oNewAtt.szTitle = szTitle;
            oNewAtt.szText = szText;
            oNewAtt.szType = szType;
            oNewAtt.nRequired = nRequired;
            oNewAtt.szMetaContent = szMetaContent;
			oNewAtt.nPrivate = nPrivate;
            oNewAtt.aOptions = aOptions;

            // add to the array
            aoAttributes.push( oNewAtt );
        }
    }

    // return
    return aoAttributes;
}

var xml2pfUsers = function( xmlResults )
{
    // create new form array object
    var aoUsers = new Array();

    // get root node
    var nNodeIndex = findNodeByName( xmlResults.responseXML,'response' );
    if ( nNodeIndex == -1 )
    {
        alert( 'Invalid response from server.' );
        return aoUsers;
    }
    var oResultsNode = xmlResults.responseXML.childNodes[nNodeIndex];

    // check for errors
    nNodeIndex = findNodeByName( oResultsNode,'error' );
    if( nNodeIndex != -1 )
    {
        // give error message
        alert( getNodeValue( oResultsNode.childNodes[nNodeIndex] ) );
        return aoUsers;
    }

    // process the results
    var nUsersIndex = findNodeByName( oResultsNode,'users' );
    if( nUsersIndex >= 0 )
    {
        // get the forms node
        var oUsersNode = oResultsNode.childNodes[nUsersIndex];

        // loop through all the rows and process
        var nNumRows = oUsersNode.childNodes.length;
        for ( var i=0; i<nNumRows; i++ )
        {
            // make sure that it is a form tag
            if ( oUsersNode.childNodes[i].nodeName != 'user' )
            {
                continue;
            }

            // process all cell nodes for this form
            var nNumCells = oUsersNode.childNodes[i].childNodes.length;
            var nId = -1;
            var szUser = '';
            var szFName = '';
            var szLName = '';
            var szEmail = '';
            var szClientName = '';
            var aPermissions = [];
            for ( var j=0; j<nNumCells; j++ )
            {
                // init
                var szName = oUsersNode.childNodes[i].childNodes[j].nodeName;
                var szValue = oUsersNode.childNodes[i].childNodes[j].childNodes.length > 0? oUsersNode.childNodes[i].childNodes[j].childNodes[0].nodeValue:'';

                // process each type
                if ( szValue != '' )
                {
                    switch( szName )
                    {
                        case 'id':
                            nId = parseInt( szValue );
                            break;
                        case 'user':
                            szUser = szValue;
                            break;
                        case 'fname':
                            szFName = szValue;
                            break;
                        case 'lname':
                            szLName = szValue;
                            break;
                        case 'email':
                            szEmail = szValue;
                            break;
                        case 'client_name':
                            szClientName = szValue;
                            break;                            
                    }
                }
            }

            // add the values to the object
            var oUser = new pfUser();
            oUser.nId = nId;
            oUser.szUser = szUser;
            oUser.szFName = szFName;
            oUser.szLName = szLName;
            oUser.szEmail = szEmail;
            oUser.szClientName = szClientName;

            // add it to the array of forms
            aoUsers.push( oUser );
        }
    }

    // return array
    return aoUsers;
}

var xml2pfClients = function( xmlResults )
{
    // create new form array object
    var aoClients = new Array();

    // get root node
    var nNodeIndex = findNodeByName( xmlResults.responseXML,'response' );
    if ( nNodeIndex == -1 )
    {
        alert( 'Invalid response from server.' );
        return aoClients;
    }
    var oResultsNode = xmlResults.responseXML.childNodes[nNodeIndex];

    // check for errors
    nNodeIndex = findNodeByName( oResultsNode,'error' );
    if( nNodeIndex != -1 )
    {
        // give error message
        alert( getNodeValue( oResultsNode.childNodes[nNodeIndex] ) );
        return aoClients;
    }

    // process the results
    var nClientsIndex = findNodeByName( oResultsNode,'clients' );
    if( nClientsIndex >= 0 )
    {
        // get the clients node
        var oClientsNode = oResultsNode.childNodes[nClientsIndex];

        // loop through all the rows and process
        var nNumRows = oClientsNode.childNodes.length;
        for ( var i=0; i<nNumRows; i++ )
        {
            // make sure that it is a client tag
            if ( oClientsNode.childNodes[i].nodeName != 'client' )
            {
                continue;
            }

            // process all cell nodes for this form
            var nNumCells = oClientsNode.childNodes[i].childNodes.length;
            var nId = -1;
            var szClientName = '';
            var szURL = '';
            var szDIR = '';
            var nMAT = 0;
            for ( var j=0; j<nNumCells; j++ )
            {
                // init
                var szName = oClientsNode.childNodes[i].childNodes[j].nodeName;
                var szValue = oClientsNode.childNodes[i].childNodes[j].childNodes.length > 0? oClientsNode.childNodes[i].childNodes[j].childNodes[0].nodeValue:'';

                // process each type
                if ( szValue != '' )
                {
                    switch( szName )
                    {
                        case 'id':
                            nId = parseInt( szValue );
                            break;
                        case 'name':
                            szClientName = szValue;
                            break;
                        case 'url':
                            szURL = szValue;
                            break;
                        case 'dir':
                            szDIR = szValue;
                            break;
                        case 'mat':
                            nMAT = parseInt( szValue );
                            break;
                    }
                }
            }

            // add the values to the object
            var oClient = new pfClient();
            oClient.nId = nId;
            oClient.szName = szClientName;
            oClient.szURL = szURL;
            oClient.szDIR = szDIR;
            oClient.nMAT = nMAT;

            // add it to the array of forms
            aoClients.push( oClient );
        }
    }

    // return array
    return aoClients;
}

var parseAttOptions = function( oAttNode )
{
    // init
    var aOptions = new Array();

    // loop through all the rows and process
    var nNumRows = oAttNode.childNodes.length;
    for ( var i=0; i<nNumRows; i++ )
    {
        // make sure that it is a option tag
        if ( oAttNode.childNodes[i].nodeName != 'option' )
        {
            continue;
        }

        // process all cell nodes for this form
        var nNumCells = oAttNode.childNodes[i].childNodes.length;
        var nId = -1;
        var szText = '';
        var szOptValue = '';
        var nDefault = 0;
        for ( var j=0; j<nNumCells; j++ )
        {
            // init
            var szName = oAttNode.childNodes[i].childNodes[j].nodeName;
            var szValue = oAttNode.childNodes[i].childNodes[j].childNodes.length > 0? oAttNode.childNodes[i].childNodes[j].childNodes[0].nodeValue:'';

            // process each type
            if ( szValue != '' )
            {
                switch( szName )
                {
                    case 'id':
                        nId = parseInt( szValue );
                        break;
                    case 'text':
                        szText = szValue;
                        break;
                    case 'value':
                        szOptValue = szValue;
                        break;
                    case 'default':
                        nDefault = parseInt( szValue );
                        break;
                }
            }
        }


        // create new pfOption object
        var oNewOpt = new pfOption();
        oNewOpt.nId = nId
        oNewOpt.szText = szText;
        oNewOpt.szValue = szOptValue;
        oNewOpt.nDefault = nDefault;

        // add to the array of options
        aOptions.push( oNewOpt );
    }

    // return options
    return aOptions;
}

var getPFObjectIndex = function ( aoPFObj, nId )
{
    // get the array index based on id
    for( var i=0; i<aoPFObj.length; i++ )
    {
        // check for match on the id
        if ( aoPFObj[i].id == nId )
        {
            return i;
        }
    }

    // not found
    return -1;

}

var findNodeByName = function( root, name )
{

    var index = -1;
    var offset = (arguments.length > 2) ? arguments[2] : -1;
    for (var i=offset+1; i<root.childNodes.length; i++) {
        if (root.childNodes[i].nodeName == name) {
            index = i;
            break;
        }
    }
    return index;
}

var getNodeValue = function( node )
{
    var value = '';
    if (node.nodeType == 3) {
        value = node.nodeValue;
    } else if (node.childNodes.length > 0) {
        for (var i=0; i<node.childNodes.length; i++) {
            value = value + node.childNodes[i].nodeValue;
        }
    }
    return value;
}

/**
  ______________________________________________________________________________
 |
 |  Interface helper functions
 |______________________________________________________________________________

 **/
var setLayout = function() {

    var main = new Jx.Layout('divPage', {top: 0, bottom: 0});
    var top = new Jx.Layout('siteHeaderBG', {width: null, left: 0, right: 0, top: 0, bottom: null, height:85});
    var left = new Jx.Layout('siteContentContainerSide', {width: 302, left: 0, right: null, top: 85, bottom: 25});
    var footer = new Jx.Layout('siteFooterContainer', {width: 302, left: 0, right: null, top: null, bottom: 0, height:25});
    var righttitle = new Jx.Layout('siteContentContainerTitle', {width: null, left: 312, right: 20, top: 85, bottom: null, height: gnRTitleHeight});
    var right = new Jx.Layout('siteContentContainerMain', {width: null, left: 312, right: 20, top: (85 + gnRTitleHeight), bottom: 0});
    var centermargin = new Jx.Layout('siteCenterMarginBar', {width: 10, left: 302, right: null, top: 85, bottom: 0});
    var rightmargin = new Jx.Layout('siteRightMarginBar', {width: 20, left: null, right: 0, top: 85, bottom: 0});

    main.resize();
}


var trapReturn = function( e )
{
    e = (e)?e:((event)?event:null);
    var charCode=(e.charCode)?e.charCode:e.keyCode;
    if (charCode == 13)
    {
        createForm();
    }
}

function openPanel( oDiv )
{
    // check for no previous panel set
    if( goOpenPanel == null )
    {
        new Effect.SlideDown( oDiv,{queue:'end',duration:0.3});
        goOpenPanel = oDiv;
    }
    else
    {
        // only open if not already open
        if ( oDiv.id == goOpenPanel.id )
        {
            return;
        }

        // close the current open div and open the new one
        new Effect.SlideUp( goOpenPanel,{queue:'front',duration:0.3});
        new Effect.SlideDown( oDiv,{queue:'end',duration:0.3});

        // store the newly open div
        goOpenPanel = oDiv;
    }
}

function closePanel()
{
    // check for no previous panel set
    if( goOpenPanel != null )
    {
        new Effect.SlideUp( goOpenPanel,{queue:'end',duration:0.3});
        goOpenPanel = null;
    }
}

var drawAttributeEntry = function ( oAtt, bForceIntoView, szContainer, bEditor )
{
    // init
    var bIsNew = true;

    // only proceed if the attribute has not been deleted
    if ( oAtt.nDeleted == 1 )
    {
        return;
    }
	
	// if we're not in editor mode, we're not logged in, and the attribute is a "private" attribute, then hide it
	if (!bEditor && !goForm.bAdministrativeMode && oAtt.nPrivate == 1)
	{
		return;
	}

    // check if the div exists
    if ( $( 'divAttribute_' + oAtt.nId ) == null )
    {
        // create new div
        var oLi = document.createElement('li');
        // append it to the main container
        $( szContainer ).appendChild( oLi );

        // set the id and class
        oLi.id = 'divAttribute_' + oAtt.nId;
        oLi.className = 'divAttributeListItem';
		oLi.style.zIndex = "1";
        
        // make sortable
        if ( bEditor )
        {
            Position.includeScrollOffsets = true;
            Sortable.create( szContainer,{constraint:false,onChange:changeSortOrder });
        }
    }
    else
    {
        // use existing
        var oLi = $( 'divAttribute_' + oAtt.nId );
        bIsNew = false;
    }

    // create the edit and delete icons
    if ( bEditor )
    {
		var oDuplicateButton = document.createElement('img');
        oDuplicateButton.id = 'imgDupButton_' + oAtt.nId;
        oDuplicateButton.src = 'images/duplicate_box.png';
        oDuplicateButton.style.width = '28px';
        oDuplicateButton.style.height = '28px';
        oDuplicateButton.style.cursor = 'pointer';
        oDuplicateButton.style.right = '74px';
        oDuplicateButton.style.top = '5px';
        oDuplicateButton.style.position = 'absolute';
        oDuplicateButton.onclick = duplicateQuestion.bind(this, oAtt);
        oDuplicateButton.alt = 'Duplicate question';
        oDuplicateButton.title = 'Duplicate question';
        var oEditButton = document.createElement('img');
        oEditButton.id = 'imgEditButton_' + oAtt.nId;
        oEditButton.src = 'images/edit_box.png';
        oEditButton.style.width = '28px';
        oEditButton.style.height = '28px';
        oEditButton.style.cursor = 'pointer';
        oEditButton.style.right = '42px';
        oEditButton.style.top = '5px';
        oEditButton.style.position = 'absolute';
        oEditButton.onclick = editQuestion.bind(this, oAtt.nId);
        oEditButton.alt = 'Edit question';
        oEditButton.title = 'Edit question';
        var oDelButton = document.createElement('img');
        oDelButton.id = 'imgDeleteButton_' + oAtt.nId;
        oDelButton.src = 'images/delete_box.png';
        oDelButton.style.width = '28px';
        oDelButton.style.height = '28px';
        oDelButton.style.cursor = 'pointer';
        oDelButton.style.right = '10px';
        oDelButton.style.top = '5px';
        oDelButton.style.position = 'absolute';
        oDelButton.onclick = deleteQuestion.bind(this, oAtt.nId);
        oDelButton.alt = 'Delete question';
        oDelButton.title = 'Delete question';
    }

    // create the contents
    var szContents = '';
    if ( oAtt.szType !== 'info' && oAtt.szType !== 'image' )
    {
        var szRequired = oAtt.nRequired == 1?'<b>*</b>':'';
        var trimmedText = trim( oAtt.szText );
        var wrappedText = wrapText(trimmedText)
        szContents = '<h5>' + wrappedText + ' ' + szRequired + '</h5>';
    }

    // process according to type
    var szDisabled = bEditor?'disabled':'';
    var szShowUnit = '';
    
	if (eval('window.' + oAtt.szType + '_draw')) {
		var szDrawThis = eval(oAtt.szType + '_draw(oAtt,bEditor);');
		if (szDrawThis !== false) {
			szContents += szDrawThis;
		} else {
			// return false means don't draw this question at all
			szContents = '';
		}
	} else {
		alert('window.' + oAtt.szType + '_draw is not defined');
		//guess there's no way to draw this attribute
		szContents = '';
	}

    // set the contents
	oLi.innerHTML = szContents;

    // add buttons as necessary
    if ( bEditor )
    {
    	if (window[oAtt.szType + "_qtype_edit"] !== undefined) {
    		//only editable if there's an edit method for the question
    		oLi.appendChild( oEditButton );
    	} else {
    		oEditButton.src = 'images/edit_box_disabled.png';
    		oEditButton.onclick = null;
    		oEditButton.title = 'This Question is not Editable';
    		oEditButton.style.cursor = '';
    		oLi.appendChild( oEditButton );
    	}
        oLi.appendChild( oDelButton );
		oLi.appendChild( oDuplicateButton );
    }

    // force the element into view if requested
    if ( bForceIntoView )
    {
        oLi.scrollIntoView();
    }
}

var timeCheckHour = function ( oTextBox )
{
    var xNum = parseInt(oTextBox.value);

    if ( !isNaN( xNum ) )
    {
        // limit to 1-12
        if ( xNum < 1 )
        {
            oTextBox.value = 1;
        }
        else if ( xNum > 12 )
        {
            oTextBox.value = 12;
        }
        else
        {
            oTextBox.value = xNum;
        }
    }

    makeDirty();
};

var timeCheckHourBlur = function ( oTextBox )
{
    var xNum = parseInt(oTextBox.value);
    if( isNaN( xNum ) )
    {
        oTextBox.value = 12;
    }
    else
    {
        oTextBox.value = xNum;
    }
};

var timeCheckMinute = function ( oTextBox )
{
    var xNum = parseInt(oTextBox.value);

    if ( !isNaN( xNum ) )
    {
        // limit to 00-59
        if ( xNum < 0 )
        {
            oTextBox.value = '00';
        }
        else if ( xNum > 59 )
        {
            oTextBox.value = '59';
        }

    }

    makeDirty();
};

var timeCheckMinuteBlur = function ( oTextBox )
{
    var xNum = parseInt(oTextBox.value);
    if( isNaN( xNum ) )
    {
        oTextBox.value = '00';
    }
    else
    {
        oTextBox.value = xNum;
        if( oTextBox.value.length == 1 )
        {
            oTextBox.value = '0' + oTextBox.value;
        }
    }
};

var getNow = function( szAttId )
{
    // get current time
    var currentTime = new Date()
    var hours = currentTime.getHours()
    var minutes = currentTime.getMinutes()

    // check for pm
    if (hours >= 12)
    {
        // set the hours to 12 hour
        hours = hours - 12;

        // set the ampm
        $( 'time_ampm_' + szAttId ).options[1].selected = true;
    }
    else
    {
        // set the ampm
        $( 'time_ampm_' + szAttId ).options[0].selected = true;
    }

    // correct for 0
    if (hours == 0)
    {
        hours = 12;
    }

    // 0 pad as necessary
    if (minutes < 10)
    {
        minutes = '0' + minutes;
    }

    // set values
    $( 'time_hour_' + szAttId ).value = hours;
    $( 'time_minute_' + szAttId ).value = minutes;

    makeDirty();
};

var countKeys = function( oText )
{
    if (oText.value.length > 255)
    {
        alert( 'You have reached the maximum limit of 255 characters.' );
        oText.value = oText.value.substr(0,255);
    }
}

/**
  ______________________________________________________________________________
 |
 |  String functions
 |______________________________________________________________________________

 **/

var safeString = function( szString )
{
    // replace all problematic chars
    szReturn = szString.replace(/\&/g,"*amp*");
    szReturn = szReturn.replace(/</g,"*lt*");
    szReturn = szReturn.replace(/>/g,"*gt*");
    szReturn = szReturn.replace(/\//g,"*fs*");
    szReturn = szReturn.replace(/\\/g,"*bs*");
    szReturn = szReturn.replace(/\'/g,"*sqt*");
    szReturn = szReturn.replace(/\"/g,"*dqt*");
    szReturn = szReturn.replace(/\,/g,"*com*");

    return szReturn;
}

var unsafeString = function ( szString )
{
    // replace all problematic chars
    szReturn = szString.replace(/\*amp\*/g,"&");
    szReturn = szReturn.replace(/\*lt\*/g,"<");
    szReturn = szReturn.replace(/\*gt\*/g,">");
    szReturn = szReturn.replace(/\*fs\*/g,"/");
    szReturn = szReturn.replace(/\*bs\*/g,"\\");
    szReturn = szReturn.replace(/\*sqt\*/g,"'");
    szReturn = szReturn.replace(/\*com\*/g,"\,");

    return szReturn;
}

var xmlSafe = function ( szString )
{
    // replace unsafe characters
    szReturn = szString.toString().replace(/\&/g,"&amp;");
    szReturn = szReturn.replace(/</g,"&lt;");
    szReturn = szReturn.replace(/>/g,"&gt;");

    // return the string
    return szReturn;
}

var xmlUnSafe = function ( szString )
{
    // replace unsafe characters
    szReturn = szString.replace(/\&amp;/g,"&");
    szReturn = szReturn.replace(/\&lt;/g,"<");
    szReturn = szReturn.replace(/\&gt;/g,">");

    // return the string
    return szReturn;
}

// Removes leading whitespaces
function LTrim( value ) {

	var re = /\s*((\S+\s*)*)/;
	return value.replace(re, "$1");

}

// Removes ending whitespaces
function RTrim( value ) {

	var re = /((\s*\S+)*)\s*/;
	return value.replace(re, "$1");

}

// Allows long question labels to break on special "%n" character
function wrapText(str) {
	return str.replace(/\\n/g, "<br/>");
}

// Removes leading and ending whitespaces
function trim( value ) {

	return LTrim(RTrim(value));

}

var deleteElement = function( szId )
{
    // get the obj and parent
    var oDel = $( szId );
    var oParent =  $( szId ).parentNode;

    // only delete the obj if it exists
    if( !oDel )
    {
        return;
    }

    // remove events
    oDel.onmouseover = null;
    oDel.onmouseout = null;
    oDel.onclick = null;

    // remove it from the DOM parent
    oParent.removeChild( oDel );

    // kill it
    oDel = null;

}

/**
  ______________________________________________________________________________
 |
 | processing indicator functions
 |______________________________________________________________________________

 **/
var gnProcessingCount = 0;
var startProcessing = function()
{
    // only show if count = 0
    if ( gnProcessingCount <= 0 )
    {
        //new Effect.Appear( $('divProcessing'));
    	YAHOO.util.Event.onAvailable('divProcessing', function() {
    		$('divProcessing').style.display = 'block';
    	});
    }

    // keep count in
    gnProcessingCount++;
}

var stopProcessing = function()
{
    // decrement count
    gnProcessingCount--;

    // only stop processing if no processes left
    if ( gnProcessingCount <= 0 )
    {
        //new Effect.Fade( $('divProcessing'));
    	YAHOO.util.Event.onAvailable('divProcessing', function() {
    	    $('divProcessing').style.display = 'none';
    	});
        gnProcessingCount = 0;
    }
}

var setSelectValues = function(oSelectBox, aszValues) {
	for (var i = 0; i < oSelectBox.options.length; i++) {
		for (var j = 0; j < aszValues.length; j++) {
			if (oSelectBox.options[i].value == aszValues[j]) {
				oSelectBox.selectedIndex = i;
			}
		}
	}
}

var getOptionById = function(nId, oAttribute) {
	for (var i = 0; i < oAttribute.aOptions.length; i++) {
		if (oAttribute.aOptions[i].nId == nId) {
			return oAttribute.aOptions[i];
		}
	}
	return null;
}

var getOptionByText = function(szText, oAttribute) {
	var aOptions = oAttribute.aOptions;
	if (aOptions === undefined) {
		aOptions = oAttribute.options;
	}
	for (var i = 0; i < aOptions.length; i++) {
		if (aOptions[i].szText && aOptions[i].szText == szText) {
			return aOptions[i];
		} else if (aOptions[i].opt_text && aOptions[i].opt_text == szText) {
			return aOptions[i];
		}
	}
	return null;
}

var getOrCreateOptionByText = function(szText, oAttribute) {
	
	var targetOpt = getOptionByText(szText,oAttribute);
	if (targetOpt == null) {
		targetOpt = new pfOption();
		if (window.gnNewOptionCount) {
			targetOpt.nId = 'new-' + ++gnNewOptionCount;
		}
		targetOpt.szText = szText;
		oAttribute.aOptions.push(targetOpt);
	}
	return targetOpt;
}

function isNum(str) {
   var test = "0123456789"
   for (i=0; i <= str.length-1; i++) {
      if (test.indexOf(str.charAt(i)) == -1) return false;
   }
   return true;
}

function saveFormDataAndCallback(szMessage, oCallback) {
	var doneUpdating;
	if (gnCurrentRecord == -1 || gbIsDirty) {
		var doSubmit = confirm('Before ' + szMessage + ', you must save the form data.  If you wish to continue and have your form data submitted, click "ok", otherwise click "cancel"');
		if (!doSubmit) return;
		
		doneUpdating = false;
		submitData(
			function ( nRecId ) {
				if (nRecId != -1) {
					getRecById(nRecId,
						function() {
							doneUpdating = true;
						}
					);
				} else {
					doneUpdating = true;
				}
			}
		);
		var nSubmitTimer = window.setInterval(
	    	function() {
	    		if (!doneUpdating || gnCurrentRecord == -1) return;
	
	    		window.clearInterval(nSubmitTimer);
				oCallback();
			}
			,200);
	} else {
		oCallback();
	}

    
}

//until YUI version 3, we need to include this:
Array.indexOf = function(a, val) {
	for (var i=0; i<a.length; i=i+1) {
		if (a[i] === val) {
			return i;
		}
	}
	return -1;
};

/**
 * 
 * @param oConfig - on object with the following properties:
 * 		outputFields (optional) - the output fields to fetch from the form
 * 		ssid - the server-side session id that identifies this session with the form-server
 * 		field - the field to run the search against
 * 		search - the text to search.  surround with double-quotes for "exact" match
 * 		formId - the id of the form to search
 * 		callback - function which will receive an array of search results after the search has completed
 * @return none
 */
function searchForRecords( oConfig ) {
	if (oConfig.outputFields) {
		var szOutputFields = oConfig.outputFields;
	} else {
		var szOutputFields = "__all__";
	}
	
	new Ajax.Request(
		gszFormServer + 'transaction.php',
		{
			method: 'post',
			parameters: 'ssid=' + oConfig.ssid + '&request=record_search&field=' + oConfig.field + '&search=' + oConfig.search + '&output_fields=' + szOutputFields + '&id=' + oConfig.formId + '&format=json',
			onComplete: function(xmlResponse) {
				var oResults = [];
				try {
					var nNodeIndex = findNodeByName( xmlResponse.responseXML,'response');
				    if ( nNodeIndex == -1 )
				    {
				        alert( 'Invalid response from server.' );
				        return;
				    }
				    var json = getNodeValue(xmlResponse.responseXML.childNodes[nNodeIndex]);
					oResults = eval('(' + json + ')');
				} catch (e) {
					alert('error parsing search results: ' + e);
				}
				oConfig.callback(oResults);
			},
			onException: function(req, exception) {
				alert('error ' + exception);
			}
		}
	);
};

var findGeomAttribute = function(oForm) {
	var oGeomAttribute = null;
	
	oForm.aAttributes.each(function(oAttribute) {
		if (oAttribute.szType == 'generic_geometry') {
			// we only support editing points right now
			oGeomAttribute = oAttribute;
			var oGeomOpt = getOptionByText('geometry_type', oGeomAttribute);
		}
	});
	
	// also check whether we've got a "the_geom" question
	for ( var i = 0; i < oForm.aAttributes.length; i++) {
		if (oForm.aAttributes[i].szType == 'x_y' || oForm.aAttributes[i].szType == 'address' || oForm.aAttributes[i].szType == 'address_unit') {
			
			// however, if we already have a geometry attribute, use it
			if (oGeomAttribute == null) {
				oGeomAttribute = {
					szTitle : 'the_geom',
					aOptions : []
				};
			}
			break;
		}
	}
	return oGeomAttribute;
};
