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.gwt.client.property; 029 030import org.opencms.gwt.client.Messages; 031import org.opencms.gwt.client.ui.input.I_CmsFormField; 032import org.opencms.gwt.client.ui.input.I_CmsHasGhostValue; 033import org.opencms.gwt.client.ui.input.I_CmsStringModel; 034import org.opencms.gwt.client.ui.input.form.CmsBasicFormField; 035import org.opencms.gwt.client.ui.input.form.CmsSimpleFormFieldPanel; 036import org.opencms.gwt.shared.property.CmsClientProperty; 037import org.opencms.gwt.shared.property.CmsClientProperty.Mode; 038import org.opencms.gwt.shared.property.CmsPathValue; 039import org.opencms.gwt.shared.property.CmsPropertyModification; 040import org.opencms.util.CmsPair; 041import org.opencms.util.CmsStringUtil; 042import org.opencms.util.CmsUUID; 043import org.opencms.xml.content.CmsXmlContentProperty; 044 045import java.util.ArrayList; 046import java.util.Collections; 047import java.util.HashMap; 048import java.util.List; 049import java.util.Map; 050 051import com.google.common.base.Joiner; 052import com.google.common.base.Objects; 053import com.google.gwt.event.logical.shared.ValueChangeEvent; 054import com.google.gwt.event.logical.shared.ValueChangeHandler; 055import com.google.gwt.event.shared.EventBus; 056import com.google.gwt.event.shared.GwtEvent; 057import com.google.gwt.event.shared.HandlerRegistration; 058import com.google.gwt.event.shared.SimpleEventBus; 059import com.google.gwt.user.client.ui.Widget; 060 061/** 062 * The sitemap entry editor class for the VFS mode.<p> 063 * 064 * @since 8.0.0 065 */ 066public class CmsSimplePropertyEditor extends A_CmsPropertyEditor { 067 068 /** The map of models of the fields. */ 069 Map<String, I_CmsStringModel> m_models = new HashMap<String, I_CmsStringModel>(); 070 071 /** The properties of the entry. */ 072 private Map<String, CmsClientProperty> m_properties; 073 074 /** 075 * Creates a new sitemap entry editor instance for the VFS mode.<p> 076 * 077 * @param propConfig the property configuration 078 * @param handler the sitemap entry editor handler 079 */ 080 public CmsSimplePropertyEditor(Map<String, CmsXmlContentProperty> propConfig, I_CmsPropertyEditorHandler handler) { 081 082 super(propConfig, handler); 083 m_properties = CmsClientProperty.makeLazyCopy(handler.getOwnProperties()); 084 } 085 086 /** 087 * @see org.opencms.gwt.client.property.A_CmsPropertyEditor#buildFields() 088 */ 089 @Override 090 public void buildFields() { 091 092 Map<String, CmsClientProperty> ownProps = m_handler.getOwnProperties(); 093 Map<String, CmsClientProperty> defaultFileProps = m_handler.getDefaultFileProperties(); 094 Map<String, CmsClientProperty> props; 095 CmsUUID id = null; 096 if (!m_handler.isFolder()) { 097 props = ownProps; 098 id = m_handler.getId(); 099 } else if (m_handler.getDefaultFileId() != null) { 100 props = defaultFileProps; 101 id = m_handler.getDefaultFileId(); 102 } else { 103 props = ownProps; 104 id = m_handler.getId(); 105 } 106 props = CmsClientProperty.makeLazyCopy(props); 107 List<String> keys = new ArrayList<String>(m_propertyConfig.keySet()); 108 moveToTop(keys, CmsClientProperty.PROPERTY_NAVTEXT); 109 moveToTop(keys, CmsClientProperty.PROPERTY_DESCRIPTION); 110 moveToTop(keys, CmsClientProperty.PROPERTY_TITLE); 111 moveToTop(keys, CmsPropertyModification.FILE_NAME_PROPERTY); 112 for (String propName : keys) { 113 buildField(props, propName, Mode.effective, id); 114 } 115 } 116 117 /** 118 * @see org.opencms.gwt.client.property.A_CmsPropertyEditor#addSpecialFields() 119 */ 120 @Override 121 protected void addSpecialFields() { 122 123 // we don't want any special fields 124 } 125 126 /** 127 * Checks whether an empty string should always be allowed for the property, regardless of validation settings. 128 * 129 * @param name the property name 130 * @return true if the empty string should always be allowed 131 */ 132 protected boolean isAlwaysAllowEmpty(String name) { 133 134 return true; 135 } 136 137 /** 138 * Can be overridden by subclasses to customize field validators. 139 * 140 * @param propDef the property definition from which the field was created 141 * @param field the field 142 */ 143 protected void maybeAddCustomValidator(CmsXmlContentProperty propDef, I_CmsFormField field) { 144 145 // do nothing 146 } 147 148 /** 149 * @see org.opencms.gwt.client.property.A_CmsPropertyEditor#setupFieldContainer() 150 */ 151 @Override 152 protected void setupFieldContainer() { 153 154 CmsSimpleFormFieldPanel panel = new CmsSimpleFormFieldPanel(); 155 m_form.setWidget(panel); 156 } 157 158 /** 159 * Builds a single form field.<p> 160 * 161 * @param ownProps the entry's own properties 162 * @param propName the property name 163 * @param mode the mode which controls which kind of field will be built 164 * @param id the id of the resource for which to build the field 165 */ 166 private void buildField( 167 Map<String, CmsClientProperty> ownProps, 168 final String propName, 169 CmsClientProperty.Mode mode, 170 CmsUUID id) { 171 172 CmsXmlContentProperty propDef = m_propertyConfig.get(propName); 173 174 if (propDef == null) { 175 String widget = CmsClientProperty.PROPERTY_TEMPLATE.equals(propName) ? "template" : "string"; 176 propDef = new CmsXmlContentProperty( 177 propName, 178 "string", 179 widget, 180 "", 181 null, 182 null, 183 null, 184 null, 185 null, 186 null, 187 null); 188 } 189 190 if (mode != Mode.effective) { 191 propDef = propDef.withNiceName(propName); 192 } 193 194 CmsClientProperty ownProp = ownProps.get(propName); 195 CmsPathValue pathValue = CmsClientProperty.getPathValue(ownProp, mode).prepend(id + "/" + propName + "/"); 196 197 //CHECK: should fields other than NavText be really automatically allowed to be empty in navigation mode? 198 CmsBasicFormField field = CmsBasicFormField.createField( 199 propDef, 200 pathValue.getPath(), 201 this, 202 Collections.<String, String> emptyMap(), 203 isAlwaysAllowEmpty(propDef.getName())); 204 maybeAddCustomValidator(propDef, field); 205 206 CmsPair<String, String> defaultValueAndOrigin = getDefaultValueToDisplay(ownProp, mode); 207 String defaultValue = ""; 208 String origin = ""; 209 if (defaultValueAndOrigin != null) { 210 defaultValue = defaultValueAndOrigin.getFirst(); 211 origin = defaultValueAndOrigin.getSecond(); 212 } 213 Widget w = (Widget)field.getWidget(); 214 I_CmsStringModel model = getStringModel(pathValue); 215 field.bind(model); 216 boolean ghost = CmsStringUtil.isEmptyOrWhitespaceOnly(pathValue.getValue()); 217 String initialValue = pathValue.getValue(); 218 if (w instanceof I_CmsHasGhostValue) { 219 ((I_CmsHasGhostValue)w).setGhostValue(defaultValue, ghost); 220 if (ghost) { 221 initialValue = null; 222 } 223 } 224 225 boolean isShowingGhost = ghost && !CmsStringUtil.isEmpty(defaultValue); 226 227 if (isShowingGhost) { 228 field.getLayoutData().put("info", origin); 229 } 230 if (!ghost || isShowingGhost) { 231 field.getLayoutData().put(CmsPropertyPanel.LD_DISPLAY_VALUE, "true"); 232 } 233 field.getLayoutData().put(CmsPropertyPanel.LD_PROPERTY, propName); 234 m_form.addField(field, initialValue); 235 } 236 237 /** 238 * Creates a string model which uses a field of a CmsClientProperty for storing its value.<p> 239 * 240 * @param id the structure id 241 * @param propName the property id 242 * @param isStructure if true, the structure value field should be used, else the resource value field 243 * 244 * 245 * @return the new model object 246 */ 247 private I_CmsStringModel createStringModel(final CmsUUID id, final String propName, final boolean isStructure) { 248 249 final CmsClientProperty property = m_properties.get(propName); 250 251 return new I_CmsStringModel() { 252 253 private boolean m_active; 254 255 private EventBus m_eventBus = new SimpleEventBus(); 256 257 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) { 258 259 return m_eventBus.addHandler(ValueChangeEvent.getType(), handler); 260 } 261 262 /** 263 * @see com.google.gwt.event.shared.HasHandlers#fireEvent(com.google.gwt.event.shared.GwtEvent) 264 */ 265 public void fireEvent(GwtEvent<?> event) { 266 267 m_eventBus.fireEvent(event); 268 } 269 270 public String getId() { 271 272 return Joiner.on("/").join(id.toString(), propName, isStructure ? "S" : "R"); 273 } 274 275 public String getValue() { 276 277 if (isStructure) { 278 return property.getStructureValue(); 279 } else { 280 return property.getResourceValue(); 281 } 282 } 283 284 public void setValue(String value, boolean notify) { 285 286 if (!m_active) { 287 m_active = true; 288 try { 289 String oldValue = getValue(); 290 boolean changed = !Objects.equal(value, oldValue); 291 if (isStructure) { 292 property.setStructureValue(value); 293 } else { 294 property.setResourceValue(value); 295 } 296 if (notify && changed) { 297 ValueChangeEvent.fire(this, value); 298 } 299 } finally { 300 m_active = false; 301 } 302 } 303 } 304 }; 305 } 306 307 /** 308 * Gets a pair of strings containing the default value to display for a given property and its source.<p> 309 * 310 * @param prop the property 311 * @param mode the mode 312 * 313 * @return a pair of the form (defaultValue, origin) 314 */ 315 private CmsPair<String, String> getDefaultValueToDisplay(CmsClientProperty prop, Mode mode) { 316 317 if ((mode == Mode.structure) && !CmsStringUtil.isEmpty(prop.getResourceValue())) { 318 String message = Messages.get().key(Messages.GUI_ORIGIN_SHARED_0); 319 320 return CmsPair.create(prop.getResourceValue(), message); 321 } 322 CmsClientProperty inheritedProperty = m_handler.getInheritedProperty(prop.getName()); 323 if (CmsClientProperty.isPropertyEmpty(inheritedProperty)) { 324 return null; 325 } 326 CmsPathValue pathValue = inheritedProperty.getPathValue(mode); 327 String message = Messages.get().key(Messages.GUI_ORIGIN_INHERITED_1, inheritedProperty.getOrigin()); 328 return CmsPair.create(pathValue.getValue(), message); 329 } 330 331 /** 332 * Creates a string model for a given property path value, and returns the same model if the same path value is passed in.<p> 333 * 334 * @param pathValue the path value 335 * 336 * @return the model for that path value 337 */ 338 private I_CmsStringModel getStringModel(CmsPathValue pathValue) { 339 340 String path = pathValue.getPath(); 341 I_CmsStringModel model = m_models.get(path); 342 if (model == null) { 343 String[] tokens = path.split("/"); 344 String id = tokens[0]; 345 String propName = tokens[1]; 346 boolean isStructure = tokens[2].equals("S"); 347 model = createStringModel(new CmsUUID(id), propName, isStructure); 348 m_models.put(path, model); 349 } 350 return model; 351 } 352 353 /** 354 * Moves the given property name to the top of the keys if present.<p> 355 * 356 * @param keys the list of keys 357 * @param propertyName the property name to move 358 */ 359 private void moveToTop(List<String> keys, String propertyName) { 360 361 if (keys.contains(propertyName)) { 362 keys.remove(propertyName); 363 keys.add(0, propertyName); 364 } 365 } 366 367}