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