001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.ecm.platform.ui.web.directory; 021 022import java.io.Serializable; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.LinkedHashMap; 028import java.util.List; 029import java.util.Locale; 030import java.util.Map; 031 032import javax.faces.component.UIComponent; 033import javax.faces.component.UIInput; 034import javax.faces.component.behavior.ClientBehaviorHolder; 035import javax.faces.context.FacesContext; 036import javax.faces.el.ValueBinding; 037 038import org.apache.commons.lang.StringUtils; 039import org.apache.commons.logging.Log; 040import org.apache.commons.logging.LogFactory; 041import org.nuxeo.common.utils.i18n.I18NUtils; 042 043/** 044 * @author <a href="mailto:glefter@nuxeo.com">George Lefter</a> 045 */ 046public class ChainSelectListboxComponent extends UIInput implements ClientBehaviorHolder { 047 048 public static final String COMPONENT_TYPE = "nxdirectory.chainSelectListbox"; 049 050 public static final String COMPONENT_FAMILY = "nxdirectory.chainSelectListbox"; 051 052 private static final Log log = LogFactory.getLog(ChainSelectListboxComponent.class); 053 054 public boolean ajaxUpdated = false; 055 056 private String directoryName; 057 058 private VocabularyEntryList directoryValues; 059 060 private Boolean displayIdAndLabel = false; 061 062 private Boolean displayObsoleteEntries = false; 063 064 private String onchange; 065 066 private int index; 067 068 private String displayIdAndLabelSeparator = " "; 069 070 private String cssStyle; 071 072 private String cssStyleClass; 073 074 private String size; 075 076 private Boolean localize = false; 077 078 private String displayValueOnlySeparator; 079 080 private String ordering; 081 082 private String display; 083 084 public ChainSelectListboxComponent() { 085 setRendererType(COMPONENT_TYPE); 086 } 087 088 @Override 089 public String getFamily() { 090 return COMPONENT_FAMILY; 091 } 092 093 public boolean isMultiSelect() { 094 ChainSelect chain = getChain(); 095 if (chain != null) { 096 boolean isLastSelect = getIndex() == chain.getSize() - 1; 097 boolean isChainMultiSelect = chain.getBooleanProperty("multiSelect", false); 098 return isLastSelect && isChainMultiSelect; 099 } 100 return false; 101 } 102 103 public String getDisplayIdAndLabelSeparator() { 104 return displayIdAndLabelSeparator; 105 } 106 107 public void setDisplayIdAndLabelSeparator(String displayIdAndLabelSeparator) { 108 this.displayIdAndLabelSeparator = displayIdAndLabelSeparator; 109 } 110 111 @Override 112 public void restoreState(FacesContext context, Object state) { 113 Object[] values = (Object[]) state; 114 super.restoreState(context, values[0]); 115 index = (Integer) values[1]; 116 displayIdAndLabel = (Boolean) values[2]; 117 displayIdAndLabelSeparator = (String) values[3]; 118 displayObsoleteEntries = (Boolean) values[4]; 119 ajaxUpdated = (Boolean) values[5]; 120 directoryName = (String) values[6]; 121 localize = (Boolean) values[7]; 122 displayValueOnlySeparator = (String) values[10]; 123 onchange = (String) values[11]; 124 cssStyle = (String) values[12]; 125 cssStyleClass = (String) values[13]; 126 size = (String) values[14]; 127 directoryValues = (VocabularyEntryList) values[15]; 128 ordering = (String) values[16]; 129 display = (String) values[17]; 130 } 131 132 @Override 133 public Object saveState(FacesContext context) { 134 Object[] values = new Object[18]; 135 values[0] = super.saveState(context); 136 values[1] = getIndex(); 137 values[2] = displayIdAndLabel; 138 values[3] = displayIdAndLabelSeparator; 139 values[4] = displayObsoleteEntries; 140 values[5] = ajaxUpdated; 141 values[6] = directoryName; 142 values[7] = localize; 143 values[10] = displayValueOnlySeparator; 144 values[11] = onchange; 145 values[12] = cssStyle; 146 values[13] = cssStyleClass; 147 values[14] = size; 148 values[15] = directoryValues; 149 values[16] = ordering; 150 values[17] = display; 151 return values; 152 } 153 154 public String getDirectoryName() { 155 ValueBinding vb = getValueBinding("directoryName"); 156 if (vb != null) { 157 return (String) vb.getValue(FacesContext.getCurrentInstance()); 158 } else { 159 return directoryName; 160 } 161 } 162 163 public void setDirectoryName(String newDirectory) { 164 directoryName = newDirectory; 165 } 166 167 public VocabularyEntryList getDirectoryValues() { 168 ValueBinding vb = getValueBinding("directoryValues"); 169 if (vb != null) { 170 return (VocabularyEntryList) vb.getValue(FacesContext.getCurrentInstance()); 171 } else { 172 return null; 173 } 174 } 175 176 public void setDirectoryValues(VocabularyEntryList directoryValues) { 177 this.directoryValues = directoryValues; 178 } 179 180 public Map<String, DirectorySelectItem> getOptions() { 181 index = getIndex(); 182 if (index == 0 || getChain().getSelection(0).getColumnValue(index - 1) != null) { 183 return rebuildOptions(); 184 } 185 return new HashMap<String, DirectorySelectItem>(); 186 } 187 188 public Boolean getDisplayIdAndLabel() { 189 return displayIdAndLabel; 190 } 191 192 public void setDisplayIdAndLabel(Boolean displayIdAndLabel) { 193 this.displayIdAndLabel = displayIdAndLabel; 194 } 195 196 public Boolean getDisplayObsoleteEntries() { 197 return displayObsoleteEntries; 198 } 199 200 public void setDisplayObsoleteEntries(Boolean showObsolete) { 201 displayObsoleteEntries = showObsolete; 202 } 203 204 public void setOnchange(String onchange) { 205 this.onchange = onchange; 206 } 207 208 public String getOnchange() { 209 return onchange; 210 } 211 212 public ChainSelect getChain() { 213 UIComponent component = getParent(); 214 while (component != null && !(component instanceof ChainSelect)) { 215 component = component.getParent(); 216 } 217 return (ChainSelect) component; 218 } 219 220 public Object getProperty(String name) { 221 ValueBinding vb = getValueBinding(name); 222 if (vb != null) { 223 return vb.getValue(FacesContext.getCurrentInstance()); 224 } else { 225 return getAttributes().get(name); 226 } 227 } 228 229 public String getStringProperty(String name, String defaultValue) { 230 String value = (String) getProperty(name); 231 return value != null ? value : defaultValue; 232 } 233 234 public Boolean getBooleanProperty(String name, Boolean defaultValue) { 235 Boolean value = (Boolean) getProperty(name); 236 return value != null ? value : defaultValue; 237 } 238 239 public String getDisplayValueOnlySeparator() { 240 return displayValueOnlySeparator; 241 } 242 243 public void setDisplayValueOnlySeparator(String displayValueOnlySeparator) { 244 this.displayValueOnlySeparator = displayValueOnlySeparator; 245 } 246 247 public boolean isAjaxUpdated() { 248 return ajaxUpdated; 249 } 250 251 public void setAjaxUpdated(boolean ajaxUpdated) { 252 this.ajaxUpdated = ajaxUpdated; 253 } 254 255 /** 256 * @return position of this component in the parent children list 257 */ 258 public Integer getIndex() { 259 // return index; 260 ValueBinding vb = getValueBinding("index"); 261 if (vb != null) { 262 return (Integer) vb.getValue(FacesContext.getCurrentInstance()); 263 } else { 264 return index; 265 } 266 } 267 268 public void setIndex(Integer index) { 269 this.index = index; 270 } 271 272 public String getCssStyle() { 273 return cssStyle; 274 } 275 276 public void setCssStyle(String cssStyle) { 277 this.cssStyle = cssStyle; 278 } 279 280 public String getCssStyleClass() { 281 return cssStyleClass; 282 } 283 284 public void setCssStyleClass(String cssStyleClass) { 285 this.cssStyleClass = cssStyleClass; 286 } 287 288 public String getSize() { 289 return size; 290 } 291 292 public void setSize(String size) { 293 this.size = size; 294 } 295 296 public Boolean getLocalize() { 297 return localize; 298 } 299 300 public void setLocalize(Boolean localize) { 301 this.localize = localize; 302 } 303 304 /** 305 * Reload listbox values based on previous selections in the chain. (functionality moved from ChainSelect) 306 */ 307 public LinkedHashMap<String, DirectorySelectItem> rebuildOptions() { 308 309 index = getIndex(); 310 311 Map<String, Serializable> filter = new HashMap<String, Serializable>(); 312 Boolean displayObsolete = getBooleanProperty("displayObsoleteEntries", Boolean.FALSE); 313 if (!displayObsolete) { 314 filter.put("obsolete", 0); 315 } 316 317 String directoryName = getDirectoryName(); 318 String defaultRootKey = getChain().getDefaultRootKey(); 319 if (index == 0) { 320 if (directoryName != null) { 321 if (DirectoryHelper.instance().hasParentColumn(directoryName)) { 322 filter.put("parent", defaultRootKey); 323 } 324 } else { 325 filter.put("parent", defaultRootKey); 326 } 327 } else { 328 boolean qualifiedParentKeys = getChain().isQualifiedParentKeys(); 329 String keySeparator = getChain().getKeySeparator(); 330 Selection sel = getChain().getSelections()[0]; 331 String parentValue = sel.getParentKey(index, qualifiedParentKeys, keySeparator); 332 if (parentValue == null) { 333 // use default parent key 334 parentValue = defaultRootKey; 335 } 336 filter.put("parent", parentValue); 337 } 338 339 VocabularyEntryList directoryValues = getDirectoryValues(); 340 341 List<DirectorySelectItem> list; 342 if (directoryName != null) { 343 list = DirectoryHelper.instance().getSelectItems(directoryName, filter); 344 } else { 345 list = DirectoryHelper.getSelectItems(directoryValues, filter); 346 } 347 348 for (DirectorySelectItem item : list) { 349 String id = (String) item.getValue(); 350 String label = item.getLabel(); 351 String translatedLabel = label; 352 Boolean localize = getBooleanProperty("localize", Boolean.FALSE); 353 if (localize) { 354 translatedLabel = translate(label); 355 } 356 item.setLocalizedLabel(translatedLabel); 357 358 String displayedLabel = translatedLabel; 359 Boolean displayIdAndLabel = getBooleanProperty("displayIdAndLabel", Boolean.FALSE); 360 if (displayIdAndLabel) { 361 displayedLabel = id + displayIdAndLabelSeparator + label; 362 } 363 item.setDisplayedLabel(displayedLabel); 364 } 365 String ordering = getStringProperty("ordering", ""); 366 if (ordering != null && !"".equals(ordering)) { 367 Collections.sort(list, new DirectorySelectItemComparator(ordering)); 368 } 369 370 LinkedHashMap<String, DirectorySelectItem> options = new LinkedHashMap<String, DirectorySelectItem>(); 371 options.clear(); 372 for (DirectorySelectItem item : list) { 373 options.put((String) item.getValue(), item); 374 } 375 376 return options; 377 } 378 379 private static String translate(String label) { 380 FacesContext context = FacesContext.getCurrentInstance(); 381 String bundleName = context.getApplication().getMessageBundle(); 382 Locale locale = context.getViewRoot().getLocale(); 383 label = I18NUtils.getMessageString(bundleName, label, null, locale); 384 return label; 385 } 386 387 /** 388 * This method reads submitted data and rebuilds the current list of values based on selections in the parent 389 * components. 390 */ 391 @Override 392 public void decode(FacesContext context) { 393 // FIXME: this code is nonsense, what it's doing and why is 394 // perfectly unclear 395 396 ChainSelect chain = getChain(); 397 if (chain.getDisplayValueOnly()) { 398 return; 399 } 400 401 index = getIndex(); 402 403 chain.setCompAtIndex(index, this); 404 List<String> keyList = chain.getSelectionKeyList(); 405 int size = chain.getSize(); 406 String formerValue = chain.getSelection(0).getColumnValue(index); 407 408 if (index == 0) { 409 chain.setLastSelectedComponentIndex(Integer.MAX_VALUE); 410 keyList.clear(); 411 } 412 413 if (chain.getLastSelectedComponentIndex() < index) { 414 for (int i = index; i < size; i++) { 415 chain.setOptions(i, null); 416 } 417 Map<String, DirectorySelectItem> options = rebuildOptions(); 418 chain.setOptions(index, options); 419 return; 420 } 421 422 Map<String, String> requestMap = context.getExternalContext().getRequestParameterMap(); 423 Map<String, String[]> requestValueMap = context.getExternalContext().getRequestParameterValuesMap(); 424 425 String name = getClientId(context); 426 427 String value = requestMap.get(name); 428 if (value == null || value.length() == 0) { 429 if (chain.getLastSelectedComponentIndex() > index) { 430 chain.setLastSelectedComponentIndex(index); 431 } 432 433 for (int i = index; i < chain.getSize(); i++) { 434 chain.setOptions(i, null); 435 } 436 Map<String, DirectorySelectItem> options = rebuildOptions(); 437 chain.setOptions(index, options); 438 } else { 439 keyList.add(value); 440 } 441 442 if (!StringUtils.equals(value, formerValue)) { 443 chain.setLastSelectedComponentIndex(index); 444 445 for (int i = index; i < chain.getSize(); i++) { 446 chain.setOptions(i, null); 447 } 448 } 449 450 String[] lastValues = requestValueMap.get(name); 451 452 boolean lastValueIsOk = lastValues != null && lastValues.length != 0 && !StringUtils.isEmpty(lastValues[0]); 453 454 Selection[] selections; 455 456 boolean stop = chain.getLastSelectedComponentIndex() < index; 457 if (index == size - 1 && lastValueIsOk && !stop) { 458 String[] keyListArray = new String[size]; 459 selections = new Selection[lastValues.length]; 460 keyListArray = keyList.toArray(keyListArray); 461 for (int i = 0; i < lastValues.length; i++) { 462 keyListArray[size - 1] = lastValues[i]; 463 selections[i] = chain.createSelection(keyListArray); 464 } 465 } else { 466 selections = new Selection[1]; 467 String[] columns = keyList.toArray(new String[0]); 468 selections[0] = chain.createSelection(columns); 469 } 470 471 if (chain.getLastSelectedComponentIndex() == index) { 472 chain.setSelections(selections); 473 } 474 475 Map<String, DirectorySelectItem> options = rebuildOptions(); 476 chain.setOptions(index, options); 477 } 478 479 public String getOrdering() { 480 return ordering; 481 } 482 483 public void setOrdering(String ordering) { 484 this.ordering = ordering; 485 } 486 487 public String getDisplay() { 488 if (display != null) { 489 return display; 490 } else if (Boolean.TRUE.equals(displayIdAndLabel)) { 491 return "idAndLabel"; 492 } 493 return "label"; 494 } 495 496 public void setDisplay(String display) { 497 this.display = display; 498 } 499 500 /** 501 * @since 6.0 502 */ 503 private static final Collection<String> EVENT_NAMES = Collections.unmodifiableCollection(Arrays.asList("blur", 504 "change", "valueChange", "click", "dblclick", "focus", "keydown", "keypress", "keyup", "mousedown", 505 "mousemove", "mouseout", "mouseover", "mouseup", "select")); 506 507 @Override 508 public Collection<String> getEventNames() { 509 return EVENT_NAMES; 510 } 511 512 @Override 513 public String getDefaultEventName() { 514 return "valueChange"; 515 } 516 517}