The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

###########################################################################
# Copyright (c) Nate Wiger http://nateware.com. All Rights Reserved.
# Please visit http://formbuilder.org for tutorials, support, and examples.
###########################################################################

# The majority of this module's methods (including new) are
# inherited directly from ::base, since they involve things
# which are common, such as parameter parsing. The only methods
# that are individual to different fields are those that affect
# the rendering, such as script() and tag()

package CGI::FormBuilder::Field::checkbox;

use strict;
use warnings;
no  warnings 'uninitialized';

use CGI::FormBuilder::Util;
use CGI::FormBuilder::Field;
use base 'CGI::FormBuilder::Field';


our $VERSION = '3.10';

sub script {
    my $self = shift;
    my $name = $self->name;

    # The way script() works is slightly backwards: First the
    # type-specific JS DOM code is generated, then this is
    # passed as a string to Field->jsfield, which wraps this
    # in the generic handling.

    # Holders for different parts of JS code
    my $jsfunc  = '';
    my $jsfield = tovar($name);
    my $close_brace = '';
    my $in = indent(my $idt = 1);   # indent

    my $alertstr = escapejs($self->jsmessage);  # handle embedded '
    $alertstr .= '\n';

    #
    # If you can believe this, the Javascript DOM actually changes
    # based on whether there are more than 1 option to checkboxes.
    # Damn damn damn I hate the JavaScript DOM so damn much!!!!!!
    #
    # Whoever designed this should be shot.
    #
    if ($self->options == 1) {

        # Simple single-element on/off checkbox
        $jsfunc .= <<EOJS;
    // $name: single-element checkbox
    var $jsfield = null;
    if (document.getElementById('$name') != null && form.elements['$name'].checked) {
        $jsfield = form.elements['$name'].value;
    }
EOJS

    } else {

        # Get field from radio buttons or checkboxes.
        # Must cycle through all again to see which is checked. Yeesh.

        $jsfunc .= <<EOJS;
    // $name: radio group or multiple checkboxes
    var $jsfield = null;
    var selected_$jsfield = 0;
    for (var loop = 0; loop < form.elements['$name'].length; loop++) {
        if (form.elements['$name']\[loop].checked) {
            $jsfield = form.elements['$name']\[loop].value;
            selected_$jsfield++;
EOJS

        # Add catch for "other" if applicable
        if ($self->other) {
            my $oth = $self->othername;
            $jsfunc .= <<EOJS;
            if ($jsfield == '$oth') $jsfield = form.elements['$oth'].value;
EOJS
        }

        $close_brace = <<EOJS;

        } // if
    } // for $name
EOJS

        # required?
        $close_brace .= <<EOJS if $self->required;
    if (! selected_$jsfield) {
        alertstr += '$alertstr';
        invalid++;
    }
EOJS

        # indent the very last if/else tests so they're in the for loop
        $in = indent($idt += 2);
    }
    
    return $self->jsfield($jsfunc, $close_brace, $in);
}

*render = \&tag;
sub tag {
    local $^W = 0;    # -w sucks
    my $self = shift;
    my $attr = $self->attr;

    my $jspre = $self->{_form}->jsprefix;

    my $tag   = '';
    my @value = $self->tag_value;   # sticky is different in <tag>
    my @opt   = $self->options;
    debug 2, "my(@opt) = \$field->options";

    # Add in our "Other:" option if applicable
    push @opt, [$self->othername, $self->{_form}{messages}->form_other_default]
             if $self->other;

    debug 2, "$self->{name}: generating $attr->{type} input type";

    my $checkbox_table = 0;  # toggle
    my $checkbox_col = 0;
    if ($self->columns > 0) {
        $checkbox_table = 1;
        my $c = $self->{_form}->class('_columns');
        $tag .= $self->{_form}->table(class => $c) . "\n";
    }

    belch "$self->{name}: No options specified for 'checkbox' field" unless @opt;
    for my $opt (@opt) {
        #  Divide up checkboxes in a user-controlled manner
        if ($checkbox_table) {
            $tag .= "  ".htmltag('tr')."\n" if $checkbox_col % $self->columns == 0;
            $tag .= '    '.htmltag('td') . $self->{_form}->font;
        }
        # Since our data structure is a series of ['',''] things,
        # we get the name from that. If not, then it's a list
        # of regular old data that we toname() if nameopts => 1
        my($o,$n) = optval($opt);

        # Must use defined() or else labels of "0" are lost
        unless (defined($n)) {
            $n = $attr->{labels}{$o};
            unless (defined($n)) {
                $n = $self->nameopts ? toname($o) : $o;
            }
        }

        ismember($o, @value) ? $attr->{checked} = 'checked'
                             : delete $attr->{checked};

        # reset some attrs
        $attr->{value} = $o;
        if (@opt == 1) {
            # single option checkboxes do not modify id
            $attr->{id} ||= tovar($attr->{name});
        } else {
            # all others add the current option name
            $attr->{id} = tovar($o eq $self->othername
                                  ? "_$attr->{name}" : "$attr->{name}_$o");
        }

        #
        # Special event handling for our _other field
        #
        # This is the only piece that's different from ::radio, since
        # here what we do is just key off whether the "Other" box is
        # being checked or unchecked.
        #
        if ($self->other && $self->javascript) {
            my $b = $self->othername;   # box
            if ($n eq $self->{_form}{messages}->form_other_default) {
                #
                # Turn on when they click the "_other" field
                #
                my $vct = "${jspre}${b}_ct";
                $attr->{onclick} = "var $vct;"
                                 . "if (this.checked && ! $vct) ${jspre}other_on('$b'); "
                                 . "else { ${jspre}other_off('$b'); $vct = 1 }";
            }
        }

        # Each radio/checkbox gets a human thingy with <label> around it
        $tag .= $self->add_before_option;
        $tag .= htmltag('input', $attr);
        $tag .= $checkbox_table
              ? (htmltag('/td')."\n    ".htmltag('td').$self->{_form}->font) : ' ';
        my $c = $self->{_form}->class('_option');
        $tag .= htmltag('label', for => $attr->{id}, class => $c)
              . ($self->cleanopts ? escapehtml($n) : $n)
              . htmltag('/label');
        $tag .= $self->add_after_option;

        $tag .= '<br />' if $self->linebreaks;

        if ($checkbox_table) {
            $checkbox_col++;
            $tag .= htmltag('/td');
            $tag .= "\n  ".htmltag('/tr') if $checkbox_col % $self->columns == 0;
        }
        $tag .= "\n";
    }
    $tag .= '  '.htmltag('/tr') if $checkbox_table && ($checkbox_col % $self->columns > 0);
    $tag .= '  '.htmltag('/table') if $checkbox_table;

    # add an additional tag for our _other field
    $tag .= ' ' . $self->othertag if $self->other;

    debug 2, "$self->{name}: generated tag = $tag";
    return $tag;       # always return scalar tag
}

1;

__END__