Bug Tracker

Opened 13 years ago

Closed 12 years ago

Last modified 12 years ago

#1209 closed enhancement (wontfix)

Form Value Function

Reported by: bgoldman Owned by:
Priority: minor Milestone: 1.2.4
Component: core Version: 1.1.2
Keywords: Cc:
Blocked by: Blocking:

Description

This function sets or gets the value of a form variable as a form would interpret it. This function is useful for jQuery core because everybody uses forms. This function makes it very easy to get and set form values without having to use fancy selectors. It's basically shorthand for a bunch of alternatives.

1) For selects and options: formVal() would return the currently selected value. formVal('foo') would select the option with that value. 2) For input text fields: formVal() would return the current value. formVal('foo') would set the value to foo. 3) For textareas: formVal() would return text(). formVal('foo') would set the text() to foo. 4) For checkboxes: formVal() would return the first matched checked value. formVal('foo') would check the boxes with value foo. 5) For radios: formVal() would return the checked value. formVal('foo') would check the radio with value foo.

    //gets the value of the first matched form field, or sets the value of all the matched form fields
    $.fn.formVal = function(newVal) {
        var self = this.get(0);
    
        if(newVal == null) {
            if(this.size() < 1) return '';
            else if(self.type == 'text') return self.value;
            else if(self.tagName == 'TEXTAREA') return this.text();
            else if(self.type == 'checkbox' || self.type == 'radio') return this.filter('input:checked').val();
            else if(self.tagName == 'OPTION') return this.parent().formVal();
            else if(self.tagName == 'SELECT') {
                var match = this.find('option:selected').eq(0);
                return (match.get(0).value != null) ? match.val() : match.text();
            }
            else return '';
        }
        
        if(self.type == 'text') this.val(newVal);
        else if(self.tagName == 'TEXTAREA') this.text(newVal);
        else if(self.type == 'checkbox' || self.type == 'radio') {
            var matches =  this.filter('[@value=' + newVal + ']');
            if(matches.size() < 1) return this;
            if(self.type == 'radio') this.filter(':checked').removeAttr('checked');
            matches.attr('checked', 'checked');
        }
        else if(self.tagName == 'OPTION') this.parent().formVal(newVal);
        else if(self.tagName == 'SELECT') {
            var match = this.find('option[@value=' + newVal + ']');

            if(match.size() < 1) {
                if(newVal == '') match = this.find('option:empty');
                else match = this.find('option').contains(newVal);
            }

            if(match.size() < 1) return this;
            this.find('option:selected').removeAttr('selected');
            match.eq(0).attr('selected', 'selected');
        }
        
        return this;
    };

Change History (6)

comment:1 Changed 13 years ago by bgoldman

I found a bug with selects and options. Here is the fixed code:

    //gets the value of the first matched form field, or sets the value of all the matched form fields
    $.fn.formVal = function(newVal) {
        var self = this.get(0);
    
        if(newVal == null) {
            if(this.size() < 1) return '';
            else if(self.type == 'text') return self.value;
            else if(self.tagName == 'TEXTAREA') return this.text();
            else if(self.type == 'checkbox' || self.type == 'radio') return this.filter('input:checked').val();
            else if(self.tagName == 'OPTION') return this.parent().formVal();
            else if(self.tagName == 'SELECT') {
                var match = this.find('option:selected').eq(0);
                return (match.is('[@value]')) ? match.val() : match.text();
            }
            else return '';
        }
        
        if(self.type == 'text') this.val(newVal);
        else if(self.tagName == 'TEXTAREA') this.text(newVal);
        else if(self.type == 'checkbox' || self.type == 'radio') {
            var matches =  this.filter('[@value=' + newVal + ']');
            if(matches.size() < 1) return this;
            if(self.type == 'radio') this.filter(':checked').removeAttr('checked');
            matches.attr('checked', 'checked');
        }
        else if(self.tagName == 'OPTION') this.parent().formVal(newVal);
        else if(self.tagName == 'SELECT') {
            var match = this.filter('option[@value=' + newVal + ']');

            if(match.size() < 1) {
                if(newVal == '') match = this.find('option:empty');
                else match = this.find('option').contains(newVal);
            }

            if(match.size() < 1) return this;
            this.find('option:selected').removeAttr('selected');
            match.eq(0).attr('selected', 'selected');
        }
        
        return this;
    };

comment:2 Changed 13 years ago by bgoldman

Fixed another bug... here we go:

    //gets the value of the first matched form field, or sets the value of all the matched form fields
    $.fn.formVal = function(newVal) {
        var self = this.get(0);
    
        if(newVal == null) {
            if(this.size() < 1) return '';
            else if(self.type == 'text') return self.value;
            else if(self.tagName == 'TEXTAREA') return this.text();
            else if(self.type == 'checkbox' || self.type == 'radio') return this.filter('input:checked').val() || '';
            else if(self.tagName == 'OPTION') return this.parent().formVal();
            else if(self.tagName == 'SELECT') {
                var match = this.find('option:selected').eq(0);
                return (match.is('[@value]')) ? match.val() : match.text();
            }
            else return '';
        }

        if(self.type == 'text') this.val(newVal);
        else if(self.tagName == 'TEXTAREA') this.text(newVal);
        else if(self.type == 'checkbox' || self.type == 'radio') {
            this.filter(':checked').removeAttr('checked');
            this.filter('[@value=' + newVal + ']').attr('checked', 'checked');
        }
        else if(self.tagName == 'OPTION') this.parent().formVal(newVal);
        else if(self.tagName == 'SELECT') {
            var match = this.filter('option[@value=' + newVal + ']');

            if(match.size() < 1) {
                if(newVal == '') match = this.find('option:empty');
                else match = this.find('option').filter(function() {
                    return ($(this).text() == newVal);
                });
            }

            if(match.size() < 1) return this;
            this.find('option:selected').removeAttr('selected');
            match.attr('selected', 'selected');
        }
        
        return this;
    };

comment:3 Changed 12 years ago by mrclay

For SELECT elements, formVal() returns the wrong value when the selected OPTION is marked up with value="": Since "" is falsy, match.is('[@value]') is false and match.text() is incorrectly returned.

Pseudocode for a fix:

if (option.value) { // non-empty string
    return option.value;
}
if (! option.outerHTML) { // definitely not IE, so trust value
    return '';
}
if (/* option.outerHTML has 'value=""' in attributes */) { // likely painful regex
    return '';
} else {
    return option.text;
}

comment:4 in reply to:  3 Changed 12 years ago by mrclay

Here's a fix for setting/getting SELECT when options might have empty value attributes, but non-empty text. Is there a unit test somewhere for this plugin?

//gets the value of the first matched form field, or sets
//the value of all the matched form fields
$.fn.formVal = function(newVal) {
    var self = this.get(0);
    var optVal = function(opt) {
        if(opt.value) return opt.value; // non-empty string, use it
        if(!opt.outerHTML) return ''; // not IE, we should trust value
        // @todo: more rigorous regex to assert value="" in attributes, not text
        return /\svalue=""(?:\s|>)/.test(opt.outerHTML) ? '' : opt.text;
    };
    if(newVal == null) {
        if(this.size() < 1) return '';
        if(self.type == 'text') return self.value;
        if(self.tagName == 'TEXTAREA') return this.text();
        if(self.type == 'checkbox' || self.type == 'radio')
          return this.filter('input:checked').val() || '';
        if(self.tagName == 'OPTION') return this.parent().formVal();
        // if a SELECT with no selection, fallthrough to return ''
        // or should re return null?
        if(self.tagName == 'SELECT' && self.selectedIndex >= 0)
          return optVal(self.options[self.selectedIndex]);
        return '';
    }
    if(self.type == 'text') this.val(newVal);
    else if(self.tagName == 'TEXTAREA') this.text(newVal);
    else if(self.type == 'checkbox' || self.type == 'radio') {
        this.filter(':checked').removeAttr('checked');
        this.filter('[@value=' + newVal + ']').attr('checked', 'checked');
    }
    else if(self.tagName == 'OPTION') this.parent().formVal(newVal);
    else if(self.tagName == 'SELECT') {
        // search for matching value
        for (var i=0, l=self.options.length; i<l; ++i) {
            if(newVal == optVal(self.options[i])) {
                self.selectedIndex = i;
                break;
            }
        }
    }
    return this;
};

comment:5 Changed 12 years ago by flesler

Resolution: wontfix
Status: newclosed

I think jQuery.form can do all or most of this. If anything is missing you can propose it for this plugin.

comment:6 Changed 12 years ago by flesler

Milestone: 1.1.31.2.4
Note: See TracTickets for help on using tickets.