if (!this.JSON) {
    JSON = {};
}
(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapeable.lastIndex = 0;
        return escapeable.test(string) ?
            '"' + string.replace(escapeable, function (a) {
                var c = meta[a];
                if (typeof c === 'string') {
                    return c;
                }
                return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// If the object has a dontEnum length property, we'll treat it as an array.

            if (typeof value.length === 'number' &&
                    !value.propertyIsEnumerable('length')) {

// The object is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
})();



/* CLASS Element */

function Element()  {
    this.display_name =  function(x) { /* a helper function */
        var tokens = x.split('.');
        var fname = tokens.pop();
        fname = fname.replace(/_id/, '');
        return fname;
    };
}


/* CLASS Form */

function Form()  { 
    this.message = "",
    this.firstMessage = function(x) {
        if (!this.message) {
            this.message = x;
        }
    };

    this.validationObjects = [];

    this.addValidationObject =  function(x, index) {
        this.validationObjects.push({field : x, index : index });
    };

    this.lastError = {
        obj : null,
        style : {}
    };

    this.runValidations = function() {
        var result = true;
        this.message = '';
        if (this.lastError.obj) {
            for (var p in this.lastError.style) {
                this.lastError.obj.style[p] = this.lastError.style[p];
            }
        }
        this.validationObjects.sort(function(a,b) { return a.index - b.index }); //sort ascending by index
        for (var i=0; i<this.validationObjects.length; i++) {
              var vobj = this.validationObjects[i].field;
              result = result && vobj.validationFunction(); /* no validation function should fail! */
              if (!result) {
                  this.lastError.obj = vobj;
                  this.lastError.style.borderWidth = vobj.style.borderWidth;
                  this.lastError.style.borderColor = vobj.style.borderColor;
                  vobj.style.borderWidth = 'medium';
                  vobj.style.borderColor = 'red';
                  vobj.scrollIntoView();
                  window.scrollBy(0,-50);
                  vobj.focus();
                  break;
              }
        }
        return result;
    };

    this.create = function() {
        return new Form(x, y, m, d);
    };

};

Form.prototype = new Element();
Form.prototype.constructor = Form;

/* CLASS Control */

function Control() {

    this.validations = {

        notNull: function(x,s) {
                      if (!x.value) {
                          Ngx.Odb.Form.firstMessage( (s || Ngx.Odb.Form.display_name(x.name)) + " cannot be null");
                          return false;
                      }
                      else {
                          return true;
                      }
                   },
        isNull: function(x,s) {
                      if (!x.value) {
                          return true;
                      }
                      else {
                          Ngx.Odb.Form.firstMessage( (s || Ngx.Odb.Form.display_name(x.name)) + " must be null");
                          return false;
                      }
                  },
         isNumber: function(x,s) {
             if (isNaN(x.value)){
                     Ngx.Odb.Form.firstMessage( (s || Ngx.Odb.Form.display_name(x.name)) + " must be a number");
                     return false;
                 }
                 else { 
                     return true;
                 }
             },
         isNotNumber: function(x,s) {
             if (!x.value || isNaN(x.value)){
                     return true;
                 }
                 else { 
                     Ngx.Odb.Form.firstMessage((s || Ngx.Odb.Form.display_name(x.name)) + " must not be a number");
                     return false;
                 }
             },
    
         inRange: function(x,s, min, max) {
             if (!isNaN(x.value) && !isNaN(min) && !isNaN(max) && x.value >= min && x.value <= max) {
                 return true;
             }
             else {
                 Ngx.Odb.Form.firstMessage((s || Ngx.Odb.Form.display_name(x.name)) + " must be a number between " + min + " and " + max);
                 return false;
             }
         },
         isPositive: function(x,s) {
             if (isNaN(x.value) || x.value == '' ) {
                 return true;
             }
             if (x.value <= 0) {
                 Ngx.Odb.Form.firstMessage((s || Ngx.Odb.Form.display_name(x.name)) + " value not valid");
                 return false;
             }
             else {
                 return true;
             }
         },
         isEmail: function(x, s) {
             objRegExp=/\w{1,}\.*\w{1,}\@\w{2,}\.\D+/g;
             if(x.value && x.value.search(objRegExp)) {
                 Ngx.Odb.Form.firstMessage((s || Ngx.Odb.Form.display_name(x.name)) + " must be a valid email id");
                 return false
             }
             else {
                 return true;
             }
         }

    };

    this.create = function() {
        return new Control();
    };


}

Control.prototype = Element;
Control.prototype.constructor = Control;


/* CLASS Date control */

function DateDropdown(x, y, m, d, nullable /* names of respective form fields on document. x is the hidden date input, others are entry dropdowns */ ) {
    this.x = x;
    this.y = y;
    this.m = m;
    this.d = d;
    this.nullable=nullable;

    var _this = this;
    this.init_date = function() {
        var day = document.main[_this.d];
        var month = document.main[_this.m];
        var year = document.main[_this.y];
        var dateobj = new Date();

        var end_year = dateobj.getFullYear()+30;
        var start_year = 1940;

        var init_date = document.main.elements[_this.x].value;
        var init_date_tokens = init_date.split("-");
        var init_year = parseInt(init_date_tokens[0]);
        var init_month = parseInt(init_date_tokens[1]);
        var init_day  = parseInt(init_date_tokens[2]);

        //alert('init date : ' + _this.x + '=' +  init_day + '=' + init_month + '=' + init_year);

        if (this.nullable == 1) {
          day.options[0] =  new Option('', '', false, false);
          month.options[0] =  new Option('', '', false, false);
          year.options[0] =  new Option('', '', false, false);
        }
        for (var i=1; i<= 31 ; i++) {
            var opt;
            if (init_day == i) {
                opt = new Option(i, i, false, true);
            }
            else {
                opt = new Option(i, i, false, false);
            }
            day.options[i] = opt;
        }
        for (var i=1; i<= 12 ; i++) {
            var opt;
            if (init_month == i) {
                opt = new Option(i, i, false, true);
            }
            else {
                opt = new Option(i, i, false, false);
            }
            month.options[i] = opt;
        }
        for (var i=start_year; i<= end_year; i++) {
            var opt;
            if (init_year == i) {
                opt = new Option(i, i, false, true);
            }
            else {
                opt = new Option(i, i, false, false);
            }
            year.options[i-start_year+1] = opt;
        }
    };

    this.check_date = function(y,m,d) {
         var date = new Date (y,m,d);
         var yy = date.getFullYear();
         var mm = date.getMonth();
         var dd = date.getDate();
         return ((yy == y) && (mm == m) && (dd == d));
    };

    this.recalc_day = function(){
        var day = document.main[_this.d];
        var month = document.main[_this.m];
        var year = document.main[_this.y];
        var sel_day = day.value;
        var sel_month = month.value;
        var sel_year = year.value;
        day.options.length = 0;

        // 0 = jan, 1 = feb, 2 = mar etc
        if (! isNaN(sel_month)) {
             sel_month = sel_month - 1;
        }

        //alert('recalculating days : ' +  sel_day + '=' + sel_month + '=' + sel_year);

        if (_this.nullable == 1) {
          day.options[0] =  new Option('', '', false, false);
        }

        for (var i=1; i<= 31 ; i++) {
            var date_flag=0;
            if (isNaN(sel_year) || sel_year == '' || isNaN(sel_month) || sel_month == '') {
                date_flag=1;
            } else if (this.check_date(sel_year,sel_month,i)) {
                date_flag=1;
            }
            if (date_flag == 1) {
                var opt;
                if (i == sel_day) {
                    opt = new Option(i, i, false, true);
                }
                else {
                    opt = new Option(i, i, false, false);
                }
                day.options[i] = opt;
            }
        }
    };

    this.update_field = function() {
         var field = document.main[_this.x];
         var day_val = parseInt(document.main[_this.d].value);
         var month_val = parseInt(document.main[_this.m].value);
         var year_val = parseInt(document.main[_this.y].value);
         if (!isNaN(day_val) && !isNaN(month_val) && !isNaN(year_val)) {
            field.value = year_val + '-' + month_val + '-' + day_val;
         }
    };

    this.create = function(x, y, m, d) {
        return new DateDropdown(x, y, m, d);
    };

}

DateDropdown.prototype = Control;
DateDropdown.prototype.constructor = DateDropdown;

/* CLASS Date control */

function Hobjectpicker( fieldname, fieldname_selected_text, main_div_name, full_base_url ) {
    this.fieldname = fieldname;
    this.fieldname_selected_text = fieldname_selected_text;
    this.main_div_name = main_div_name;
    this.full_base_url = full_base_url;
    var _this = this;

    this.leaf_nodes = function() {
        var main = document.getElementById(_this.main_div_name);
        var main_lis = main.getElementsByTagName("li");
        if(main_lis.length){
            for(var t=0; t<main_lis.length;t++){
                var inner_ul = main_lis[t].getElementsByTagName("ul");
                var count = main_lis[t].id.split("/");
                if(!inner_ul.length && count.length>2){
                    main_lis[t].className="leaf_node";
                }
            }
        }
    }

    this.show_picker = function() {
       var div_arr = document.getElementsByTagName("div");
       var prefix = _this.main_div_name.slice(0,_this.main_div_name.lastIndexOf("_")+1);
       for(var q=0;q<div_arr.length;q++){
           var div_prefix = div_arr[q].id.search(prefix);
           var div_id = div_arr[q].id;
           if(div_prefix ==0 && div_id != _this.main_div_name){
               div_arr[q].style.display="none";
           }
           else if(div_prefix ==0 && div_id == _this.main_div_name){
               if(div_arr[q].style.display=="none") {
                   div_arr[q].style.display="block";
               }
               else {
                   div_arr[q].style.display="none";
               }
           }
       }
    };

    this.select_kid = function(xpath,xid) {
        /* copying path to textbox */
        var fld = document.main.elements[_this.fieldname];
        fld.value = xid;
        document.getElementById(_this.fieldname_selected_text).innerHTML = xpath.substring(xpath.indexOf('/')+1);
        if (this.onchange) { 
            this.onchange.call(fld);
        }
    };

    this.show_kids = function(xpath,xid) {
        var xelement = document.getElementById(xpath);
        var uls = xelement.getElementsByTagName("ul");
        /* hide all kids */
        _this.hide_kids(xelement.id);
        /* hide/show currently selected item  */
        if(uls.length){
            if(uls[0].style.display=="block") {
                uls[0].style.display="none";
                xelement.style.backgroundImage="url('" + _this.full_base_url + 'themes/Default/images/arrow.gif' +"')";
            }
            else {
                uls[0].style.display="block";
                xelement.style.backgroundImage="url('" + _this.full_base_url + 'themes/Default/images/arrow-open.gif' +"')";
            }
        }
    };


    this.hide_kids = function(xnode_id) {
        var sup = document.getElementById(_this.main_div_name);
        var lis = sup.getElementsByTagName("li");
        for(var j=0; j<lis.length;j++){
            var li_obj = lis[j];
            var place = xnode_id.lastIndexOf("/");
            var last_place = li_obj.id.lastIndexOf("/");
            if((xnode_id != li_obj.id) && (place == last_place)){
                var uls = li_obj.getElementsByTagName("ul");
                if(uls.length){
                    uls[0].style.display="none";
                    li_obj.style.backgroundImage="url('" + _this.full_base_url + 'themes/Default/images/arrow.gif' +"')";
                }
            }
        }
    };



    this.create = function(fieldname, fieldname_selected_text, main_div_name, full_base_url) {
        return new Hobjectpicker(fieldname,fieldname_selected_text, main_div_name, full_base_url);
    };
    
    this.get_element = function(element,member){
        /* select only LI */    
        do{
            if (member == "parent"){
                element = element.parentNode;
            }
            else if(member == "child") {
                element = element.firstChild;
            }
            else if(member == "prev_sibling") {
                element = element.previousSibling;
            }
            else if(member == "next_sibling") {
                element = element.nextSibling;
            }
        }while(element && (element.nodeName!="LI" || element.nodeType!=1));
        return element;
    }
    
    this.get_key = function(ev,xpath,xid){
        var doc_obj = document.getElementById(xpath);
        var key_obj = window.event? event : ev;
        var key = key_obj.keyCode ? key_obj.keyCode : key_obj.charCode;
        if((key==40 || key==39 || key==13) && xpath==_this.fieldname_selected_text) {
            var main_div = document.getElementById(_this.main_div_name);
            var main_div_arr = main_div.getElementsByTagName("a");
            if(main_div.style.display=="none") {
                _this.show_picker();
            }
            main_div_arr[0].focus();
        }
        if (key == 27){
            _this.hide();
        }
        else if(key == 40){ //down arrow
            doc_obj.blur();
            var sib = _this.get_element(doc_obj,'next_sibling');
            var obj_ul = doc_obj.getElementsByTagName("ul");
            if(obj_ul.length!=0 && obj_ul[0].style.display=="block"){
                var obj_li = obj_ul[0].getElementsByTagName("li");
                var sub_an = obj_li[0].getElementsByTagName("a");
                sub_an[0].focus();
            }
            else if(sib) {
                    var sub_an = sib.getElementsByTagName("a");
                    sub_an[0].focus();
            }
            else
            {
                 _this.next_item(doc_obj);
            }
        }
        else if(key == 38) { //up arrow
            var sib = _this.get_element(doc_obj,'prev_sibling');
            if(sib) {
                _this.prev_item(sib);
            }
            else
            {
                var sib = _this.get_element(doc_obj,'parent');
                if(sib){
                    var sub_an = sib.getElementsByTagName("a");
                    sub_an[0].focus();
                }
                else {
            //        alert("Can't move further..");
                }
            }
        }
        else if(key == 37) { //left arrow
            var sib = _this.get_element(doc_obj,'parent');
            if(sib) {
                var sub_an = sib.getElementsByTagName("a");
                sub_an[0].focus();
                _this.show_kids(sib.id,sib.value);
            }
             else
             {
          //       alert("Can't move further..");
             }
        }
        else if(key == 39) { //right arrow
            _this.show_kids(xpath,xid);
            var sub_li = doc_obj.getElementsByTagName("li");
            if(sub_li.length) {
                var sub_an = sub_li[0].getElementsByTagName("a");
                sub_an[0].focus();
            }
             else
             {
          //       alert("Can't move further..");
             }
        }
        else if(key == 13 && xpath!=_this.fieldname_selected_text){
            _this.select_kid(xpath,xid);
            _this.show_picker();
        }
    }
    
    this.prev_item = function(sib){
        var sup_ul = sib.getElementsByTagName("ul");
                if(sup_ul.length==0 || sup_ul[0].style.display=="none"){
                    var sub_an = sib.getElementsByTagName("a");
                    sub_an[0].focus();
                }
                else{
                    var children = sib.getElementsByTagName("li");
                    var child = children[0];
                    var last_child;
                    do{
                        last_child = child;
                        child = _this.get_element(child,'next_sibling');
                    }while(child)
                    var ul = last_child.getElementsByTagName("ul");
                    if(ul.length==0 || ul[0].style.display=="none"){
                        var sub_an = last_child.getElementsByTagName("a");
                        sub_an[0].focus();
                    }
                    else{
                        _this.prev_item(last_child);
                    }
                }
    }
    
    this.next_item = function(doc_obj){
        var par = _this.get_element(doc_obj,'parent');
        if(par){
            var par_sib = _this.get_element(par,'next_sibling');
            if(par_sib){
                var sub_an = par_sib.getElementsByTagName("a");
                sub_an[0].focus();
            }
            else{
                _this.next_item(par);
            }
        }
        else{
        //    alert("Can't move further..");
        }
    }
    
    this.hide = function(){
        document.getElementById(_this.main_div_name).style.display="none";
    }
    
    this.click = function(xpath,xid) {
        /* to select a leaf node */
        var obj = document.getElementById(xpath);
        if(obj.className=="leaf_node") {
            _this.select_kid(xpath,xid);
            _this.show_picker();
        }
        else {
            _this.show_kids(xpath,xid);
        }
    }
    
}

Hobjectpicker.prototype = Control;
Hobjectpicker.prototype.constructor = Hobjectpicker;


/* CLASS Objectpicker
 *
 * This is the replacement for dojo based dependent dropdowns implementation, 
 * We hope it is lighter weight and easier to understand and maintain
 */

function Objectpicker(fname, args) {

    var _this = this;

    this.do_onchange = function(/* event */evt) {
        this.cvalue = this.selectedValue();
        this.onchange(this.selectedName(), this.selectedValue());
    };

    this.onchange = function(name, value) {
        //functions for others to hook 
    };

    /* rows are an array of name, value pairs */
    /* the value can be a string or an object, user should know what they are retrieving */

        
    /* return the name field of selected row */
    this.selectedName = function() {
        return this.form_field.options[this.form_field.selectedIndex].text;
    };
    this.selectedValue = function() {
        return this.form_field.options[this.form_field.selectedIndex].value;
    };

    /* returns the (unflattened) selected row from cached_cdata */
    this.selectedRow = function(s) {
        for (var i=0; i<cdata.length; i++) {
            if (cdata[i][0] == s) {
                return cdata[i][1];
            }
        }
        return null;
    };

    this.check_constraints = function (row) {
       if (!this.constraint_fields) {
           return true;
       }
       for (var i=0; i<this.constraint_fields.length; i++) {
            var cname = this.constraint_fields[i];
            //var cfield = dojo.widget.manager.getWidgetById(cname).form_field;
            var cfield = document.getElementById(cname);
            var cvalue = cfield.options[cfield.selectedIndex].value
            //alert("checking constraint " + cname + " row val " + row[cname] + " con val " + cvalue);
            if (row[cname] &&  cvalue >0 &&
                row[cname] != cvalue )
            {
                //alert("check constraint failed");
                return false;
            }
       }
       return true;
    };

    /* a constrained objectpicker is deemed unconstrained if all of its constraining fields are set to null */

    this.is_unconstrained = function() {
       var result = true;


       if (!this.constraint_fields) {
           return false;
       }
       for (var i=0; i<this.constraint_fields.length; i++) {
            var cname = this.constraint_fields[i];
            var cfield = document.getElementById(cname);
            var cvalue = parseInt(cfield.options[cfield.selectedIndex].value);
            if (cvalue > 0 ) {
                result = false;
                break;
            }
        }
        return result;
    };
        

    /* recalc re-creates the options list of the select box */
    /* based on data, constraints, and values of constraining fields */
    /* while any constraining field set to null acts to relax the constraint by allowing all relevant values */
    /* the end condition of all constraining fields set to null causes this field to become disabled */
    /* this is not a mathematical consequence, but a usability issue */
    /* in that a fully unconstrained user input list which is otherwise constrained, is usually senseless */

    this.recalc = function() {

        if (_this.is_unconstrained()) {
            _this.form_field.options.length = 0;
            _this.form_field.innerHTML = '';
                if (_this.cis_filter>0) {
                    opt = new Option('Select here','', false, false);
                    _this.form_field.options[0] = opt;
                }
                else {
                    opt = new Option(_this.cnull_option_display_name,'', false, false);
                    _this.form_field.options[0] = opt;
                }
                    

            _this.form_field.setAttribute('disabled', true);
            _this.do_onchange();
            return;
        }
        else {
            _this.form_field.removeAttribute('disabled');
        }
          
        if (!_this.cdata) { 
            _this.do_onchange();
            return; 
        }

        _this.form_field.options.length = 0;
        _this.form_field.innerHTML = '';
        var counter = 0;
   
        var optstring = '';
        if (_this.cis_filter>0) {
            opt = new Option('Select here','', false, false);
            _this.form_field.options[counter++] = opt;
        }
        else {
            if (_this.cforce_selection > 0) {
                opt = new Option('Select here','-1', false, false);
            }
            else {
                opt = new Option('Select here','', false, false);
            }
            _this.form_field.options[counter++] = opt;
        }


        //optstring += '<option value=""></option>'

        var selIndex = null;
        var validOptions = 0;
        for (var i=0; i<_this.cdata.length; i++) {
            if (_this.check_constraints(_this.cdata[i][1])) {
                if (_this.cvalue == _this.cdata[i][1].id) {
                    opt = new Option(_this.cdata[i][0],_this.cdata[i][1].id,true, true);
                    selIndex = counter;
                }
                else {
                    opt = new Option(_this.cdata[i][0],_this.cdata[i][1].id,false, false);
                }
                validOptions++;
                _this.form_field.options[counter++] = opt;
            }
        }

        if (selIndex) {
            _this.form_field.selectedIndex = selIndex;
        }

        if (_this.cnull_option > 0 && _this.cis_filter <= 0) {
            opt = new Option(_this.cnull_option_display_name,'', false, false);
            _this.form_field.options[counter++] = opt;
        }
       
        if (_this.cnull_option > 0 && _this.cis_filter>0) {
            opt = new Option(_this.cnull_option_display_name,'null', false, false);
            _this.form_field.options[counter++] = opt;
        }   
            
        if (validOptions == 0) {
            _this.form_field.setAttribute("disabled", true);
        }

        _this.do_onchange();

    };

    this.valuename = function(v) { 
        for (var i=0; i<this.cdata.length; i++) {
            if (this.cdata[i][1].id == v) {
                return cdata[i][0];
            }
        }
        return '';
    };

    /* the qm api, a standardized set of wrappers around organically grown widgets */

    this.q_initialize = function(/* numeric id */ v) {
        for (var i=0; i<this.form_field.options.length; i++) {
            if (this.form_field.options[i].value == v) {
                this.form_field.options[i].selected = true;
            }
            else {
                this.form_field.options[i].selected = false;
            }
        }
    };

    if (fname) {
        this.form_field = document.getElementById(fname);
        this.cforce_selection = parseInt(args.force_selection);
        this.cnull_option = parseInt(args.null_option);
        this.cnull_option_display_name = args.null_option_display_name;
        this.cis_filter = parseInt(args.is_filter);
    
        /* set the style of select input field from args if specified */
        /* don't use setAttribute because IE does not like that for style property */
    
        if (args.style_string) {
            this.form_field.style.cssText = args.style_string;
        }
    
        /* allow data to be sent inline via data argument */
        /* TODO support remote data fetching if required */
    
        if (args.data) {
            this.cdata = JSON.parse(args.data);
        }
    
        this.cvalue = parseInt(args.value);
    
        /* set up constraints on the dataset */
        /* these are typically mapped from foreign key relationships in datamodel */
    
        if (args.constraints) { /* */
    
            this.constraint_fields = JSON.parse(args.constraints);
            for(var i=0; i<this.constraint_fields.length; i++) {
                var c = this.constraint_fields[i];
                //dojo.event.connect(dojo.widget.manager.getWidgetById(c).form_field, "onchange", this, "recalc");
                document.getElementById(c).onchange = this.recalc;
            }
    
        }
    
        /* surface the onchange event of form_field for others to hook */
    
        // dojo.event.connect(this.form_field, "onchange", this, "do_onchange");
    
        /* do the first recalc to create visible/available options list from data given constraints */
    
        this.recalc();
    
        /* initialize with value if present */
    
        if (this.value) {
            this.q_initialize(this.value);
        }
        
        /* disable if required */
    
        document.addOnLoadFunction( function() { if (_this.is_unconstrained()) {
                                                     _this.form_field.setAttribute('disabled', true);
                                                 } 
                                               });
    }
    

    this.create = function(fname, args) {
        return new Objectpicker(fname, args);
    };
}

Objectpicker.prototype = new Control();
Objectpicker.prototype.constructor = Objectpicker;

/* GLOBAL Ngx */

var Ngx = { 

    Odb: {
        Form: new Form(),
        Control: new Control(),
        DateDropdown: new DateDropdown(),
        Objectpicker: new Objectpicker(),
        Hobjectpicker: new Hobjectpicker()
    }

}

