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