Coverage Report - org.apache.tapestry.contrib.palette.Palette
 
Classes in this File Line Coverage Branch Coverage Complexity
Palette
10% 
10% 
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>&nbsp;</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>&nbsp;</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 &lt;select&gt; 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 &lt;th&gt; reserved for the
 122  
  * title above the selected items &lt;select&gt; (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>&nbsp;</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 (&lt;select&gt; and &lt;button&gt; 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  
 }