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        if ((dialog != null) && (dialog.getWidth() > 20)) {
209            getForm().getWidget().truncate("property_editing", dialog.getWidth() - 20);
210        }
211    }
212
213    /**
214     * Sets the names of properties which can be edited.<p>
215     *
216     * @param propertyNames the property names
217     */
218    public void setPropertyNames(List<String> propertyNames) {
219
220        m_allProps = propertyNames;
221    }
222
223    /**
224     * Method to add special, non-property fields.<p>
225     */
226    protected void addSpecialFields() {
227
228        String firstTab = m_form.getWidget().getDefaultGroup();
229        if (m_handler.hasEditableName()) {
230            // the root entry name can't be edited
231            CmsBasicFormField urlNameField = createUrlNameField();
232            m_form.addField(firstTab, urlNameField);
233        }
234    }
235
236    /**
237     * Builds and renders the fields for the properties.<p>
238     */
239    protected abstract void buildFields();
240
241    /**
242     * Creates the text field for editing the URL name.<p>
243     *
244     * @return the newly created form field
245      */
246    protected CmsBasicFormField createUrlNameField() {
247
248        if (m_urlNameField != null) {
249            m_urlNameField.unbind();
250        }
251
252        String description = message(Messages.GUI_URLNAME_PROPERTY_DESC_0);
253        String label = message(Messages.GUI_URLNAME_PROPERTY_0);
254        final CmsTextBox textbox = new CmsTextBox();
255        textbox.setTriggerChangeOnKeyPress(true);
256        textbox.setInhibitValidationForKeypresses(true);
257
258        CmsBasicFormField result = new CmsBasicFormField(FIELD_URLNAME, description, label, null, textbox);
259        result.getLayoutData().put("property", A_CmsPropertyEditor.FIELD_URLNAME);
260        String urlName = m_handler.getName();
261        if (urlName == null) {
262            urlName = "";
263        }
264        String parent = CmsResource.getParentFolder(m_handler.getPath());
265        CmsUUID id = m_handler.getId();
266
267        result.setValidator(new CmsUrlNameValidator(parent, id));
268        I_CmsStringModel model = getUrlNameModel(urlName);
269        result.getWidget().setFormValueAsString(model.getValue());
270        result.bind(model);
271        //result.getWidget().setFormValueAsString(getUrlNameModel().getValue());
272        m_urlNameField = result;
273        return result;
274    }
275
276    /**
277     * Gets the title from a map of field values.<p>
278     *
279     * @param fieldValues the map of field values
280     * @return the title
281     */
282    protected String getTitle(Map<String, String> fieldValues) {
283
284        for (Map.Entry<String, String> entry : fieldValues.entrySet()) {
285            if (entry.getKey().contains("/NavText/")) {
286                return entry.getValue();
287            }
288        }
289        return null;
290    }
291
292    /**
293     * Lazily creates the model object for the URL name field.<p>
294     *
295     * @param urlName the initial value for the URL name
296     *
297     * @return the model object for the URL name field
298     */
299    protected CmsDefaultStringModel getUrlNameModel(String urlName) {
300
301        if (m_urlNameModel == null) {
302            m_urlNameModel = new CmsDefaultStringModel("urlname");
303            m_urlNameModel.setValue(urlName, false);
304        }
305        return m_urlNameModel;
306    }
307
308    /**
309     * Returns a localized message from the message bundle.<p>
310     *
311     * @param key the message key
312     * @param args the message parameters
313     *
314     * @return the localized message
315     */
316    protected String message(String key, Object... args) {
317
318        return Messages.get().key(key, args);
319    }
320
321    /**
322     * Sets the ghost value for a form field if its normal value is empty and the field's widget supports ghost values.<p>
323     *
324     * @param field the form field
325     * @param value the ghost value to set
326     * @param ghostMode if true, sets the widget to ghost mode
327     */
328    protected void setGhostValue(I_CmsFormField field, String value, boolean ghostMode) {
329
330        I_CmsFormWidget widget = field.getWidget();
331        if ((widget instanceof I_CmsHasGhostValue) && (value != null)) {
332            ((I_CmsHasGhostValue)widget).setGhostValue(value, ghostMode);
333        }
334    }
335
336    /**
337     * Sets up the widget which will contain the input fields for the properties.<p>
338     */
339    protected abstract void setupFieldContainer();
340
341    /**
342     * Sets the contents of the URL name field in the form.<p>
343     *
344     * @param urlName the new URL name
345     */
346    protected void setUrlNameField(String urlName) {
347
348        m_form.getField(FIELD_URLNAME).getWidget().setFormValueAsString(urlName);
349    }
350
351    /**
352     * Shows an error message next to the URL name input field.<p>
353     *
354     * @param message the message which should be displayed, or null if no message should be displayed
355     */
356    protected void showUrlNameError(String message) {
357
358        m_form.getField(FIELD_URLNAME).getWidget().setErrorMessage(message);
359    }
360
361    /**
362     * Helper method for creating the template selection widget.<p>
363     *
364     * @return the template selector widget
365     */
366    private I_CmsFormWidget createTemplateSelector() {
367
368        if (m_handler.useAdeTemplates()) {
369
370            CmsSelectBox selectBox = null;
371            Map<String, String> values = new LinkedHashMap<String, String>();
372            for (Map.Entry<String, CmsClientTemplateBean> templateEntry : m_handler.getPossibleTemplates().entrySet()) {
373                CmsClientTemplateBean template = templateEntry.getValue();
374                String title = template.getTitle();
375                if ((title == null) || (title.length() == 0)) {
376                    title = template.getSitePath();
377                }
378                values.put(template.getSitePath(), title);
379            }
380            selectBox = new CmsPropertySelectBox(values);
381            return selectBox;
382        } else {
383            CmsTextBox textbox = new CmsTextBox();
384            return textbox;
385        }
386    }
387
388    /**
389     * Helper method for removing hidden properties from a map of property configurations.<p>
390     *
391     * The map passed into the method is not changed; a map which only contains the non-hidden
392     * property definitions is returned.<p>
393     *
394     * @param propConfig the property configuration
395     *
396     * @return the filtered property configuration
397     */
398    private Map<String, CmsXmlContentProperty> removeHiddenProperties(Map<String, CmsXmlContentProperty> propConfig) {
399
400        Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>();
401        for (Map.Entry<String, CmsXmlContentProperty> entry : propConfig.entrySet()) {
402            if (!m_handler.isHiddenProperty(entry.getKey())) {
403                result.put(entry.getKey(), entry.getValue());
404            }
405        }
406        return result;
407    }
408}