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