/* 
Gizmo(QP) Web Framework - http://gizmojo.org/
------------------------------------------------------------------------------
Copyright (C) 2007 Mario Ruggier
License: GNU LGPL, http://www.gnu.org/copyleft/lesser.html
------------------------------------------------------------------------------
$Id: gform.js 522 2007-04-09 08:11:14Z mario $ 
*/

// ---------------------------------------------------------------------------

function GForm (gizmoid) {
    this.gizmoid = gizmoid;
    this.widget_names = []; // defines an order
    this.widgets = {}; // by widget_name
    this.elem_names = ['errornotice']; // "exposed" DOM element names
    this.event_handlers = { 
        'status_check': bind(this.on_status_check, this), 
        'status_change': bind(this.on_status_change, this) 
    };
    this.use_options_from = {}; 
}
GForm.prototype = new Gizmo;
GForm.prototype.enabled = true;

GForm.prototype.onload = function () {
    this.update_status();
};
GForm.prototype.get_widget = function (widget_name) {
    // (widget_name:str) -> GWidget
    return this.widgets[widget_name];
};
GForm.prototype.get_requireds = function () {
    var requireds = [];
    for (var i=0; i<this.widget_names.length; i++) {
        var widget = this.widgets[this.widget_names[i]];
        if (widget.required) {
            requireds.push(widget);
        }
    }
    return requireds;
};
GForm.prototype.get_requireds_status = function () {
    // () -> [ FieldStatus ]
    var requireds_status = [];
    var requireds = this.get_requireds();
    for (var i=0; i<requireds.length; i++) {
        requireds_status.push(requireds[i].get_status()); 
    }
    return requireds_status;
};
GForm.prototype.get_submit_widgets = function () {
    // () -> [ SubmitWidget ] 
    var submit_widgets = [];
    for (var widget_name in this.widgets) {
        var widget = this.widgets[widget_name];
        if (widget instanceof SubmitWidget) { 
            submit_widgets.push(widget);
        }
    }
    return submit_widgets;
};
/* 
The GForm.get_all_widgets_status() method is what gets assigned as onclick 
handler to a GWidget's submit button that defines a value_disabled attribute.

The GForm.get_widget_status(widget_name) method is what gets assigned as the 
<check_evt> event handler of a GWidget. This in turn calls 
GForm.set_widget_status(FieldStatus), that calls GForm.update_status().

    FieldStatus object: 
    fs = {
        name : str,        # field name
        value : anything,  # field value
        ok : bool,         # value is ok
        message : str,     # error or ok message
    } 

*/
GForm.prototype.get_all_widgets_status = function () {
    // () -> bool
    var no_update_status = true;
    for (var name in this.widgets) {
        this.get_widget_status(name, no_update_status);
    }
    this.update_status();
    return this.enabled;
};
GForm.prototype.get_widget_status = function (name, no_update_status) {
    // (name:str) -> true
    // Calls set_widget_status(FieldStatus).
    var widget = this.get_widget(name);
    // client-side check
    var field_status = widget.get_status(); 
    if ((!field_status.ok) || (!widget.callback)) {
        this.set_widget_status(field_status, no_update_status);
    }
    else { // server-side check (callback)
        if (widget.callback == 'json') {
            var value = widget.get_value();
            try {
                // Convention for "callback_base_action" :
                // if a "cba" (Hidden)GWidget is defined, use it
                // else default to using form's own action base.
                var callback_base_action = this.get_widget('cba').get_value();
            } 
            catch (e) {
                var callback_base_action = '';
            }
            var url = callback_base_action + name+'.json?'+name+'='+value;
            var d = loadJSONDoc(url);
            d.addCallbacks(
                bind(this.set_widget_status, this), 
                bind(this.set_errornotice, this));            
        }
    }
    return true;
};
GForm.prototype.set_widget_status = function (fs, no_update_status) {
    var widget = this.get_widget(fs.name);
    widget.set_status(fs);
    if (!no_update_status) {
        this.update_status();
    }
};
GForm.prototype.is_fs_really_ok = function (fs, widget) {
    // temporary - this check for callback fields should really be on 
    // widget.get_status(), but that calls widget.set_status() (via 
    // GForm.get_widget_status()), which we do not want to do here.
    if (!fs.ok || ((widget.callback)&&(!widget.is_ok()))) {
        return false; 
    }
    else {
        return true;
    }
};
GForm.prototype.set_errornotice = function (error) {
    // (error:htmlString) 
    var errornotice = this.get_elem('errornotice');
    //replaceChildNodes(errornotice, error);
    errornotice.innerHTML = error; 
};
GForm.prototype.update_status = function () {
    this.handle_event('status_check');
    for (var name in this.widgets) {
        var widget = this.widgets[name];
        if (!widget.is_ok()) {
            if (this.enabled) {
                this.enabled = false;
                this.handle_event('status_change');
            }
            return;
        }
    }
    if (!this.enabled) {
        this.enabled = true;
        this.handle_event('status_change');
    }
};
GForm.prototype.get_not_ok_requireds = function () {
    // () -> [ GWidget ]
    var reqs_status = this.get_requireds_status();
    var not_ok_reqs = []; 
    for (var i=0; i<reqs_status.length; i++) {
        var fs = reqs_status[i];
        var widget = this.get_widget(fs.name);
        if (!this.is_fs_really_ok(fs, widget)) {
            not_ok_reqs.push(widget); 
        }
    }
    return not_ok_reqs;    
};
GForm.prototype.on_status_check = function () {
    var reqs_status = this.get_requireds_status();
    var reqs_messages = [];
    for (var i=0; i<reqs_status.length; i++) {
        var fs = reqs_status[i];
        var widget = this.get_widget(fs.name);
        var css_class = 'ok';
        if (!this.is_fs_really_ok(fs, widget)) {
            css_class = 'not_ok';             
        }
        reqs_messages.push('<span class="'+css_class+'">'+
                                    widget.get_elem_text('title')+'</span>');
    }
    var errornotice = '';
    if (reqs_messages.length>0) {
        errornotice = reqs_messages.join(' - ');
    }
    this.set_errornotice(errornotice);
}; 
GForm.prototype.on_status_change = function () {
    var submit_widgets = this.get_submit_widgets();
    if (this.enabled) {
        for (var i=0; i<submit_widgets.length; i++) {
            submit_widgets[i].enable();
        }
    }
    else {
        for (var i=0; i<submit_widgets.length; i++) {
            submit_widgets[i].disable();
        }
    }
};
GForm.prototype.update_options_from = function (name) {
    if (this.use_options_from[name]!=null) {
        var from_widget = this.use_options_from[name];
        this.use_options_from[name] = null;
        var from_elem = from_widget.get_field_elem();
        var widget = this.get_widget(name);
        var elem = widget.get_field_elem();
        // Remember selected value
        var from_selected_index = from_elem.selectedIndex;
        var value = widget.get_value();
        // Clone options
        var clone_from_elem = from_elem.cloneNode(true);
        replaceChildNodes(elem, clone_from_elem.childNodes);
        // Adjust selected value
        elem.options[from_selected_index].removeAttribute('selected');
        elem.value = value;
        setNodeAttribute(elem.options[elem.selectedIndex], 'selected', true);
    }
};

// ---------------------------------------------------------------------------

// Set as default onkeypress evnet handler for StringWidgets, to 
// disable submit behavior on a carraige return. 
function no_return_sbmt(e) {
    if (!e) {
        e = window.event;
    }
    var key = typeof e.keyCode != 'undefined' ? e.keyCode : e.charCode;
    if (key == 13) {
        return false;
    }
    return true;
}

/* Sample generated HTML for a Widget : 
<div id="%(gizmoid)s" class="TypeWidget widget">
    <div id="%(gizmoid)s_title" class="title">%(title)s*</div>
    <div id="%(gizmoid)s_content" class="content">
        <input type="text" name="%(gizmoid)s" 
               onchange="register.get_widget_status(this.name);" />
        <div id="%(gizmoid)s_hint" class="hint">Only alphanumeric...</div>
        <div id="%(gizmoid)s_error" class="error"></div>
    </div>
</div>
*/
function GWidget (gizmoid) {
    this.gizmoid = gizmoid;
    this.elem_names = ['title', 'content', 'hint', 'error'];
}
GWidget.prototype = new Gizmo;
GWidget.prototype.event_handlers = { 'status_change': null };
GWidget.prototype.required = false;
GWidget.prototype.enabled = true;
GWidget.prototype.callback = null;
GWidget.prototype.ok = null;
GWidget.prototype.message_required = 'Required';
GWidget.prototype.spec = null;
GWidget.prototype.field_elem_name = null;
GWidget.prototype.jvc = null; // new scrambler on each this.scramble_message()

GWidget.prototype.is_ok = function () {
    if (this.ok==null) { 
        return (this.required)?(Boolean(this.get_value())?true:false):true; 
    }
    return this.ok;
};
GWidget.prototype.get_message = function () {
    return this.get_elem_text('error'); 
};
GWidget.prototype.set_ok = function (ok) { 
    var fs = {
        'name': this.gizmoid,
        'value': this.get_value(),
        'ok': ok,
        'message': this.spec?ok?this.spec.mok:this.spec.mer:''
    };
    this.set_status(fs);
    return ok;
};
GWidget.prototype.set_status = function (fs) { // set_fs
    var error = this.get_elem('error');
    replaceChildNodes(error, fs.message); 
    if (fs.ok) {
        addElementClass(error, "ok");
        if (!this.ok) {
            this.ok = true;
            this.handle_event('status_change');
        }
    }
    else {
        removeElementClass(error, "ok");
        if (this.ok || this.ok==null) {
            this.ok = false;
            this.handle_event('status_change');
        }
    }
    if (fs.message) {
        this.scramble_message();        
    }
};
GWidget.prototype.scramble_message = function (delay) {
    if (!delay) {
        delay = 200;
    }
    var jvs_name = this.gizmoid+'.jvs';
    this.jvs = new jvScrambler(jvs_name, 
                        this.gizmoid+'_error', this.get_message());
    this.jvs.go('framework Gizmo(QP)) is way MORE than just COOL!');
    var timer = setTimeout(jvs_name+'.reset()', delay);
};
GWidget.prototype.check_required = function (fs) { 
    if (!fs.value) {
        fs.ok = false;
        fs.message = this.message_required;
    }
};
GWidget.prototype.get_value = function () {
    return this.get_field_elem().value;
};
GWidget.prototype.get_field_elem = function () {
    return getElementsByTagAndClassName(this.field_elem_name, null, 
                    this.get_elem('content'))[0];
};
GWidget.prototype.get_status = function () { // get_fs() ?
    var fs = {
        'name': this.gizmoid,
        'value': this.get_value(),
        'ok': true,
        'message': ''
    };
    if (this.required) {
        this.check_required(fs);
        if (!fs.ok) { 
            return fs; 
        }
    }
    if (this.spec && fs.value) {
        if (!this.spec.test(fs.value)) {
            logDebug(this.gizmoid+' '+this.spec.mer+' '+fs.value);
            fs.ok = false;
        }
        fs.message = this.spec.get_message(fs.ok);
    }
    return fs;
};
GWidget.prototype.enable = function () {
    this.enabled = true;
    this.get_field_elem().removeAttribute('disabled');
};
GWidget.prototype.disable = function () {
    this.enabled = false;
    setNodeAttribute(this.get_field_elem(), 'disabled', true);
};

// ---------------------------------------------------------------------------

function StringWidget (gizmoid) {
    this.gizmoid = gizmoid;
}
StringWidget.prototype = new GWidget;
StringWidget.prototype.field_elem_name = 'input';
StringWidget.prototype.value_hint = '';

StringWidget.prototype.get_value = function () {
    var value = this.get_field_elem().value;
    return (value!=this.value_hint) ? value : '';
}; 
StringWidget.prototype.check_required = function (fs) { 
    if (!fs.value || (fs.value==this.value_hint)) {
        fs.ok = false;
        fs.message = this.message_required;
    }
};
StringWidget.prototype.check_value_hint = function (focus_or_blur) {
    var e = this.get_field_elem();
    if (focus_or_blur) {
        if (e.value == this.value_hint) {
            e.value = '';
        }
    }
    else {
        if ((e.value=='') && (this.value_hint!='')) {
            e.value = this.value_hint;
        }
    }
};

function PasswordWidget (gizmoid) {
    this.gizmoid = gizmoid;
}
PasswordWidget.prototype = new StringWidget;


function HiddenWidget (gizmoid) {
    this.gizmoid = gizmoid;
}
HiddenWidget.prototype = new StringWidget;
HiddenWidget.prototype.get_field_elem = function () {
    return $(this.gizmoid);
}


function ButtonWidget (gizmoid) {
    this.gizmoid = gizmoid;
}
ButtonWidget.prototype = new StringWidget;
ButtonWidget.prototype.value_enabled = null;
ButtonWidget.prototype.value_disabled = null;
ButtonWidget.prototype.set_value = function (value) {
    setNodeAttribute(this.get_field_elem(), 'value', value);
}
ButtonWidget.prototype.enable = function() {
    this.enabled = true;
    if (this.value_disabled) {
        this.set_value(this.value_enabled);
    }
    else {
        this.get_field_elem().removeAttribute('disabled');
    }
}
ButtonWidget.prototype.disable = function () {
    this.enabled = false;
    if (this.value_disabled) {
        this.set_value(this.value_disabled);
    }
    else {
        setNodeAttribute(this.get_field_elem(), 'disabled', true);
    }
}

function SubmitWidget (gizmoid) {
    this.gizmoid = gizmoid;
}
SubmitWidget.prototype = new ButtonWidget;


function TextWidget (gizmoid) {
    this.gizmoid = gizmoid;
}
TextWidget.prototype = new StringWidget;
TextWidget.prototype.field_elem_name = 'textarea';

// ---------------------------------------------------------------------------

function SingleSelectWidget (gizmoid) {
    this.gizmoid = gizmoid;
}
SingleSelectWidget.prototype = new GWidget;
SingleSelectWidget.prototype.field_elem_name = 'select';

// ---------------------------------------------------------------------------
