001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.acacia.client.widgets; 029 030import org.opencms.acacia.client.CmsAttributeHandler; 031import org.opencms.acacia.client.css.I_CmsWidgetsLayoutBundle; 032import org.opencms.acacia.client.ui.CmsAttributeValueView; 033import org.opencms.acacia.shared.CmsEntity; 034import org.opencms.ade.contenteditor.client.CmsContentEditor; 035import org.opencms.ade.contenteditor.client.I_CmsEntityChangeListener; 036import org.opencms.ade.contenteditor.client.css.I_CmsLayoutBundle; 037import org.opencms.gwt.client.ui.input.CmsSelectBox; 038import org.opencms.util.CmsStringUtil; 039 040import java.util.Arrays; 041import java.util.LinkedHashMap; 042import java.util.List; 043 044import com.google.common.collect.Maps; 045import com.google.gwt.dom.client.Element; 046import com.google.gwt.event.dom.client.FocusEvent; 047import com.google.gwt.event.dom.client.FocusHandler; 048import com.google.gwt.event.logical.shared.ValueChangeEvent; 049import com.google.gwt.event.logical.shared.ValueChangeHandler; 050import com.google.gwt.event.shared.HandlerRegistration; 051import com.google.gwt.user.client.Window; 052import com.google.gwt.user.client.ui.Composite; 053import com.google.gwt.user.client.ui.Widget; 054 055/** 056 * Select widget which uses other values from the content as select options.<p> 057 * 058 * This works as follows: The widget is given a configuration consisting of three pipe-separated OpenCms content value paths. 059 * The first path is used to select a set of nested content values. The second and third paths are relative to the first path 060 * and are used to select a select option and a select option display text from the nested contents matching the first path. 061 * Note that if you omit indexes on a component of the first path, all indexes will be matched. 062 * You can also use relative paths for the first path. Like '../NestedNode' or '../../OtherNode'.<p> 063 * 064 * The widget attaches event listeners to the editor so it can dynamically update the list of select options when the content changes. 065 */ 066public class CmsDependentSelectWidget extends Composite implements I_CmsEditWidget, I_CmsHasDisplayDirection { 067 068 /** The global select box. */ 069 protected CmsSelectBox m_selectBox = new CmsSelectBox(); 070 071 /** Value of the activation. */ 072 private boolean m_active = true; 073 074 /** Path components of the base path. */ 075 private String[] m_basePath; 076 077 /** Path components of the path used to select the 'nice name' for the select option. */ 078 private String[] m_descriptionPath; 079 080 /** The last value set through the setValue method. This is not necessarily the current widget value. */ 081 private String m_externalValue; 082 083 /** Path components of the path used to select the option value. */ 084 private String[] m_valuePath; 085 086 /** 087 * Creates a new widget instance.<p> 088 * 089 * @param configuration the widget configuration 090 */ 091 public CmsDependentSelectWidget(String configuration) { 092 093 List<String> listConfig = CmsStringUtil.splitAsList(configuration, "|"); 094 if (listConfig.size() == 0) { 095 Window.alert("Illegal dependent select widget configuration: " + configuration); 096 } 097 if (listConfig.size() == 1) { 098 m_basePath = splitPath(listConfig.get(0)); 099 m_valuePath = splitPath("VALUE"); 100 m_descriptionPath = m_valuePath; 101 } else if (listConfig.size() == 2) { 102 m_basePath = splitPath(listConfig.get(0)); 103 m_valuePath = splitPath(listConfig.get(1)); 104 m_descriptionPath = m_valuePath; 105 } else if (listConfig.size() >= 3) { 106 m_basePath = splitPath(listConfig.get(0)); 107 m_valuePath = splitPath(listConfig.get(1)); 108 m_descriptionPath = splitPath(listConfig.get(2)); 109 } 110 111 // Place the check above the box using a vertical panel. 112 m_selectBox.addStyleName(I_CmsWidgetsLayoutBundle.INSTANCE.widgetCss().selectBoxPanel()); 113 m_selectBox.setPopupResize(false); 114 // add some styles to parts of the selectbox. 115 m_selectBox.getOpener().addStyleName(I_CmsWidgetsLayoutBundle.INSTANCE.widgetCss().selectBoxSelected()); 116 m_selectBox.getSelectorPopup().addStyleName(I_CmsLayoutBundle.INSTANCE.globalWidgetCss().selectBoxPopup()); 117 m_selectBox.addValueChangeHandler(new ValueChangeHandler<String>() { 118 119 public void onValueChange(ValueChangeEvent<String> event) { 120 121 fireChangeEvent(); 122 123 } 124 125 }); 126 127 update(CmsContentEditor.getEntity()); 128 initWidget(m_selectBox); 129 } 130 131 /** 132 * @see com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.google.gwt.event.dom.client.FocusHandler) 133 */ 134 public HandlerRegistration addFocusHandler(FocusHandler handler) { 135 136 return addDomHandler(handler, FocusEvent.getType()); 137 } 138 139 /** 140 * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler) 141 */ 142 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) { 143 144 return addHandler(handler, ValueChangeEvent.getType()); 145 } 146 147 /** 148 * Represents a value change event.<p> 149 * Please edit the blog entry text. 150 */ 151 public void fireChangeEvent() { 152 153 ValueChangeEvent.fire(this, m_selectBox.getFormValueAsString()); 154 155 } 156 157 /** 158 * @see org.opencms.acacia.client.widgets.I_CmsHasDisplayDirection#getDisplayingDirection() 159 */ 160 public Direction getDisplayingDirection() { 161 162 return m_selectBox.displayingAbove() ? Direction.above : Direction.below; 163 } 164 165 /** 166 * @see com.google.gwt.user.client.ui.HasValue#getValue() 167 */ 168 public String getValue() { 169 170 return m_selectBox.getFormValueAsString(); 171 } 172 173 /** 174 * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#isActive() 175 */ 176 public boolean isActive() { 177 178 return m_active; 179 } 180 181 /** 182 * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#onAttachWidget() 183 */ 184 public void onAttachWidget() { 185 186 super.onAttach(); 187 } 188 189 /** 190 * @see com.google.gwt.user.client.ui.Widget#onLoad() 191 */ 192 @Override 193 public void onLoad() { 194 195 update(CmsContentEditor.getEntity()); 196 197 CmsContentEditor.addEntityChangeListener(new I_CmsEntityChangeListener() { 198 199 public void onEntityChange(CmsEntity entity) { 200 201 update(CmsContentEditor.getEntity()); 202 } 203 }, null); 204 } 205 206 /** 207 * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#owns(com.google.gwt.dom.client.Element) 208 */ 209 public boolean owns(Element element) { 210 211 return getElement().isOrHasChild(element); 212 } 213 214 /** 215 * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#setActive(boolean) 216 */ 217 public void setActive(boolean active) { 218 219 // check if value change. If not do nothing. 220 if (m_active == active) { 221 return; 222 } 223 // set new value. 224 m_active = active; 225 // set the new value to the selectbox. 226 m_selectBox.setEnabled(active); 227 // fire change event if necessary. 228 if (active) { 229 fireChangeEvent(); 230 } 231 232 } 233 234 /** 235 * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#setName(java.lang.String) 236 */ 237 public void setName(String name) { 238 239 // no input field so nothing to do 240 241 } 242 243 /** 244 * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object) 245 */ 246 public void setValue(String value) { 247 248 setValue(value, false); 249 250 } 251 252 /** 253 * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object, boolean) 254 */ 255 public void setValue(String value, boolean fireEvents) { 256 257 m_selectBox.setFormValueAsString(value); 258 m_externalValue = value; 259 if (fireEvents) { 260 fireChangeEvent(); 261 } 262 263 } 264 265 /** 266 * Updates the select options from the given entity.<p> 267 * 268 * @param entity a top-level content entity 269 */ 270 public void update(CmsEntity entity) { 271 272 List<Object> baseObjects = CmsEntity.getValuesForPath(entity, getAbsolutePath(m_basePath)); 273 LinkedHashMap<String, String> options = Maps.newLinkedHashMap(); 274 for (Object baseObject : baseObjects) { 275 List<Object> valueValues = CmsEntity.getValuesForPath(baseObject, m_valuePath); 276 List<Object> descriptionValues = CmsEntity.getValuesForPath(baseObject, m_descriptionPath); 277 if (valueValues.size() > 0) { 278 String value = (String)valueValues.get(0); 279 String description = value; 280 if (descriptionValues.size() > 0) { 281 description = (String)descriptionValues.get(0); 282 } 283 options.put(value, description); 284 } 285 } 286 replaceOptions(options); 287 } 288 289 /** 290 * Returns the absolute path elements. 291 * In case off a relative path indicated by leading '..' elements, the local value path will be used as a starting point. 292 * Otherwise the given path elements will be returned.<p> 293 * 294 * @param pathElements the path elements 295 * 296 * @return the absolute path elements 297 */ 298 private String[] getAbsolutePath(String[] pathElements) { 299 300 String[] result = pathElements; 301 if (pathElements[0].equals("..")) { 302 Widget parent = getParent(); 303 CmsAttributeValueView valueView = null; 304 while ((parent != null) && !(parent instanceof CmsAttributeValueView)) { 305 parent = parent.getParent(); 306 } 307 if (parent instanceof CmsAttributeValueView) { 308 valueView = (CmsAttributeValueView)parent; 309 } 310 if (valueView != null) { 311 CmsAttributeHandler handler = valueView.getHandler(); 312 String localPath = handler.getSimplePath(-1); 313 int pathIndex = 0; 314 while (pathElements[pathIndex].equals("..")) { 315 pathIndex++; 316 localPath = localPath.substring(0, localPath.lastIndexOf("/")); 317 } 318 String[] localPathElements = splitPath(localPath); 319 // join the to arrays 320 result = Arrays.copyOf(localPathElements, (localPathElements.length + pathElements.length) - pathIndex); 321 System.arraycopy( 322 pathElements, 323 pathIndex, 324 result, 325 localPathElements.length, 326 pathElements.length - pathIndex); 327 } 328 } 329 return result; 330 } 331 332 /** 333 * Replaces the select options with the given options.<p> 334 * 335 * @param options the map of select options (keys are option values, values are option descriptions) 336 */ 337 private void replaceOptions(LinkedHashMap<String, String> options) { 338 339 String oldValue = m_selectBox.getFormValueAsString(); 340 for (String additionalValue : new String[] {oldValue, m_externalValue}) { 341 if (!options.containsKey(additionalValue)) { 342 options.put(additionalValue, additionalValue); 343 } 344 } 345 if (options.containsKey("")) { 346 options.put( 347 "", 348 org.opencms.gwt.client.Messages.get().key( 349 org.opencms.gwt.client.Messages.GUI_SELECTBOX_EMPTY_SELECTION_0)); 350 } 351 m_selectBox.setItems(options); 352 m_selectBox.setFormValueAsString(oldValue); 353 } 354 355 /** 356 * Splits a path into components.<p> 357 * 358 * @param path the path to split 359 * @return the path components 360 */ 361 private String[] splitPath(String path) { 362 363 path = path.replaceAll("^/", "").replaceAll("/$", ""); 364 return path.split("/"); 365 } 366 367}