Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / httemplate / elements / checkbox-tristate.html
1 <%doc>
2 A tristate checkbox (with three values: true, false, and null).
3 Internally, this creates a checkbox, coupled via javascript to a hidden
4 field that actually contains the value.  For now, the only values these
5 can have are 1, -1, and empty.  Clicking the checkbox cycles between them.
6
7 For compatibility with regular checkboxes, empty is the false state and 
8 -1 is the indeterminate state.
9
10 Displaying these is a problem.  "indeterminate" is a standard HTML5 attribute
11 but some browsers display it in unhelpful ways (e.g. Firefox slightly grays
12 the checkbox, approximately #dddddd), and checkboxes ignore nearly all CSS 
13 styling.
14 </%doc>
15 <%shared>
16 my $init = 0;
17 </%shared>
18 % if ( !$init ) {
19 %   $init = 1;
20 <STYLE>
21 input.partial {
22   position: absolute;
23   opacity: 0;
24   z-index: 1;
25 }
26 input.partial + label::before {
27   position: relative;
28   content: "\2610";
29 }
30 input.partial:checked + label::before {
31   content: "\2611";
32 }
33 input.partial:indeterminate + label::before {
34   content: "\2612";
35 }
36 </STYLE>
37 <SCRIPT TYPE="text/javascript">
38 function tristate_onclick() {
39   var checkbox = this;
40   var input = checkbox.input;
41   if ( input.value == "" ) { // false -> true
42     input.value = "1";
43     checkbox.checked = true;
44     checkbox.indeterminate = false;
45   } else if ( input.value == "1" ) { // true -> indeterminate
46     input.value = "-1";
47     checkbox.checked = false;
48     checkbox.indeterminate = true;
49   } else if ( input.value == "-1" ) { // indeterminate -> false
50     input.value = "";
51     checkbox.checked = false;
52     checkbox.indeterminate = false;
53   }
54 }
55
56 var tristates = [];
57 var tristate_boxes = [];
58 window.onload = function() { // don't do this until all of the checkboxes exist
59 %#  tristates = document.getElementsByClassName('tristate'); # curse you, IE8
60   var all_inputs = document.getElementsByTagName('input');
61   for (var i=0; i < all_inputs.length; i++) {
62     if ( all_inputs[i].className == 'tristate' ) {
63       tristates.push(all_inputs[i]);
64     }
65   }
66   for (var i=0; i < tristates.length; i++) {
67     tristate_boxes[i] =
68       document.getElementById('checkbox_' + tristates[i].name);
69     // make sure they can find each other
70     tristate_boxes[i].input = tristates[i];
71     tristates[i].checkbox = tristate_boxes[i];
72     // set event handler
73     tristate_boxes[i].onclick = tristate_onclick;
74     // set initial value
75     if ( tristates[i].value == "-1" ) {
76       tristate_boxes[i].indeterminate = true
77     }
78     if ( tristates[i].value == "1" ) {
79       tristate_boxes[i].checked = true;
80     }
81   }
82 };
83 </SCRIPT>
84 % } # end of $init
85 <INPUT TYPE="hidden" NAME="<% $opt{field} %>"
86                      ID="<% $opt{id} %>"
87                      VALUE="<% $curr_value %>"
88                      CLASS="tristate">
89 <INPUT TYPE="checkbox" ID="checkbox_<%$opt{field}%>" CLASS="partial">
90 <LABEL />
91 <%init>
92
93 my %opt = @_;
94
95 # might be useful but I'm not implementing it yet
96 #my $onchange = $opt{'onchange'}
97 #                 ? 'onChange="'. $opt{'onchange'}. '(this)"'
98 #                 : '';
99
100 $opt{'id'} ||= 'hidden_'.$opt{'field'};
101 my $curr_value = '-1';
102 if (exists $opt{curr_value}) {
103   $curr_value = $opt{curr_value};
104   $curr_value = '' unless $curr_value eq '-1' or $curr_value eq '1';
105 }
106
107 </%init>