001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (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.seo;
029
030import org.opencms.gwt.client.CmsCoreProvider;
031import org.opencms.gwt.client.Messages;
032import org.opencms.gwt.client.property.A_CmsPropertyEditor;
033import org.opencms.gwt.client.property.CmsPropertySubmitHandler;
034import org.opencms.gwt.client.property.CmsSimplePropertyEditor;
035import org.opencms.gwt.client.property.I_CmsPropertyEditorHandler;
036import org.opencms.gwt.client.rpc.CmsRpcAction;
037import org.opencms.gwt.client.ui.CmsFieldSet;
038import org.opencms.gwt.client.ui.CmsListItemWidget;
039import org.opencms.gwt.client.ui.CmsPopup;
040import org.opencms.gwt.client.ui.CmsPushButton;
041import org.opencms.gwt.client.ui.CmsScrollPanel;
042import org.opencms.gwt.client.ui.css.I_CmsInputLayoutBundle;
043import org.opencms.gwt.client.ui.input.I_CmsFormField;
044import org.opencms.gwt.client.ui.input.form.CmsForm;
045import org.opencms.gwt.client.ui.input.form.I_CmsFormHandler;
046import org.opencms.gwt.client.ui.input.form.I_CmsFormSubmitHandler;
047import org.opencms.gwt.shared.CmsListInfoBean;
048import org.opencms.gwt.shared.CmsListInfoBean.StateIcon;
049import org.opencms.gwt.shared.alias.CmsAliasBean;
050import org.opencms.util.CmsUUID;
051import org.opencms.xml.content.CmsXmlContentProperty;
052
053import java.util.LinkedHashMap;
054import java.util.List;
055import java.util.Map;
056
057import com.google.gwt.core.client.GWT;
058import com.google.gwt.dom.client.Style;
059import com.google.gwt.dom.client.Style.Overflow;
060import com.google.gwt.dom.client.Style.Unit;
061import com.google.gwt.event.dom.client.ClickEvent;
062import com.google.gwt.event.dom.client.ClickHandler;
063import com.google.gwt.user.client.Timer;
064import com.google.gwt.user.client.rpc.AsyncCallback;
065import com.google.gwt.user.client.ui.FlowPanel;
066
067/**
068 * The SEO options dialog, which makes it possible to both edit the SEO relevant properties of
069 * a resource as well as alias paths for the resource.<p>
070 */
071public class CmsSeoOptionsDialog extends CmsPopup implements I_CmsFormHandler {
072
073    /** The alias messages. */
074    protected static CmsAliasMessages aliasMessages = new CmsAliasMessages();
075
076    /** The properties which should be displayed. */
077    protected static String[] seoProperties = new String[] {"Title", "Description", "Keywords"};
078
079    /** The validation has detected an error. */
080    protected static final int VALIDATION_FAILED = 2;
081
082    /** The validation has finished successfully. */
083    protected static final int VALIDATION_OK = 0;
084
085    /** The validation isn't finished yet. */
086    protected static final int VALIDATION_RUNNING = 1;
087
088    /** The inner alias list. */
089    protected CmsAliasList m_aliasList;
090
091    /** The validation status for the aliases. */
092    protected int m_aliasValidationStatus;
093
094    /** The root panel for this dialog. */
095    protected FlowPanel m_panel = new FlowPanel();
096
097    /** The validation status for the properties. */
098    protected int m_propertyValidationStatus;
099
100    /** The structure id of the resource whose aliases are being edited. */
101    protected CmsUUID m_structureId;
102
103    /** The property editor instance. */
104    A_CmsPropertyEditor m_propertyEditor;
105
106    /** The form submit handler. */
107    private I_CmsFormSubmitHandler m_formSubmitHandler;
108
109    /**
110     * The field set containing the properties relevant for SEO.
111     */
112    private CmsFieldSet m_propertyFieldset = new CmsFieldSet();
113
114    /**
115     * Creates a new dialog instance.<p>
116     *
117     * @param structureId the structure id of the resource whose aliases are being edited
118     * @param infoBean a bean containing the information to display in the resource info box
119     * @param aliases the existing aliases of the resource
120     * @param propertyConfig the property configuration
121     * @param propertyEditorHandler the property editor handler
122     */
123    public CmsSeoOptionsDialog(
124        CmsUUID structureId,
125        CmsListInfoBean infoBean,
126        List<CmsAliasBean> aliases,
127        Map<String, CmsXmlContentProperty> propertyConfig,
128        I_CmsPropertyEditorHandler propertyEditorHandler) {
129
130        super(aliasMessages.seoOptions());
131        setGlassEnabled(true);
132        setAutoHideEnabled(false);
133        setModal(true);
134
135        //-----------------------INFO BOX -------------------------------------------
136
137        CmsListItemWidget liWidget = new CmsListItemWidget(infoBean);
138        liWidget.setStateIcon(StateIcon.standard);
139        m_panel.add(liWidget);
140
141        //------------------------ PROPERTIES ------------------------------------------
142        LinkedHashMap<String, CmsXmlContentProperty> props = new LinkedHashMap<String, CmsXmlContentProperty>();
143        for (String seoProperty : seoProperties) {
144            if (propertyConfig.containsKey(seoProperty)) {
145                props.put(seoProperty, propertyConfig.get(seoProperty));
146            }
147        }
148        m_propertyEditor = new CmsSimplePropertyEditor(props, propertyEditorHandler);
149        m_propertyEditor.getForm().setFormHandler(this);
150        m_formSubmitHandler = new CmsPropertySubmitHandler(propertyEditorHandler);
151        m_structureId = structureId;
152        m_propertyFieldset.getElement().getStyle().setMarginTop(10, Unit.PX);
153        m_propertyFieldset.getContentPanel().getElement().getStyle().setOverflow(Overflow.VISIBLE);
154        m_propertyFieldset.setLegend(
155            org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_PROPERTIES_0));
156        m_propertyEditor.initializeWidgets(this);
157        m_panel.add(m_propertyFieldset);
158
159        //------------------------ ALIASES ------------------------------------------
160
161        CmsFieldSet aliasFieldset = new CmsFieldSet();
162        aliasFieldset.setLegend(aliasMessages.aliases());
163        m_aliasList = new CmsAliasList(structureId, aliases);
164        aliasFieldset.getElement().getStyle().setMarginTop(10, Unit.PX);
165        CmsScrollPanel scrollPanel = GWT.create(CmsScrollPanel.class);
166        scrollPanel.setWidget(m_aliasList);
167        aliasFieldset.addContent(scrollPanel);
168        m_panel.add(scrollPanel);
169        Style style = scrollPanel.getElement().getStyle();
170        style.setProperty("minHeight", "300px"); //$NON-NLS-1$ //$NON-NLS-2$
171        style.setProperty("maxHeight", "450px"); //$NON-NLS-1$ //$NON-NLS-2$
172        style.setOverflowY(Overflow.AUTO);
173
174        setMainContent(m_panel);
175        addButton(createCancelButton());
176        addButton(saveButton());
177
178    }
179
180    /**
181     * Loads the aliases for a given page.<p>
182     *
183     * @param structureId the structure id of the page
184     * @param callback the callback for the loaded aliases
185     */
186    public static void loadAliases(final CmsUUID structureId, final AsyncCallback<List<CmsAliasBean>> callback) {
187
188        final CmsRpcAction<List<CmsAliasBean>> action = new CmsRpcAction<List<CmsAliasBean>>() {
189
190            @Override
191            public void execute() {
192
193                start(200, true);
194                CmsCoreProvider.getVfsService().getAliasesForPage(structureId, this);
195            }
196
197            @Override
198            protected void onResponse(List<CmsAliasBean> result) {
199
200                stop(false);
201                callback.onSuccess(result);
202            }
203        };
204        action.execute();
205    }
206
207    public boolean isSubmitting() {
208
209        // TODO Auto-generated method stub
210        return false;
211    }
212
213    /**
214     * @see org.opencms.gwt.client.ui.input.form.I_CmsFormHandler#onSubmitValidationResult(org.opencms.gwt.client.ui.input.form.CmsForm, boolean)
215     */
216    public void onSubmitValidationResult(CmsForm form, boolean ok) {
217
218        m_propertyValidationStatus = ok ? VALIDATION_OK : VALIDATION_FAILED;
219        update(true);
220    }
221
222    /**
223     * @see org.opencms.gwt.client.ui.input.form.I_CmsFormHandler#onValidationResult(org.opencms.gwt.client.ui.input.form.CmsForm, boolean)
224     */
225    public void onValidationResult(CmsForm form, boolean ok) {
226
227        m_propertyValidationStatus = ok ? VALIDATION_OK : VALIDATION_FAILED;
228        update(false);
229    }
230
231    /**
232     * Saves the aliases for a given page.<p>
233     *
234     * @param uuid the page structure id
235     * @param aliases the aliases to save
236     */
237    public void saveAliases(final CmsUUID uuid, final List<CmsAliasBean> aliases) {
238
239        final CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
240
241            /**
242             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
243             */
244            @Override
245            public void execute() {
246
247                start(200, true);
248                CmsCoreProvider.getVfsService().saveAliases(uuid, aliases, this);
249
250            }
251
252            /**
253             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
254             */
255            @Override
256            public void onResponse(Void result) {
257
258                stop(false);
259            }
260
261        };
262        action.execute();
263    }
264
265    /**
266     * @see org.opencms.gwt.client.ui.CmsPopup#show()
267     */
268    @Override
269    public void show() {
270
271        m_propertyFieldset.addContent(m_propertyEditor.getForm().getWidget());
272        m_propertyFieldset.addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().formGradientBackground());
273        super.show();
274        notifyWidgetsOfOpen();
275    }
276
277    /**
278     * Updates the validation status and optionally submits the data.<p>
279     *
280     * @param submit the submit flag
281     */
282    public void update(boolean submit) {
283
284        boolean ok = (m_propertyValidationStatus == VALIDATION_OK) && (m_aliasValidationStatus == VALIDATION_OK);
285        if (submit && ok) {
286            saveProperties();
287            saveAliases();
288            hide();
289        }
290    }
291
292    /**
293     * The method which is called when the user clicks the save button of the dialog.<p>
294     */
295    protected void onClickSave() {
296
297        Timer timer = new Timer() {
298
299            @Override
300            public void run() {
301
302                m_aliasList.clearValidationErrors();
303                m_aliasValidationStatus = VALIDATION_RUNNING;
304                m_propertyValidationStatus = VALIDATION_RUNNING;
305                m_aliasList.validate(new Runnable() {
306
307                    public void run() {
308
309                        m_aliasValidationStatus = !m_aliasList.hasValidationErrors()
310                        ? VALIDATION_OK
311                        : VALIDATION_FAILED;
312                        update(true);
313
314                    }
315                });
316                m_propertyEditor.getForm().validateAndSubmit();
317            }
318        };
319        // slight delay so that the validation doesn't interfere with validations triggered by the change event
320        timer.schedule(20);
321    }
322
323    /**
324     * Saves the aliases.<p>
325     */
326    protected void saveAliases() {
327
328        List<CmsAliasBean> aliases = m_aliasList.getAliases();
329        saveAliases(m_structureId, aliases);
330    }
331
332    /**
333     * Saves the properties.<p>
334     */
335    protected void saveProperties() {
336
337        m_propertyEditor.getForm().handleSubmit(m_formSubmitHandler);
338    }
339
340    /**
341     * Creates the cancel button.<p>
342     *
343     * @return the cancel button
344     */
345    private CmsPushButton createCancelButton() {
346
347        addDialogClose(null);
348        CmsPushButton button = new CmsPushButton();
349        button.setText(Messages.get().key(Messages.GUI_CANCEL_0));
350        button.setUseMinWidth(true);
351        button.addClickHandler(new ClickHandler() {
352
353            /**
354             * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
355             */
356            public void onClick(ClickEvent event) {
357
358                CmsSeoOptionsDialog.this.hide();
359            }
360        });
361
362        return button;
363    }
364
365    /**
366     * Tells all widgets that the dialog has been opened.<p>
367     */
368    private void notifyWidgetsOfOpen() {
369
370        for (Map.Entry<String, I_CmsFormField> fieldEntry : m_propertyEditor.getForm().getFields().entrySet()) {
371            fieldEntry.getValue().getWidget().setAutoHideParent(this);
372        }
373    }
374
375    /**
376     * Creates the OK button.<p>
377     *
378     * @return the OK button
379     */
380    private CmsPushButton saveButton() {
381
382        CmsPushButton button = new CmsPushButton();
383        button.setText(Messages.get().key(Messages.GUI_SAVE_0));
384        button.setUseMinWidth(true);
385        button.addClickHandler(new ClickHandler() {
386
387            /**
388             * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
389             */
390            public void onClick(ClickEvent event) {
391
392                CmsSeoOptionsDialog.this.onClickSave();
393            }
394        });
395        return button;
396    }
397
398}