]>
Commit | Line | Data |
---|---|---|
f0cfe83e AD |
1 | define("dijit/_PaletteMixin", [ |
2 | "dojo/_base/declare", // declare | |
3 | "dojo/dom-attr", // domAttr.set | |
4 | "dojo/dom-class", // domClass.add domClass.remove | |
5 | "dojo/dom-construct", // domConstruct.create domConstruct.place | |
6 | "dojo/_base/event", // event.stop | |
7 | "dojo/keys", // keys | |
8 | "dojo/_base/lang", // lang.getObject | |
9 | "./_CssStateMixin", | |
10 | "./focus", | |
11 | "./typematic" | |
12 | ], function(declare, domAttr, domClass, domConstruct, event, keys, lang, _CssStateMixin, focus, typematic){ | |
13 | ||
14 | // module: | |
15 | // dijit/_PaletteMixin | |
16 | ||
17 | return declare("dijit._PaletteMixin", [_CssStateMixin], { | |
18 | // summary: | |
19 | // A keyboard accessible palette, for picking a color/emoticon/etc. | |
20 | // description: | |
21 | // A mixin for a grid showing various entities, so the user can pick a certain entity. | |
22 | ||
23 | // defaultTimeout: Number | |
24 | // Number of milliseconds before a held key or button becomes typematic | |
25 | defaultTimeout: 500, | |
26 | ||
27 | // timeoutChangeRate: Number | |
28 | // Fraction of time used to change the typematic timer between events | |
29 | // 1.0 means that each typematic event fires at defaultTimeout intervals | |
30 | // Less than 1.0 means that each typematic event fires at an increasing faster rate | |
31 | timeoutChangeRate: 0.90, | |
32 | ||
33 | // value: String | |
34 | // Currently selected color/emoticon/etc. | |
35 | value: "", | |
36 | ||
37 | // _selectedCell: [private] Integer | |
38 | // Index of the currently selected cell. Initially, none selected | |
39 | _selectedCell: -1, | |
40 | ||
41 | /*===== | |
42 | // _currentFocus: [private] DomNode | |
43 | // The currently focused cell (if the palette itself has focus), or otherwise | |
44 | // the cell to be focused when the palette itself gets focus. | |
45 | // Different from value, which represents the selected (i.e. clicked) cell. | |
46 | _currentFocus: null, | |
47 | =====*/ | |
48 | ||
49 | /*===== | |
50 | // _xDim: [protected] Integer | |
51 | // This is the number of cells horizontally across. | |
52 | _xDim: null, | |
53 | =====*/ | |
54 | ||
55 | /*===== | |
56 | // _yDim: [protected] Integer | |
57 | // This is the number of cells vertically down. | |
58 | _yDim: null, | |
59 | =====*/ | |
60 | ||
61 | // tabIndex: String | |
62 | // Widget tab index. | |
63 | tabIndex: "0", | |
64 | ||
65 | // cellClass: [protected] String | |
66 | // CSS class applied to each cell in the palette | |
67 | cellClass: "dijitPaletteCell", | |
68 | ||
69 | // dyeClass: [protected] Constructor | |
70 | // Constructor for Object created for each cell of the palette. | |
71 | // dyeClass should implements dijit.Dye interface | |
72 | dyeClass: null, | |
73 | ||
74 | // summary: String | |
75 | // Localized summary for the palette table | |
76 | summary: '', | |
77 | _setSummaryAttr: "paletteTableNode", | |
78 | ||
79 | _dyeFactory: function(value /*===== , row, col, title =====*/){ | |
80 | // summary: | |
81 | // Return instance of dijit.Dye for specified cell of palette | |
82 | // tags: | |
83 | // extension | |
84 | ||
85 | // Remove string support for 2.0 | |
86 | var dyeClassObj = typeof this.dyeClass == "string" ? lang.getObject(this.dyeClass) : this.dyeClass; | |
87 | return new dyeClassObj(value); | |
88 | }, | |
89 | ||
90 | _preparePalette: function(choices, titles) { | |
91 | // summary: | |
92 | // Subclass must call _preparePalette() from postCreate(), passing in the tooltip | |
93 | // for each cell | |
94 | // choices: String[][] | |
95 | // id's for each cell of the palette, used to create Dye JS object for each cell | |
96 | // titles: String[] | |
97 | // Localized tooltip for each cell | |
98 | ||
99 | this._cells = []; | |
100 | var url = this._blankGif; | |
101 | ||
102 | this.connect(this.gridNode, "ondijitclick", "_onCellClick"); | |
103 | ||
104 | for(var row=0; row < choices.length; row++){ | |
105 | var rowNode = domConstruct.create("tr", {tabIndex: "-1"}, this.gridNode); | |
106 | for(var col=0; col < choices[row].length; col++){ | |
107 | var value = choices[row][col]; | |
108 | if(value){ | |
109 | var cellObject = this._dyeFactory(value, row, col, titles[value]); | |
110 | ||
111 | var cellNode = domConstruct.create("td", { | |
112 | "class": this.cellClass, | |
113 | tabIndex: "-1", | |
114 | title: titles[value], | |
115 | role: "gridcell" | |
116 | }, rowNode); | |
117 | ||
118 | // prepare cell inner structure | |
119 | cellObject.fillCell(cellNode, url); | |
120 | ||
121 | cellNode.idx = this._cells.length; | |
122 | ||
123 | // save cell info into _cells | |
124 | this._cells.push({node:cellNode, dye:cellObject}); | |
125 | } | |
126 | } | |
127 | } | |
128 | this._xDim = choices[0].length; | |
129 | this._yDim = choices.length; | |
130 | ||
131 | // Now set all events | |
132 | // The palette itself is navigated to with the tab key on the keyboard | |
133 | // Keyboard navigation within the Palette is with the arrow keys | |
134 | // Spacebar selects the cell. | |
135 | // For the up key the index is changed by negative the x dimension. | |
136 | ||
137 | var keyIncrementMap = { | |
138 | UP_ARROW: -this._xDim, | |
139 | // The down key the index is increase by the x dimension. | |
140 | DOWN_ARROW: this._xDim, | |
141 | // Right and left move the index by 1. | |
142 | RIGHT_ARROW: this.isLeftToRight() ? 1 : -1, | |
143 | LEFT_ARROW: this.isLeftToRight() ? -1 : 1 | |
144 | }; | |
145 | for(var key in keyIncrementMap){ | |
146 | this.own( | |
147 | typematic.addKeyListener( | |
148 | this.domNode, | |
149 | {charOrCode:keys[key], ctrlKey:false, altKey:false, shiftKey:false}, | |
150 | this, | |
151 | function(){ | |
152 | var increment = keyIncrementMap[key]; | |
153 | return function(count){ this._navigateByKey(increment, count); }; | |
154 | }(), | |
155 | this.timeoutChangeRate, | |
156 | this.defaultTimeout | |
157 | ) | |
158 | ); | |
159 | } | |
160 | }, | |
161 | ||
162 | postCreate: function(){ | |
163 | this.inherited(arguments); | |
164 | ||
165 | // Set initial navigable node. | |
166 | this._setCurrent(this._cells[0].node); | |
167 | }, | |
168 | ||
169 | focus: function(){ | |
170 | // summary: | |
171 | // Focus this widget. Puts focus on the most recently focused cell. | |
172 | ||
173 | // The cell already has tabIndex set, just need to set CSS and focus it | |
174 | focus.focus(this._currentFocus); | |
175 | }, | |
176 | ||
177 | _onCellClick: function(/*Event*/ evt){ | |
178 | // summary: | |
179 | // Handler for click, enter key & space key. Selects the cell. | |
180 | // evt: | |
181 | // The event. | |
182 | // tags: | |
183 | // private | |
184 | ||
185 | var target = evt.target; | |
186 | ||
187 | // Find TD associated with click event. For ColorPalette user likely clicked IMG inside of TD | |
188 | while(target.tagName != "TD"){ | |
189 | if(!target.parentNode || target == this.gridNode){ // probably can never happen, but just in case | |
190 | return; | |
191 | } | |
192 | target = target.parentNode; | |
193 | } | |
194 | ||
195 | var value = this._getDye(target).getValue(); | |
196 | ||
197 | // First focus the clicked cell, and then send onChange() notification. | |
198 | // onChange() (via _setValueAttr) must be after the focus call, because | |
199 | // it may trigger a refocus to somewhere else (like the Editor content area), and that | |
200 | // second focus should win. | |
201 | this._setCurrent(target); | |
202 | focus.focus(target); | |
203 | this._setValueAttr(value, true); | |
204 | ||
205 | event.stop(evt); | |
206 | }, | |
207 | ||
208 | _setCurrent: function(/*DomNode*/ node){ | |
209 | // summary: | |
210 | // Sets which node is the focused cell. | |
211 | // description: | |
212 | // At any point in time there's exactly one | |
213 | // cell with tabIndex != -1. If focus is inside the palette then | |
214 | // focus is on that cell. | |
215 | // | |
216 | // After calling this method, arrow key handlers and mouse click handlers | |
217 | // should focus the cell in a setTimeout(). | |
218 | // tags: | |
219 | // protected | |
220 | if("_currentFocus" in this){ | |
221 | // Remove tabIndex on old cell | |
222 | domAttr.set(this._currentFocus, "tabIndex", "-1"); | |
223 | } | |
224 | ||
225 | // Set tabIndex of new cell | |
226 | this._currentFocus = node; | |
227 | if(node){ | |
228 | domAttr.set(node, "tabIndex", this.tabIndex); | |
229 | } | |
230 | }, | |
231 | ||
232 | _setValueAttr: function(value, priorityChange){ | |
233 | // summary: | |
234 | // This selects a cell. It triggers the onChange event. | |
235 | // value: String | |
236 | // Value of the cell to select | |
237 | // tags: | |
238 | // protected | |
239 | // priorityChange: Boolean? | |
240 | // Optional parameter used to tell the select whether or not to fire | |
241 | // onChange event. | |
242 | ||
243 | // clear old selected cell | |
244 | if(this._selectedCell >= 0){ | |
245 | domClass.remove(this._cells[this._selectedCell].node, this.cellClass + "Selected"); | |
246 | } | |
247 | this._selectedCell = -1; | |
248 | ||
249 | // search for cell matching specified value | |
250 | if(value){ | |
251 | for(var i = 0; i < this._cells.length; i++){ | |
252 | if(value == this._cells[i].dye.getValue()){ | |
253 | this._selectedCell = i; | |
254 | domClass.add(this._cells[i].node, this.cellClass + "Selected"); | |
255 | break; | |
256 | } | |
257 | } | |
258 | } | |
259 | ||
260 | // record new value, or null if no matching cell | |
261 | this._set("value", this._selectedCell >= 0 ? value : null); | |
262 | ||
263 | if(priorityChange || priorityChange === undefined){ | |
264 | this.onChange(value); | |
265 | } | |
266 | }, | |
267 | ||
268 | onChange: function(/*===== value =====*/){ | |
269 | // summary: | |
270 | // Callback when a cell is selected. | |
271 | // value: String | |
272 | // Value corresponding to cell. | |
273 | }, | |
274 | ||
275 | _navigateByKey: function(increment, typeCount){ | |
276 | // summary: | |
277 | // This is the callback for typematic. | |
278 | // It changes the focus and the highlighed cell. | |
279 | // increment: | |
280 | // How much the key is navigated. | |
281 | // typeCount: | |
282 | // How many times typematic has fired. | |
283 | // tags: | |
284 | // private | |
285 | ||
286 | // typecount == -1 means the key is released. | |
287 | if(typeCount == -1){ return; } | |
288 | ||
289 | var newFocusIndex = this._currentFocus.idx + increment; | |
290 | if(newFocusIndex < this._cells.length && newFocusIndex > -1){ | |
291 | var focusNode = this._cells[newFocusIndex].node; | |
292 | this._setCurrent(focusNode); | |
293 | ||
294 | // Actually focus the node, for the benefit of screen readers. | |
295 | // Use defer because IE doesn't like changing focus inside of an event handler | |
296 | this.defer(lang.hitch(focus, "focus", focusNode)); | |
297 | } | |
298 | }, | |
299 | ||
300 | _getDye: function(/*DomNode*/ cell){ | |
301 | // summary: | |
302 | // Get JS object for given cell DOMNode | |
303 | ||
304 | return this._cells[cell.idx].dye; | |
305 | } | |
306 | }); | |
307 | ||
308 | /*===== | |
309 | declare("dijit.Dye", | |
310 | null, | |
311 | { | |
312 | // summary: | |
313 | // Interface for the JS Object associated with a palette cell (i.e. DOMNode) | |
314 | ||
315 | constructor: function(alias, row, col){ | |
316 | // summary: | |
317 | // Initialize according to value or alias like "white" | |
318 | // alias: String | |
319 | }, | |
320 | ||
321 | getValue: function(){ | |
322 | // summary: | |
323 | // Return "value" of cell; meaning of "value" varies by subclass. | |
324 | // description: | |
325 | // For example color hex value, emoticon ascii value etc, entity hex value. | |
326 | }, | |
327 | ||
328 | fillCell: function(cell, blankGif){ | |
329 | // summary: | |
330 | // Add cell DOMNode inner structure | |
331 | // cell: DomNode | |
332 | // The surrounding cell | |
333 | // blankGif: String | |
334 | // URL for blank cell image | |
335 | } | |
336 | } | |
337 | ); | |
338 | =====*/ | |
339 | ||
340 | }); |