/*
 Experimental cross browser javascript library for XForms
 Dave Raggett (c) 1999 W3C (MIT, INRIA & Keo University)
*/

// used for initializing data
// required data must have values
// before the form can be submitted.
var optional = false;
var required = true;

function noneMissing(data)
{
    var str = data.checkMissing();

    if (str != "")
    {
        alert("The following fields need to be filled" +
            " before the form can be submitted:\n\n" + str);
        return false;
    }

    return true;
}

function submit(data)
{
    if (noneMissing(data))
    {
        var str = data.print(0);
        var win = window.open("", "Submit",
        "toolbar=no, location=no, scrollbars=yes, " +
        "resizable=yes, width=400, height=650");
        win.document.write(
        "<html>\n<head><title>Form Sumission</title></head>\n<body>\n"+
        "<h1>Submitted data in XML</h1>\n" +
        "<p><i>Need to add an outer element with a URI\n" +
        "that references the form's data type definition," +
        "which acts as the data model for submitted data.</p>\n" +
        "<pre>" + str + "</pre>\n</body></html>");
        win.document.close();
    }

    return false;
}

// used to get a field by value of name attribute
function getFieldByName(form, name)
{
    for (i = 0; i < form.length; ++i)
        if (form[i].name == name)
            return form[i];

    alert("Can't find field called " + name);
    return null;
}

// the fields created by the browser don't
// have an extensible prototype so we have
// to add methods to each field explicitly

function bindField(form, name, datum)
{
    var field = getFieldByName(form, name);

    if (field)
    {
        field.toSet = view_toSet;
        field.toGet = view_toGet;
        field.notify = view_notify;
        field.update = view_update;

        field.datum = datum; // instance of Datum
        datum.addView(field);
    }
    return field;
}

function bindView(field, datum)
{
    if (field)
    {
        field.toSet = view_toSet;
        field.toGet = view_toGet;
        field.notify = view_notify;
        field.update = view_update;

        field.datum = datum; // instance of Datum
        datum.addView(field);
    }
    return field;
}

function bindArrayIndex(form, name, column, i)
{
    var field = form[name];
    bindView(field[i], column.data[i]);
    column.rows = i + i;
}

// this works with IE but sadly, Netscape
// Navigator doesn't support id attribute
function bindFieldId(id, datum)
{
    var field;

    eval("field = " + id);

    field.toSet = span_toSet;
    field.toGet = span_toGet;
    field.notify = view_notify;
    field.update = view_update;

    field.datum = datum; // instance of Datum
    datum.addView(field);
    return field;
}

// some functions we will add to all presentation fields

function view_toGet()
{
    return this.value;
}

function view_toSet(value)
{
    this.value = value;
}

function view_notify()
{
    this.datum.notify(this);
}

function view_update()
{
    if (this.datum)
        this.toSet(this.datum.value);
    else
        alert("view with undefined datum");
}

function select_toGet()
{
    if (this.selectedIndex == 0)
        return null;

    return this.options[this.selectedIndex].text;
}

function select_toSet(value)
{
    if (value != null)
        this.options[this.selectedIndex].text = value;
}

function span_toGet()
{
    return this.innerText;
}

function span_toSet(value)
{
    this.innerText = value;
}

function Datum_indent(depth)
{
    for (var str = ""; depth > 0; --depth)
        str += "  ";

    return str;
}

function Datum_printValue(indent)
{
    str = (this.value ? this.value : "&lt;null/>");
    return str;
}

function Datum_print(indent)
{
    var str = this.indent(indent) + "&lt;"+this.name+">";
    str += this.printValue();
    str += "&lt;/"+this.name+">\n";
    return str;
}

function Datum_checkMissing()
{
    if (this.required && this.value == null)
    {
        return "   " + this.name + "\n";
    }

    return "";
}

// used to ensure all views and dependents are up to date
function Datum_updateViews()
{
    if (this.views)
    {
        for (var i = 0; i < this.views.length; ++i)
            this.views[i].update();
    }

    if (this.dependents)
    {
        for (var j = 0; j < this.dependents.length; ++j)
            this.dependents[j].update();
    }
}

// views call this when they loose the focus
function Datum_notify(view)
{
    this.value = view.toGet();
    this.updateViews();
}

function Datum_addView(view)
{
    if (this.views == null)
        this.views = new Array(view);
    else
        this.views[this.views.length] = view;
}

// calculated values are dependent on other data
function Datum_addDependent(datum)
{
    if (this.dependents == null)
        this.dependents = new Array(datum);
    else
        this.dependents[this.dependents.length] = datum;
}

// overridden for calculated fields
function Datum_update()
{
}

// base class for data values
function Datum(name)
{
    this.views = null;       // array of HTML fields
    this.dependents = null;  // array of dependents
    this.value = null;       // holds the actual data value
    this.name = name;        // used as tag name for submission
    this.submit = true;      // whether field is submitted
}

Datum.prototype.indent = Datum_indent;
Datum.prototype.printValue = Datum_printValue;
Datum.prototype.print = Datum_print;
Datum.prototype.checkMissing = Datum_checkMissing;
Datum.prototype.updateViews = Datum_updateViews;
Datum.prototype.notify = Datum_notify;
Datum.prototype.addView = Datum_addView;
Datum.prototype.addDependent = Datum_addDependent;
Datum.prototype.update = Datum_update;

// datum for monetary values
function Money(name)
{
    this.name = name;
    this.dependents = null;  // array of dependents
    this.value = null;       // holds the actual data value
    this.name = name;        // used as tag name for submission
    this.submit = true;      // whether field is submitted
    this.currency = "$";     // currency symbol
}

Money.prototype = new Datum;

function Enum_print(indent)
{
    var str = this.indent(indent) + "&lt;enum name='" + this.name + "'>\n";

    for (var i = 0; i < this.items.length; ++i)
    {
        str += this.indent(indent+1) + "&lt;value>"
                +this.items[i]+"&lt;/value>\n";
    }

    str += this.indent(indent) + "&lt;/enum>\n";
    return str;
}

// base class for enumerated types
function Enum(name, items)
{
    this.name = name;
    this.items = items;
}

Enum.prototype = new Datum;
//Enum.prototype.print = Enum_print;

function Group_addMember(name)
{
    if (this.members == null)
        this.members = new Array(name);
    else
        this.members[this.members.length] = name;
}

function Group_print(indent)
{
    var str = this.indent(indent) + "&lt;" + this.name + ">\n";

    if (this.members)
        for (var i = 0; i < this.members.length; ++i)
            str += this[this.members[i]].print(indent+1);

    str += this.indent(indent) + "&lt;/" + this.name + ">\n";
    return str;
}

function Group_checkMissing()
{
    var str = "";

    if (this.members)
        for (var i = 0; i < this.members.length; ++i)
            str += this[this.members[i]].checkMissing();
    
    return str;
}

function Group_addDatum(name, required)
{
    this[name] = new Datum(name);
    this.addMember(name);

    if (required)
        this[name].required = true;
}

function Group_addEnum(name, set, required)
{
    this[name] = new Enum(name, set);
    this.addMember(name);

    if (required)
        this[name].required = true;
}

function Group_addTable(name)
{
    this[name] = new DataTable(name);
    this.addMember(name);
}

function Group_addGroup(name)
{
    this[name] = new Group(name);
    this.addMember(name);
}

// base class for grouped data
function Group(name)
{
    this.name = name;
    this.members = null;
}

Group.prototype = new Datum;
Group.prototype.addMember = Group_addMember;
Group.prototype.print = Group_print;
Group.prototype.checkMissing = Group_checkMissing;
Group.prototype.addDatum = Group_addDatum;
Group.prototype.addEnum = Group_addEnum;
Group.prototype.addTable = Group_addTable;
Group.prototype.addGroup = Group_addGroup;

// DataTables are used for things like line items
// where you want to enter one or more rows of
// items where each row has the same properties
// for instance, {quantity, product code, unitprice}
// The data table is organised as a collection of
// data columns where each column defines the
// behavior for all data in that column.
function DataTable(name)
{
    this.name = name;
    this.rows = 0;
}

DataTable.prototype = new Group;

function DataTable_addColumn(name)
{
    this[name] = new DataColumn(name);
    this.addMember(name);
}

DataTable.prototype.addColumn = DataTable_addColumn;

// prints tables as a sequence of rows
// doesn't print last row which is always blank
function DataTable_print(indent)
{
    var row, col, str = "";

    for (row = 0; row < this.rows - 1; ++row)
    {
        str += this.indent(indent) + "&lt;" + this.name + ">\n";

        if (this.members)
            for (col = 0; col< this.members.length; ++col)
                str += this[this.members[col]].print(indent+1, row);

        str += this.indent(indent) + "&lt;/" + this.name + ">\n";
    }
    return str;
}

DataTable.prototype.print = DataTable_print;

function DataTable_initRow(i)
{
    if (this.members)
    {
        for (var col = 0; col < this.members.length; ++col)
            this[this.members[col]].initRow(i);

        if (i >= this.rows)
            this.rows = i + 1;
    }
}

DataTable.prototype.initRow = DataTable_initRow;

function DataTable_isEmptyRow(i)
{
    if (i < this.rows && this.members)
    {
        for (var col = 0; col < this.members.length; ++col)
        {
            if (! this[this.members[col]].isEmptyRow(i))
                return false;
        }
    }

    return true;
}

DataTable.prototype.isEmptyRow = DataTable_isEmptyRow;

function DataTable_nullRow(i)
{
    if (i < this.rows && this.members)
    {
        for (var col = 0; col < this.members.length; ++col)
            this[this.members[col]].nullRow(i);
    }
}

DataTable.prototype.nullRow = DataTable_nullRow;

// DataColumn is an object with an array of
// data which all exhibit the same behavior
function DataColumn(name)
{
    this.name = name;
    this.data = null;
    this.rows = 0;
}

DataColumn.prototype = new Datum;

// print datum at given row, column
function DataColumn_print(indent, row)
{
    if (this.data && row < this.data.length)
        return this.data[row].print(indent);

    return "<error/>";
}

DataColumn.prototype.print = DataColumn_print;

function DataColumn_initRow(i)
{
    if (!this.data)
        this.data = new Array(new Datum(this.name));

    if (!this.data[i])
        this.data[i] = new Datum(this.name);
}

DataColumn.prototype.initRow = DataColumn_initRow;

function DataColumn_isEmptyRow(i)
{
    if (this.data[i].value == null || this.data[i].value == "")
        return true;

    return false;
}

DataColumn.prototype.isEmptyRow = DataColumn_isEmptyRow;


function DataColumn_nullRow(i)
{
    this.data[i] = null;
}

DataColumn.prototype.nullRow = DataColumn_nullRow;

function isDigit(c)
{
    return ("0" <= c && c <= "9");
}

// convert to string and round to nearest cent
// suppress decimal point if cents are zero
function dollars(value)
{
    cents = (100 * value) % 100;
    value = new String(value + 0.005);

    for (i = 1; i < value.length; ++i)
        if (value.charAt(i) == ".")
            break;

    if (cents >= 1)
        return value.substring(0, i+3);

    return value.substring(0, i);
}

