Friday, November 14, 2008

CFSELECT required="true" (Well .. not really)

I was never a big fan of the old html cfforms, and consequently never learned much about them. Though they have probably improved over time. While googling I came across the old question of why the required attribute does not work unless the list supports multiple selections. Having a few minutes to kill I decided to explore it.

The common response was along the lines of "You should not expect it to work. Something is always selected if the list size is one". Technically, that is true. But to me it comes down to the fact that the validation was implemented differently than some people might expect, given how they use select lists. The validation code in /CFIDE/scripts/cfform.js is clearly geared towards multiple selection lists, not single.

// if this form field is a select list
if(_c=="SELECT"){

// verify at least one item was selected
for(i=0;i<_b.length;i++){
if(_b.options[i].selected){
return true;
}
}

return false;
}
...


Now I saw a few examples of tweaking cfform.js to produce the desired results. But they were not exactly what I had in mind. If I were to change the script, I would use "" as the default to represent no-selection. But have an optional attribute called "noSelection". So you could use other values like zero (0), etcetera when needed.


/*
REPLACEMENT JAVASCRIPT
*/
if( _c == "SELECT" ) {
var idx = _b.selectedIndex;
var isValid = false;
var _defaultValue = "";

// use the supplied default value
if ( document.getElementById && _b.getAttribute("noSelection") ) {
_defaultValue = _b.getAttribute("noSelection").toLowerCase();
}

if ( _b.type == "select-one" ) {
// something other than the default value is selected
isValid = ( _b.options[idx].value.toLowerCase() != _defaultValue );
}
else {
// multiple list: at least one item is selected
isValid = ( idx >= 0 );
}
return isValid;
}
...


<!---
Use the default "" to represent no selection
--->
<cfform name="someForm" format="xml" skin="basic">
<cfformgroup type="horizontal">
<cfselect name="Company" label="Company" required="true">
<option value="">-- select ---</option>
<option value="1">Company A</option>
<option value="2">Company B</option>
</cfselect>
<cfinput type="submit" name="submitBtn" value="Submit" >
</cfformgroup>
</cfform>

<!---
Use "0" to represent no selection
--->
<cfform name="someForm" format="xml" skin="basic">
<cfformgroup type="horizontal">
<cfselect name="Company" label="Company" required="true" noSelection="0"
message="Do not pass go. Do not collect $200. Not until you select a Company.">
<option value="0">-- select ---</option>
<option value="1">Company A</option>
<option value="2">Company B</option>
</cfselect>

<cfinput type="submit" name="submitBtn" value="Submit" >
</cfformgroup>
</cfform>

Now I do not really have a need for this. But if I ever do, I now know where to look ;)



11 comments:

  1. I've been using this code you put up for quite some time on my web server. Makes cfselect's so much easier as I can make them required with a blank initial value without writing my own JS. Thanks!

    ReplyDelete
  2. I am glad you found it useful!

    Cheers
    Leigh

    ReplyDelete
  3. Just replace the code in cfform with following works like a charm
    Orignal
    if(_c=="SELECT"){
    for(i=0;i<_b.length;i++){
    if(_b.options[i].selected){
    return true;

    New code:
    if(_c=="SELECT"){
    for(i=0;i<_b.length;i++){
    if(_b.options[i].selected && _b.options[i].value != ""){
    return true;

    ReplyDelete
  4. @Anonymous,

    Adding the extra "noSelection" attribute code gives you a lot more flexibility. Then you could use any value to represent no-selection, rather than being limited to just "".

    -Leigh

    ReplyDelete
  5. @Leigh

    I didn't get what do u mean by adding extra noSelection attribute.

    --
    Sushil

    ReplyDelete
  6. @Sushil,

    The javascript above checks the cfselect for an html attribute named "noSelection". If it exists, its value is used to represent nothing selected. So you can use whatever value you want:

    Example:
    <cfselect required="true" noSelection="" ...>
    <cfselect required="true" noSelection="0" ...>
    <cfselect required="true" noSelection="ALL" ...>
    <cfselect required="true" noSelection="-None-" ...>

    -Leigh

    ReplyDelete
  7. I got it. Thanks!!!!

    --
    Sushil

    ReplyDelete
  8. My web host wouldn't change the cfform.js nor did the scriptsrc on the cfform tag didn't work. So I used the following on my form page itself:

    startscript tag
    var _CF_hasValue_old = _CF_hasValue;
    _CF_hasValue=function(_b,_c,_d)
    {
    if (_b.type == 'select-one')
    { var bSelected = false;
    for (var i=0; i<_b.options.length; i++)
    {
    if (_b.options[i].selected == true && _b.options[i].value != '')
    { bSelected = true; break;
    }
    } return bSelected;
    } else
    return _CF_hasValue_old(_b,_c,_d);
    }
    endscript tag

    ReplyDelete
  9. @fermin.aguilar,

    ..nor did the scriptsrc on the cfform tag didn't work

    That usually happens when the format selected does not "pass-through" extra html attributes, or at least not for the cfselect element. You can confirm the issue by doing a view source. In those cases, yours is a good work around.

    -Leigh

    ReplyDelete
  10. With cf8, a workaround is binding the selected value to an invisible text input for validation.

    <SELECT NAME="dept_select">
    <OPTION VALUE=""> -- Select -- </option>
    <OPTION VALUE="1">1</option>
    <OPTION VALUE="2">2</option>
    </SELECT>
    <CFINPUT NAME="dept"
    TYPE="Text"
    STYLE="visibility:hidden"
    BIND="{dept_select}"
    REQUIRED="Yes"
    MESSAGE="Please input Department!">

    ReplyDelete
  11. @kf

    Very slick idea! I did notice one problem in IE8. CF attempts to focus() the hidden field and of course IE complains:

    "Can't move focus to the control because it is invisible, not enabled, or of a type that does not accept the focus."

    Sometimes I hate IE ...

    -Leigh

    ReplyDelete