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}