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.file.CmsResource;
031import org.opencms.gwt.client.Messages;
032import org.opencms.gwt.client.ui.CmsNotification;
033import org.opencms.gwt.client.ui.CmsNotification.Type;
034import org.opencms.gwt.client.ui.CmsPopup;
035import org.opencms.gwt.client.ui.input.CmsDefaultStringModel;
036import org.opencms.gwt.client.ui.input.CmsSelectBox;
037import org.opencms.gwt.client.ui.input.CmsTextBox;
038import org.opencms.gwt.client.ui.input.I_CmsFormField;
039import org.opencms.gwt.client.ui.input.I_CmsFormWidget;
040import org.opencms.gwt.client.ui.input.I_CmsHasGhostValue;
041import org.opencms.gwt.client.ui.input.I_CmsStringModel;
042import org.opencms.gwt.client.ui.input.form.CmsBasicFormField;
043import org.opencms.gwt.client.ui.input.form.CmsForm;
044import org.opencms.gwt.client.ui.input.form.CmsWidgetFactoryRegistry;
045import org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetMultiFactory;
046import org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget;
047import org.opencms.gwt.shared.property.CmsClientTemplateBean;
048import org.opencms.util.CmsUUID;
049import org.opencms.xml.content.CmsXmlContentProperty;
050
051import java.util.LinkedHashMap;
052import java.util.List;
053import java.util.Map;
054
055import com.google.common.base.Optional;
056import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
057
058/**
059 * The abstract base class for dialogs to edit properties.<p>
060 *
061 *  @since 8.0.0
062 */
063public abstract class A_CmsPropertyEditor implements I_CmsFormWidgetMultiFactory {
064
065    /** The field id for the link selector widget. */
066    public static final String FIELD_LINK = "field_link";
067
068    /** The field id of the "url name" form field. */
069    public static final String FIELD_URLNAME = "field_urlname";
070
071    /** The list of all property names. */
072    protected List<String> m_allProps;
073
074    /** The reason to disable the form input fields. */
075    protected String m_disabledReason;
076
077    /** True if only the name edit field is disabled. */
078    protected boolean m_nameOnlyDisabled;
079
080    /** The form containing the fields. */
081    protected CmsForm m_form;
082
083    /** The handler for this sitemap entry editor. */
084    protected I_CmsPropertyEditorHandler m_handler;
085
086    /** The configuration of the properties. */
087    protected Map<String, CmsXmlContentProperty> m_propertyConfig;
088
089    /** The URL name field. */
090    protected I_CmsFormField m_urlNameField;
091
092    /** The model for the URL name field. */
093    protected CmsDefaultStringModel m_urlNameModel;
094
095    /** True if the 'disabled' notification has already been sent. */
096    private boolean m_disabledNotificationSent;
097
098    /**
099     * Creates a new sitemap entry editor.<p>
100     *
101     * @param handler the handler
102     * @param propertyConfig the property configuration
103     */
104    public A_CmsPropertyEditor(
105        Map<String, CmsXmlContentProperty> propertyConfig,
106        final I_CmsPropertyEditorHandler handler) {
107
108        CmsForm form = new CmsForm(null);
109        m_form = form;
110        m_handler = handler;
111        m_propertyConfig = removeHiddenProperties(propertyConfig);
112
113    }
114
115    /**
116     * Checks whether a widget can be used in the sitemap entry editor, and throws an exception otherwise.<p>
117     *
118     * @param key the widget key
119     * @param widget the created widget
120     */
121    public static void checkWidgetRequirements(String key, I_CmsFormWidget widget) {
122
123        if (widget instanceof CmsTinyMCEWidget) {
124            return;
125        }
126        if (!((widget instanceof I_CmsHasGhostValue) && (widget instanceof HasValueChangeHandlers<?>))) {
127            throw new CmsWidgetNotSupportedException(key);
128        }
129    }
130
131    /**
132     * @see org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetMultiFactory#createFormWidget(java.lang.String, java.util.Map, com.google.common.base.Optional)
133     */
134    public I_CmsFormWidget createFormWidget(
135        String key,
136        Map<String, String> widgetParams,
137        Optional<String> defaultValue) {
138
139        I_CmsFormWidget result = null;
140
141        if ("template".equals(key)) {
142            result = createTemplateSelector();
143        } else if (CmsTextBox.WIDGET_TYPE.equals(key)) {
144            CmsTextBox textBox = new CmsTextBox().colorWhite();
145            textBox.setErrorMessageWidth("345px");
146            textBox.setTriggerChangeOnKeyPress(true);
147            // we need this because the tab containing the text box may not be visible
148            // at the time the error message is set, so measuring the field's size would
149            // yield an invalid value
150            result = textBox;
151        } else if (CmsSelectBox.WIDGET_TYPE.equals(key)) {
152            final CmsPropertySelectBox box = new CmsPropertySelectBox(widgetParams);
153            result = box;
154
155        } else {
156            result = CmsWidgetFactoryRegistry.instance().createFormWidget(key, widgetParams, defaultValue);
157            checkWidgetRequirements(key, result);
158        }
159        return result;
160    }
161
162    /**
163     * Disables all input to the form.<p>
164     *
165     * @param disabledReason the reason to display to the user
166     * @param nameOnlyDisabled true if only the name editing field is disabled
167     */
168    public void disableInput(String disabledReason, boolean nameOnlyDisabled) {
169
170        m_disabledReason = disabledReason;
171        m_nameOnlyDisabled = nameOnlyDisabled;
172        if (!nameOnlyDisabled) {
173            for (I_CmsFormField field : m_form.getFields().values()) {
174                field.getWidget().setEnabled(false);
175            }
176        }
177        m_urlNameField.getWidget().setEnabled(false);
178        if (!m_disabledNotificationSent && (m_disabledReason != null)) {
179            CmsNotification.get().send(Type.WARNING, m_disabledReason);
180            m_disabledNotificationSent = true;
181        }
182    }
183
184    /**
185     * Gets the form for the properties.<p>
186     *
187     * @return the property form
188     */
189    public CmsForm getForm() {
190
191        return m_form;
192    }
193
194    /**
195     * Initializes the widgets for editing the properties.<p>
196     *
197     * @param dialog the dialog which the property editor is part of
198     */
199    public void initializeWidgets(CmsPopup dialog) {
200
201        // creates tabs, etc. if necessary
202        setupFieldContainer();
203        addSpecialFields();
204        // create fields and add them to the correct location
205        buildFields();
206        m_form.setValidatorClass("org.opencms.gwt.CmsDefaultFormValidator");
207        m_form.render();
208    }
209
210    /**
211     * Sets the names of properties which can be edited.<p>
212     *
213     * @param propertyNames the property names
214     */
215    public void setPropertyNames(List<String> propertyNames) {
216
217        m_allProps = propertyNames;
218    }
219
220    /**
221     * Method to add special, non-property fields.<p>
222     */
223    protected void addSpecialFields() {
224
225        String firstTab = m_form.getWidget().getDefaultGroup();
226        if (m_handler.hasEditableName()) {
227            // the root entry name can't be edited
228            CmsBasicFormField urlNameField = createUrlNameField();
229            m_form.addField(firstTab, urlNameField);
230        }
231    }
232
233    /**
234     * Builds and renders the fields for the properties.<p>
235     */
236    protected abstract void buildFields();
237
238    /**
239     * Creates the text field for editing the URL name.<p>
240     *
241     * @return the newly created form field
242      */
243    protected CmsBasicFormField createUrlNameField() {
244
245        if (m_urlNameField != null) {
246            m_urlNameField.unbind();
247        }
248
249        String description = message(Messages.GUI_URLNAME_PROPERTY_DESC_0);
250        String label = message(Messages.GUI_URLNAME_PROPERTY_0);
251        final CmsTextBox textbox = new CmsTextBox();
252        textbox.setTriggerChangeOnKeyPress(true);
253        textbox.setInhibitValidationForKeypresses(true);
254
255        CmsBasicFormField result = new CmsBasicFormField(FIELD_URLNAME, description, label, null, textbox);
256        result.getLayoutData().put("property", A_CmsPropertyEditor.FIELD_URLNAME);
257        String urlName = m_handler.getName();
258        if (urlName == null) {
259            urlName = "";
260        }
261        String parent = CmsResource.getParentFolder(m_handler.getPath());
262        CmsUUID id = m_handler.getId();
263
264        result.setValidator(new CmsUrlNameValidator(parent, id));
265        I_CmsStringModel model = getUrlNameModel(urlName);
266        result.getWidget().setFormValueAsString(model.getValue());
267        result.bind(model);
268        //result.getWidget().setFormValueAsString(getUrlNameModel().getValue());
269        m_urlNameField = result;
270        return result;
271    }
272
273    /**
274     * Gets the title from a map of field values.<p>
275     *
276     * @param fieldValues the map of field values
277     * @return the title
278     */
279    protected String getTitle(Map<String, String> fieldValues) {
280
281        for (Map.Entry<String, String> entry : fieldValues.entrySet()) {
282            if (entry.getKey().contains("/NavText/")) {
283                return entry.getValue();
284            }
285        }
286        return null;
287    }
288
289    /**
290     * Lazily creates the model object for the URL name field.<p>
291     *
292     * @param urlName the initial value for the URL name
293     *
294     * @return the model object for the URL name field
295     */
296    protected CmsDefaultStringModel getUrlNameModel(String urlName) {
297
298        if (m_urlNameModel == null) {
299            m_urlNameModel = new CmsDefaultStringModel("urlname");
300            m_urlNameModel.setValue(urlName, false);
301        }
302        return m_urlNameModel;
303    }
304
305    /**
306     * Returns a localized message from the message bundle.<p>
307     *
308     * @param key the message key
309     * @param args the message parameters
310     *
311     * @return the localized message
312     */
313    protected String message(String key, Object... args) {
314
315        return Messages.get().key(key, args);
316    }
317
318    /**
319     * Sets the ghost value for a form field if its normal value is empty and the field's widget supports ghost values.<p>
320     *
321     * @param field the form field
322     * @param value the ghost value to set
323     * @param ghostMode if true, sets the widget to ghost mode
324     */
325    protected void setGhostValue(I_CmsFormField field, String value, boolean ghostMode) {
326
327        I_CmsFormWidget widget = field.getWidget();
328        if ((widget instanceof I_CmsHasGhostValue) && (value != null)) {
329            ((I_CmsHasGhostValue)widget).setGhostValue(value, ghostMode);
330        }
331    }
332
333    /**
334     * Sets up the widget which will contain the input fields for the properties.<p>
335     */
336    protected abstract void setupFieldContainer();
337
338    /**
339     * Sets the contents of the URL name field in the form.<p>
340     *
341     * @param urlName the new URL name
342     */
343    protected void setUrlNameField(String urlName) {
344
345        m_form.getField(FIELD_URLNAME).getWidget().setFormValueAsString(urlName);
346    }
347
348    /**
349     * Shows an error message next to the URL name input field.<p>
350     *
351     * @param message the message which should be displayed, or null if no message should be displayed
352     */
353    protected void showUrlNameError(String message) {
354
355        m_form.getField(FIELD_URLNAME).getWidget().setErrorMessage(message);
356    }
357
358    /**
359     * Helper method for creating the template selection widget.<p>
360     *
361     * @return the template selector widget
362     */
363    private I_CmsFormWidget createTemplateSelector() {
364
365        if (m_handler.useAdeTemplates()) {
366
367            CmsSelectBox selectBox = null;
368            Map<String, String> values = new LinkedHashMap<String, String>();
369            for (Map.Entry<String, CmsClientTemplateBean> templateEntry : m_handler.getPossibleTemplates().entrySet()) {
370                CmsClientTemplateBean template = templateEntry.getValue();
371                String title = template.getTitle();
372                if ((title == null) || (title.length() == 0)) {
373                    title = template.getSitePath();
374                }
375                values.put(template.getSitePath(), title);
376            }
377            selectBox = new CmsPropertySelectBox(values);
378            return selectBox;
379        } else {
380            CmsTextBox textbox = new CmsTextBox();
381            return textbox;
382        }
383    }
384
385    /**
386     * Helper method for removing hidden properties from a map of property configurations.<p>
387     *
388     * The map passed into the method is not changed; a map which only contains the non-hidden
389     * property definitions is returned.<p>
390     *
391     * @param propConfig the property configuration
392     *
393     * @return the filtered property configuration
394     */
395    private Map<String, CmsXmlContentProperty> removeHiddenProperties(Map<String, CmsXmlContentProperty> propConfig) {
396
397        Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>();
398        for (Map.Entry<String, CmsXmlContentProperty> entry : propConfig.entrySet()) {
399            if (!m_handler.isHiddenProperty(entry.getKey())) {
400                result.put(entry.getKey(), entry.getValue());
401            }
402        }
403        return result;
404    }
405}