All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.webui.jsf.component.AddRemove Maven / Gradle / Ivy

There is a newer version: 4.4.0.1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2007-2018 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.webui.jsf.component;

import com.sun.faces.annotation.Component;
import com.sun.faces.annotation.Property;
import com.sun.webui.jsf.model.Option;
import com.sun.webui.jsf.model.OptionGroup;
import com.sun.webui.jsf.model.Separator;
import com.sun.webui.jsf.model.list.ListItem;
import com.sun.webui.jsf.theme.ThemeStyles;
import com.sun.webui.jsf.util.ComponentUtilities;
import com.sun.webui.jsf.util.MessageUtil;
import com.sun.webui.jsf.util.ThemeUtilities;
import com.sun.webui.jsf.util.JavaScriptUtilities;
import com.sun.webui.theme.Theme;

import java.text.Collator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

/**
 * The AddRemove component is used to construct a list of selected items.
 * 

* 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. *

*/ @Component(type = "com.sun.webui.jsf.AddRemove", family = "com.sun.webui.jsf.AddRemove", displayName = "Add Remove List", instanceName = "addRemoveList", tagName = "addRemove", helpKey = "projrave_ui_elements_palette_wdstk-jsf1.2_add_remove_list", propertiesHelpKey = "projrave_ui_elements_palette_wdstk-jsf1.2_propsheets_addremovelist_props") public class AddRemove extends ListSelector implements ListManager { /** * The component id for the ADD button */ public static final String ADD_BUTTON_ID = "_addButton"; //NOI18N /** * The facet name of the add button */ public static final String ADD_BUTTON_FACET = "addButton"; //NOI18N /** * The component id for the ADD ALL button */ public static final String ADDALL_BUTTON_ID = "_addAllButton"; //NOI18N /** * The facet name of the Add All button */ public static final String ADDALL_BUTTON_FACET = "addAllButton"; //NOI18N /** * The component ID for the remove button */ public static final String REMOVE_BUTTON_ID = "_removeButton"; //NOI18N /** * The facet name of the remove button */ public static final String REMOVE_BUTTON_FACET = "removeButton"; //NOI18N /** * The component ID for the remove all button */ public static final String REMOVEALL_BUTTON_ID = "_removeAllButton"; //NOI18N /** * The facet name of the "Remove All" button */ public static final String REMOVEALL_BUTTON_FACET = "removeAllButton"; //NOI18N /** * The component ID for the move up button */ public static final String MOVEUP_BUTTON_ID = "_moveUpButton"; //NOI18N /** * The facet name of the "Move Up" button */ public static final String MOVEUP_BUTTON_FACET = "moveUpButton"; //NOI18N /** * The component ID for the move down button */ public static final String MOVEDOWN_BUTTON_ID = "_moveDownButton"; //NOI18N /** * The facet name of the "Move Down" button */ public static final String MOVEDOWN_BUTTON_FACET = "moveDownButton"; //NOI18N /** * The component ID for the items list */ public static final String AVAILABLE_LABEL_ID = "_availableLabel"; //NOI18N /** * The facet name of the label over the "Available" list */ public static final String AVAILABLE_LABEL_FACET = "availableLabel"; //NOI18N /** * The facet name of the label readonly case */ public static final String READ_ONLY_LABEL_FACET = "readonlyLabel"; //NOI18N /** * The component ID for the selected list */ public static final String SELECTED_LABEL_ID = "_selectedLabel"; //NOI18N /** * The facet name of the label over the "Selected" list */ public static final String SELECTED_LABEL_FACET = "selectedLabel"; //NOI18N /** * Facet name for the header facet */ public static final String HEADER_FACET = "header"; //NOI18N /** * The facet name of the header (component label) */ public static final String HEADER_ID = "_header"; //NOI18N /** * Facet name for the footer facet */ public static final String FOOTER_FACET = "footer"; //NOI18N /** * The id of the label component that functions as the label above the available list */ public static final String AVAILABLE_ID = "_available"; //NOI18N /** * The available label text key. */ public static final String AVAILABLE_TEXT_KEY = "AddRemove.available"; //NOI18N /** * The ID of the component that functions as the label above the "Selected" list */ public static final String SELECTED_ID = "_selected"; //NOI18N /** * The selected label text key. */ public static final String SELECTED_TEXT_KEY = "AddRemove.selected"; //NOI18N /** * The ID of the component readonly case */ public static final String READONLY_ID = "_readonly"; //NOI18N /** * String representing "return false" printed at the end of the javascript event handlers */ public static final String RETURN = "return false;"; //NOI18N /** * Name of the JavaScript function which is responsible for adding elements from the availble list to the selected list */ public static final String ADD_FUNCTION = ".add(); "; //NOI18N /** * Add button text key. */ public static final String ADD_TEXT_KEY = "AddRemove.add"; //NOI8N /** * Add button text key, vertical layout. */ public static final String ADDVERTICAL_TEXT_KEY = "AddRemove.addVertical"; //NOI8N /** * Name of the JavaScript function which is responsible for selecting * all the available items */ public static final String ADDALL_FUNCTION = ".addAll();"; //NOI18N /** * Add all button text key. */ public static final String ADDALL_TEXT_KEY = "AddRemove.addAll"; //NOI8N /** * Add all button text key, vertical layout. */ public static final String ADDALLVERTICAL_TEXT_KEY = "AddRemove.addAllVertical"; //NOI8N /** * Name of the JavaScript function which removes items from the seleted list */ public static final String REMOVE_FUNCTION = ".remove(); "; //NOI18N /** * Remove button text key. */ public static final String REMOVE_TEXT_KEY = "AddRemove.remove"; //NOI8N /** * Remove button text key, vertical layout */ public static final String REMOVEVERTICAL_TEXT_KEY = "AddRemove.removeVertical"; //NOI8N /** * Name of the JavaScript function which removes all the items from the seleted list */ public static final String REMOVEALL_FUNCTION = ".removeAll(); "; //NOI18N /** * Remove all button text key. */ public static final String REMOVEALL_TEXT_KEY = "AddRemove.removeAll"; //NOI8N /** * Remove all button text key, vertical layout. */ public static final String REMOVEALLVERTICAL_TEXT_KEY = "AddRemove.removeAllVertical"; //NOI8N /** * Name of the JavaScript function which moves elements up */ public static final String MOVEUP_FUNCTION = ".moveUp(); "; //NOI18N /** * Move up button text key. */ public static final String MOVEUP_TEXT_KEY = "AddRemove.moveUp"; //NOI8N /** * Name of the JavaScript function which moves elements down */ public static final String MOVEDOWN_FUNCTION = ".moveDown();"; //NOI18N /** * Move down button text key. */ public static final String MOVEDOWN_TEXT_KEY = "AddRemove.moveDown"; //NOI8N /** * Name of the JavaScript function that updates the buttons */ public static final String UPDATEBUTTONS_FUNCTION = ".updateButtons(); "; //NOI8N /** * Name of the JavaScript function that handles changes on the available list */ public static final String AVAILABLE_ONCHANGE_FUNCTION = ".availableOnChange(); "; //NOI8N /** * Name of the JavaScript function which handles changes to the selected list */ public static final String SELECTED_ONCHANGE_FUNCTION = ".selectedOnChange(); "; //NOI8N /** * The name of the JavaScript function used to hook up the correct * add and remove functions when the component allows items to be * added to the selected items list more than once */ public static final String MULTIPLEADDITIONS_FUNCTION = ".allowMultipleAdditions()"; //NOI8N public static final String SPACER_STRING = "_"; //NOI18N private static final String KEY_STRING = "a"; //NOI18N private static final String DUP_STRING = "1"; //NOI18N /** * The string used as a separator between the selected values */ public static final String SEPARATOR_VALUE = "com.sun.webui.jsf.separator"; //NOI8N /** * The label level key. It is used to overwrite the Label component's default value. */ public static final String ADDREMOVE_LABEL_LEVEL = "AddRemove.labelLevel"; //NOI18N private TreeMap availableItems = null; private TreeMap selectedItems = null; private Collator collator = null; private String allValues = ""; //NOI18N private String selectedValues = ""; //NOI18N private static final boolean DEBUG = false; /** * Constructor for the AddRemove component */ public AddRemove() { setMultiple(true); setRendererType("com.sun.webui.jsf.AddRemove"); } /** *

Return the family for this component.

*/ @Override public String getFamily() { return "com.sun.webui.jsf.AddRemove"; } /** * Get the number of rows to disaplay (the default is 12) * @return the number of rows to disaplay */ @Override public int getRows() { int rows = super.getRows(); if (rows < 1) { // FIXME: Should be in Theme // rows = 12; // Don't alter the Bean value. //super.setRows(rows); } return rows; } /** * Get the separator string that is used to separate the selected values on the client. * The default value is "|". When the AddRemove component is decoded, the * value is taken from a hidden variable whose value is a list of the * values of all the options in the list representing the selected items. * Consider a case where the AddRemove has a list of options including * * * Assume that these two options are disabled. If the separator * string is set to "|", then the value of the hidden * variable will be |1|2|. * * You will only need to set this variable if the string * representation of one of the option values contain the * character "|". If you do need to change from the default, * bear in mind that the value of the hidden component * is sent as part of the body of the HTTP request body. * Make sure to select a character that does not change * the syntax of the request. * @return The separator string. */ public String getSeparator() { // FIXME: Either should be in Theme or configurable. // return "|"; //NOI18N } /** * Returns an iterator over the selected items * This function will return one separator element com.sun.web.ui.separator * in addition to the selected items even if the selected list is empty. * @return an iterator over the selected items */ public Iterator getSelectedItems() { FacesContext context = FacesContext.getCurrentInstance(); // Initialize selectedItems and selectedValues. Iterator itr = getListItems(context, true); return selectedItems.values().iterator(); } /** * This function returns a String consisting of the String representation of the * values of all the available Options, separated by the separator * String (see getSeparator()) * @return eturns a String consisting of the String representation of the * values of all the available Options, separated by the separator * String */ public String getAllValues() { return allValues; } /** * This function returns a String consisting of the String representation of the * values of the selected Options, separated by the separator * String * @return a String consisting of the String representation of the * values of the selected Options, separated by the separator * String */ public String getSelectedValues() { FacesContext context = FacesContext.getCurrentInstance(); // Initialize selectedItems and selectedValues. Iterator itr = getListItems(context, true); return selectedValues; } // Buttons /** * Get or create the ADD button. Retrieves the component specified by the * addButton facet (if there is one) or creates a new Button component. * @return A UI Component for the Add button * @param context The FacesContext for the request * @deprecated See getAddButtonComponent(); */ public UIComponent getAddButtonComponent(FacesContext context) { return getAddButtonComponent(); } /** * Return a component that implements the add button. * If a facet named addButton is found * that component is returned. Otherwise a Button component * is returned. It is assigned the id
* getId() + "_addButton"
*

* If the facet is not defined then the returned Button * component is re-intialized every time this method is called. *

* * @return an add button component */ public UIComponent getAddButtonComponent() { if (DEBUG) { log("getAddButtonComponent()"); //NOI18N } return getButtonFacet(ADD_BUTTON_FACET, false, getTheme().getMessage( isVertical() ? ADDVERTICAL_TEXT_KEY : ADD_TEXT_KEY), ADD_FUNCTION); } /** * Return a component that implements the add all button. * If a facet named addAllButton is found * that component is returned. Otherwise a Button component * is returned. It is assigned the id
* getId() + "_addAllButton"
*

* If the facet is not defined then the returned Button * component is re-intialized every time this method is called. *

* * @return an add all button component */ public UIComponent getAddAllButtonComponent() { if (DEBUG) { log("getAddAllButtonComponent()"); //NOI18N } return getButtonFacet(ADDALL_BUTTON_FACET, false, getTheme().getMessage( isVertical() ? ADDALLVERTICAL_TEXT_KEY : ADDALL_TEXT_KEY), ADDALL_FUNCTION); } /** * Return a component that implements the remove button. * If a facet named removeButton is found * that component is returned. Otherwise a Button component * is returned. It is assigned the id
* getId() + "_removeButton"
*

* If the facet is not defined then the returned Button * component is re-intialized every time this method is called. *

* * @return a remove button component */ public UIComponent getRemoveButtonComponent() { if (DEBUG) { log("getRemoveButtonComponent()"); //NOI18N } return getButtonFacet(REMOVE_BUTTON_FACET, false, getTheme().getMessage( isVertical() ? REMOVEVERTICAL_TEXT_KEY : REMOVE_TEXT_KEY), REMOVE_FUNCTION); } /** * Return a component that implements the remove all button. * If a facet named removeAllButton is found * that component is returned. Otherwise a Button component * is returned. It is assigned the id
* getId() + "_removeAllButton"
*

* If the facet is not defined then the returned Button * component is re-intialized every time this method is called. *

* * @return a remove all button component */ public UIComponent getRemoveAllButtonComponent() { if (DEBUG) { log("getRemoveAllButtonComponent()"); //NOI18N } return getButtonFacet(REMOVEALL_BUTTON_FACET, false, getTheme().getMessage( isVertical() ? REMOVEALLVERTICAL_TEXT_KEY : REMOVEALL_TEXT_KEY), REMOVEALL_FUNCTION); } /** * Return a component that implements the move up button. * If a facet named moveUpButton is found * that component is returned. Otherwise a Button component * is returned. It is assigned the id
* getId() + "_moveUpButton"
*

* If the facet is not defined then the returned Button * component is re-intialized every time this method is called. *

* * @return a move up button component */ public UIComponent getMoveUpButtonComponent() { if (DEBUG) { log("getMoveUpButtonComponent()"); //NOI18N } return getButtonFacet(MOVEUP_BUTTON_FACET, false, getTheme().getMessage(MOVEUP_TEXT_KEY), MOVEUP_FUNCTION); } /** * Return a component that implements the move down button. * If a facet named moveDownButton is found * that component is returned. Otherwise a Button component * is returned. It is assigned the id
* getId() + "_moveDownButton"
*

* If the facet is not defined then the returned Button * component is re-intialized every time this method is called. *

* * @return a move down button component */ public UIComponent getMoveDownButtonComponent() { if (DEBUG) { log("getMoveDownButtonComponent()"); //NOI18N } return getButtonFacet(MOVEDOWN_BUTTON_FACET, false, getTheme().getMessage(MOVEDOWN_TEXT_KEY), MOVEDOWN_FUNCTION); } /** * Return a component that implements a button facet. * If a facet named facetName is found * that component is returned. Otherwise a Button component * is returned. It is assigned the id
* getId() + "_"
*

* If the facet is not defined then the returned Button * component is re-intialized every time this method is called. *

* * @param facetName the name of the facet to return or create * @param primary if false the button is not a primary button * @param text the button text * @param onclickFunction the javascript function name for the onclick event * * @return a button facet component */ private UIComponent getButtonFacet(String facetName, boolean primary, String text, String onclickFunction) { if (DEBUG) { log("getButtonFacet() " + facetName); //NOI18N } // Check if the page author has defined the facet // UIComponent buttonComponent = getFacet(facetName); if (buttonComponent != null) { if (DEBUG) { log("\tFound facet"); //NOI18N } return buttonComponent; } // Return the private facet or create one, but initialize // it every time // // We know it's a Button // Button button = (Button) ComponentUtilities.getPrivateFacet(this, facetName, true); if (button == null) { if (DEBUG) { log("create Button"); //NOI18N } button = new Button(); button.setId(ComponentUtilities.createPrivateFacetId(this, facetName)); ComponentUtilities.putPrivateFacet(this, facetName, button); } initButtonFacet(button, primary, text, onclickFunction); return button; } /** * Initialize a Button facet. * * @param button the Button instance * @param primary if false the button is not a primary button * @param text the button text * @param onclickFunction the javascript function name for the onclick event */ private void initButtonFacet(Button button, boolean primary, String text, String onclickFunction) { button.setPrimary(primary); button.setText(text); int tindex = getTabIndex(); if (tindex > 0) { button.setTabIndex(tindex); } StringBuffer buff = new StringBuffer(256); buff.append(JavaScriptUtilities.getDomNode(getFacesContext(), this)).append(onclickFunction).append(RETURN); button.setOnClick(buff.toString()); // NOTE: the original behavior would have set this // on the developer defined facet. It was determined that // a developer defined facet should not be modified. // button.setDisabled(isDisabled()); } // Labels /** * Return a component that implements a label for the available list. * If a facet named availableLabel is found * that component is returned. Otherwise a Label component * is returned. It is assigned the id
* getId() + "_availableLabel"
*

* If the facet is not defined then the returned Label * component is re-intialized every time this method is called. *

* * @return an available list label facet component */ public UIComponent getAvailableLabelComponent() { if (DEBUG) { log("getAvailableLabelComponent()"); //NOI18N } // Prepare to call getLabelFacet so that it can be // re-initialized if it exists or created if it doesn't. // Preparing this information before we know if there // is a developer defined facet, is a hit, but there // is less likelyhood of a developer defined facet, so // optimize for the majority case. // This extensibility should be accomplished by the // developer providing // an AVAILABLE_LABEL_FACET. So that "getAvailableItemsLabel" // would not have to be expressed as an AddRemove attribute. // But in general it is more work for the developer. // // Alternatively, there could have been a way to // allow the developer to override the default // message key "AddRemove.available" with the text // that they desired, like an "messageBundle" attribute. // We could have used "param" tags for this, for example. // String labelString = getAvailableItemsLabel(); if (labelString == null || labelString.length() == 0) { labelString = getTheme().getMessage(AVAILABLE_TEXT_KEY); } String styleClass = getTheme().getStyleClass(ThemeStyles.ADDREMOVE_LABEL2); String forId = getLabelFacetForId(AVAILABLE_ID); return getLabelFacet(AVAILABLE_LABEL_FACET, labelString, styleClass, forId); } /** * Return a component that implements a label for the selected list. * If a facet named selectedLabel is found * that component is returned. Otherwise a Label component * is returned. It is assigned the id
* getId() + "_selectedLabel"
*

* If the facet is not defined then the returned Label * component is re-intialized every time this method is called. *

* * @return a selected list label facet component */ public UIComponent getSelectedLabelComponent() { if (DEBUG) { log("getSelectedLabelComponent()"); //NOI18N } // Prepare to call getLabelFacet so that it can be // re-initialized if it exists or created if it doesn't. // Preparing this information before we know if there // is a developer defined facet, is a hit, but there // is less likelyhood of a developer defined facet, so // optimize for the majority case. // This extensibility should be accomplished by the // developer providing // an AVAILABLE_LABEL_FACET. So that "getAvailableItemsLabel" // would not have to be expressed as an AddRemove attribute. // But in general it is more work for the developer. // // Alternatively, there could have been a way to // allow the developer to override the default // message key "AddRemove.selected" with the text // that they desired, like an "messageBundle" attribute. // We could have used "param" tags for this, for example. // String labelString = getSelectedItemsLabel(); if (labelString == null || labelString.length() == 0) { labelString = getTheme().getMessage(SELECTED_TEXT_KEY); } String styleClass = getTheme().getStyleClass(ThemeStyles.ADDREMOVE_LABEL2); // The problem here is that the indicators will work // properly and render in the appropriate place, but the // Label for attribut of the label will point to the // available list. The selected list really needs to be // a component and not renderer as a select in // AddRemoveRenderer. // String forId = getClientId(FacesContext.getCurrentInstance()); return getLabelFacet(SELECTED_LABEL_FACET, labelString, styleClass, forId); } /** * Return a component that implements a label for the readOnly selected list. * If a facet named selectedLabel is found * that component is returned. Otherwise a Label component * is returned. It is assigned the id
* getId() + "_selectedLabel"
*

* If the facet is not defined then the returned Label * component is re-intialized every time this method is called. *

* * @return a selected list label facet component */ public UIComponent getReadOnlyLabelComponent() { if (DEBUG) { log("getReadOnlyLabelComponent()"); //NOI18N } // Prepare to call getLabelFacet so that it can be // re-initialized if it exists or created if it doesn't. // Preparing this information before we know if there // is a developer defined facet, is a hit, but there // is less likelyhood of a developer defined facet, so // optimize for the majority case. // This extensibility should be accomplished by the // developer providing // an AVAILABLE_LABEL_FACET. So that "getAvailableItemsLabel" // would not have to be expressed as an AddRemove attribute. // But in general it is more work for the developer. // // Alternatively, there could have been a way to // allow the developer to override the default // message key "AddRemove.selected" with the text // that they desired, like an "messageBundle" attribute. // We could have used "param" tags for this, for example. // String labelString = getSelectedItemsLabel(); if (labelString == null || labelString.length() == 0) { labelString = getTheme().getMessage(SELECTED_TEXT_KEY); } String styleClass = getTheme().getStyleClass(ThemeStyles.ADDREMOVE_LABEL2_READONLY); return getLabelFacet(READ_ONLY_LABEL_FACET, labelString, styleClass, null); } /** * Return a component that implements a label for the AddRemove component. * If a facet named header is found * that component is returned. Otherwise a Label component * is returned. It is assigned the id
* getId() + "_header"
*

* If the facet is not defined then the returned Label * component is re-intialized every time this method is called. *

* * @return a header list label facet component */ public UIComponent getHeaderComponent() { if (DEBUG) { log("getHeaderComponent()"); //NOI18N } String labelString = getLabel(); String styleClass = getTheme().getStyleClass(ThemeStyles.ADDREMOVE_LABEL); String forId = getClientId( FacesContext.getCurrentInstance()).concat(AVAILABLE_ID); return getLabelFacet(HEADER_FACET, labelString, styleClass, forId); } /** * Return a component that implements a label for the facetName role. * If a facet named facetName is found * that component is returned. Otherwise a Label component * is returned. It is assigned the id
* getId() + "facetName"
*

* If the facet is not defined then the returned Label * component is re-intialized every time this method is called. *

* * @param facetName the name of the facet to return or create * @param labelText the text for the label * @param styleClass the label styleClass * @param forId the component id that this facet labels * * @return a label facet component */ private UIComponent getLabelFacet(String facetName, String labelText, String styleClass, String forId) { if (DEBUG) { log("getLabelFacet() for " + facetName); //NOI18N } // Check if the page author has defined a label facet // UIComponent labelComponent = getFacet(facetName); if (labelComponent != null) { if (DEBUG) { log("\tFound facet."); //NOI18N } return labelComponent; } // There was an implicit policy for the HEADER_FACET // that if getLabel() returned null then no label facet // was returned or created. Follow that here if // labelText is null, return null. For callers of this // method in this file, labelText will only be null if getLabel // returns null or empty string, when called by getHeaderComponent. // if (labelText == null) { return null; } // Return the private facet or create one, but initialize // it every time // // We know it's a Label // Label label = (Label) ComponentUtilities.getPrivateFacet(this, facetName, true); if (label == null) { label = new Label(); label.setId(ComponentUtilities.createPrivateFacetId(this, facetName)); ComponentUtilities.putPrivateFacet(this, facetName, label); } initLabelFacet(label, facetName, labelText, styleClass, forId); return label; } /** * Initialize a Label component for the role of * facetName. * If forId is null, setLabeledComponent is called * with this as the parameter.
* * @param label the Button instance * @param facetName the name of the facet to return or create * @param labelText the text for the label * @param styleClass the label styleClass * @param forId the component id that this facet labels */ protected void initLabelFacet(Label label, String facetName, String labelText, String styleClass, String forId) { if (DEBUG) { log("initLabelFacet()"); //NOI18N } if (label == null) { return; } // Not sure why this is done. // if (labelText == null || labelText.length() < 1) { // TODO - maybe print a default? labelText = new String(); } // By default, the Label component sets the label level to a default // value. Since we don't want any level to be set, we need to set the // label level to a value outside of the valid range. label.setLabelLevel(Integer.parseInt(getTheme().getMessage( ADDREMOVE_LABEL_LEVEL))); label.setText(labelText); label.setStyleClass(styleClass); // This policy is based on the original behavior. // For the available and selected facets, forId is set // For the header facet, setLabeledComponent is called. // Not sure how valid that is in the general case. // if (forId != null) { label.setFor(forId); } return; } /** * Return an id for a label facet. * The format of the id is *
* getClientId() + idSuffix
*/ private String getLabelFacetForId(String idSuffix) { // Note that the id returned here does not have the form // of an AddRemove child element id but the form // of a form element child id. // // "form1:addremoveid_idsuffix" // // The renderer MUST render the HTML element referred to by idSuffix // in the same manner. // // TODO - what should we show here? return getClientId( FacesContext.getCurrentInstance()).concat(idSuffix); } /** * Implement this method so that it returns the DOM ID of the * HTML element which should receive focus when the component * receives focus, and to which a component label should apply. * Usually, this is the first element that accepts input. * * @param context The FacesContext for the request * @return The client id, also the JavaScript element id * * @deprecated * @see #getLabeledElementId * @see #getFocusElementId */ @Override public String getPrimaryElementID(FacesContext context) { // Don't return getLabeledElementId here in case // callers can't handle null when isReadOnly is true. // return this.getClientId(context).concat(AVAILABLE_ID); } /** * Returns the absolute ID of an HTML element suitable for use as * the value of an HTML LABEL element's for attribute. * If the ComplexComponent has sub-compoents, and one of * the sub-components is the target of a label, if that sub-component * is a ComplexComponent, then * getLabeledElementId must called on the sub-component and * the value returned. The value returned by this * method call may or may not resolve to a component instance. *

* If isReadOnly returns true * null is returned. *

* * @param context The FacesContext used for the request * @return An abolute id suitable for the value of an HTML LABEL element's * for attribute. */ @Override public String getLabeledElementId(FacesContext context) { if (isReadOnly()) { return null; } else { return this.getClientId(context).concat(AVAILABLE_ID); } } /** * Returns the id of an HTML element suitable to * receive the focus. * If the ComplexComponent has sub-compoents, and one of * the sub-components is to reveive the focus, if that sub-component * is a ComplexComponent, then * getFocusElementId must called on the sub-component and * the value returned. The value returned by this * method call may or may not resolve to a component instance. *

* This implementation returns the value of * getLabeledElementId. *

* * @param context The FacesContext used for the request */ @Override public String getFocusElementId(FacesContext context) { return getLabeledElementId(context); } private Theme getTheme() { return ThemeUtilities.getTheme(FacesContext.getCurrentInstance()); } /** * Retrieve an Iterator of ListSelector.ListItem representing the available selections only. * This method is used by the renderer, to create the options of * the list of available items. * @return an Iterator over {@link ListItem}. * @param context The FacesContext used for the request * @param rulerAtEnd If true, a disabled list item with a blank label is appended at * the end of the options. The role of the blank * item is to guarantee that the width of the lists * do not change when items are moved from one to the * other. * @throws javax.faces.FacesException If something goes wrong when the options are processed */ @Override public Iterator getListItems(FacesContext context, boolean rulerAtEnd) throws FacesException { if (DEBUG) { log("getListItems()");//NOI18N } Locale locale = context.getViewRoot().getLocale(); if (DEBUG) { log("\tLocale is " + locale.toString()); //NOI18N } collator = Collator.getInstance(locale); collator.setStrength(Collator.IDENTICAL); availableItems = new TreeMap(collator); selectedItems = new TreeMap(collator); // Retrieve the current selections. If there are selected // objects, mark the corresponding items as selected. processOptions(context, collator, locale, rulerAtEnd); // We construct a string representation of all values (whether // they are selected or not) before we remove the selected // items in the processSelections step allValues = constructValueString(availableItems); processSelections(); // We construct a string representation of the selected values // only selectedValues = constructValueString(selectedItems, SEPARATOR_VALUE); return availableItems.values().iterator(); } /** * Evaluates the list of available Options, creating a ListItem for each * one. * @param context The FacesContext * @param rulerAtEnd the end of the options. The role of the blank * item is to guarantee that the width of the lists * do not change when items are moved from one to the * other. */ protected void processOptions(FacesContext context, Collator collator, Locale locale, boolean rulerAtEnd) { if (DEBUG) { log("processOptions()"); //NOI18N } Option[] options = getOptions(); int length = options.length; ListItem listItem = null; String label = null; String lastKey = ""; //NOI18N String longestString = ""; //NOI18N StringBuffer unsortedKeyBuffer = new StringBuffer(100); boolean sorted = isSorted(); for (int counter = 0; counter < length; ++counter) { if (options[counter] instanceof OptionGroup) { String msg = MessageUtil.getMessage("com.sun.webui.jsf.resources.LogMessages", //NOI18N "AddRemove.noGrouping"); //NOI18N log(msg); continue; } if (options[counter] instanceof Separator) { String msg = MessageUtil.getMessage("com.sun.webui.jsf.resources.LogMessages", //NOI18N "AddRemove.noGrouping"); //NOI18N log(msg); continue; } // Convert the option to a list item listItem = createListItem(options[counter]); label = listItem.getLabel(); if (label.length() > longestString.length()) { longestString = label; } if (sorted) { availableItems.put(label, listItem); if (collator.compare(label, lastKey) > 0) { lastKey = label; } } else { // If the page author does not want the list items to be // sorted (alphabetically by locale), then they're // supposed to be sorted by the order they were added. // Maps are not guaranteed to return items in the order // they were added, so we have to create this order // artificially. We do that by creating a successively // longer key for each element. (a, aa, aaa...). unsortedKeyBuffer.append(KEY_STRING); availableItems.put(unsortedKeyBuffer.toString(), listItem); lastKey = unsortedKeyBuffer.toString(); } } if (rulerAtEnd) { // It looks the like "5" is extra padding or a margin // between the last letter of an item and the list // box border. This should be a Theme property. // SPACER_STRING should also be a Theme property // int seplength = longestString.length() + 5; StringBuffer labelBuffer = new StringBuffer(seplength); for (int counter = 0; counter < seplength; ++counter) { labelBuffer.append(SPACER_STRING); } ListItem item = new ListItem(labelBuffer.toString()); item.setDisabled(true); item.setValue(SEPARATOR_VALUE); if (sorted) { lastKey = lastKey.concat(KEY_STRING); availableItems.put(lastKey, item); //NOI18N lastKey = lastKey.concat(KEY_STRING); selectedItems.put(lastKey, item); //NOI18N } else { unsortedKeyBuffer.append(KEY_STRING); availableItems.put(unsortedKeyBuffer.toString(), item); unsortedKeyBuffer.append(KEY_STRING); selectedItems.put(unsortedKeyBuffer.toString(), item); } } if (DEBUG) { log("AvailableItems keys"); //NOI18N Iterator iterator = availableItems.keySet().iterator(); while (iterator.hasNext()) { log("next key " + iterator.next().toString()); //NOI18N } } } private String constructValueString(TreeMap map) { return constructValueString(map, null); } private String constructValueString(TreeMap map, String filter) { // Set up the "All values" string. This is rendered as a // hidden input on the client side, and is used to StringBuffer valuesBuffer = new StringBuffer(392); Iterator values = map.values().iterator(); ListItem listItem = null; String separator = getSeparator(); valuesBuffer.append(separator); while (values.hasNext()) { listItem = (ListItem) (values.next()); if (filter != null && listItem.getValue().equals(filter)) { continue; } valuesBuffer.append(listItem.getValue()); valuesBuffer.append(separator); } return valuesBuffer.toString(); } /** * Retrieve an Iterator of ListSelector.ListItem representing the selected selections only. * This method is used by the renderer, to create the options of * the list of selected items. It is also used when calculating a string * representation of the value of the component. * @return An Iterator over the selected ListItem */ public Iterator getSelectedListItems() { return selectedItems.values().iterator(); } /** * Marks options corresponding to objects listed as values of this * component as selected. * @param list A list representation of the selected values * @param processed If true, compare the values object by * object (this is done if we compare the value of the object with * with the list items). If false, perform a string comparison of * the string representation of the submitted value of the * component with the string representation of the value from the * list items (this is done if we compare the submitted values * with the list items). */ @Override protected void markSelectedListItems(java.util.List list, boolean processed) { if (DEBUG) { log("markSelectedListItems()"); //NOI18N } // The "selected" variable is an iteration over the selected // items // CR 6359071 // Drive the comparisons from the selected list vs. the // available list. This results in the resulting mapped // selected list reflecting the order of the original // selected list. // Iterator selected = list.iterator(); boolean allowDups = isDuplicateSelections(); // The selected items are sorted if "isSorted" is true and // "isMoveButtons" is false. If "isMoveButtons" is true then // the selected items are not sorted even if "isSorted" is true. // They appear as they were inserted. // If "isSorted" is false and "isMoveButtons" is false, the selected // items will appear as they were inserted. // boolean sorted = isSorted() && !isMoveButtons(); // Use the HashMap "removeItems" to record the selected // items that must be removed from the available items. // This allows us to not use the available item keys in the // selectedItems list, enabling the // selectedItems to be sorted as inserted. // Map removeItems = new HashMap(); // Devise a key to use for the selectedItems. Use the same // strategy as used for available items. Create an increasing // String of the letter KEY_STRING as selected items are recorded. // If sorting, use the available item key. // String selectedKey = ""; //NOI18N while (selected.hasNext()) { Object selectedValue = selected.next(); // The "keys" are the keys of the options on the available map // Need to "rewind" for every selected item. // Iterator keys = availableItems.keySet().iterator(); // Does the current listItem match the selected value? boolean match = false; while (keys.hasNext()) { Object key = keys.next(); // The next object from the available map // Object nextItem = availableItems.get(key); ListItem listItem = null; // If we get an exception just log it and continue. // It's cheaper this way than testing with "instanceof". // try { listItem = (ListItem) nextItem; } catch (Exception e) { log("An available item was not a ListItem."); //NOI18N continue; } if (DEBUG) { log("Now processing ListItem " + //NOI18N listItem.getValue()); log("\tSelected object value: " + //NOI18N String.valueOf(selectedValue)); log("\tSelected object type: " + //NOI18N selectedValue.getClass().getName()); if (processed) { log("\tMatching the values by " + //NOI18N "object.equals()"); //NOI18N } else { log("\tMatching the values by string" + //NOI18N "comparison on converted values."); //NOI18N } } if (processed) { match = listItem.getValueObject().equals(selectedValue); } else { // Recall that "processed" means that we compare using the // actual value of this component, and this case means that // we compare from the submitted values. In other words, in // this scenario, the selectedValue is an already converted // String. match = selectedValue.toString().equals(listItem.getValue()); } // Note that elements in the selected list that do // not match will not appear in the "selectedItems" // TreeMap. // if (!match) { continue; } if (DEBUG) { log("\tListItem and selected item match"); //NOI18N } // Ensure that the selectedItems are sorted appropriately. // Use the sort order of the available items if sorted // and the insertion order if not. // if (sorted) { selectedKey = key.toString(); } else { selectedKey = selectedKey.concat(KEY_STRING); } // See if we have a dup. If dups are allowed // create a new unique key for the dup and add it // to the selectedItems. // If not a dup, add it to the removeItems map // and add it to the selectedItems. // if (removeItems.containsKey(key)) { if (allowDups) { // In case users are allowed to add the same // item more than once, use this complicated // procedure. // The assumption is that "1" comes before "a". // if (DEBUG) { log("\tAdding duplicate " + //NOI18N "and creating unique key."); //NOI18N } String key2 = selectedKey.toString().concat(DUP_STRING); while (selectedItems.containsKey(key2)) { key2 = key2.concat(DUP_STRING); } selectedItems.put(key2, listItem); } else { if (DEBUG) { log("\tDuplicates not allowed " + //NOI18N "ignoring this duplicate selected item."); //NOI18N } } } else { // Add the found key to the removeItems map // and add to the selectedItems. // removeItems.put(key, null); selectedItems.put(selectedKey, listItem); } // We have a match break the loop // break; } if (DEBUG) { if (!match) { log("\tSelected value " + //NOI18N String.valueOf(selectedValue) + " not present on the list of options."); //NOI18N } } } if (!allowDups) { if (DEBUG) { log("\tRemove the selected items from " + "the available items"); //NOI18N } Iterator keys = removeItems.keySet().iterator(); Object key = null; while (keys.hasNext()) { key = keys.next(); availableItems.remove(key); } } } @Override public boolean mainListSubmits() { return false; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Tag attribute methods // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Hide value @Property(name = "value", isHidden = true, isAttribute = false) @Override public Object getValue() { return super.getValue(); } // Hide labelLevel @Property(name = "labelLevel", isHidden = true, isAttribute = false) @Override public int getLabelLevel() { return super.getLabelLevel(); } // Hide separators @Property(name = "separators", isHidden = true, isAttribute = false) @Override public boolean isSeparators() { return super.isSeparators(); } // Hide onBlur @Property(name = "onBlur", isHidden = true, isAttribute = false) @Override public String getOnBlur() { return super.getOnBlur(); } // Hide onChange @Property(name = "onChange", isHidden = true, isAttribute = false) @Override public String getOnChange() { return super.getOnChange(); } // Hide onClick @Property(name = "onClick", isHidden = true, isAttribute = false) @Override public String getOnClick() { return super.getOnClick(); } // Hide onDblClick @Property(name = "onDblClick", isHidden = true, isAttribute = false) @Override public String getOnDblClick() { return super.getOnDblClick(); } // Hide onFocus @Property(name = "onFocus", isHidden = true, isAttribute = false) @Override public String getOnFocus() { return super.getOnFocus(); } // Hide onKeyDown @Property(name = "onKeyDown", isHidden = true, isAttribute = false) @Override public String getOnKeyDown() { return super.getOnKeyDown(); } // Hide onKeyPress @Property(name = "onKeyPress", isHidden = true, isAttribute = false) @Override public String getOnKeyPress() { return super.getOnKeyPress(); } // Hide onKeyUp @Property(name = "onKeyUp", isHidden = true, isAttribute = false) @Override public String getOnKeyUp() { return super.getOnKeyUp(); } // Hide onMouseDown @Property(name = "onMouseDown", isHidden = true, isAttribute = false) @Override public String getOnMouseDown() { return super.getOnMouseDown(); } // Hide onMouseMove @Property(name = "onMouseMove", isHidden = true, isAttribute = false) @Override public String getOnMouseMove() { return super.getOnMouseMove(); } // Hide onMouseOut @Property(name = "onMouseOut", isHidden = true, isAttribute = false) @Override public String getOnMouseOut() { return super.getOnMouseOut(); } // Hide onMouseOver @Property(name = "onMouseOver", isHidden = true, isAttribute = false) @Override public String getOnMouseOver() { return super.getOnMouseOver(); } // Hide onMouseUp @Property(name = "onMouseUp", isHidden = true, isAttribute = false) @Override public String getOnMouseUp() { return super.getOnMouseUp(); } // Hide onSelect @Property(name = "onSelect", isHidden = true, isAttribute = false) @Override public String getOnSelect() { return super.getOnSelect(); } /** *

The label for the list of available items.

*/ @Property(name = "availableItemsLabel", displayName = "Available Items label", category = "Appearance", editorClassName = "com.sun.rave.propertyeditors.StringPropertyEditor") private String availableItemsLabel = null; /** *

The label for the list of available items.

*/ public String getAvailableItemsLabel() { if (this.availableItemsLabel != null) { return this.availableItemsLabel; } ValueExpression _vb = getValueExpression("availableItemsLabel"); if (_vb != null) { return (String) _vb.getValue(getFacesContext().getELContext()); } return null; } /** *

The label for the list of available items.

* @see #getAvailableItemsLabel() */ public void setAvailableItemsLabel(String availableItemsLabel) { this.availableItemsLabel = availableItemsLabel; } /** *

If duplicateSelections is set to true, items in the available * list are not removed when they are added to the selected list. * The user is permitted to add an available item more than once * to the list of selected items. The list of selected items would * then contain duplicate entries.

*/ @Property(name = "duplicateSelections", displayName = "Allow Duplicate Selections", category = "Data") private boolean duplicateSelections = false; private boolean duplicateSelections_set = false; /** *

If duplicateSelections is set to true, items in the available * list are not removed when they are added to the selected list. * The user is permitted to add an available item more than once * to the list of selected items. The list of selected items would * then contain duplicate entries.

*/ public boolean isDuplicateSelections() { if (this.duplicateSelections_set) { return this.duplicateSelections; } ValueExpression _vb = getValueExpression("duplicateSelections"); if (_vb != null) { Object _result = _vb.getValue(getFacesContext().getELContext()); if (_result == null) { return false; } else { return ((Boolean) _result).booleanValue(); } } return false; } /** *

If duplicateSelections is set to true, items in the available * list are not removed when they are added to the selected list. * The user is permitted to add an available item more than once * to the list of selected items. The list of selected items would * then contain duplicate entries.

* @see #isDuplicateSelections() */ public void setDuplicateSelections(boolean duplicateSelections) { this.duplicateSelections = duplicateSelections; this.duplicateSelections_set = true; } /** *

Show the Move Up and Move Down buttons.

*/ @Property(name = "moveButtons", displayName = "Move Buttons", category = "Appearance") private boolean moveButtons = false; private boolean moveButtons_set = false; /** *

Show the Move Up and Move Down buttons.

*/ public boolean isMoveButtons() { if (this.moveButtons_set) { return this.moveButtons; } ValueExpression _vb = getValueExpression("moveButtons"); if (_vb != null) { Object _result = _vb.getValue(getFacesContext().getELContext()); if (_result == null) { return false; } else { return ((Boolean) _result).booleanValue(); } } return false; } /** *

Show the Move Up and Move Down buttons.

* @see #isMoveButtons() */ public void setMoveButtons(boolean moveButtons) { this.moveButtons = moveButtons; this.moveButtons_set = true; } /** *

Show the Add All and Remove All buttons.

*/ @Property(name = "selectAll", displayName = "Select All Buttons", category = "Appearance") private boolean selectAll = false; private boolean selectAll_set = false; /** *

Show the Add All and Remove All buttons.

*/ public boolean isSelectAll() { if (this.selectAll_set) { return this.selectAll; } ValueExpression _vb = getValueExpression("selectAll"); if (_vb != null) { Object _result = _vb.getValue(getFacesContext().getELContext()); if (_result == null) { return false; } else { return ((Boolean) _result).booleanValue(); } } return false; } /** *

Show the Add All and Remove All buttons.

* @see #isSelectAll() */ public void setSelectAll(boolean selectAll) { this.selectAll = selectAll; this.selectAll_set = true; } /** *

The label for the list of selected items.

*/ @Property(name = "selectedItemsLabel", displayName = "Selected Items label", category = "Appearance", editorClassName = "com.sun.rave.propertyeditors.StringPropertyEditor") private String selectedItemsLabel = null; /** *

The label for the list of selected items.

*/ public String getSelectedItemsLabel() { if (this.selectedItemsLabel != null) { return this.selectedItemsLabel; } ValueExpression _vb = getValueExpression("selectedItemsLabel"); if (_vb != null) { return (String) _vb.getValue(getFacesContext().getELContext()); } return null; } /** *

The label for the list of selected items.

* @see #getSelectedItemsLabel() */ public void setSelectedItemsLabel(String selectedItemsLabel) { this.selectedItemsLabel = selectedItemsLabel; } /** *

If sorted is set to true, the items on the available list are shown in * alphabetical order. The items on the selected options list are * also shown in alphabetical order, unless the moveButtons * attribute is true, in which case the user is expected to order * the elements.

*/ @Property(name = "sorted", displayName = "Sorted", category = "Data") private boolean sorted = false; private boolean sorted_set = false; /** *

If sorted is set to true, the items on the available list are shown in * alphabetical order. The items on the selected options list are * also shown in alphabetical order, unless the moveButtons * attribute is true, in which case the user is expected to order * the elements.

*/ public boolean isSorted() { if (this.sorted_set) { return this.sorted; } ValueExpression _vb = getValueExpression("sorted"); if (_vb != null) { Object _result = _vb.getValue(getFacesContext().getELContext()); if (_result == null) { return false; } else { return ((Boolean) _result).booleanValue(); } } return false; } /** *

If sorted is set to true, the items on the available list are shown in * alphabetical order. The items on the selected options list are * also shown in alphabetical order, unless the moveButtons * attribute is true, in which case the user is expected to order * the elements.

* @see #isSorted() */ public void setSorted(boolean sorted) { this.sorted = sorted; this.sorted_set = true; } /** *

Use the vertical layout instead of the default horizontal layout. The * vertical layout displays the available items list above the selected * items list.

*/ @Property(name = "vertical", displayName = "Vertical layout", category = "Appearance") private boolean vertical = false; private boolean vertical_set = false; /** *

Use the vertical layout instead of the default horizontal layout. The * vertical layout displays the available items list above the selected * items list.

*/ public boolean isVertical() { if (this.vertical_set) { return this.vertical; } ValueExpression _vb = getValueExpression("vertical"); if (_vb != null) { Object _result = _vb.getValue(getFacesContext().getELContext()); if (_result == null) { return false; } else { return ((Boolean) _result).booleanValue(); } } return false; } /** *

Use the vertical layout instead of the default horizontal layout. The * vertical layout displays the available items list above the selected * items list.

* @see #isVertical() */ public void setVertical(boolean vertical) { this.vertical = vertical; this.vertical_set = true; } /** *

Restore the state of this component.

*/ @Override public void restoreState(FacesContext _context, Object _state) { Object _values[] = (Object[]) _state; super.restoreState(_context, _values[0]); this.availableItemsLabel = (String) _values[1]; this.duplicateSelections = ((Boolean) _values[2]).booleanValue(); this.duplicateSelections_set = ((Boolean) _values[3]).booleanValue(); this.moveButtons = ((Boolean) _values[4]).booleanValue(); this.moveButtons_set = ((Boolean) _values[5]).booleanValue(); this.selectAll = ((Boolean) _values[6]).booleanValue(); this.selectAll_set = ((Boolean) _values[7]).booleanValue(); this.selectedItemsLabel = (String) _values[8]; this.sorted = ((Boolean) _values[9]).booleanValue(); this.sorted_set = ((Boolean) _values[10]).booleanValue(); this.vertical = ((Boolean) _values[11]).booleanValue(); this.vertical_set = ((Boolean) _values[12]).booleanValue(); } /** *

Save the state of this component.

*/ @Override public Object saveState(FacesContext _context) { Object _values[] = new Object[13]; _values[0] = super.saveState(_context); _values[1] = this.availableItemsLabel; _values[2] = this.duplicateSelections ? Boolean.TRUE : Boolean.FALSE; _values[3] = this.duplicateSelections_set ? Boolean.TRUE : Boolean.FALSE; _values[4] = this.moveButtons ? Boolean.TRUE : Boolean.FALSE; _values[5] = this.moveButtons_set ? Boolean.TRUE : Boolean.FALSE; _values[6] = this.selectAll ? Boolean.TRUE : Boolean.FALSE; _values[7] = this.selectAll_set ? Boolean.TRUE : Boolean.FALSE; _values[8] = this.selectedItemsLabel; _values[9] = this.sorted ? Boolean.TRUE : Boolean.FALSE; _values[10] = this.sorted_set ? Boolean.TRUE : Boolean.FALSE; _values[11] = this.vertical ? Boolean.TRUE : Boolean.FALSE; _values[12] = this.vertical_set ? Boolean.TRUE : Boolean.FALSE; return _values; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy