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