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.ui.contenteditor;
029
030import org.opencms.gwt.client.CmsCoreProvider;
031import org.opencms.gwt.client.I_CmsEditableData;
032import org.opencms.gwt.client.Messages;
033import org.opencms.gwt.client.rpc.CmsRpcAction;
034import org.opencms.gwt.client.ui.CmsAlertDialog;
035import org.opencms.gwt.client.ui.CmsCancelCloseException;
036import org.opencms.gwt.client.ui.CmsConfirmDialog;
037import org.opencms.gwt.client.ui.CmsIFrame;
038import org.opencms.gwt.client.ui.CmsPopup;
039import org.opencms.gwt.client.ui.I_CmsConfirmDialogHandler;
040import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
041import org.opencms.gwt.client.util.CmsDebugLog;
042import org.opencms.gwt.client.util.CmsDomUtil;
043import org.opencms.gwt.client.util.CmsDomUtil.Method;
044import org.opencms.util.CmsStringUtil;
045
046import java.util.HashMap;
047import java.util.Map;
048
049import com.google.gwt.core.client.Scheduler;
050import com.google.gwt.core.client.Scheduler.ScheduledCommand;
051import com.google.gwt.dom.client.FormElement;
052import com.google.gwt.event.shared.HandlerRegistration;
053import com.google.gwt.user.client.Command;
054import com.google.gwt.user.client.Window;
055import com.google.gwt.user.client.Window.ClosingEvent;
056import com.google.gwt.user.client.Window.ClosingHandler;
057import com.google.gwt.user.client.ui.RootPanel;
058
059/**
060 * XML content editor dialog.<p>
061 *
062 * @since 8.0.0
063 */
064public final class CmsContentEditorDialog {
065
066    /**
067     * Additional options for the editor dialog.<p>
068     */
069    public static class DialogOptions {
070
071        /** Suggested editor width. */
072        private Integer m_suggestedWidth;
073
074        /** Suggested editor height. */
075        private Integer m_suggestedHeight;
076
077        /**
078         * Returns the suggestedHeight.<p>
079         *
080         * @return the suggestedHeight
081         */
082        public Integer getSuggestedHeight() {
083
084            return m_suggestedHeight;
085        }
086
087        /**
088         * Returns the suggestedWidth.<p>
089         *
090         * @return the suggestedWidth
091         */
092        public Integer getSuggestedWidth() {
093
094            return m_suggestedWidth;
095        }
096
097        /**
098         * Sets the suggestedHeight.<p>
099         *
100         * @param suggestedHeight the suggestedHeight to set
101         */
102        public void setSuggestedHeight(Integer suggestedHeight) {
103
104            m_suggestedHeight = suggestedHeight;
105        }
106
107        /**
108         * Sets the suggestedWidth.<p>
109         *
110         * @param suggestedWidth the suggestedWidth to set
111         */
112        public void setSuggestedWidth(Integer suggestedWidth) {
113
114            m_suggestedWidth = suggestedWidth;
115        }
116
117    }
118
119    /** Name of exported dialog close function. */
120    private static final String CLOSING_METHOD_NAME = "cms_ade_closeEditorDialog";
121
122    /** Name attribute value for editor iFrame. */
123    private static final String EDITOR_IFRAME_NAME = "cmsAdvancedDirectEditor";
124
125    /** The dialog instance. */
126    private static CmsContentEditorDialog INSTANCE;
127
128    /** The window closing handler registration. */
129    private HandlerRegistration m_closingHandlerRegistration;
130
131    /** The popup instance. */
132    private CmsPopup m_dialog;
133
134    /** The currently edit element data. */
135    private I_CmsEditableData m_editableData;
136
137    /** The editor handler. */
138    private I_CmsContentEditorHandler m_editorHandler;
139
140    /** The form element. */
141    private FormElement m_form;
142
143    /** Flag indicating if a new resource needs to created. */
144    private boolean m_isNew;
145
146    /** The content creation mode. */
147    private String m_mode;
148
149    /**
150     * Hiding constructor.<p>
151     */
152    private CmsContentEditorDialog() {
153
154        exportClosingMethod();
155    }
156
157    /**
158     * Generates the form to post to the editor frame.<p>
159     *
160     * @param editableData the data about the resource which should be edited
161     * @param isNew true if the resource to be edited doesn'T already exist
162     * @param target the target of the form to be created
163     * @param mode the mode for creating new elements
164     *
165     * @return the form element which, when submitted, opens the editor
166     */
167    public static FormElement generateForm(I_CmsEditableData editableData, boolean isNew, String target, String mode) {
168
169        // create a form to submit a post request to the editor JSP
170        Map<String, String> formValues = new HashMap<String, String>();
171        if (editableData.getSitePath() != null) {
172            formValues.put("resource", editableData.getSitePath());
173        }
174        if (editableData.getElementLanguage() != null) {
175            formValues.put("elementlanguage", editableData.getElementLanguage());
176        }
177        if (editableData.getElementName() != null) {
178            formValues.put("elementname", editableData.getElementName());
179        }
180
181        // in case the editor is opened within this window, use the current URI as back link
182        String backlink = "_self".equals(target)
183        ? CmsCoreProvider.get().getUri()
184        : CmsCoreProvider.get().getContentEditorBacklinkUrl();
185
186        formValues.put("backlink", backlink);
187        formValues.put("redirect", "true");
188        formValues.put("directedit", "true");
189        formValues.put("editcontext", CmsCoreProvider.get().getUri());
190        formValues.put("nofoot", "1");
191        if (isNew) {
192            formValues.put("newlink", editableData.getNewLink());
193            formValues.put("editortitle", editableData.getNewTitle());
194        }
195
196        String postCreateHandler = editableData.getPostCreateHandler();
197        if (postCreateHandler != null) {
198            formValues.put("postCreateHandler", postCreateHandler);
199        }
200        if (mode != null) {
201            formValues.put("mode", mode);
202        }
203        FormElement formElement = CmsDomUtil.generateHiddenForm(
204            CmsCoreProvider.get().link(CmsCoreProvider.get().getContentEditorUrl()),
205            Method.post,
206            target,
207            formValues);
208        return formElement;
209    }
210
211    /**
212     * Returns the dialogs instance.<p>
213     *
214     * @return the dialog instance
215     */
216    public static CmsContentEditorDialog get() {
217
218        if (INSTANCE == null) {
219            INSTANCE = new CmsContentEditorDialog();
220        }
221        return INSTANCE;
222    }
223
224    /**
225     * Closes the dialog.<p>
226     */
227    static void closeEditDialog() {
228
229        get().close();
230
231    }
232
233    /**
234     * Opens the content editor dialog for the given element.<p>
235     *
236     * @param editableData the editable data
237     * @param isNew <code>true</code> when creating a new resource
238     * @param mode the content creation mode
239     * @param dlgOptions the additional dialog options
240     * @param editorHandler the editor handler
241     */
242    public void openEditDialog(
243        final I_CmsEditableData editableData,
244        boolean isNew,
245        String mode,
246        final DialogOptions dlgOptions,
247        I_CmsContentEditorHandler editorHandler) {
248
249        m_mode = mode;
250
251        if ((m_dialog != null) && m_dialog.isShowing()) {
252            CmsDebugLog.getInstance().printLine("Dialog is already open, cannot open another one.");
253            return;
254        }
255        m_isNew = isNew;
256        m_editableData = editableData;
257        m_editorHandler = editorHandler;
258        if (m_isNew || (editableData.getStructureId() == null)) {
259            openDialog(dlgOptions);
260        } else {
261            CmsRpcAction<String> action = new CmsRpcAction<String>() {
262
263                @Override
264                public void execute() {
265
266                    show(true);
267                    CmsCoreProvider.getVfsService().getSitePath(getEditableData().getStructureId(), this);
268                }
269
270                @Override
271                protected void onResponse(String result) {
272
273                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(result)) {
274                        getEditableData().setSitePath(result);
275                        openDialog(dlgOptions);
276                    } else {
277                        CmsAlertDialog alert = new CmsAlertDialog(
278                            Messages.get().key(Messages.ERR_TITLE_ERROR_0),
279                            Messages.get().key(Messages.ERR_RESOURCE_UNAVAILABLE_1, getEditableData().getSitePath()));
280                        alert.center();
281                    }
282                    stop(false);
283                }
284            };
285            action.execute();
286        }
287    }
288
289    /**
290     * Closes the dialog.<p>
291     */
292    protected void close() {
293
294        if (m_dialog != null) {
295            m_dialog.hide();
296            m_dialog = null;
297            m_editorHandler.onClose(
298                m_editableData.getSitePath(),
299                m_editableData.getStructureId(),
300                m_isNew,
301                false,
302                false);
303            m_editorHandler = null;
304        }
305        if (m_form != null) {
306            m_form.removeFromParent();
307            m_form = null;
308        }
309        if (m_closingHandlerRegistration != null) {
310            m_closingHandlerRegistration.removeHandler();
311            m_closingHandlerRegistration = null;
312        }
313    }
314
315    /**
316     * Returns the editable data.<p>
317     *
318     * @return the editable data
319     */
320    protected I_CmsEditableData getEditableData() {
321
322        return m_editableData;
323    }
324
325    /**
326     * Execute on window close.<p>
327     * Will ask the user if the edited content should be saved.<p>
328     */
329    protected void onWindowClose() {
330
331        boolean savePage = Window.confirm(
332            Messages.get().key(Messages.GUI_EDITOR_SAVE_BEFORE_LEAVING_1, m_editableData.getSitePath()));
333        if (savePage) {
334            saveEditorContent();
335        }
336    }
337
338    /**
339     * Opens the dialog for the given sitepath.<p>
340     *
341     * @param dlgOptions the additional dialog options
342     */
343    protected void openDialog(DialogOptions dlgOptions) {
344
345        m_dialog = new CmsPopup(
346            Messages.get().key(Messages.GUI_DIALOG_CONTENTEDITOR_TITLE_0)
347                + " - "
348                + (m_isNew ? m_editableData.getNewTitle() : m_editableData.getSitePath()));
349        m_dialog.addStyleName(I_CmsLayoutBundle.INSTANCE.contentEditorCss().contentEditor());
350
351        // calculate width
352        int width = Window.getClientWidth();
353        width = (width < 1350) ? width - 50 : 1300;
354        if (dlgOptions.getSuggestedWidth() != null) {
355            width = dlgOptions.getSuggestedWidth().intValue();
356        }
357
358        m_dialog.setWidth(width);
359
360        // calculate height
361        int height = Window.getClientHeight() - 50;
362        height = (height < 645) ? 645 : height;
363        if (dlgOptions.getSuggestedHeight() != null) {
364            height = dlgOptions.getSuggestedHeight().intValue();
365        }
366        m_dialog.setHeight(height);
367
368        m_dialog.setGlassEnabled(true);
369        m_dialog.setUseAnimation(false);
370        final CmsIFrame editorFrame = new CmsIFrame(EDITOR_IFRAME_NAME, "");
371        m_dialog.addDialogClose(new Command() {
372
373            /**
374             * @see com.google.gwt.user.client.Command#execute()
375             */
376            public void execute() {
377
378                CmsConfirmDialog confirmDlg = new CmsConfirmDialog(
379                    Messages.get().key(Messages.GUI_EDITOR_CLOSE_CAPTION_0),
380                    Messages.get().key(Messages.GUI_EDITOR_CLOSE_TEXT_0));
381                confirmDlg.setHandler(new I_CmsConfirmDialogHandler() {
382
383                    public void onClose() {
384
385                        // do nothing
386                    }
387
388                    public void onOk() {
389
390                        // make sure the onunload event is triggered within the editor frames for ALL browsers
391                        editorFrame.setUrl("about:blank");
392                        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
393
394                            public void execute() {
395
396                                CmsContentEditorDialog.this.close();
397                            }
398                        });
399                    }
400                });
401                confirmDlg.center();
402                // Let the confirm dialog handle the closing
403                throw new CmsCancelCloseException();
404            }
405        });
406
407        m_dialog.add(editorFrame);
408        m_dialog.setPositionFixed();
409        m_dialog.center();
410        m_form = generateForm(m_editableData, m_isNew, EDITOR_IFRAME_NAME, m_mode);
411        RootPanel.getBodyElement().appendChild(m_form);
412        m_form.submit();
413
414        // adding on close handler
415        m_closingHandlerRegistration = Window.addWindowClosingHandler(new ClosingHandler() {
416
417            public void onWindowClosing(ClosingEvent event) {
418
419                onWindowClose();
420            }
421        });
422    }
423
424    /**
425     * Exports the close method to the window object, so it can be accessed from within the content editor iFrame.<p>
426     */
427    private native void exportClosingMethod() /*-{
428                $wnd[@org.opencms.gwt.client.ui.contenteditor.CmsContentEditorDialog::CLOSING_METHOD_NAME] = function() {
429                        @org.opencms.gwt.client.ui.contenteditor.CmsContentEditorDialog::closeEditDialog()();
430                };
431    }-*/;
432
433    /**
434      * Saves the current editor content synchronously.<p>
435     */
436    private native void saveEditorContent() /*-{
437                var iFrame = $wnd.frames[@org.opencms.gwt.client.ui.contenteditor.CmsContentEditorDialog::EDITOR_IFRAME_NAME];
438                if (iFrame != null) {
439                        var editFrame = iFrame["edit"];
440                        if (editFrame != null) {
441                                var editorFrame = editFrame.frames["editform"];
442                                if (editorFrame != null) {
443                                        var editForm = editorFrame.$("#EDITOR");
444                                        editForm.find("input[name='action']").attr("value",
445                                                        "saveexit");
446                                        if (editForm != null) {
447                                                var data = editForm.serializeArray(editForm);
448                                                editorFrame.$.ajax({
449                                                        type : 'POST',
450                                                        async : false,
451                                                        url : editForm.attr("action"),
452                                                        data : data,
453                                                        success : function(result) {
454                                                                // nothing to do
455                                                        },
456                                                        dataType : "html"
457                                                });
458                                        }
459                                }
460                        }
461                }
462    }-*/;
463}