0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package com.sun.rave.web.ui.component;
0042:
0043: import java.beans.Beans;
0044: import java.text.Collator;
0045: import java.text.MessageFormat;
0046: import java.util.Comparator;
0047: import java.util.Iterator;
0048: import java.util.Locale;
0049: import java.util.Properties;
0050: import java.util.TreeMap;
0051: import java.util.Map;
0052: import java.util.HashMap;
0053: import javax.faces.FacesException;
0054: import javax.faces.application.FacesMessage;
0055: import javax.faces.component.UIInput;
0056: import javax.faces.component.UIComponent;
0057: import javax.faces.component.ValueHolder;
0058: import javax.faces.context.FacesContext;
0059: import javax.faces.convert.ConverterException;
0060: import javax.faces.el.MethodBinding;
0061: import javax.faces.el.MethodNotFoundException;
0062: import javax.faces.el.EvaluationException;
0063: import com.sun.rave.web.ui.component.Button;
0064: import com.sun.rave.web.ui.component.TextField;
0065: import com.sun.rave.web.ui.component.Label;
0066: import com.sun.rave.web.ui.component.StaticText;
0067: import com.sun.rave.web.ui.model.Option;
0068: import com.sun.rave.web.ui.model.OptionGroup;
0069: import com.sun.rave.web.ui.model.Separator;
0070: import com.sun.rave.web.ui.model.list.ListItem;
0071: import com.sun.rave.web.ui.theme.Theme;
0072: import com.sun.rave.web.ui.theme.ThemeStyles;
0073: import com.sun.rave.web.ui.util.MessageUtil;
0074: import com.sun.rave.web.ui.util.ThemeUtilities;
0075: import com.sun.rave.web.ui.validator.StringLengthValidator;
0076:
0077: /**
0078: * Use the AddRemove component when the web application user makes selections from a list and they need to see the currently selected items displayed together, and/or they need to reorder the selected items.
0079: * @author avk
0080: */
0081:
0082: public class AddRemove extends AddRemoveBase implements ListManager {
0083:
0084: /**
0085: * The component id for the ADD button
0086: */
0087: public static final String ADD_BUTTON_ID = "_addButton"; //NOI18N
0088: /**
0089: * The facet name of the add button
0090: */
0091: public static final String ADD_BUTTON_FACET = "addButton"; //NOI18N
0092:
0093: /**
0094: * The component id for the ADD ALL button
0095: */
0096: public static final String ADDALL_BUTTON_ID = "_addAllButton"; //NOI18N
0097: /**
0098: * The facet name of the Add All button
0099: */
0100: public static final String ADDALL_BUTTON_FACET = "addAllButton"; //NOI18N
0101:
0102: /**
0103: * The component ID for the remove button
0104: */
0105: public static final String REMOVE_BUTTON_ID = "_removeButton"; //NOI18N
0106: /**
0107: * The facet name of the remove button
0108: */
0109: public static final String REMOVE_BUTTON_FACET = "removeButton"; //NOI18N
0110:
0111: /**
0112: * The component ID for the remove all button
0113: */
0114: public static final String REMOVEALL_BUTTON_ID = "_removeAllButton"; //NOI18N
0115: /**
0116: * The facet name of the "Remove All" button
0117: */
0118: public static final String REMOVEALL_BUTTON_FACET = "removeAllButton"; //NOI18N
0119:
0120: /**
0121: * The component ID for the move up button
0122: */
0123: public static final String MOVEUP_BUTTON_ID = "_moveUpButton"; //NOI18N
0124: /**
0125: * The facet name of the "Move Up" button
0126: */
0127: public static final String MOVEUP_BUTTON_FACET = "moveUpButton"; //NOI18N
0128:
0129: /**
0130: * The component ID for the move down button
0131: */
0132: public static final String MOVEDOWN_BUTTON_ID = "_moveDownButton"; //NOI18N
0133: /**
0134: * The facet name of the "Move Down" button
0135: */
0136: public static final String MOVEDOWN_BUTTON_FACET = "moveDownButton"; //NOI18N
0137:
0138: /**
0139: * The component ID for the items list
0140: */
0141: public static final String AVAILABLE_LABEL_ID = "_availableLabel"; //NOI18N
0142: /**
0143: * The facet name of the label over the "Available" list
0144: */
0145: public static final String AVAILABLE_LABEL_FACET = "availableLabel"; //NOI18N
0146:
0147: /**
0148: * The component ID for the selected list
0149: */
0150: public static final String SELECTED_LABEL_ID = "_selectedLabel"; //NOI18N
0151: /**
0152: * The facet name of the label over the "Selected" list
0153: */
0154: public static final String SELECTED_LABEL_FACET = "selectedLabel"; //NOI18N
0155:
0156: /**
0157: * Facet name for the header facet
0158: */
0159: public static final String HEADER_FACET = "header"; //NOI18N
0160: /**
0161: * The facet name of the header (component label)
0162: */
0163: public static final String HEADER_ID = "_header"; //NOI18N
0164:
0165: /**
0166: * Facet name for the footer facet
0167: */
0168: public static final String FOOTER_FACET = "footer"; //NOI18N
0169:
0170: /**
0171: * The id of the label component that functions as the label above the available list
0172: */
0173: public static final String AVAILABLE_ID = "_available"; //NOI18N
0174: /**
0175: * The ID of the component that functions as the label above the "Selected" list
0176: */
0177: public static final String SELECTED_ID = "_selected"; //NOI18N
0178:
0179: /**
0180: * The name of the component attribute that stores the name of the JavaScript object that organizes the JavsSCript functions for the component
0181: */
0182: public static final String JSOBJECT = "com.sun.rave.web.ui.AddRemoveJS";
0183: /**
0184: * Represents the "javascript:" printed at the start of javascript event handler code
0185: */
0186: public static final String JAVASCRIPT_PREFIX = "javascript: ";
0187: /**
0188: * String representing "return false" printed at the end of the javascript event handlers
0189: */
0190: public static final String RETURN = "return false;";
0191: /**
0192: * Name of the JavaScript function which is responsible for adding elements from the availble list to the selected list
0193: */
0194: public static final String ADD_FUNCTION = ".add(); ";
0195: /**
0196: * Name of the JavaScript function which is responsible for selecting
0197: * all the available items
0198: */
0199: public static final String ADDALL_FUNCTION = ".addAll();";
0200: /**
0201: * Name of the JavaScript function which removes items from the seleted list
0202: */
0203: public static final String REMOVE_FUNCTION = ".remove(); ";
0204: /**
0205: * Name of the JavaScript function which removes all the items from the seleted list
0206: */
0207: public static final String REMOVEALL_FUNCTION = ".removeAll(); ";
0208: /**
0209: * Name of the JavaScript function which moves elements up
0210: */
0211: public static final String MOVEUP_FUNCTION = ".moveUp(); ";
0212: /**
0213: * Name of the JavaScript function which moves elements down
0214: */
0215: public static final String MOVEDOWN_FUNCTION = ".moveDown();";
0216: /**
0217: * Name of the JavaScript function that updates the buttons
0218: */
0219: public static final String UPDATEBUTTONS_FUNCTION = ".updateButtons(); ";
0220: /**
0221: * Name of the JavaScript function that handles changes on the available list
0222: */
0223: public static final String AVAILABLE_ONCHANGE_FUNCTION = ".availableOnChange(); ";
0224: /**
0225: * Name of the JavaScript function which handles changes to the selected list
0226: */
0227: public static final String SELECTED_ONCHANGE_FUNCTION = ".selectedOnChange(); ";
0228: /**
0229: * The name of the JavaScript function used to hook up the correct
0230: * add and remove functions when the component allows items to be
0231: * added to the selected items list more than once
0232: */
0233: public static final String MULTIPLEADDITIONS_FUNCTION = ".allowMultipleAdditions()";
0234:
0235: public static final String SPACER_STRING = "_"; //NOI18N
0236:
0237: /**
0238: * The string used as a separator between the selected values
0239: */
0240: public static final String SEPARATOR_VALUE = "com.sun.rave.web.ui.separator";
0241:
0242: private TreeMap availableItems = null;
0243: private TreeMap selectedItems = null;
0244: private Collator collator = null;
0245:
0246: private String allValues = "";
0247: private String selectedValues = "";
0248:
0249: private static final boolean DEBUG = false;
0250:
0251: /**
0252: * Constructor for the AddRemove component
0253: */
0254: public AddRemove() {
0255: setMultiple(true);
0256: }
0257:
0258: /**
0259: * Get the number of rows to disaplay (the default is 12)
0260: * @return the number of rows to disaplay
0261: */
0262: public int getRows() {
0263:
0264: int rows = super .getRows();
0265: if (rows < 1) {
0266: rows = 12;
0267: super .setRows(rows);
0268: }
0269: return rows;
0270: }
0271:
0272: /**
0273: * Get the separator string that is used to separate the selected values on the client.
0274: * The default value is "|". When the AddRemove component is decoded, the
0275: * value is taken from a hidden variable whose value is a list of the
0276: * values of all the options in the list representing the selected items.
0277: * Consider a case where the AddRemove has a list of options including
0278: * <option value="1">One</option>
0279: * <option value="2">Two</option>
0280: * Assume that these two options are disabled. If the separator
0281: * string is set to "|", then the value of the hidden
0282: * variable will be |1|2|.
0283: *
0284: * You will only need to set this variable if the string
0285: * representation of one of the option values contain the
0286: * character "|". If you do need to change from the default,
0287: * bear in mind that the value of the hidden component
0288: * is sent as part of the body of the HTTP request body.
0289: * Make sure to select a character that does not change
0290: * the syntax of the request.
0291: * @return The separator string.
0292: */
0293: public String getSeparator() {
0294: return "|";
0295: }
0296:
0297: /**
0298: * Returns an iterator over the selected items
0299: * @return an iterator over the selected items
0300: */
0301: public Iterator getSelectedItems() {
0302: return selectedItems.values().iterator();
0303: }
0304:
0305: /**
0306: * This function returns a String consisting of the String representation of the
0307: * values of all the available Options, separated by the separator
0308: * String (see getSeparator())
0309: * @return eturns a String consisting of the String representation of the
0310: * values of all the available Options, separated by the separator
0311: * String
0312: */
0313: public String getAllValues() {
0314: return allValues;
0315: }
0316:
0317: /**
0318: * This function returns a String consisting of the String representation of the
0319: * values of the selected Options, separated by the separator
0320: * String
0321: * @return a String consisting of the String representation of the
0322: * values of the selected Options, separated by the separator
0323: * String
0324: */
0325: public String getSelectedValues() {
0326: return selectedValues;
0327: }
0328:
0329: // Buttons
0330: /**
0331: * Get or create the ADD button. Retrieves the component specified by the
0332: * addButton facet (if there is one) or creates a new Button component.
0333: * @return A UI Component for the Add button
0334: * @param context The FacesContext for the request
0335: */
0336: public UIComponent getAddButtonComponent(FacesContext context) {
0337:
0338: if (DEBUG)
0339: log("getAddButtonComponent()");
0340:
0341: String id = getId();
0342:
0343: // Check if the page author has defined an addbutton facet
0344: UIComponent buttonComponent = getFacet(ADD_BUTTON_FACET);
0345: // If the page author has not defined a button facet,
0346: // check if the page author specified a button.
0347: if (buttonComponent == null) {
0348:
0349: String buttonLabel = getTheme().getMessage("AddRemove.add");
0350: if (!isVertical()) {
0351: buttonLabel = buttonLabel.concat(" > "); //NOI18N
0352: }
0353:
0354: buttonComponent = createButton(buttonLabel, ADD_BUTTON_ID,
0355: ADD_BUTTON_FACET);
0356: ((Button) buttonComponent).setPrimary(true);
0357: StringBuffer jsBuffer = new StringBuffer(200);
0358: jsBuffer.append(getAttributes().get(JSOBJECT));
0359: jsBuffer.append(ADD_FUNCTION);
0360: jsBuffer.append(RETURN);
0361: ((Button) buttonComponent).setOnClick(jsBuffer.toString());
0362: } else if (DEBUG) {
0363: log("\tFound facet."); //NOI18N
0364: }
0365: if (buttonComponent != null && isDisabled()) {
0366: buttonComponent.getAttributes().put("disabled",
0367: Boolean.TRUE); //NOI18N
0368: }
0369: return buttonComponent;
0370: }
0371:
0372: /**
0373: * Get or create the ADD button. Retrieves the component specified by the
0374: * addButton facet (if there is one) or creates a new Button component.
0375: * @return A UI Component for the Add button
0376: */
0377: public UIComponent getAddAllButtonComponent() {
0378:
0379: if (DEBUG)
0380: log("getAddAllButtonComponent()");
0381:
0382: String id = getId();
0383:
0384: // Check if the page author has defined an addbutton facet
0385: UIComponent buttonComponent = getFacet(ADDALL_BUTTON_FACET);
0386: // If the page author has not defined a button facet,
0387: // check if the page author specified a button.
0388: if (buttonComponent == null) {
0389:
0390: String buttonLabel = getTheme().getMessage(
0391: "AddRemove.addAll");
0392: if (!isVertical()) {
0393: buttonLabel = buttonLabel.concat(" >> "); //NOI18N
0394: }
0395:
0396: buttonComponent = createButton(buttonLabel,
0397: ADDALL_BUTTON_ID, ADDALL_BUTTON_FACET);
0398: ((Button) buttonComponent).setPrimary(false);
0399: StringBuffer jsBuffer = new StringBuffer(200);
0400: jsBuffer.append(JAVASCRIPT_PREFIX);
0401: jsBuffer.append(getAttributes().get(JSOBJECT));
0402: jsBuffer.append(ADDALL_FUNCTION);
0403: jsBuffer.append(RETURN);
0404: ((Button) buttonComponent).setOnClick(jsBuffer.toString());
0405: } else if (DEBUG) {
0406: log("\tFound facet."); //NOI18N
0407: }
0408: if (buttonComponent != null && isDisabled()) {
0409: buttonComponent.getAttributes().put("disabled",
0410: Boolean.TRUE); //NOI18N
0411: }
0412: return buttonComponent;
0413: }
0414:
0415: /**
0416: * Get or create the REMOVE button. Retrieves the component specified by the
0417: * removeButton facet (if there is one) or creates a new Button component.
0418: * @return A UI Component for the Remove button
0419: */
0420: public UIComponent getRemoveButtonComponent() {
0421:
0422: if (DEBUG)
0423: log("getRemoveButtonComponent()");
0424:
0425: String id = getId();
0426:
0427: // Check if the page author has defined an removebutton facet
0428: UIComponent buttonComponent = getFacet(REMOVE_BUTTON_FACET);
0429: // If the page author has not defined a button facet,
0430: // check if the page author specified a button.
0431: if (buttonComponent == null) {
0432:
0433: String buttonLabel = getTheme().getMessage(
0434: "AddRemove.remove");
0435: if (!isVertical()) {
0436: buttonLabel = " < ".concat(buttonLabel); //NOI18N
0437: }
0438: buttonComponent = createButton(buttonLabel,
0439: REMOVE_BUTTON_ID, REMOVE_BUTTON_FACET);
0440: ((Button) buttonComponent).setPrimary(true);
0441: StringBuffer jsBuffer = new StringBuffer(200);
0442: jsBuffer.append(JAVASCRIPT_PREFIX);
0443: jsBuffer.append(getAttributes().get(JSOBJECT));
0444: jsBuffer.append(REMOVE_FUNCTION);
0445: jsBuffer.append(RETURN);
0446: ((Button) buttonComponent).setOnClick(jsBuffer.toString());
0447: } else if (DEBUG) {
0448: log("\tFound facet."); //NOI18N
0449: }
0450: if (buttonComponent != null && isDisabled()) {
0451: buttonComponent.getAttributes().put("disabled",
0452: Boolean.TRUE); //NOI18N
0453: }
0454: return buttonComponent;
0455: }
0456:
0457: /**
0458: * Get or create the REMOVE button. Retrieves the component specified by the
0459: * removeButton facet (if there is one) or creates a new Button component.
0460: * @return A UI Component for the Remove button
0461: */
0462: public UIComponent getRemoveAllButtonComponent() {
0463:
0464: if (DEBUG)
0465: log("getRemoveAllButtonComponent()");
0466:
0467: String id = getId();
0468:
0469: // Check if the page author has defined an removebutton facet
0470: UIComponent buttonComponent = getFacet(REMOVEALL_BUTTON_FACET);
0471: // If the page author has not defined a button facet,
0472: // check if the page author specified a button.
0473: if (buttonComponent == null) {
0474:
0475: String buttonLabel = getTheme().getMessage(
0476: "AddRemove.removeAll");
0477: if (!isVertical()) {
0478: buttonLabel = " << ".concat(buttonLabel); //NOI18N
0479: }
0480:
0481: buttonComponent = createButton(buttonLabel,
0482: REMOVEALL_BUTTON_ID, REMOVEALL_BUTTON_FACET);
0483: ((Button) buttonComponent).setPrimary(false);
0484: StringBuffer jsBuffer = new StringBuffer(200);
0485: jsBuffer.append(JAVASCRIPT_PREFIX);
0486: jsBuffer.append(getAttributes().get(JSOBJECT));
0487: jsBuffer.append(REMOVEALL_FUNCTION);
0488: jsBuffer.append(RETURN);
0489: ((Button) buttonComponent).setOnClick(jsBuffer.toString());
0490: } else if (DEBUG) {
0491: log("\tFound facet."); //NOI18N
0492: }
0493: if (buttonComponent != null && isDisabled()) {
0494: buttonComponent.getAttributes().put("disabled",
0495: Boolean.TRUE); //NOI18N
0496: }
0497: return buttonComponent;
0498: }
0499:
0500: /**
0501: * Get or create the MOVEUP button. Retrieves the component specified by the
0502: * moveUpButton facet (if there is one) or creates a new Button component.
0503: * @return A UI Component for the MoveUp button
0504: */
0505: public UIComponent getMoveUpButtonComponent() {
0506:
0507: if (DEBUG)
0508: log("getMoveUpButtonComponent()");
0509:
0510: String id = getId();
0511:
0512: // Check if the page author has defined an moveUpbutton facet
0513: UIComponent buttonComponent = getFacet(MOVEUP_BUTTON_FACET);
0514: // If the page author has not defined a button facet,
0515: // check if the page author specified a button.
0516: if (buttonComponent == null) {
0517:
0518: buttonComponent = createButton(getTheme().getMessage(
0519: "AddRemove.moveUp"), //NOI18N
0520: MOVEUP_BUTTON_ID, MOVEUP_BUTTON_FACET);
0521: ((Button) buttonComponent).setPrimary(false);
0522: StringBuffer jsBuffer = new StringBuffer(200);
0523: jsBuffer.append(JAVASCRIPT_PREFIX);
0524: jsBuffer.append(getAttributes().get(JSOBJECT));
0525: jsBuffer.append(MOVEUP_FUNCTION);
0526: jsBuffer.append(RETURN);
0527: ((Button) buttonComponent).setOnClick(jsBuffer.toString());
0528: } else if (DEBUG) {
0529: log("\tFound facet."); //NOI18N
0530: }
0531: if (buttonComponent != null && isDisabled()) {
0532: buttonComponent.getAttributes().put("disabled",
0533: Boolean.TRUE); //NOI18N
0534: }
0535: return buttonComponent;
0536: }
0537:
0538: /**
0539: * Get or create the MOVEDOWN button. Retrieves the component specified by the
0540: * moveDownButton facet (if there is one) or creates a new Button component.
0541: * @return A UI Component for the MoveDown button
0542: */
0543: public UIComponent getMoveDownButtonComponent() {
0544:
0545: if (DEBUG)
0546: log("getMoveDownButtonComponent()");
0547:
0548: String id = getId();
0549:
0550: // Check if the page author has defined an moveDownbutton facet
0551: UIComponent buttonComponent = getFacet(MOVEDOWN_BUTTON_FACET);
0552: // If the page author has not defined a button facet,
0553: // check if the page author specified a button.
0554: if (buttonComponent == null) {
0555:
0556: buttonComponent = createButton(getTheme().getMessage(
0557: "AddRemove.moveDown"), //NOI18N
0558: MOVEDOWN_BUTTON_ID, MOVEDOWN_BUTTON_FACET);
0559: ((Button) buttonComponent).setPrimary(false);
0560: StringBuffer jsBuffer = new StringBuffer(200);
0561: jsBuffer.append(JAVASCRIPT_PREFIX);
0562: jsBuffer.append(getAttributes().get(JSOBJECT));
0563: jsBuffer.append(MOVEDOWN_FUNCTION);
0564: jsBuffer.append(RETURN);
0565: ((Button) buttonComponent).setOnClick(jsBuffer.toString());
0566: } else if (DEBUG) {
0567: log("\tFound facet."); //NOI18N
0568: }
0569: if (buttonComponent != null && isDisabled()) {
0570: buttonComponent.getAttributes().put("disabled",
0571: Boolean.TRUE); //NOI18N
0572: }
0573: return buttonComponent;
0574: }
0575:
0576: // Labels
0577: /**
0578: * Gets or creates a component for the "available" list
0579: * label. Retrieves the availableLabel facet if one was
0580: * specified, or creates a new label component.
0581: * @return A UIComponent for the list label
0582: */
0583: public UIComponent getAvailableLabelComponent() {
0584:
0585: if (DEBUG)
0586: log("getAvailableLabelComponent()");
0587:
0588: String id = getId();
0589:
0590: // Check if the page author has defined a label facet
0591: UIComponent label = null;
0592: //mbohm (6452122): check for !Beans.isDesignTime
0593: if (!Beans.isDesignTime()) {
0594: label = getFacet(AVAILABLE_LABEL_FACET);
0595: }
0596:
0597: // If the page author has not defined a label facet,
0598: // check if the page author specified a label.
0599: if (label == null) {
0600:
0601: String labelString = getAvailableItemsLabel();
0602: if (labelString == null || labelString.length() == 0) {
0603: labelString = getTheme().getMessage(
0604: "AddRemove.available"); //NOI18N
0605: }
0606:
0607: String styleClass = null;
0608: int labelLevel = 2;
0609: if (getLabel() == null || getLabel().length() == 0) {
0610: styleClass = getTheme().getStyleClass(
0611: ThemeStyles.ADDREMOVE_LABEL);
0612: } else {
0613: styleClass = getTheme().getStyleClass(
0614: ThemeStyles.ADDREMOVE_LABEL2);
0615: labelLevel = 3;
0616: }
0617: // TODO - what should we show here?
0618: String forID = getClientId(
0619: FacesContext.getCurrentInstance()).concat(
0620: AVAILABLE_ID);
0621: label = createLabel(labelString, styleClass, forID,
0622: labelLevel, AVAILABLE_LABEL_ID,
0623: AVAILABLE_LABEL_FACET);
0624: } else if (DEBUG) {
0625: log("\tFound facet."); //NOI18N
0626: }
0627:
0628: return label;
0629: }
0630:
0631: /**
0632: * Gets or creates a component for the "selected" list
0633: * label. Retrieves the selectedLabel facet if one was
0634: * specified, or creates a new label component.
0635: * @return A UIComponent for the list label
0636: */
0637: public UIComponent getSelectedLabelComponent() {
0638:
0639: if (DEBUG)
0640: log("getSelectedLabelComponent()");
0641:
0642: String id = getId();
0643:
0644: // Check if the page author has defined a label facet
0645: UIComponent label = null;
0646: //mbohm (6452122): check for !Beans.isDesignTime
0647: if (!Beans.isDesignTime()) {
0648: label = getFacet(SELECTED_LABEL_FACET);
0649: }
0650:
0651: // If the page author has not defined a label facet,
0652: // check if the page author specified a label.
0653: if (label == null) {
0654:
0655: String labelString = getSelectedItemsLabel();
0656: if (labelString == null || labelString.length() == 0) {
0657: labelString = getTheme().getMessage(
0658: "AddRemove.selected"); //NOI18N
0659: }
0660:
0661: String styleClass = null;
0662: int labelLevel = 2;
0663: if (getLabel() == null || getLabel().length() == 0) {
0664: styleClass = getTheme().getStyleClass(
0665: ThemeStyles.ADDREMOVE_LABEL);
0666: } else {
0667: styleClass = getTheme().getStyleClass(
0668: ThemeStyles.ADDREMOVE_LABEL2);
0669: labelLevel = 3;
0670: }
0671: // TODO - what should we show here?
0672: String forID = getClientId(
0673: FacesContext.getCurrentInstance()).concat(
0674: SELECTED_ID);
0675: label = createLabel(labelString, styleClass, forID,
0676: labelLevel, SELECTED_LABEL_ID, SELECTED_LABEL_FACET);
0677: } else if (DEBUG) {
0678: log("\tFound facet."); //NOI18N
0679: }
0680:
0681: return label;
0682: }
0683:
0684: /**
0685: * Gets or creates a component for the "component" list
0686: * label. Retrieves the componentLabel facet if one was
0687: * specified, or creates a new label component.
0688: * @return A UIComponent for the list label
0689: */
0690: public UIComponent getHeaderComponent() {
0691:
0692: if (DEBUG)
0693: log("getHeaderComponent()");
0694:
0695: String id = getId();
0696:
0697: // Check if the page author has defined a label facet
0698: UIComponent label = null;
0699: //mbohm (6452122): check for !Beans.isDesignTime
0700: if (!Beans.isDesignTime()) {
0701: label = getFacet(HEADER_FACET);
0702: }
0703:
0704: // If the page author has not defined a label facet,
0705: // check if the page author specified a label.
0706: if (label == null) {
0707:
0708: String labelString = getLabel();
0709: if (labelString == null || labelString.length() == 0) {
0710: return null;
0711: }
0712:
0713: String styleClass = getTheme().getStyleClass(
0714: ThemeStyles.ADDREMOVE_LABEL);
0715: label = createLabel(labelString, styleClass, null, 2,
0716: HEADER_ID, HEADER_FACET);
0717: ((Label) label).setLabeledComponent(this );
0718: } else if (DEBUG) {
0719: log("\tFound facet."); //NOI18N
0720: }
0721:
0722: return label;
0723: }
0724:
0725: public String getPrimaryElementID(FacesContext context) {
0726: return this .getClientId(context).concat(AVAILABLE_ID);
0727: }
0728:
0729: private Label createLabel(String labelString, String styleClass,
0730: String forID, int labelLevel, String id, String facetName) {
0731:
0732: if (DEBUG)
0733: log("createLabel()");
0734:
0735: // If we find a label, define a component and add it to the
0736: // children, unless it has been added in a previous cycle
0737: // (the component is being redisplayed).
0738:
0739: if (labelString == null || labelString.length() < 1) {
0740: // TODO - maybe print a default?
0741: labelString = new String();
0742: }
0743:
0744: Label label = new Label();
0745: label.setLabelLevel(labelLevel);
0746: label.setId(getId().concat(id));
0747: label.setText(labelString);
0748: label.setStyleClass(styleClass);
0749: if (forID != null) {
0750: label.setFor(forID);
0751: }
0752:
0753: //mbohm (6452122): always put in facet, since jsf1.2 is not forgiving of "parentless" Label
0754: this .getFacets().put(facetName, label);
0755:
0756: return label;
0757: }
0758:
0759: private Button createButton(String text, String id, String facetName) {
0760:
0761: if (DEBUG)
0762: log("createButton()");
0763:
0764: Button button = new Button();
0765: button.setId(getId().concat(id));
0766: //button.setImmediate(true);
0767: button.setText(text);
0768: if (getTabIndex() > 0) {
0769: button.setTabIndex(getTabIndex());
0770: }
0771: button.setPrimary(true);
0772: // <RAVE>
0773: // getFacets().put(facetName, button);
0774: if (!Beans.isDesignTime())
0775: getFacets().put(facetName, button);
0776: // </RAVE>
0777: return button;
0778: }
0779:
0780: private StaticText createText(String text, String id,
0781: String facetName) {
0782:
0783: if (DEBUG)
0784: log("createText()");
0785:
0786: // If we find a label, define a component and add it to the
0787: // children, unless it has been added in a previous cycle
0788: // (the component is being redisplayed).
0789:
0790: if (text == null || text.length() < 1) {
0791: // TODO - maybe print a default?
0792: text = new String();
0793: }
0794:
0795: StaticText field = new StaticText();
0796: field.setValue(text);
0797: field.setId(getId().concat(id));
0798: // <RAVE>
0799: // getFacets().put(facetName, field);
0800: if (!Beans.isDesignTime())
0801: getFacets().put(facetName, field);
0802: // </RAVE>
0803: return field;
0804: }
0805:
0806: private Theme getTheme() {
0807: return ThemeUtilities.getTheme(FacesContext
0808: .getCurrentInstance());
0809: }
0810:
0811: /**
0812: * Retrieve an Iterator of ListSelector.ListItem representing the available selections only.
0813: * This method is used by the renderer, to create the options of
0814: * the list of available items.
0815: * @return an Iterator over {@link ListItem}.
0816: * @param context The FacesContext used for the request
0817: * @param rulerAtEnd If true, a disabled list item with a blank label is appended at
0818: * the end of the options. The role of the blank
0819: * item is to guarantee that the width of the lists
0820: * do not change when items are moved from one to the
0821: * other.
0822: * @throws javax.faces.FacesException If something goes wrong when the options are processed
0823: */
0824: public Iterator getListItems(FacesContext context,
0825: boolean rulerAtEnd) throws FacesException {
0826:
0827: if (DEBUG)
0828: log("getListItems()");
0829:
0830: Locale locale = context.getViewRoot().getLocale();
0831: if (DEBUG)
0832: log("\tLocale is " + locale.toString());
0833: collator = Collator.getInstance(locale);
0834: collator.setStrength(Collator.IDENTICAL);
0835:
0836: availableItems = new TreeMap(collator);
0837: selectedItems = new TreeMap(collator);
0838:
0839: // Retrieve the current selections. If there are selected
0840: // objects, mark the corresponding items as selected.
0841: processOptions(context, collator, locale, rulerAtEnd);
0842:
0843: // We construct a string representation of all values (whether
0844: // they are selected or not) before we remove the selected
0845: // items in the processSelections step
0846: allValues = constructValueString(availableItems);
0847:
0848: processSelections();
0849:
0850: // We construct a string representation of the selected values
0851: // only
0852: selectedValues = constructValueString(selectedItems,
0853: SEPARATOR_VALUE);
0854:
0855: return availableItems.values().iterator();
0856: }
0857:
0858: /**
0859: * Evaluates the list of available Options, creating a ListItem for each
0860: * one.
0861: * @param context The FacesContext
0862: * @param rulerAtEnd the end of the options. The role of the blank
0863: * item is to guarantee that the width of the lists
0864: * do not change when items are moved from one to the
0865: * other.
0866: */
0867: protected void processOptions(FacesContext context,
0868: Collator collator, Locale locale, boolean rulerAtEnd) {
0869:
0870: if (DEBUG)
0871: log("processOptions()");
0872:
0873: Option[] options = getOptions();
0874: int length = options.length;
0875:
0876: ListItem listItem = null;
0877: String label = null;
0878: String lastKey = "";
0879: String longestString = "";
0880: StringBuffer unsortedKeyBuffer = new StringBuffer(100);
0881:
0882: for (int counter = 0; counter < length; ++counter) {
0883:
0884: if (options[counter] instanceof OptionGroup) {
0885: String msg = MessageUtil.getMessage(
0886: "com.sun.rave.web.ui.resources.LogMessages", //NOI18N
0887: "AddRemove.noGrouping"); //NOI18N
0888: log(msg);
0889: continue;
0890: }
0891: if (options[counter] instanceof Separator) {
0892: String msg = MessageUtil.getMessage(
0893: "com.sun.rave.web.ui.resources.LogMessages", //NOI18N
0894: "AddRemove.noGrouping"); //NOI18N
0895: log(msg);
0896: continue;
0897: }
0898: // Convert the option to a list item
0899: listItem = createListItem(options[counter]);
0900:
0901: label = listItem.getLabel();
0902: if (label.length() > longestString.length()) {
0903: longestString = label;
0904: }
0905:
0906: if (isSorted()) {
0907:
0908: availableItems.put(label, listItem);
0909: if (collator.compare(label, lastKey) > 0) {
0910: lastKey = label;
0911: }
0912: } else {
0913:
0914: // If the page author does not want the list items to be
0915: // sorted (alphabetically by locale), then they're
0916: // supposed to be sorted by the order they were added.
0917: // Maps are not guaranteed to return items in the order
0918: // they were added, so we have to create this order
0919: // artificially. We do that by creating a successively
0920: // longer key for each element. (a, aa, aaa...).
0921: unsortedKeyBuffer.append("a"); //NOI18N
0922: availableItems.put(unsortedKeyBuffer.toString(),
0923: listItem);
0924: lastKey = unsortedKeyBuffer.toString();
0925: }
0926: }
0927:
0928: if (rulerAtEnd) {
0929:
0930: int seplength = longestString.length() + 5;
0931: StringBuffer labelBuffer = new StringBuffer(seplength);
0932:
0933: for (int counter = 0; counter < seplength; ++counter) {
0934: labelBuffer.append(SPACER_STRING);
0935: }
0936: ListItem item = new ListItem(labelBuffer.toString());
0937: item.setDisabled(true);
0938: item.setValue(SEPARATOR_VALUE);
0939: if (isSorted()) {
0940: lastKey = lastKey.concat("a"); //NOI18N
0941: availableItems.put(lastKey, item); //NOI18N
0942: lastKey = lastKey.concat("a"); //NOI18N
0943: selectedItems.put(lastKey, item); //NOI18N
0944: } else {
0945: unsortedKeyBuffer.append("a"); //NOI18N
0946: availableItems.put(unsortedKeyBuffer.toString(), item);
0947: unsortedKeyBuffer.append("a"); //NOI18N
0948: selectedItems.put(unsortedKeyBuffer.toString(), item);
0949: }
0950: }
0951:
0952: if (DEBUG) {
0953: log("AvailableItems keys");
0954: Iterator iterator = availableItems.keySet().iterator();
0955: while (iterator.hasNext()) {
0956: log("next key " + iterator.next().toString());
0957: }
0958: }
0959: }
0960:
0961: private String constructValueString(TreeMap map) {
0962: return constructValueString(map, null);
0963: }
0964:
0965: private String constructValueString(TreeMap map, String filter) {
0966:
0967: // Set up the "All values" string. This is rendered as a
0968: // hidden input on the client side, and is used to
0969: StringBuffer valuesBuffer = new StringBuffer(392);
0970: Iterator values = map.values().iterator();
0971: ListItem listItem = null;
0972: String separator = getSeparator();
0973: valuesBuffer.append(separator);
0974: while (values.hasNext()) {
0975: listItem = (ListItem) (values.next());
0976: if (filter != null && listItem.getValue().equals(filter)) {
0977: continue;
0978: }
0979: valuesBuffer.append(listItem.getValue());
0980: valuesBuffer.append(separator);
0981: }
0982: return valuesBuffer.toString();
0983: }
0984:
0985: /**
0986: * Retrieve an Iterator of ListSelector.ListItem representing the selected selections only.
0987: * This method is used by the renderer, to create the options of
0988: * the list of selected items. It is also used when calculating a string
0989: * representation of the value of the component.
0990: * @return An Iterator over the selected ListItem
0991: */
0992: public Iterator getSelectedListItems() {
0993: return selectedItems.values().iterator();
0994: }
0995:
0996: /**
0997: * Marks options corresponding to objects listed as values of this
0998: * component as selected.
0999: * @param list A list representation of the selected values
1000: * @param processed If true, compare the values object by
1001: * object (this is done if we compare the value of the object with
1002: * with the list items). If false, perform a string comparison of
1003: * the string representation of the submitted value of the
1004: * component with the string representation of the value from the
1005: * list items (this is done if we compare the submitted values
1006: * with the list items). */
1007: protected void markSelectedListItems(java.util.List list,
1008: boolean processed) {
1009:
1010: if (DEBUG)
1011: log("markSelectedListItems()"); //NOI18N
1012:
1013: // The "selected" variable is an iteration over the selected
1014: // items
1015:
1016: // CR 6359071 and 6369187
1017: // Drive the comparisons from the selected list vs. the
1018: // available list. This results in the resulting mapped
1019: // selected list reflecting the order of the original
1020: // selected list.
1021: //
1022: Iterator selected = list.iterator();
1023:
1024: boolean allowDups = isDuplicateSelections();
1025:
1026: // The selected items are sorted if "isSorted" is true and
1027: // "isMoveButtons" is false. If "isMoveButtons" is true then
1028: // the selected items are not sorted even if "isSorted" is true.
1029: // They appear as they were inserted.
1030: // If "isSorted" is false and "isMoveButtons" is false, the selected
1031: // items will appear as they were inserted.
1032: //
1033: boolean sorted = isSorted() && !isMoveButtons();
1034:
1035: // Use the HashMap "removeItems" to record the selected
1036: // items that must be removed from the available items.
1037: // This allows us to not use the available item keys in the
1038: // selectedItems list, enabling the
1039: // selectedItems to be sorted as inserted.
1040: //
1041: Map removeItems = new HashMap();
1042:
1043: // Devise a key to use for the selectedItems. Use the same
1044: // strategy as used for available items. Create an increasing
1045: // String of the letter KEY_STRING as selected items are recorded.
1046: // If sorting, use the available item key.
1047: //
1048: String selectedKey = ""; //NOI18N
1049:
1050: while (selected.hasNext()) {
1051:
1052: Object selectedValue = selected.next();
1053:
1054: // The "keys" are the keys of the options on the available map
1055: // Need to "rewind" for every selected item.
1056: //
1057: Iterator keys = availableItems.keySet().iterator();
1058:
1059: // Does the current listItem match the selected value?
1060: boolean match = false;
1061:
1062: while (keys.hasNext()) {
1063:
1064: Object key = keys.next();
1065: // The next object from the available map
1066: //
1067: Object nextItem = availableItems.get(key);
1068: ListItem listItem = null;
1069:
1070: // If we get an exception just log it and continue.
1071: // It's cheaper this way than testing with "instanceof".
1072: //
1073: try {
1074: listItem = (ListItem) nextItem;
1075: } catch (Exception e) {
1076: log("An available item was not a ListItem."); //NOI18N
1077: continue;
1078: }
1079:
1080: if (DEBUG) {
1081: log("Now processing ListItem " + //NOI18N
1082: listItem.getValue());
1083: log("\tSelected object value: " + //NOI18N
1084: String.valueOf(selectedValue));
1085: log("\tSelected object type: " + //NOI18N
1086: selectedValue.getClass().getName());
1087: if (processed) {
1088: log("\tMatching the values by " + //NOI18N
1089: "object.equals()"); //NOI18N
1090: } else {
1091: log("\tMatching the values by string" + //NOI18N
1092: "comparison on converted values."); //NOI18N
1093: }
1094: }
1095:
1096: if (processed) {
1097: match = listItem.getValueObject().equals(
1098: selectedValue);
1099: } else {
1100: // Recall that "processed" means that we compare using the
1101: // actual value of this component, and this case means that
1102: // we compare from the submitted values. In other words, in
1103: // this scenario, the selectedValue is an already converted
1104: // String.
1105: match = selectedValue.toString().equals(
1106: listItem.getValue());
1107: }
1108:
1109: // Note that elements in the selected list that do
1110: // not match will not appear in the "selectedItems"
1111: // TreeMap.
1112: //
1113: if (!match) {
1114: continue;
1115: }
1116:
1117: if (DEBUG)
1118: log("\tListItem and selected item match"); //NOI18N
1119:
1120: // Ensure that the selectedItems are sorted appropriately.
1121: // Use the sort order of the available items if sorted
1122: // and the insertion order if not.
1123: //
1124: if (sorted) {
1125: selectedKey = key.toString();
1126: } else {
1127: selectedKey = selectedKey.concat("a");
1128: }
1129:
1130: // See if we have a dup. If dups are allowed
1131: // create a new unique key for the dup and add it
1132: // to the selectedItems.
1133: // If not a dup, add it to the removeItems map
1134: // and add it to the selectedItems.
1135: //
1136: if (removeItems.containsKey(key)) {
1137: if (allowDups) {
1138: // In case users are allowed to add the same
1139: // item more than once, use this complicated
1140: // procedure.
1141: // The assumption is that "1" comes before "a".
1142: //
1143: if (DEBUG) {
1144: log("\tAdding duplicate " + //NOI18N
1145: "and creating unique key."); //NOI18N
1146: }
1147: String key2 = selectedKey.toString()
1148: .concat("1");
1149: selectedItems.put(key2, listItem);
1150: } else {
1151: if (DEBUG) {
1152: log("\tDuplicates not allowed " + //NOI18N
1153: "ignoring this duplicate selected item."); //NOI18N
1154: }
1155: }
1156: } else {
1157: // Add the found key to the removeItems map
1158: // and add to the selectedItems.
1159: //
1160: removeItems.put(key, null);
1161: selectedItems.put(selectedKey, listItem);
1162: }
1163:
1164: // We have a match break the loop
1165: //
1166: break;
1167: }
1168: if (DEBUG) {
1169: if (!match) {
1170: log("\tSelected value "
1171: + //NOI18N
1172: String.valueOf(selectedValue)
1173: + " not present on the list of options."); //NOI18N
1174: }
1175: }
1176: }
1177:
1178: if (!allowDups) {
1179: if (DEBUG) {
1180: log("\tRemove the selected items from "
1181: + "the available items"); //NOI18N
1182: }
1183: Iterator keys = removeItems.keySet().iterator();
1184: Object key = null;
1185: while (keys.hasNext()) {
1186: key = keys.next();
1187: availableItems.remove(key);
1188: }
1189: }
1190: }
1191:
1192: public boolean mainListSubmits() {
1193: return false;
1194: }
1195: }
|