Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
Palette |
|
| 1.6285714285714286;1.629 |
1 | // Copyright 2004, 2005 The Apache Software Foundation |
|
2 | // |
|
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
|
4 | // you may not use this file except in compliance with the License. |
|
5 | // You may obtain a copy of the License at |
|
6 | // |
|
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
|
8 | // |
|
9 | // Unless required by applicable law or agreed to in writing, software |
|
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
|
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
12 | // See the License for the specific language governing permissions and |
|
13 | // limitations under the License. |
|
14 | ||
15 | package org.apache.tapestry.contrib.palette; |
|
16 | ||
17 | import java.util.ArrayList; |
|
18 | import java.util.Collections; |
|
19 | import java.util.HashMap; |
|
20 | import java.util.Iterator; |
|
21 | import java.util.List; |
|
22 | import java.util.Map; |
|
23 | ||
24 | import org.apache.tapestry.BaseComponent; |
|
25 | import org.apache.tapestry.IAsset; |
|
26 | import org.apache.tapestry.IForm; |
|
27 | import org.apache.tapestry.IMarkupWriter; |
|
28 | import org.apache.tapestry.IRequestCycle; |
|
29 | import org.apache.tapestry.IScript; |
|
30 | import org.apache.tapestry.PageRenderSupport; |
|
31 | import org.apache.tapestry.Tapestry; |
|
32 | import org.apache.tapestry.TapestryUtils; |
|
33 | import org.apache.tapestry.components.Block; |
|
34 | import org.apache.tapestry.form.FormComponentContributorContext; |
|
35 | import org.apache.tapestry.form.IPropertySelectionModel; |
|
36 | import org.apache.tapestry.form.ValidatableFieldExtension; |
|
37 | import org.apache.tapestry.form.ValidatableFieldSupport; |
|
38 | import org.apache.tapestry.form.validator.Required; |
|
39 | import org.apache.tapestry.form.validator.Validator; |
|
40 | import org.apache.tapestry.html.Body; |
|
41 | import org.apache.tapestry.json.JSONLiteral; |
|
42 | import org.apache.tapestry.json.JSONObject; |
|
43 | import org.apache.tapestry.valid.IValidationDelegate; |
|
44 | import org.apache.tapestry.valid.ValidationConstants; |
|
45 | import org.apache.tapestry.valid.ValidatorException; |
|
46 | ||
47 | /** |
|
48 | * A component used to make a number of selections from a list. The general look is a pair of |
|
49 | * <select> elements. with a pair of buttons between them. The right element is a list of |
|
50 | * values that can be selected. The buttons move values from the right column ("available") to the |
|
51 | * left column ("selected"). |
|
52 | * <p> |
|
53 | * This all takes a bit of JavaScript to accomplish (quite a bit), which means a {@link Body} |
|
54 | * component must wrap the Palette. If JavaScript is not enabled in the client browser, then the |
|
55 | * user will be unable to make (or change) any selections. |
|
56 | * <p> |
|
57 | * Cross-browser compatibility is not perfect. In some cases, the |
|
58 | * {@link org.apache.tapestry.contrib.form.MultiplePropertySelection}component may be a better |
|
59 | * choice. |
|
60 | * <p> |
|
61 | * <table border=1> |
|
62 | * <tr> |
|
63 | * <td>Parameter</td> |
|
64 | * <td>Type</td> |
|
65 | * <td>Direction</td> |
|
66 | * <td>Required</td> |
|
67 | * <td>Default</td> |
|
68 | * <td>Description</td> |
|
69 | * </tr> |
|
70 | * <tr> |
|
71 | * <td>selected</td> |
|
72 | * <td>{@link List}</td> |
|
73 | * <td>in</td> |
|
74 | * <td>yes</td> |
|
75 | * <td> </td> |
|
76 | * <td>A List of selected values. Possible selections are defined by the model; this should be a |
|
77 | * subset of the possible values. This may be null when the component is renderred. When the |
|
78 | * containing form is submitted, this parameter is updated with a new List of selected objects. |
|
79 | * <p> |
|
80 | * The order may be set by the user, as well, depending on the sortMode parameter.</td> |
|
81 | * </tr> |
|
82 | * <tr> |
|
83 | * <td>model</td> |
|
84 | * <td>{@link IPropertySelectionModel}</td> |
|
85 | * <td>in</td> |
|
86 | * <td>yes</td> |
|
87 | * <td> </td> |
|
88 | * <td>Works, as with a {@link org.apache.tapestry.form.PropertySelection}component, to define the |
|
89 | * possible values.</td> |
|
90 | * </tr> |
|
91 | * <tr> |
|
92 | * <td>sort</td> |
|
93 | * <td>string</td> |
|
94 | * <td>in</td> |
|
95 | * <td>no</td> |
|
96 | * <td>{@link SortMode#NONE}</td> |
|
97 | * <td>Controls automatic sorting of the options.</td> |
|
98 | * </tr> |
|
99 | * <tr> |
|
100 | * <td>rows</td> |
|
101 | * <td>int</td> |
|
102 | * <td>in</td> |
|
103 | * <td>no</td> |
|
104 | * <td>10</td> |
|
105 | * <td>The number of rows that should be visible in the Pallete's <select> elements.</td> |
|
106 | * </tr> |
|
107 | * <tr> |
|
108 | * <td>tableClass</td> |
|
109 | * <td>{@link String}</td> |
|
110 | * <td>in</td> |
|
111 | * <td>no</td> |
|
112 | * <td>tapestry-palette</td> |
|
113 | * <td>The CSS class for the table which surrounds the other elements of the Palette.</td> |
|
114 | * </tr> |
|
115 | * <tr> |
|
116 | * <td>selectedTitleBlock</td> |
|
117 | * <td>{@link Block}</td> |
|
118 | * <td>in</td> |
|
119 | * <td>no</td> |
|
120 | * <td>"Selected"</td> |
|
121 | * <td>If specified, allows a {@link Block}to be placed within the <th> reserved for the |
|
122 | * title above the selected items <select> (on the right). This allows for images or other |
|
123 | * components to be placed there. By default, the simple word <code>Selected</code> is used.</td> |
|
124 | * </tr> |
|
125 | * <tr> |
|
126 | * <td>availableTitleBlock</td> |
|
127 | * <td>{@link Block}</td> |
|
128 | * <td>in</td> |
|
129 | * <td>no</td> |
|
130 | * <td>"Available"</td> |
|
131 | * <td>As with selectedTitleBlock, but for the left column, of items which are available to be |
|
132 | * selected. The default is the word <code>Available</code>.</td> |
|
133 | * </tr> |
|
134 | * <tr> |
|
135 | * <td>selectImage <br> |
|
136 | * selectDisabledImage <br> |
|
137 | * deselectImage <br> |
|
138 | * deselectDisabledImage <br> |
|
139 | * upImage <br> |
|
140 | * upDisabledImage <br> |
|
141 | * downImage <br> |
|
142 | * downDisabledImage</td> |
|
143 | * <td>{@link IAsset}</td> |
|
144 | * <td>in</td> |
|
145 | * <td>no</td> |
|
146 | * <td> </td> |
|
147 | * <td>If any of these are specified then they override the default images provided with the |
|
148 | * component. This allows the look and feel to be customized relatively easily. |
|
149 | * <p> |
|
150 | * The most common reason to replace the images is to deal with backgrounds. The default images are |
|
151 | * anti-aliased against a white background. If a colored or patterned background is used, the |
|
152 | * default images will have an ugly white fringe. Until all browsers have full support for PNG |
|
153 | * (which has a true alpha channel), it is necessary to customize the images to match the |
|
154 | * background.</td> |
|
155 | * </tr> |
|
156 | * </table> |
|
157 | * <p> |
|
158 | * A Palette requires some CSS entries to render correctly ... especially the middle column, which |
|
159 | * contains the two or four buttons for moving selections between the two columns. The width and |
|
160 | * alignment of this column must be set using CSS. Additionally, CSS is commonly used to give the |
|
161 | * Palette columns a fixed width, and to dress up the titles. Here is an example of some CSS you can |
|
162 | * use to format the palette component: |
|
163 | * |
|
164 | * <pre> |
|
165 | * TABLE.tapestry-palette TH |
|
166 | * { |
|
167 | * font-size: 9pt; |
|
168 | * font-weight: bold; |
|
169 | * color: white; |
|
170 | * background-color: #330066; |
|
171 | * text-align: center; |
|
172 | * } |
|
173 | * |
|
174 | * TD.available-cell SELECT |
|
175 | * { |
|
176 | * font-weight: normal; |
|
177 | * background-color: #FFFFFF; |
|
178 | * width: 200px; |
|
179 | * } |
|
180 | * |
|
181 | * TD.selected-cell SELECT |
|
182 | * { |
|
183 | * font-weight: normal; |
|
184 | * background-color: #FFFFFF; |
|
185 | * width: 200px; |
|
186 | * } |
|
187 | * |
|
188 | * TABLE.tapestry-palette TD.controls |
|
189 | * { |
|
190 | * text-align: center; |
|
191 | * vertical-align: middle; |
|
192 | * width: 60px; |
|
193 | * } |
|
194 | * </pre> |
|
195 | * |
|
196 | * <p> |
|
197 | * As of 4.0, this component can be validated. |
|
198 | * </p> |
|
199 | * |
|
200 | * @author Howard Lewis Ship |
|
201 | */ |
|
202 | ||
203 | 1 | public abstract class Palette extends BaseComponent implements ValidatableFieldExtension |
204 | { |
|
205 | private static final int MAP_SIZE = 7; |
|
206 | ||
207 | /** |
|
208 | * A set of symbols produced by the Palette script. This is used to provide proper names for |
|
209 | * some of the HTML elements (<select> and <button> elements, etc.). |
|
210 | */ |
|
211 | private Map _symbols; |
|
212 | ||
213 | /** @since 3.0 * */ |
|
214 | public abstract void setAvailableColumn(PaletteColumn column); |
|
215 | ||
216 | /** @since 3.0 * */ |
|
217 | public abstract void setSelectedColumn(PaletteColumn column); |
|
218 | ||
219 | public abstract void setName(String name); |
|
220 | ||
221 | public abstract void setForm(IForm form); |
|
222 | ||
223 | /** @since 4.0 */ |
|
224 | public abstract void setRequiredMessage(String message); |
|
225 | ||
226 | protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) |
|
227 | { |
|
228 | // Next few lines of code is similar to AbstractFormComponent (which, alas, extends from |
|
229 | // AbstractComponent, not from BaseComponent). |
|
230 | 0 | IForm form = TapestryUtils.getForm(cycle, this); |
231 | ||
232 | 0 | setForm(form); |
233 | ||
234 | 0 | if (form.wasPrerendered(writer, this)) |
235 | 0 | return; |
236 | ||
237 | 0 | IValidationDelegate delegate = form.getDelegate(); |
238 | ||
239 | 0 | delegate.setFormComponent(this); |
240 | ||
241 | 0 | form.getElementId(this); |
242 | ||
243 | 0 | if (form.isRewinding()) |
244 | { |
|
245 | 0 | if (!isDisabled()) |
246 | { |
|
247 | 0 | rewindFormComponent(writer, cycle); |
248 | } |
|
249 | } |
|
250 | 0 | else if (!cycle.isRewinding()) |
251 | { |
|
252 | 0 | if (!isDisabled()) |
253 | 0 | delegate.registerForFocus(this, ValidationConstants.NORMAL_FIELD); |
254 | ||
255 | 0 | renderFormComponent(writer, cycle); |
256 | ||
257 | 0 | if (delegate.isInError()) |
258 | 0 | delegate.registerForFocus(this, ValidationConstants.ERROR_FIELD); |
259 | } |
|
260 | ||
261 | 0 | super.renderComponent(writer, cycle); |
262 | 0 | } |
263 | ||
264 | protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle) |
|
265 | { |
|
266 | 0 | _symbols = new HashMap(MAP_SIZE); |
267 | ||
268 | 0 | runScript(cycle); |
269 | ||
270 | 0 | constructColumns(); |
271 | ||
272 | 0 | getValidatableFieldSupport().renderContributions(this, writer, cycle); |
273 | 0 | } |
274 | ||
275 | protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle) |
|
276 | { |
|
277 | 0 | String[] values = cycle.getParameters(getName()); |
278 | ||
279 | 0 | int count = Tapestry.size(values); |
280 | ||
281 | 0 | List selected = new ArrayList(count); |
282 | 0 | IPropertySelectionModel model = getModel(); |
283 | ||
284 | 0 | for (int i = 0; i < count; i++) |
285 | { |
|
286 | 0 | String value = values[i]; |
287 | 0 | Object option = model.translateValue(value); |
288 | ||
289 | 0 | selected.add(option); |
290 | } |
|
291 | ||
292 | 0 | setSelected(selected); |
293 | ||
294 | try |
|
295 | { |
|
296 | 0 | getValidatableFieldSupport().validate(this, writer, cycle, selected); |
297 | } |
|
298 | 0 | catch (ValidatorException e) |
299 | { |
|
300 | 0 | getForm().getDelegate().record(e); |
301 | 0 | } |
302 | 0 | } |
303 | ||
304 | /** |
|
305 | * {@inheritDoc} |
|
306 | */ |
|
307 | public void overrideContributions(Validator validator, FormComponentContributorContext context, |
|
308 | IMarkupWriter writer, IRequestCycle cycle) |
|
309 | { |
|
310 | // we know this has to be a Required validator |
|
311 | 1 | Required required = (Required)validator; |
312 | ||
313 | 1 | JSONObject profile = context.getProfile(); |
314 | ||
315 | 1 | if (!profile.has(ValidationConstants.CONSTRAINTS)) { |
316 | 1 | profile.put(ValidationConstants.CONSTRAINTS, new JSONObject()); |
317 | } |
|
318 | 1 | JSONObject cons = profile.getJSONObject(ValidationConstants.CONSTRAINTS); |
319 | ||
320 | 1 | required.accumulateProperty(cons, getClientId(), |
321 | new JSONLiteral("[tapestry.form.validation.isPalleteSelected]")); |
|
322 | ||
323 | 1 | required.accumulateProfileProperty(this, profile, |
324 | ValidationConstants.CONSTRAINTS, required.buildMessage(context, this)); |
|
325 | 1 | } |
326 | ||
327 | /** |
|
328 | * {@inheritDoc} |
|
329 | */ |
|
330 | public boolean overrideValidator(Validator validator, IRequestCycle cycle) |
|
331 | { |
|
332 | 2 | if (Required.class.isAssignableFrom(validator.getClass())) |
333 | 1 | return true; |
334 | ||
335 | 0 | return false; |
336 | } |
|
337 | ||
338 | protected void cleanupAfterRender(IRequestCycle cycle) |
|
339 | { |
|
340 | 0 | _symbols = null; |
341 | ||
342 | 0 | setAvailableColumn(null); |
343 | 0 | setSelectedColumn(null); |
344 | ||
345 | 0 | super.cleanupAfterRender(cycle); |
346 | 0 | } |
347 | ||
348 | /** |
|
349 | * Executes the associated script, which generates all the JavaScript to support this Palette. |
|
350 | */ |
|
351 | private void runScript(IRequestCycle cycle) |
|
352 | { |
|
353 | 0 | PageRenderSupport pageRenderSupport = TapestryUtils.getPageRenderSupport(cycle, this); |
354 | ||
355 | 0 | setImage(pageRenderSupport, cycle, "selectImage", getSelectImage()); |
356 | 0 | setImage(pageRenderSupport, cycle, "selectDisabledImage", getSelectDisabledImage()); |
357 | 0 | setImage(pageRenderSupport, cycle, "deselectImage", getDeselectImage()); |
358 | 0 | setImage(pageRenderSupport, cycle, "deselectDisabledImage", getDeselectDisabledImage()); |
359 | ||
360 | 0 | if (isSortUser()) |
361 | { |
|
362 | 0 | setImage(pageRenderSupport, cycle, "upImage", getUpImage()); |
363 | 0 | setImage(pageRenderSupport, cycle, "upDisabledImage", getUpDisabledImage()); |
364 | 0 | setImage(pageRenderSupport, cycle, "downImage", getDownImage()); |
365 | 0 | setImage(pageRenderSupport, cycle, "downDisabledImage", getDownDisabledImage()); |
366 | } |
|
367 | ||
368 | 0 | _symbols.put("palette", this); |
369 | ||
370 | 0 | getScript().execute(this, cycle, pageRenderSupport, _symbols); |
371 | 0 | } |
372 | ||
373 | /** |
|
374 | * Extracts its asset URL, sets it up for preloading, and assigns the preload reference as a |
|
375 | * script symbol. |
|
376 | */ |
|
377 | private void setImage(PageRenderSupport pageRenderSupport, IRequestCycle cycle, |
|
378 | String symbolName, IAsset asset) |
|
379 | { |
|
380 | 0 | String url = asset.buildURL(); |
381 | 0 | String reference = pageRenderSupport.getPreloadedImageReference(this, url); |
382 | ||
383 | 0 | _symbols.put(symbolName, reference); |
384 | 0 | } |
385 | ||
386 | public Map getSymbols() |
|
387 | { |
|
388 | 0 | return _symbols; |
389 | } |
|
390 | ||
391 | /** |
|
392 | * Constructs a pair of {@link PaletteColumn}s: the available and selected options. |
|
393 | */ |
|
394 | private void constructColumns() |
|
395 | { |
|
396 | // Build a Set around the list of selected items. |
|
397 | ||
398 | 0 | List selected = getSelected(); |
399 | ||
400 | 0 | if (selected == null) |
401 | 0 | selected = Collections.EMPTY_LIST; |
402 | ||
403 | 0 | String sortMode = getSort(); |
404 | ||
405 | 0 | boolean sortUser = sortMode.equals(SortMode.USER); |
406 | ||
407 | 0 | List selectedOptions = null; |
408 | ||
409 | 0 | if (sortUser) |
410 | { |
|
411 | 0 | int count = selected.size(); |
412 | 0 | selectedOptions = new ArrayList(count); |
413 | ||
414 | 0 | for (int i = 0; i < count; i++) |
415 | 0 | selectedOptions.add(null); |
416 | } |
|
417 | ||
418 | 0 | PaletteColumn availableColumn = new PaletteColumn((String) _symbols.get("availableName"), |
419 | (String)_symbols.get("availableName"), getRows()); |
|
420 | 0 | PaletteColumn selectedColumn = new PaletteColumn(getName(), getClientId(), getRows()); |
421 | ||
422 | // Each value specified in the model will go into either the selected or available |
|
423 | // lists. |
|
424 | ||
425 | 0 | IPropertySelectionModel model = getModel(); |
426 | ||
427 | 0 | int count = model.getOptionCount(); |
428 | ||
429 | 0 | for (int i = 0; i < count; i++) |
430 | { |
|
431 | 0 | Object optionValue = model.getOption(i); |
432 | ||
433 | 0 | PaletteOption o = new PaletteOption(model.getValue(i), model.getLabel(i)); |
434 | ||
435 | 0 | int index = selected.indexOf(optionValue); |
436 | 0 | boolean isSelected = index >= 0; |
437 | ||
438 | 0 | if (sortUser && isSelected) |
439 | { |
|
440 | 0 | selectedOptions.set(index, o); |
441 | 0 | continue; |
442 | } |
|
443 | ||
444 | 0 | PaletteColumn c = isSelected ? selectedColumn : availableColumn; |
445 | ||
446 | 0 | c.addOption(o); |
447 | } |
|
448 | ||
449 | 0 | if (sortUser) |
450 | { |
|
451 | 0 | Iterator i = selectedOptions.iterator(); |
452 | 0 | while (i.hasNext()) |
453 | { |
|
454 | 0 | PaletteOption o = (PaletteOption) i.next(); |
455 | 0 | selectedColumn.addOption(o); |
456 | 0 | } |
457 | } |
|
458 | ||
459 | 0 | if (sortMode.equals(SortMode.VALUE)) |
460 | { |
|
461 | 0 | availableColumn.sortByValue(); |
462 | 0 | selectedColumn.sortByValue(); |
463 | } |
|
464 | 0 | else if (sortMode.equals(SortMode.LABEL)) |
465 | { |
|
466 | 0 | availableColumn.sortByLabel(); |
467 | 0 | selectedColumn.sortByLabel(); |
468 | } |
|
469 | ||
470 | 0 | setAvailableColumn(availableColumn); |
471 | 0 | setSelectedColumn(selectedColumn); |
472 | 0 | } |
473 | ||
474 | public boolean isSortUser() |
|
475 | { |
|
476 | 0 | return getSort().equals(SortMode.USER); |
477 | } |
|
478 | ||
479 | public abstract Block getAvailableTitleBlock(); |
|
480 | ||
481 | public abstract IAsset getDeselectDisabledImage(); |
|
482 | ||
483 | public abstract IAsset getDeselectImage(); |
|
484 | ||
485 | public abstract IAsset getDownDisabledImage(); |
|
486 | ||
487 | public abstract IAsset getDownImage(); |
|
488 | ||
489 | public abstract IAsset getSelectDisabledImage(); |
|
490 | ||
491 | public abstract IPropertySelectionModel getModel(); |
|
492 | ||
493 | public abstract int getRows(); |
|
494 | ||
495 | public abstract Block getSelectedTitleBlock(); |
|
496 | ||
497 | public abstract IAsset getSelectImage(); |
|
498 | ||
499 | public abstract String getSort(); |
|
500 | ||
501 | public abstract IAsset getUpDisabledImage(); |
|
502 | ||
503 | public abstract IAsset getUpImage(); |
|
504 | ||
505 | /** |
|
506 | * Returns false. Palette components are never disabled. |
|
507 | * |
|
508 | * @since 2.2 |
|
509 | */ |
|
510 | public boolean isDisabled() |
|
511 | { |
|
512 | 0 | return false; |
513 | } |
|
514 | ||
515 | /** @since 2.2 * */ |
|
516 | ||
517 | public abstract List getSelected(); |
|
518 | ||
519 | /** @since 2.2 * */ |
|
520 | ||
521 | public abstract void setSelected(List selected); |
|
522 | ||
523 | /** |
|
524 | * Injected. |
|
525 | * |
|
526 | * @since 4.0 |
|
527 | */ |
|
528 | public abstract IScript getScript(); |
|
529 | ||
530 | /** |
|
531 | * Injected. |
|
532 | * |
|
533 | * @since 4.0 |
|
534 | */ |
|
535 | public abstract ValidatableFieldSupport getValidatableFieldSupport(); |
|
536 | ||
537 | /** |
|
538 | * @see org.apache.tapestry.form.AbstractFormComponent#isRequired() |
|
539 | */ |
|
540 | public boolean isRequired() |
|
541 | { |
|
542 | 0 | return getValidatableFieldSupport().isRequired(this); |
543 | } |
|
544 | } |