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