/* -*- javascript -*-
     Copyright 2006 TJM Enterprises, Inc..
     All Rights Reserved
     System        : BACKGROUND_UPDATE_JS :
     Object Name   : $RCS_FILE$
     Revision      : $REVISION$
     Date          : Mon Apr 3 20:32:58 2006
     Created By    : Bradford McKesson, TJM Enterprises, Inc.
     Created       : Mon Apr 3 20:32:58 2006

     Last Modified : <030310.0724>
     ID            : $Id: js.etf,v 2.5 2004/03/25 21:58:00 jon Exp $
     Source        : $Source: /export/cvs/cvsroot/me/macros/js.etf,v $
     Description
     Notes
     $Log: js.etf,v $
     Revision 2.5  2004/03/25 21:58:00  jon
     Final release from steve

 * 2009-01-02 bwm: jquery.autogrow on textarea in datagrid
 * 2008-03-20 bwm:
 * 2007-12-11 bwm: remove use of req_ in field names so the dg_ funcs work
 *   on either payable (pay_) or request (req_) datagrids
 *   This should delay the final day of reckoning for the dg functions for
 *   another year.
 */
var ns4 = document.layers;
var ie4 = document.all;
var nn6 = document.getElementById && !document.all;

var request_queue = new Array();

// treat inObj as associative array, convert it into an
// HTTP POST-able string.
function object_to_post(inObj, inKVsep, inItemSep) {
    if (!inKVsep) { inKVsep = '=' }
    if (!inItemSep) { inItemSep = '&' }

    var result = new Array();
    for (p in inObj) {
        // ### tree of objects not handled.
        if (inObj[p] || isset(inObj[p])) {
            result.push(urlencode(p.toString()) + inKVsep +  urlencode(inObj[p].toString()));
        } else {
            window.warn('Expected element '+p+' not defined');
            // alert (typeof(inObj[p]));
        }
    }
    return result.join(inItemSep);
}

function queue_extract_prefix(part)  {
    var last_open_bracket = part.lastIndexOf('[');

    var prefix_name = '';
    if (last_open_bracket > -1) {
        prefix_name = part.substring(0, last_open_bracket);
    }
    return prefix_name;
}


// Action
// store data for later sending to server
// usually clientside, but on delete will immediately send to server.
// inElement has a bbrecord container.
function queue_update(inElement, inArgList, inEvent) {
    window.dispatcher.error('OBSELETE queue_update called');
    var row = dg_get_row_for_element(inElement);
    if (row && row.widget) {
        return row.widget.queue_update(inElement, inArgList, inEvent);

    }
    return false;
}

// Compensate for not getting a reliable focus/blur/change
// event on table row.
// Track current row.  If it changes then do queue_send() on the
// previously current row.
// ### could be our first synthetic event.
// OBSELETE
function focus_row(inElement, inArgList, inEvent) {
    window.dispatcher.error('OBSELETE focus_row called');
    var elrow = getAncestorByClass(inElement, 'bbrecord');
    var ctr = null;
    return elrow.widget.focus_record(inElement, inArgList, inEvent);

    if (!window.dispatcher.focus_row) {
        if (elrow) {
            window.dispatcher.focus_row = elrow;
        }
    } else if (window.dispatcher.focus_row != elrow) {
        queue_send(window.dispatcher.focus_row, inArgList, json_receive, inEvent);
        replace_class(window.dispatcher.focus_row, 'hilight', 'norm');
        window.dispatcher.focus_row = elrow;
        update_totals(inElement);
    }
    replace_class(window.dispatcher.focus_row, 'norm', 'hilight');
    return true;
}

// Action
// index.php?view=aj_view&action=tablename_session_update
// inArgList is ignored in favor of the grid row's .queue property.
function queue_send(inElement, inArgList, inCB, inEvent) {
    window.dispatcher.error('OBSELETE queue_send called');
    var row = null;
    if (!(inElement.widget && (inElement.widget.isa('bbrecord')))) {
        row = dg_get_row_for_element(inElement);
    } else {
        row = inElement;
    }
    return row.queue_send(inElement, inArgList, inCB, inEvent);

    return false;
}


// Action
// Submit data to server without leaving current page.
// The default callback will update the current page with
// the response.
// minimum expected entries in inArgList:
// { view: view_name, action: action_name, key: key, table: table_name }
// probably also want {element: <Element object ref>}
// inCB is a function(inReq, inArgList, inElement, inSuccess) to call when the
// request is completed.
function background_send(inElement, inArgList, inCB, inEvent) {
    if (inElement instanceof Event) {
        debug_msg("received event, shifting");
        inElement = inArgList
        inArgList = inCB;
        inCB = inEvent;
    }

    if (inElement.getValue) {
        // send new field value
        // ### caller should have assigned value if desired
        inArgList[ inElement.name ] = inElement.getValue();
    }

    var ht = getHTTPObject();
    var postdata = object_to_post(inArgList);
    // debug_msg(sending: inArgList.toString());
    // ht.open("POST", 'index.php', true);
    ht.open("POST", window.location.pathname, true);
    ht.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    ht.onreadystatechange = cb_background_send();
    ht.send(postdata);
    var req = { ht: ht, args: inArgList, cb: inCB, element: inElement, stamp: Date.now(), finished: false}

    request_queue.push(req);
    debug_msg('Sending ' + postdata);
    return false;
}

// called by event (onreadystatechanged).
// Scan request_queue to find which one(s) finished, call their handlers.
// AFAIK, no args can be passed to the xmlhttprequest callback.
//    We can use anonymous function closure but IE6 leaks closures...
// We compensate by grouping request, callback, and args in a list in
// background_send(), then looking up the appropriate group here.
function cb_background_send() {
    //window.debug('cb_background_send');
    var a = request_queue.length;
    var nd = new Date();
    var n = nd.getDate();
    var diff;
    var remove = new Array();
    for(var i = 0; i < a; i++) {
        var req = request_queue[i];
        diff = n - request_queue[i].stamp;

        if (!req.finished && (req.ht.readyState == 4)) {
            // finished
            if (req.cb && !req.finished) {
                try {
                    if (req.cb.invoke) {
                        window.dispatcher.debug("background_update: invoke() ");
                        req.cb.invoke(req.ht, req.args, req.element, 1);
                    } else {
                        window.dispatcher.debug("background_update: normal_call ");
                        req.cb(req.ht, req.args, req.element, 1);
                    }
                } catch(e) {
                    window.error('Callback failed: ' + e.toString() + ';<br> ' + req.ht.responseText);
                }
            }
            req.finished = true;
            remove.push(i);
        } else if (diff > 45000) {
            // timeout error
            if (req.cb) {
                try {
                    if (req.cb.invoke) {
                        req.cb.invoke(req.ht, req.args, req.element, 1);
                    } else {
                        req.cb(req.ht, req.args, req.element, 0);
                    }
                } catch(e) {
                    window.error('Callback w/ timeout failed: ' + e.toString() + ';<br> ' + req.ht.responseText);
                }
            }
            req.finished = true;
            remove.push(i);
        } else if (req.finished) {
            remove.push(i);
        }
    }

    // remove finished requests.
    var ra = remove.length;
    for (i = 0; i < ra; i++) {
        if (request_queue[i] && request_queue[i].finished) {
            // .finished is an attempt to avoid multi-thread issue
            // of over-deletion.  under-deletion is tolerable.
            // skipping a pending completion is tolerable.
            request_queue.splice(remove[i], 1);
        }
    }
}

// cb_background_send() sometimes misses updates.
// queue_scan() makes sure all responses are handled semi-promptly.
window.request_queue_scan = false;
function start_queue_scan() {
    if (!window.request_queue_scan) {
        window.request_queue_scan = setInterval(cb_background_send, 1000);
    }
}

function stop_queue_scan() {
    if(window.request_queue_scan) {
        try {
            clearInterval(window.request_queue_scan);
        } finally {
            window.request_queue_scan = false;
        }
    }
}

// receive and display reponse from background_send
function debug_server_reply(inReq, inArgs, inElement, inSuccess) {
    if (inSuccess) {
        debug_msg(inReq.responseText);
    } else {
        debug_msg("Timeout Error");
    }
}


var dsr_count  = 0;
function debug_msg(inMsg, inFile, inFunc, inLine) {

    dsr_count++;
    if (window.info) {
        window.info(''+dsr_count+': '+inMsg);
    } else {
        // old logger
        var parent_msgs = document.getElementById('debug_msgs');
        var cnode = document.createElement('p');
        if (!msgs) {
            // window.alert("debug message block debug_msgs missing");
        } else {
            cnode.innerHTML=''+dsr_count+': '+inMsg;
            msgs.appendChild(cnode);
        }
    }
}

// process response from background_send() for a field update.
// Response can include instructions to update other fields as well.
// JSON form:
/* (dump standard template var $p)
   {records: [{key: table: field1: field2: } ,...],
   record_columns [{}],
   _url
   _navtarget
   _top
   _temlate
   _tablename
   }
 */
// we expect a single record returned-- the record matching the
// table,key that was submitted
// ### would like to accept any data presently displayed for updating
// OBSELETE use browser_base::record::json_receive/_dispatch
function json_receive(inReq, inArgs, inElement, inSuccess) {
    var ok = true;
    if (inSuccess) {
        var stat = inReq.status;
        if (stat == 200) {
            var jtxt = inReq.responseText;
            debug_server_reply(inReq, inArgs, inElement, inSuccess);
            var otxt = JSON.parse(jtxt);

            if (otxt instanceof Object) {
                //var alname = inElement.name;
                var table = inArgs['table'];
                var fieldname = inArgs['field_name'];
                //var alias_name = str_extract_alias_name(alname);
                var ep = window.dispatcher.event_proceed.toString();
                window.debug("event_proceed = "+ep);
                window.debug("otxt.receive: '"+otxt.receive+"'");
                if (otxt['aj_action_response'] &&
                    (otxt['aj_action_response'] != ep)) {
                    // basically server had an error, dont 'proceed' with
                    // usual processing.
                    window.warn('server action failed in json_receive()');

                    ok = false;
                } else {

                    if (otxt['receive'] == 'record_update'
                        && inArgs['key'] != 'insert') {
                        // show (and/or replace) an existing record
                        receive_record_update(otxt, inArgs, inElement);
                        // observer
                        if (fieldname && (fieldname.indexOf('item_number') > -1)) {
                            dg_focus_row(dg_get_row_for_element(inElement), 'qty_ordered]');
                        }
                    } else if (otxt['receive'] == 'record_delete') {
                        // remove record from display.
                        receive_record_delete(otxt, inArgs, inElement);
                        return true;
                    } else if (otxt['receive'] == 'record_insert') {
                        // ### add new record.
                    } else if (otxt['receive'] == 'choose_dialog') {
                        receive_choose_dialog(otxt, inArgs, inElement);
                        return true;
                    }

                    var tr = null;
                    if (inArgs['key'] == 'insert') {
                        // ### compensate for server sending record_update
                        // instead of record_insert
                        // user typed in the 'insert' row
                        receive_record_insert(otxt, inArgs, inElement);
                        receive_record_update(otxt, inArgs, inElement);
                        // find and focus the qty_ordered column (### invoice specific)
                        dg_focus_row(dg_get_row_for_element(inElement), 'qty_ordered]');

                    }
                    if (window.external_handler) {
                        // ### obselete
                        window.external_handler(inElement, inArgs, otxt);
                    }
                } // if aj_action_response

                // multiple dispatch
                window.warn('multiple dispatch in json_receive');
                var bbrow = getAncestorByClass(inElement, 'bbrecord');
                if (bbrow && bbrow.widget) {
                    window.warn('multiple dispatch in json_receive');
                    bbrow.widget._dispatch_aj_receive(otxt, inArgs);
                }
            } else {
                window.error("Cannot create JSON object " + inReq.statusText);
                ok = false;
            }
        } else {
            window.error(" HTTP error " + stat + " " + inReq.statusText);
            ok = false;
        }
    } else {
        window.error(" Timeout error: could not contact server ");
        ok = false;
    }
    return ok;
}

// Server response handlers

// Open the choose_dialog dialog (expect a record_update
// or record_insert after pick)
function receive_choose_dialog(inResponse,inArgs, inElement) {
    var otxt = inResponse;
    var ok = true;
    if (otxt['choose_dialog']) {
        debug_msg('using choose_dialog');
        if (choose_dialog_active) {
            dg_clear_choose_dialog();
        }
        dg_load_choose_dialog(otxt['choose_dialog'], inElement);
        // dont change anything til user picks or cancels.
        ok = true;
    } else {
        debug_msg('Nothing to choose in otxt');
        ok = true;
    }
    return ok;
}

// store changes to the record containing inElement
function receive_record_update(inResponse, inArgs, inElement) {
    var otxt = inResponse;
    if (inElement.setValue) {
        var alname = inElement.name;
        var table = inArgs['table'];
        var fieldname = inArgs['field_name'];
        var alias_name = str_extract_alias_name(alname);
        if (otxt['records'][0][alias_name]) {
            // simple edit, with server corrections
            inElement.setValue(otxt['records'][0][alias_name]);
        }
    }

    var row = getAncestorByClass(inElement, 'bbrecord');
    if (row) {
        // ### possible event feedback loop
        dg_fill_row(row, inArgs, otxt);
    } else {
        window.dispatcher.error("inElement is not in a bbrecord ("+inElement.tagName+") " + JSON.stringify(otxt));
    }
}

// remove row containing inElement from datagrid
function receive_record_delete(inResponse, inArgs, inElement) {
    var alname = inElement.name;
    var alias_name = str_extract_alias_name(alname);
    if (inResponse['records'][0][alias_name]) {
        var row = dg_get_row_for_element(inElement);
        if (row) {
            row.parentNode.removeChild(row);
        }
    }
}

// create a new insert row
function receive_record_insert(inResponse, inArgs, inElement) {
    var tr = dg_get_row_for_element(inElement);
    var new_tr = dg_duplicate_row(tr, inArgs, inResponse);
    dg_clear_row(new_tr, inArgs);
}

// return lowest DOM element enclosing all fields of the same
// datagrid row as inFormElement (expect <TR> unless i reformat)
function dg_get_row_for_element(inFormElement) {
    if (inFormElement.widget && (inFormElement.widget.isa('bbfield'))) { // bbfield
        debug('given bbfield input element '+inFormElement.tagName + ' named ' + inFormElement.name);
        return inFormElement.widget.record.elem;
    } else if (inFormElement.widget && (inFormElement.widget.isa('bbrecord'))) {
        debug('given bbrecord input element '+inFormElement.tagName + ' named ' + inFormElement.name);
        return inFormElement;
    } else {
        warn('found non-bbfield input element '+inFormElement.tagName + ' named ' + inFormElement.name);
        var tr = getAncestorByClass(inFormElement, 'bbrecord');
        if (!tr) {
            debug_msg('<br>cannot find table row. Found '+tr.tagName + ' instead.');
        }
        return tr;
    }
}

// Clear pick list <div>, load results from inPicks into list.
// onclick() of a pick listitem, update value of inRtnElement,
// and trigger onblur of inRtnElement (simulate user data entry)
var choose_dialog_active = false;
function dg_load_choose_dialog(inPicks, inRtnElement) {
    var choose_dialog = document.getElementById('choose_dialog');
    choose_dialog.widget.load_picks(inPicks, inRtnElement);
    choose_dialog_active = true; // prevent spurious dg updates
    return;
}


// remove all items from choose_dialog window
function dg_clear_choose_dialog() {
    var choose_dialog = document.getElementById('choose_dialog');
    if (!choose_dialog) {
        warn("cannot find 'choose_dialog'");
    }
    choose_dialog.widget.erase();
    choose_dialog_active = false; // prevent spurious dg updates
}

// request data to populate DDL
function dg_load_ddl(inSelectElem) {
    var n = inSelectElem.name;
    var ok = true;
    if (!n || !window.pmap[n]) {
        ok = false;
        debug_msg('Element has no model: ' + inSelectElem.toString());

    }
    if (ok && !((inSelectElem.tagName == 'SELECT') ||
                (inSelectElem.tagName == 'select')) ) {
        debug_msg('Not a <select> element: ' + inSelectElem.toString());
        ok = false;
    }
    var w = window.pmap[n];
    var fake_event = new Object();
    fake_event.type = 'change';
    fake_event.target = inSelectElem;
    background_send(inSelectElem, w, json_receive, fake_event);
    return ok;
}

// Receive data for Drop Down List options from server
// Populate inElem with the options.
function json_receive_ddl(inReq, inArgs, inElem, inSuccess) {
    var ok = (inSuccess)?true:false;
    if (ok) {
        var stat = inReq.status;
        if (stat != 200) {
            ok = false;
            debug_msg('Fail HTTP status '+stat);
        }
    }

    if (ok) {
        var jtxt = inReq.responseText;
        debug_server_reply(inReq, inArgs, inElement, inSuccess);
        var otxt = JSON.parse(jtxt);
        if (!(otxt instanceof Object)) {
            ok = false;
            debug_msg('Fail Cannot decode server response: '+jtxt);
        }
    }

    if (ok) {
        ok = ok && (inElement instanceof Object);
        if (ok) {
            ok = ok && ((inElement.tagName == 'SELECT') ||
                        (inElement.tagName == 'select'));
            debug_msg('Fail Invalid element '+inElem);
        }
    }

    if (ok) {
        // have valid JSON structure, valid element
        var result = otxt['records'];
        var len = result.length;
        var opt_html = '';
        for(var i = 0; i < len; i++) {
            if (result[i]) {
                opt_html = "<option value='" + result[i]['ddl'] + "'>" +
                          result[i]['description'] + "<\/option>\n";
            } else {
                debug_msg("Invalid member " + result[i] + " skipped.");
            }
        }
        inElem.innerHTML = opt_html;
        n = inElem.name;
        inElem.setValue(inArgs[n][n]);
    }

    return ok;
}

/////////////////////////
// Datagrid methods
/////////////////////////

// copy the 'new record' row, populate it with response data
function dg_duplicate_row(inRow, inArgs, inResponse) {
    debug('dg_duplicate_row');
    var tbl = inRow.parentNode;
    var new_row = inRow.cloneNode(true);

    tbl.appendChild(new_row);
    dg_fill_row(inRow, inArgs, inResponse, true);

    // cloneNode did not copy event handlers
    // DOM Event model does not allow inquiries about existing event handlers.
    // dg_fill_row(...true) already fixed up window.recordmap values for inRow
    // we reprovision new_row
    widgetize(new_row.parentNode);
    dg_add_grid_events(new_row);
    // ### re-populate DDLs on inRow for the new item

   return new_row;
}

// clear data out of the new record row.
function dg_clear_row(inRow, inArgs) {
    var inputs = inRow.getElementsByTagName('input');
    var selects = inRow.getElementsByTagName('select');
    var textareas = inRow.getElementsByTagName('textarea');
    var len = inputs.length;
    for (var i = 0; i < len; i++) {
        inputs[i].setValue('');
    }
    len = selects.length;
    for (var i = 0; i < len; i++) {
        selects[i].setValue('');
    }
    len = textareas.length;
    for (var i = 0; i < len; i++) {
        textareas[i].setValue('');
    }
}


// Copy record data from inResponse into <input> of inRow
// If inInsert, then remove 'insert' from the field name and onblur
//   inArgs is the window.pmap value for the element that generated
// the event leading to dg_fill_row() being called;
//   inResponse is the server response to inArgs.
function dg_fill_row(inRow, inArgs, inResponse, inInsert) {
    window.dispatcher.warn('dg_fill_row for ' + inArgs['key']);

    if (inRow.widget.queue) {
        // ### item_number change calls background_send directly
        // (which it should not do)
        // and leaves a stale queue.  We are about to blow away
        // everything on the line with a new/updated record, so
        // the queue contents are irrelevent
        // clean up any pending changes to the old record
        delete inRow.widget.queue;
    }
    var nl_inputs = inRow.getElementsByTagName('input');
    var nl_selects = inRow.getElementsByTagName('select');
    var nl_textareas = inRow.getElementsByTagName('textarea');
    var input_labels = inRow.getElementsByTagName('a');
    var inputs = new Array();
    var k = 0;
    append_nodelist(inputs, nl_inputs, true);
    append_nodelist(inputs, nl_selects);
    append_nodelist(inputs, nl_textareas);
    debug_msg('nl_inputs.length: '+nl_inputs.length + ' inputs.length: '+inputs.length);
    // ### select textarea
    var len = inputs.length;
    var evt_src_element_name = inArgs['part_prefix'];
    for (var i = 0; i < len; i++) {
        // extract aliasname
        try {
        if (inputs[i] && inputs[i].name) {
            var old_name = inputs[i].name;
            var pfxfn = inputs[i].name;
            var alias_name = str_extract_alias_name(inputs[i].name);

            // save value
            if (inResponse['records'][0][alias_name]) {
                inputs[i].setValue(inResponse['records'][0][alias_name]);
            } else {
                warn('alias_name not in record: ' + alias_name);
            }

            // save value label
            var label_elem = document.getElementById(inputs[i].name);
            if (label_elem) {
                // ### how should server specify the label for a value
                // that has an associated label?
                var label_alias = alias_name + '_description';
                if (inResponse['records'][0][label_alias] !== undefined) {
                    window.info('found label elem '+inputs[i].name + ' labeled '+inResponse['records'][0][label_alias]);
                    label_elem.setLabel(inResponse['records'][0][label_alias]);
                } else {
                    window.error('obselete fill row no ' + label_alias + ' found');
                    label_elem.setLabel('----');
                }
            }

            if (inInsert) {
                // we are filling the insert row
                // convert insert row into regular row
                inRow.widget.set_ui_state('is_new_record', true);
                var a = clone(inputs[i].widget.data); // clone(inArgs);
                a['key'] = inResponse['key'];

                // remove insert from field name
                var keyedfn = str_replace_last(pfxfn, 'insert', a['key']);
                var keyedfn_prefix = str_extract_prefix(keyedfn);
                debug('Changing insert row to '+keyedfn+ ' from key '+a['key']);
                a[pfxfn] = undefined;
                delete a[pfxfn];
                a[evt_src_element_name] = undefined;
                delete a[evt_src_element_name];
                a['part_prefix'] = keyedfn_prefix;
                a['action'] = a['table'] + '_aj_update';
                inputs[i].name = keyedfn;
                window.recordmap[keyedfn_prefix] = clone(a);
                if (inputs[i].widget && inputs[i].widget.isa('bbfield')) {
                    inputs[i].widget.connect_to_data();
                    // window.debug(inputs[i].name + ' new data: '+to_string(inputs[i].widget.data));
                }
                for (k = 0; k < input_labels.length; k++) {
                    // rename the label.  document.getElementById does not
                    // work here because we just duplicated inRow
                    if (input_labels[k].id == old_name) {
                        input_labels[k].id = keyedfn;

                        // probably fails in IE
                        break;
                    }
                }

                // begin OBSELETE
                a['part_prefix'] = keyedfn;
                a[keyedfn] = inputs[i].getValue();
                a['old_value'] = inputs[i].getValue();

                // find field_name for alias_name
                var table_len = inResponse['_tablename'].length;
                a['field_name'] = alias_name.substr(table_len);

                var e = inputs[i];

                window.pmap[keyedfn] = a;
                // end OBSELETE

            } else {
                debug('not inserting anything for (' + inputs[i].name +')'+ inResponse['key']);
            }
        } // if input
        else {
            error('invalid element ['+i+']');
        }
        } catch(e) {
            error('exception '+e.toString());
        }

    } // for i
    dg_add_grid_events(inRow);


}

// find and focus on the field named inName in inRow
function dg_focus_row(inRow, inName) {
    var nl_inputs = inRow.getElementsByTagName('input');
    var nl_selects = inRow.getElementsByTagName('select');
    var nl_textareas = inRow.getElementsByTagName('textarea');
    var inputs = new Array();
    append_nodelist(inputs, nl_inputs);
    append_nodelist(inputs, nl_selects);
    append_nodelist(inputs, nl_textareas);
    var len = inputs.length;
    for (var i = 0; i < len; i++) {
        if (inputs[i].name && (inputs[i].name.indexOf(inName) > -1)) {
            info('focusing on '+inputs[i].name);
            inputs[i].focus();
            if (inputs[i].tagName.toLowerCase() != 'select') {
                inputs[i].select();
            }
            break;
        }
    }
    return;
}

// find arguments that were used to construct inElem
// inElem is a bbfield
// window.pmap is associative array matching db fields from PHP to
//   form element names in JS.
// prefixed_name: form element's complete name in PHP array notation
function get_initial_arglist(inElem) {
    if (inElem.widget && (inElem.widget.isa('bbfield'))) {
        return inElem.widget.data;
    } else {
        error('Invalid field element: '+inElem.name);
        var prefixed_name = '';
        if (inElem.name) {
            prefixed_name = inElem.name;
        } else if (inElem.id) {
            prefixed_name = inElem.id;
        } else {
            // error
            debug_msg('ERROR: element has no name and no id: ' + inElem.tagName);
        }
        return window.pmap[prefixed_name];
    }
}

function dg_update_totals(inElem, inArgs, inSrv) {
    if (window.update_totals) {
        window.update_totals(inElem, inArgs, inSrv);
    } else {
        window.error("update_totals() not defined");
    }
}

/////////////////////////
// Constructor/Static
/////////////////////////
function dg_provision_input(inInputElem) {

    // HACK: field-specific registrations should be elsewhere.
    if (inInputElem.name.indexOf('item_number]') >= 0) {
        var ep = clone(inInputElem.widget.data);
        // queue_create_new_queue(get_initial_arglist(inInputElem));
        if (inInputElem.name.indexOf('[insert]') >= 0) {
            ep.action = ep.table + '_aj_insert';
        }
        window.dispatcher.register_aj
                  ('change', inInputElem, background_send, json_receive, ep, 'change_itemnumber_bg_send_json_recv');

    } else {
        var ep = (inInputElem.widget?clone(inInputElem.widget.data) : clone(get_initial_arglist(inInputElem)));
        if ((inInputElem.name.indexOf('unit_price]') >= 0)
            || (inInputElem.name.indexOf('quantity]') >= 0)
            || (inInputElem.name.indexOf('qty_ordered]') >= 0)
            || (inInputElem.name.indexOf('unit_cost]') >= 0)
            || (inInputElem.name.indexOf('taxable]') >= 0)
            || (inInputElem.name.indexOf('tax_amt]') >= 0)) {
            // window.info("registering change "+inInputElem.name);
            window.dispatcher.register('change', inInputElem, dg_update_totals, ep, 'change_'+inInputElem.name+'_dg_update_totals');
        }
        if ((inInputElem.name.indexOf('unit_price]') >= 0)
            || (inInputElem.name.indexOf('quantity]') >= 0)
            || (inInputElem.name.indexOf('unit_cost]') >= 0)
            || (inInputElem.name.indexOf('qty_ordered]') >= 0)) {
            // these change the extended price but no automatic
            // onchange fires
            window.dispatcher.register('change', inInputElem, dg_queue_ext_price, ep, 'change_'+inInputElem.name+'_dg_queue_ext_price');
        }
        if ((inInputElem.name.indexOf('description]') >= 0)) {
            window.dispatcher.register('blur', inInputElem, dg_check_ship_pt, ep, 'change_'+inInputElem.name+'_check_ship_pt');
        }
        window.dispatcher.register('change', inInputElem, queue_update, ep, 'change_'+inInputElem.name+'_queue_update');
    }
    window.dispatcher.register('focus', inInputElem, focus_row, ep, 'focus_'+inInputElem.name+'_focus_row');

    return null;
}

// anything that can change the extended price should queue
// the extended price
function dg_queue_ext_price(inElement, inArgList, inEvent) {
    var row = inElement.widget.record.elem;
    var extprice = getElementMatchingName(row, 'extended_price]');
    if (extprice) {
        // an update_total or equivilent should already be dispatched
        row.widget.queue_update(extprice, extprice.widget.data, inEvent);
    }

}

// if ship_pt is not set, then show the dialog to set it.
function dg_check_ship_pt(inElement, inArgList, inEvent) {
    var should_open = false;
    if (inElement.widget && inElement.widget.isa('bbfield')) {
        var x = inElement.widget.record.elem;
        var lists = getElementMatchingName(x, '_lineship_pt]');
        window.dispatcher.debug('dg_check_ship_pt found '+lists.name+' with value '+lists.getValue());
        if (lists && (lists.getValue() != 0)) {
            // -1 == Force order
            should_open = false;
        } else {
            should_open = true;
        }

        // if it is a inserted record and ship_pt_dialog
        // never shown for it then show it reguardless of ship pt value
        if (!should_open && x.widget.get_ui_state('is_new_record') &&
            !x.widget.get_ui_state('ship_pt_dialog_shown')) {
            should_open = true;
        }
    } else {
        should_open = true;
    }
    if (should_open) {
        // x.widget.set_ui_state('ship_pt_dialog_shown', true);
        return show_window('ship_pt_dialog');
    }
    return null;
}

function dg_provision_select(inSelectElem) {
    var ep = clone(get_initial_arglist(inSelectElem));
    window.dispatcher.register('change', inSelectElem, queue_update, ep, 'change_'+inSelectElem.name+'_queue_update');
    return null;
}

function dg_provision_textarea(inTextareaElem) {
    var ep = clone(get_initial_arglist(inTextareaElem));
    // HACK: field-specific event registrations should be elsewhere.
    if ((inTextareaElem.name.indexOf('description]') >= 0)) {
        window.dispatcher.register('blur', inTextareaElem, dg_check_ship_pt, ep, 'change_'+inTextareaElem.name+'_check_ship_pt');
    }
    window.dispatcher.register('change', inTextareaElem, queue_update, ep, 'change_'+inTextareaElem.name+'_queue_update');
    window.dispatcher.register('change', inTextareaElem, focus_row, ep, 'change_'+inTextareaElem.name+'_focus_row');

    if (window.jQuery && window.jQuery.fn.autogrow) {
        window.info('jquery.fn.autogrow reprovision');
        window.jQuery(inTextareaElem).autogrow();
    }
    return null;
}

// Initialize a datagrid (or slice of datagrid)
// install background_send(x, json_receive, this)
// event handler on all fields on change event
function dg_add_grid_events(inGridElem) {
    var inputs = inGridElem.getElementsByTagName('input');
    var selects = inGridElem.getElementsByTagName('select');
    var textareas = inGridElem.getElementsByTagName('textarea');
    var i = 0;
    var len=inputs.length;
    for (i=0; i < len; i++) {
        dg_provision_input(inputs[i]);
    }

    len=selects.length;
    for (i=0; i < len; i++) {
        dg_provision_select(selects[i]);
    }
    len=textareas.length;
    for (i=0; i < len; i++) {
        dg_provision_textarea(textareas[i]);
    }

    /*
    window.info('reprovisioning '+ len + ' textareas');

    if (window.jQuery && window.jQuery.autogrow && textareas.length) {
        // jQuery(element).clone(true) copies events registered with jQuery
        // and avoids the whole reprovisioning issue.
        // but window.dispatcher does not do that.
        window.info('jquery.autogrow reprovision');
        window.jQuery('textarea.jqautogrow', inGridElem).autogrow();
    }
     */
}

// datagrid widget
// the datagrid is a collection of records
// INCOMPLETE
function bbdatagrid(inWidgetElem, inModelID) {
    bbwidget.apply(this, [inWidgetElem, inModelID]);
}

//////////////////////////////
// Utilities
//////////////////////////////


// find, return last field name in php-format form-urlencoded array
function str_extract_alias_name(part) {
    var last_open_bracket = part.lastIndexOf('[');
    var last_close_bracket = part.lastIndexOf(']');
    var alias_name = part.substring(last_open_bracket+1, last_close_bracket);
    return alias_name;
}
function str_extract_prefix(part)  {
    var last_open_bracket = part.lastIndexOf('[');

    var prefix_name = '';
    if (last_open_bracket > -1) {
        prefix_name = part.substring(0, last_open_bracket);
    }
    return prefix_name;
} // str_extract_prefix

// Derived from edit_item.js by Marty.
function getHTTPObject() {
  var xmlhttp;
/*@cc_on
  @if (@_jscript_version >= 5)
  try {
    xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) {
    try {
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (E) {
      xmlhttp = false;
    }
  }
  @else
    xmlhttp = false;
  @end @*/
  if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
    try {
      xmlhttp = new XMLHttpRequest();
    } catch (e) {
      xmlhttp = false;
    }
  }
  return xmlhttp;
}

/////////////////////////////
// Main
/////////////////////////////
start_queue_scan();
