Changeset 8248
- Timestamp:
- 04/23/07 12:13:03 (19 months ago)
- Location:
- dijit/trunk
- Files:
-
- 4 modified
-
form/Checkbox.js (modified) (6 diffs)
-
form/templates/Checkbox.html (modified) (1 diff)
-
tests/form/test_Checkbox.html (modified) (4 diffs)
-
themes/tundra/tundra.css (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
dijit/trunk/form/Checkbox.js
r7824 r8248 14 14 [dijit.base.FormElement, dijit.base.TemplatedWidget], 15 15 { 16 // summary 17 // Same as a native HTML checkbox, but with fancy styling 16 // summary: 17 // Same as an HTML checkbox, but with fancy styling. 18 19 // Implementation details 20 // 21 // pattern: MVC 22 // Control: User interacts with real html inputs 23 // Event listeners are added for input node events 24 // These handlers make sure to update the view based on input state 25 // View: The view is basically the the dijit (tundra) sprint image. 26 // Model: The dijit checked state is synched with the input node. 27 // 28 // There are two modes: 29 // 1. Image not used or failed to load 30 // 2. Image loaded and used. 31 // In case 1, the regular html inputs are shown and used by the user. 32 // In case 2, the regular html inputs are invisible but still used by 33 // the user. They are turned invisible and overlay the dijit image. 34 // 35 // Layout 36 // Styling is controlled in 3 places: tundra, template, and 37 // programmatically in Checkbox.js. The latter is required 38 // because of two modes of dijit checkbox: image loaded, vs 39 // image not loaded. Also for accessibility it is important 40 // that dijit work with images off (a browser preference). 18 41 19 42 templatePath: dojo.uri.moduleUri("dijit.form", "templates/Checkbox.html"), 20 43 21 // Value of "type" attribute for <input>, and waiRole attribute also. 22 // User probably shouldn't adjust this. 44 // Value of "type" attribute for <input> 23 45 _type: "checkbox", 24 46 … … 33 55 value: "on", 34 56 35 // This shared object keeps track of all widgets, grouped by name36 _groups: { },37 38 postMixInProperties: function(){39 dijit.form.Checkbox.superclass.postMixInProperties.apply(this, arguments);40 41 // set tabIndex="0" because if tabIndex=="" user won't be able to tab to the field42 if(!this.disabled && this.tabIndex==""){ this.tabIndex="0"; }43 },44 45 57 postCreate: function(){ 46 58 47 59 // find the image to use, as notated in the CSS file, but use it as a foreground 48 // image49 60 var bi = dojo.html.getComputedStyle(this.imageContainer, "background-image"); 50 61 var href = bi.charAt(4)=='"' ? bi.slice(5,-2) : bi.slice(4,-1); // url(foo) --> foo, url("foo") --> foo … … 52 63 var img = this.imageNode = document.createElement("img"); 53 64 var self=this; 65 66 // inputNode.checked must be assigned before img.onload handler 67 this.inputNode.checked=this.checked; 68 // note: onImageLoad may get called as a side-effect of this assignment 54 69 img.onload = function(){ self.onImageLoad(); } 55 70 img.src = href; 56 57 this._setValue(this.checked); 58 59 // find any associated label and create a labelled-by relationship 60 // assumes <label for="inputId">label text </label> rather than 61 // <label><input type="xyzzy">label text</label> 62 var notcon = true; 63 if(this.id != ""){ 64 var labels = document.getElementsByTagName("label"); 65 if (labels != null && labels.length > 0){ 66 for(var i=0; i<labels.length; i++){ 67 if (labels[i].htmlFor == this.id){ 68 labels[i].id = (labels[i].htmlFor + "label"); 69 this._connectEvents(labels[i]); 70 dijit.util.wai.setAttr(this.domNode, "waiState", "labelledby", labels[i].id); 71 break; 72 } 73 } 74 } 75 } 76 this._connectEvents(this.domNode); 77 // this is needed here for IE 78 this.inputNode.checked=this.checked; 79 this._register(); 71 this._setDisabled(this.disabled); 80 72 }, 81 73 82 74 onImageLoad: function(){ 83 75 this.imageLoaded = true; 76 84 77 // set image container size to just show one sprite 85 this.width = 16; // this.imageNode.width/6; 86 this.height = 16; // this.imageNode.height/2; 87 this.imageContainer.style.width = this.width + "px"; 88 this.imageContainer.style.height = this.height + "px"; 89 90 // Hide the HTML native checkbox and display the image instead 91 this.inputNode.style.display="none"; 78 if (!this.width) { 79 this.width = 16; 80 } // dojo.html.getPixelValue is not succeeding for all browsers 81 if (!this.height) { 82 this.height = 16; 83 } 84 85 // Turn the input element invisible and make sure it overlays 86 // the dojo image container. 87 dojo.html.addClass(this.inputNode,"dojoCheckboxInputInvisible"); 88 dojo.html.addClass(this.imageContainer,"dojoCheckboxImageContainer"); 89 90 dojo.html.applyBrowserClass(this.domNode); 91 92 var imageContainerStyle = this.imageContainer.style 93 var inputStyle = this.inputNode.style; 94 var domNodeStyle = this.domNode.style; 95 96 // Force size based on width and height. 97 inputStyle.width = imageContainerStyle.width = this.width + "px"; 98 inputStyle.height = imageContainerStyle.height = this.height + "px"; 99 domNodeStyle.position = "relative"; 100 domNodeStyle.fontFamily = "monospace"; // webkit spacing hack 101 102 // User will always interact with input element 103 this._connectEvents(this.inputNode); 104 92 105 this.imageContainer.appendChild(this.imageNode); 93 94 // position image to display right sprite 95 this._setValue(this.checked); 96 }, 97 98 uninitialize: function(){ 99 this._deregister(); 100 }, 101 106 107 this._updateView(); 108 }, 109 102 110 _connectEvents: function(/*DomNode*/ node){ 111 dojo.event.browser.addListener(node, "onfocus", dojo.lang.hitch(this, "mouseOver")); 112 dojo.event.browser.addListener(node, "onblur", dojo.lang.hitch(this, "mouseOut")); 103 113 dojo.event.browser.addListener(node, "onmouseover", dojo.lang.hitch(this, "mouseOver")); 104 114 dojo.event.browser.addListener(node, "onmouseout", dojo.lang.hitch(this, "mouseOut")); 105 dojo.event.browser.addListener(node, "onkey", dojo.lang.hitch(this, "onKey")); 106 dojo.event.browser.addListener(node, "onclick", dojo.lang.hitch(this, "_onClick")); 115 dojo.event.browser.addListener(node, "onclick", dojo.lang.hitch(this, "onClick")); 107 116 dojo.html.disableSelection(node); 108 117 }, 109 118 110 _onClick: function(/*Event*/ e){ 111 if(this.disabled == false){ 112 this.setValue(!this.checked); 113 } 114 dojo.event.browser.stopEvent(e); 115 this.onClick(); 116 }, 117 118 _register: function(){ 119 // summary: add this widget to _groups 120 if(this._groups[this.name] == null){ 121 this._groups[this.name]=[]; 122 } 123 this._groups[this.name].push(this); 124 }, 125 126 _deregister: function(){ 127 // summary: remove this widget from _groups 128 var idx = dojo.lang.find(this._groups[this.name], this, true); 129 this._groups[this.name].splice(idx, 1); 130 }, 131 132 setValue: function(/*boolean*/ bool){ 133 // summary: set the checkbox state 134 this._setValue(bool); 135 }, 136 137 onClick: function(){ 138 // summary: user overridable callback function for checkbox being clicked 139 }, 140 141 onKey: function(/*Event*/ e){ 142 // summary: callback when user hits a key 143 var k = dojo.event.browser.keys; 144 if(e.key == " "){ 145 this._onClick(e); 146 } 119 _setDisabled: function(/*Boolean*/ disabled){ 120 this.domNode.disabled = this.inputNode.disabled = this.disabled = disabled; 121 }, 122 123 onValueChanged: function(newValue){ 124 // summary: implement this to capture value change events. 125 }, 126 127 _lastValueReported: null, 128 setValue: function(newValue){ 129 // summary: set the value of the widget. 130 if (newValue != this._lastValueReported){ 131 this.onValueChanged(newValue); 132 this._lastValueReported = newValue; 133 } 134 }, 135 136 getValue: function(){ 137 // summary: get the value of the widget. 138 return this._lastValueReported; 139 }, 140 141 onClick: function(/*Event*/ e){ 142 // dojo.event.browser.stopEvent(e); // bad for Opera 143 this._updateView(); 147 144 }, 148 145 … … 150 147 // summary: callback when user moves mouse over checkbox 151 148 this.hover=true; 152 this._ setValue(this.checked);149 this._updateView(); 153 150 }, 154 151 … … 156 153 // summary: callback when user moves mouse off of checkbox 157 154 this.hover=false; 158 this._ setValue(this.checked);155 this._updateView(); 159 156 }, 160 157 161 158 // offset from left of image 162 159 _leftOffset: 0, 163 164 _setValue: function(/*Boolean*/ bool){ 165 // summary: 166 // sets checkbox to given value 167 // set state of hidden checkbox node to correspond to given value. 168 this.checked = bool; 169 this.inputNode.checked = this.checked; 170 if(this.disabled){ 171 this.inputNode.setAttribute("disabled",true); 172 }else{ 173 this.inputNode.removeAttribute("disabled"); 174 } 175 dijit.util.wai.setAttr(this.domNode, "waiState", "checked", this.checked); 160 161 _updateView: function(/*Optional Widget*/ awidget) { 162 var w = awidget || this; 163 w.checked=w.inputNode.checked; 164 this.setValue(w.checked? this.inputNode.value:""); 165 166 this._setDisabled(w.disabled); 176 167 177 168 // show the right sprite, depending on state of checkbox 178 if(this.imageLoaded){ 179 var left = this._leftOffset + (this.checked ? 0 : this.width ) + (this.disabled ? this.width*2 : (this.hover ? this.width*4 : 0 )); 180 this.imageNode.style.marginLeft = -1*left + "px"; 181 } 169 if(w.imageLoaded){ 170 var left = w._leftOffset + (w.checked ? 0 : w.width ) + (w.disabled ? this.width*2 : (w.hover ? w.width*4 : 0 )); 171 w.imageNode.style.marginLeft = -1*left + "px"; 172 } 173 if (!awidget) { 174 this.updateContext(); 175 } 176 }, 177 178 updateContext: function(){ 179 // stub; 182 180 } 183 181 } … … 187 185 dijit.form.Checkbox, 188 186 { 189 // summary 190 // Same as an HTML radio button, but with fancy styling 191 187 // summary: 188 // Same as an HTML radio, but with fancy styling. 189 190 // Implementation details 191 // 192 // Specialization: 193 // We keep track of dijit radio groups so that we can update the state 194 // of all the siblings (the "context") in a group based on input 195 // events. We don't rely on browser radio grouping. 196 // 197 // At the time of implementation not all browsers fire the same events 198 // when a different radio button in a group is checked (and the previous 199 // unchecked). When the events do fire, e.g. a focus event on the newly 200 // checked radio, the checked state of that "newly checked" radio is 201 // set to true in some browsers and false in others. 202 // It is vital that the view of the resulting input states be correct 203 // so that at the time of form submission the intended data is sent. 204 192 205 _type: "radio", 193 194 _onClick: function(/*Event*/ e){ 195 if(!this.disabled && !this.checked){ 196 this.setValue(true); 197 } 198 e.stopPropagation(); 199 this.onClick(); 200 }, 201 202 setValue: function(/*boolean*/ bool){ 203 this._setValue(bool); 204 205 // if turning this widget on, then turn others in same group off 206 if(bool){ 207 dojo.lang.forEach(this._groups[this.name], function(widget){ 208 if(widget != this){ 209 widget._setValue(false); 210 } 211 }, this); 212 } 206 207 // This shared object keeps track of all widgets, grouped by name 208 _groups: { }, 209 210 _register: function(){ 211 // summary: add this widget to _groups 212 if(this._groups[this.name] == null){ 213 this._groups[this.name]=[]; 214 } 215 this._groups[this.name].push(this); 216 }, 217 218 _deregister: function(){ 219 // summary: remove this widget from _groups 220 var idx = dojo.lang.find(this._groups[this.name], this, true); 221 this._groups[this.name].splice(idx, 1); 222 }, 223 224 uninitialize: function(){ 225 this._deregister(); 226 }, 227 228 updateContext: function() { 229 dojo.lang.forEach(this._groups[this.name], function(widget){ 230 if(widget != this){ 231 widget._updateView(widget); 232 } 233 }, this); 213 234 }, 214 235 215 236 onImageLoad: function(){ 216 // position to second row of sprites (the radio buttons)217 237 this._leftOffset = 96; // this.imageNode.width/2; 238 this._register(); 218 239 dijit.form.Checkbox.prototype.onImageLoad.call(this); 219 240 } -
dijit/trunk/form/templates/Checkbox.html
r7525 r8248 1 <span class="dojoInlineBox" tabIndex="${this.tabIndex}" waiRole="${this._type}" id="${this.id}"2 ><div dojoAttachPoint="imageContainer" class="dojoCheckbox" style="overflow:hidden;"></div 3 ><input type="${this._type}" name="${this.name}" value="${this.value}" dojoAttachPoint="inputNode"1 <span 2 > <span dojoAttachPoint="imageContainer" class="dojoCheckbox" style="overflow:hidden;"></span 3 ><input dojoAttachPoint="inputNode" class="dojoCheckboxInput" id="${this.id}" tabIndex="${this.tabIndex}" type="${this._type}" name="${this.name}" value="${this.value}" 4 4 ></span> -
dijit/trunk/tests/form/test_Checkbox.html
r7245 r8248 35 35 }); 36 36 </script> 37 <style> label { margin-right: 0.80em; } </style> 37 38 </head> 38 39 <body class="tundra" style="padding: 50px;"> 39 40 40 41 <p> 41 Here are some checkboxes. Try clicking, and hovering :42 Here are some checkboxes. Try clicking, and hovering, tabbing, and using the space bar to select: 42 43 </p> 43 44 … … 62 63 <br> 63 64 <div id="checkboxContainer"></div> 64 <label for="c heckboxContainer">cb6: instantiated from script</label>65 <label for="cb6">cb6: instantiated from script</label> 65 66 <br> 67 <p> 68 Here are some radio buttons. Try clicking, and hovering, tabbing, and arrowing 69 </p> 66 70 <p> 67 71 <span>Radio group #1:</span> … … 74 78 </p> 75 79 <p> 76 <span>Radio group #2: </span>80 <span>Radio group #2: (no default value, and has breaks)</span><br> 77 81 <input type="radio" name="g2" id="g2rb1" value="top40" dojoType="dijit.form.RadioButton"/> 78 <label for="g2rb1">top 40</label> 82 <label for="g2rb1">top 40</label><br> 79 83 <input type="radio" name="g2" id="g2rb2" value="oldies" dojoType="dijit.form.RadioButton"/> 80 <label for="g2rb2">oldies</label> 81 <input type="radio" name="g2" id="g2rb3" value="country" dojoType="dijit.form.RadioButton" disabled="disabled" checked="checked"/> 82 <label for="g2rb3">country</label> 84 <label for="g2rb2">oldies</label><br> 85 <input type="radio" name="g2" id="g2rb3" value="country" dojoType="dijit.form.RadioButton"/> 86 <label for="g2rb3">country</label><br> 87 (Note if using keyboard: tab to navigate, and use arrow or space to select) 83 88 </p> 84 89 <p> … … 86 91 <input type="radio" name="g3" id="g3rb1" value="rock"/> 87 92 <label for="g3rb1">rock</label> 88 <input type="radio" name="g3" id="g3rb2" value="jazz" />93 <input type="radio" name="g3" id="g3rb2" value="jazz" disabled="disabled"/> 89 94 <label for="g3rb2">jazz</label> 90 <input type="radio" name="g3" id="g3rb3" value="classical" disabled="disabled"checked="checked"/>95 <input type="radio" name="g3" id="g3rb3" value="classical" checked="checked"/> 91 96 <label for="g3rb3">classical</label> 92 97 </p> -
dijit/trunk/themes/tundra/tundra.css
r8036 r8248 151 151 } 152 152 153 153 154 .dojoCheckbox { 154 /* location of checkbox sprint image */ 155 background-image: url(checkmark.gif); 155 background-image: url(checkmark.gif); /* checkbox sprint image */ 156 width: 16px; 157 height: 16px; 158 } 159 160 /* to go in dijit.css */ 161 .dojoCheckboxImageContainer, 162 .dojoCheckboxInputInvisible { 163 border: 0; 164 margin: 0; 165 padding: 0; 166 } 167 168 /* to go in dijit.css */ 169 .dojoCheckbox { 170 /* to go in dijit.css */ 171 position: relative; 172 } 173 174 /* to go in dijit.css */ 175 .dojoCheckboxImageContainer, 176 .dojoCheckboxInputInvisible { 177 position: absolute; 178 left: 0; 179 top: 0; 180 } 181 182 /* to go in dijit.css */ 183 .dojoCheckboxInputInvisible { 184 z-index: 100; 185 opacity: 0.01; 186 filter: alpha(opacity=0); 187 } 188 189 /* to go in dijit.css */ 190 .dj_ie .dojoCheckboxImageContainer, 191 .dj_ie .dojoCheckboxInputInvisible { 192 top: 3px; 156 193 } 157 194