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.ade.containerpage.client.ui.groupeditor;
029
030import org.opencms.ade.containerpage.client.CmsContainerpageController;
031import org.opencms.ade.containerpage.client.CmsContainerpageEvent;
032import org.opencms.ade.containerpage.client.CmsContainerpageEvent.EventType;
033import org.opencms.ade.containerpage.client.CmsContainerpageHandler;
034import org.opencms.ade.containerpage.client.Messages;
035import org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer;
036import org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel;
037import org.opencms.ade.containerpage.client.ui.CmsGroupContainerElementPanel;
038import org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle;
039import org.opencms.ade.containerpage.shared.CmsContainerElement;
040import org.opencms.ade.containerpage.shared.CmsContainerElementData;
041import org.opencms.gwt.client.ui.CmsPopup;
042import org.opencms.gwt.client.ui.CmsPushButton;
043import org.opencms.gwt.client.ui.I_CmsButton.ButtonColor;
044import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
045import org.opencms.gwt.client.ui.css.I_CmsToolbarButtonLayoutBundle;
046import org.opencms.gwt.client.ui.input.CmsLabel;
047import org.opencms.gwt.client.util.CmsDomUtil;
048import org.opencms.gwt.client.util.CmsPositionBean;
049import org.opencms.util.CmsStringUtil;
050
051import java.util.ArrayList;
052import java.util.HashSet;
053import java.util.Iterator;
054import java.util.List;
055import java.util.Map;
056import java.util.Set;
057
058import com.google.gwt.core.client.GWT;
059import com.google.gwt.dom.client.DivElement;
060import com.google.gwt.dom.client.Element;
061import com.google.gwt.dom.client.Style;
062import com.google.gwt.dom.client.Style.Display;
063import com.google.gwt.dom.client.Style.Position;
064import com.google.gwt.dom.client.Style.Unit;
065import com.google.gwt.event.dom.client.ClickEvent;
066import com.google.gwt.event.dom.client.ClickHandler;
067import com.google.gwt.uibinder.client.UiBinder;
068import com.google.gwt.uibinder.client.UiField;
069import com.google.gwt.user.client.Command;
070import com.google.gwt.user.client.Window;
071import com.google.gwt.user.client.ui.Composite;
072import com.google.gwt.user.client.ui.FlowPanel;
073import com.google.gwt.user.client.ui.HTMLPanel;
074import com.google.gwt.user.client.ui.RootPanel;
075import com.google.gwt.user.client.ui.Widget;
076
077/**
078 * Abstract group editor.<p>
079 *
080 * @since 8.5.0
081 */
082public abstract class A_CmsGroupEditor extends Composite {
083
084    /** The ui-binder interface for this widget. */
085    interface I_CmsGroupEditorUiBinder extends UiBinder<HTMLPanel, A_CmsGroupEditor> {
086        // GWT interface, nothing to do here
087    }
088
089    /** The dialog base height. The height without any content. */
090    private static final int DIALOG_BASE_HEIGHT = 103;
091
092    /** The ui-binder for this widget. */
093    private static I_CmsGroupEditorUiBinder uiBinder = GWT.create(I_CmsGroupEditorUiBinder.class);
094
095    /** The container marker div element. */
096    @UiField
097    protected DivElement m_containerMarker;
098
099    /** The dialog element. */
100    @UiField
101    protected FlowPanel m_dialogContent;
102
103    /** The container element data. */
104    protected CmsContainerElementData m_elementData;
105
106    /** The overlay div element. */
107    @UiField
108    protected DivElement m_overlayDiv;
109
110    /** List of elements when editing started, use to restore on cancel. */
111    private List<CmsContainerPageElementPanel> m_backUpElements;
112
113    /** The dialog cancel button. */
114    private CmsPushButton m_cancelButton;
115
116    /** The container-page controller. */
117    private CmsContainerpageController m_controller;
118
119    /** The group-container place-holder. */
120    private Element m_editingPlaceholder;
121
122    /** The editor popup dialog. */
123    private CmsPopup m_editorDialog;
124
125    /** The editor HTML-id. */
126    private String m_editorId;
127
128    /** The editor widget. */
129    private HTMLPanel m_editorWidget;
130
131    /** The group-container. */
132    private CmsGroupContainerElementPanel m_groupContainer;
133
134    /** The group container element position. */
135    private CmsPositionBean m_groupContainerPosition;
136
137    /** The the container page handler. */
138    private CmsContainerpageHandler m_handler;
139
140    /** The index position of the group-container inside it's parent. */
141    private int m_indexPosition;
142
143    /** The parent container. */
144    private CmsContainerPageContainer m_parentContainer;
145
146    /** The dialog save button. */
147    private CmsPushButton m_saveButton;
148
149    /**
150     * Constructor.<p>
151     *
152     * @param groupContainer the group-container
153     * @param controller the container-page controller
154     * @param handler the container-page handler
155     */
156    protected A_CmsGroupEditor(
157        CmsGroupContainerElementPanel groupContainer,
158        CmsContainerpageController controller,
159        CmsContainerpageHandler handler) {
160
161        m_controller = controller;
162        m_handler = handler;
163        m_editorWidget = uiBinder.createAndBindUi(this);
164        initWidget(m_editorWidget);
165        m_editorId = HTMLPanel.createUniqueId();
166        m_editorWidget.getElement().setId(m_editorId);
167        m_groupContainer = groupContainer;
168        m_backUpElements = new ArrayList<CmsContainerPageElementPanel>();
169        Iterator<Widget> it = m_groupContainer.iterator();
170        while (it.hasNext()) {
171            Widget w = it.next();
172            if (w instanceof CmsContainerPageElementPanel) {
173                m_backUpElements.add((CmsContainerPageElementPanel)w);
174            }
175        }
176        m_parentContainer = (CmsContainerPageContainer)m_groupContainer.getParentTarget();
177        m_groupContainerPosition = CmsPositionBean.getBoundingClientRect(m_groupContainer.getElement());
178        m_editingPlaceholder = createPlaceholder(m_groupContainer.getElement());
179        m_groupContainer.setEditingPlaceholder(m_editingPlaceholder);
180        m_groupContainer.setEditingMarker(m_containerMarker);
181        m_indexPosition = m_parentContainer.getWidgetIndex(m_groupContainer);
182        // inserting placeholder element
183        m_parentContainer.getElement().insertBefore(m_editingPlaceholder, m_groupContainer.getElement());
184        m_editorWidget.add(m_groupContainer, m_editorId);
185        Style style = m_groupContainer.getElement().getStyle();
186        style.setPosition(Position.ABSOLUTE);
187        style.setLeft(m_groupContainerPosition.getLeft(), Unit.PX);
188        style.setTop(m_groupContainerPosition.getTop(), Unit.PX);
189        style.setWidth(m_groupContainerPosition.getWidth(), Unit.PX);
190        style.setZIndex(I_CmsLayoutBundle.INSTANCE.constants().css().zIndexGroupContainer());
191        m_containerMarker.getStyle().setLeft(m_groupContainerPosition.getLeft() - 3, Unit.PX);
192        m_containerMarker.getStyle().setTop(m_groupContainerPosition.getTop() - 4, Unit.PX);
193        m_containerMarker.getStyle().setWidth(m_groupContainerPosition.getWidth() + 4, Unit.PX);
194        m_containerMarker.getStyle().setHeight(m_groupContainerPosition.getHeight() + 4, Unit.PX);
195        m_containerMarker.getStyle().setBackgroundColor(
196            CmsDomUtil.getEffectiveBackgroundColor(m_parentContainer.getElement()));
197        m_groupContainer.getElementOptionBar().setVisible(false);
198        m_groupContainer.getElementOptionBar().removeStyleName(
199            I_CmsToolbarButtonLayoutBundle.INSTANCE.toolbarButtonCss().cmsHovering());
200
201        RootPanel.get().addStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().groupcontainerEditing());
202        addInputFields();
203        m_editorDialog = new CmsPopup();
204        addButtons();
205        if (m_saveButton != null) {
206            m_saveButton.disable(Messages.get().key(Messages.GUI_GROUPCONTAINER_LOADING_DATA_0));
207        }
208        m_editorDialog.setGlassEnabled(false);
209        m_editorDialog.setModal(false);
210        m_editorDialog.addDialogClose(new Command() {
211
212            /**
213             * @see com.google.gwt.user.client.Command#execute()
214             */
215            public void execute() {
216
217                cancelEdit();
218            }
219        });
220    }
221
222    /**
223     * Returns the group container widget.<p>
224     *
225     * @return the group container widget
226     */
227    public CmsGroupContainerElementPanel getGroupContainerWidget() {
228
229        return m_groupContainer;
230    }
231
232    /**
233     * Returns the the container page handler.<p>
234     *
235     * @return the the container page handler
236     */
237    public CmsContainerpageHandler getHandler() {
238
239        return m_handler;
240    }
241
242    /**
243     * Hides the editor pop-up. Use during inline editing.<p>
244     */
245    public void hidePopup() {
246
247        m_editorDialog.getElement().getStyle().setDisplay(Display.NONE);
248    }
249
250    /**
251     * Reinitializes the option bar buttons on the contained elements.<p>
252     */
253    public abstract void reinitializeButtons();
254
255    /**
256     * Shows the editor pop-up.<p>
257     */
258    public void showPopup() {
259
260        m_editorDialog.getElement().getStyle().clearDisplay();
261    }
262
263    /**
264     * Updates the backup elements.<p>
265     *
266     * @param updateElements the updated element data
267     */
268    public void updateBackupElements(Map<String, CmsContainerElementData> updateElements) {
269
270        ArrayList<CmsContainerPageElementPanel> updatedList = new ArrayList<CmsContainerPageElementPanel>();
271        String containerId = m_groupContainer.getContainerId();
272        for (CmsContainerPageElementPanel element : m_backUpElements) {
273            if (updateElements.containsKey(element.getId())
274                && CmsStringUtil.isNotEmptyOrWhitespaceOnly(
275                    updateElements.get(element.getId()).getContents().get(containerId))) {
276                CmsContainerElementData elementData = updateElements.get(element.getId());
277                try {
278                    CmsContainerPageElementPanel replacer = m_controller.getContainerpageUtil().createElement(
279                        elementData,
280                        m_groupContainer,
281                        false);
282                    if (element.getInheritanceInfo() != null) {
283                        // in case of inheritance container editing, keep the inheritance info
284                        replacer.setInheritanceInfo(element.getInheritanceInfo());
285                    }
286                    updatedList.add(replacer);
287
288                } catch (Exception e) {
289                    // in this case keep the old version
290                    updatedList.add(element);
291                }
292            } else {
293                updatedList.add(element);
294            }
295        }
296        m_backUpElements = updatedList;
297    }
298
299    /**
300     * Adds a button to the dialog.<p>
301     *
302     * @param button the button to add
303     */
304    protected void addButton(Widget button) {
305
306        if (m_editorDialog != null) {
307            m_editorDialog.addButton(button);
308        }
309    }
310
311    /**
312     * Adds the buttons to the dialog.<p>
313     */
314    protected abstract void addButtons();
315
316    /**
317     * Adds a cancel button to the dialog.<p>
318     */
319    protected void addCancelButton() {
320
321        if (m_editorDialog != null) {
322            m_cancelButton = new CmsPushButton();
323            m_cancelButton.setText(Messages.get().key(Messages.GUI_BUTTON_CANCEL_TEXT_0));
324            m_cancelButton.setUseMinWidth(true);
325            m_cancelButton.setButtonStyle(ButtonStyle.TEXT, ButtonColor.BLUE);
326            m_cancelButton.addClickHandler(new ClickHandler() {
327
328                public void onClick(ClickEvent event) {
329
330                    cancelEdit();
331                }
332            });
333            m_editorDialog.addButton(m_cancelButton);
334        }
335    }
336
337    /**
338     * Adds an input field with the given label to the dialog.<p>
339     *
340     * @param label the label
341     * @param inputWidget the input widget
342     */
343    protected void addInputField(String label, Widget inputWidget) {
344
345        FlowPanel row = new FlowPanel();
346        row.setStyleName(I_CmsLayoutBundle.INSTANCE.groupcontainerCss().inputRow());
347        CmsLabel labelWidget = new CmsLabel(label);
348        labelWidget.setStyleName(I_CmsLayoutBundle.INSTANCE.groupcontainerCss().inputLabel());
349        row.add(labelWidget);
350        inputWidget.addStyleName(I_CmsLayoutBundle.INSTANCE.groupcontainerCss().inputBox());
351        row.add(inputWidget);
352        m_dialogContent.add(row);
353    }
354
355    /**
356     * Adds the required input fields to the dialog.<p>
357     */
358    protected abstract void addInputFields();
359
360    /**
361     * Adds the save button to the dialog.<p>
362     */
363    protected void addSaveButton() {
364
365        if (m_editorDialog != null) {
366            m_saveButton = new CmsPushButton();
367            m_saveButton.setText(Messages.get().key(Messages.GUI_BUTTON_SAVE_TEXT_0));
368            m_saveButton.setUseMinWidth(true);
369            m_saveButton.setButtonStyle(ButtonStyle.TEXT, ButtonColor.GREEN);
370            m_saveButton.addClickHandler(new ClickHandler() {
371
372                public void onClick(ClickEvent event) {
373
374                    saveEdit();
375                }
376            });
377            m_editorDialog.addButton(m_saveButton);
378        }
379    }
380
381    /**
382     * On click function for cancel button.<p>
383     */
384    protected abstract void cancelEdit();
385
386    /**
387     * Clears the static instance reference.<p>
388     */
389    protected abstract void clearInstance();
390
391    /**
392     * Closes the dialog.<p>
393     *
394     * @param breakingUp <code>true</code> if the group container is to be removed
395     */
396    protected void closeDialog(boolean breakingUp) {
397
398        m_controller.stopEditingGroupcontainer();
399        m_editingPlaceholder.removeFromParent();
400        m_editorDialog.hide();
401        RootPanel.get().removeStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().groupcontainerEditing());
402        if (!breakingUp) {
403            m_groupContainer.clearEditingPlaceholder();
404            Style style = m_groupContainer.getElement().getStyle();
405            style.clearPosition();
406            style.clearTop();
407            style.clearLeft();
408            style.clearZIndex();
409            style.clearWidth();
410            m_parentContainer.insert(m_groupContainer, m_indexPosition);
411            m_groupContainer.getElementOptionBar().setVisible(true);
412            if (!m_groupContainer.iterator().hasNext()) {
413                // group-container is empty, mark it
414                m_groupContainer.addStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().emptyGroupContainer());
415            }
416        }
417        clearInstance();
418        removeFromParent();
419        if (!m_controller.getData().isUseClassicEditor()) {
420            for (Widget element : m_groupContainer) {
421                if (element instanceof CmsContainerPageElementPanel) {
422                    ((CmsContainerPageElementPanel)element).removeInlineEditor();
423                }
424            }
425        }
426        m_controller.reinitializeButtons();
427        m_controller.reInitInlineEditing();
428        m_controller.fireEvent(new CmsContainerpageEvent(EventType.elementEdited));
429    }
430
431    /**
432     * Creates a place-holder for the group-container.<p>
433     *
434     * @param element the element
435     *
436     * @return the place-holder widget
437     */
438    protected Element createPlaceholder(Element element) {
439
440        Element result = CmsDomUtil.clone(element);
441        result.addClassName(I_CmsLayoutBundle.INSTANCE.containerpageCss().groupcontainerPlaceholder());
442        result.getStyle().setBackgroundColor("transparent");
443        return result;
444    }
445
446    /**
447     * Returns the list of back up elements.<p>
448     *
449     * @return the back up elements
450     */
451    protected List<CmsContainerPageElementPanel> getBackUpElements() {
452
453        return m_backUpElements;
454    }
455
456    /**
457     * Returns the container page controller.<p>
458     *
459     * @return the container page controller
460     */
461    protected CmsContainerpageController getController() {
462
463        return m_controller;
464    }
465
466    /**
467     * Returns the ids of the contained elements and group-container itself.<p>
468     *
469     * @return the element ids
470     */
471    protected Set<String> getElementIds() {
472
473        Set<String> subItems = new HashSet<String>();
474        Iterator<Widget> it = m_groupContainer.iterator();
475        while (it.hasNext()) {
476            Widget w = it.next();
477            if (w instanceof CmsContainerPageElementPanel) {
478                subItems.add(((CmsContainerPageElementPanel)w).getId());
479            }
480        }
481        subItems.add(m_groupContainer.getId());
482        return subItems;
483    }
484
485    /**
486     * Returns the element data of the contained elements.<p>
487     *
488     * @return the contained elements data
489     */
490    protected List<CmsContainerElement> getElements() {
491
492        List<CmsContainerElement> subItems = new ArrayList<CmsContainerElement>();
493        Iterator<Widget> it = m_groupContainer.iterator();
494        while (it.hasNext()) {
495            Widget w = it.next();
496            if (w instanceof CmsContainerPageElementPanel) {
497                CmsContainerPageElementPanel elementWidget = (CmsContainerPageElementPanel)w;
498                CmsContainerElement element = new CmsContainerElement();
499                element.setClientId(elementWidget.getId());
500                element.setResourceType(elementWidget.getNewType());
501                element.setCreateNew(elementWidget.isCreateNew());
502                element.setSitePath(elementWidget.getSitePath());
503                element.setNewEditorDisabled(elementWidget.isNewEditorDisabled());
504                subItems.add(element);
505            }
506        }
507        return subItems;
508    }
509
510    /**
511     * Returns the group container widget index position.<p>
512     *
513     * @return the index position
514     */
515    protected int getIndexPosition() {
516
517        return m_indexPosition;
518    }
519
520    /**
521     * Returns the parent container widget.<p>
522     *
523     * @return the parent container widget
524     */
525    protected CmsContainerPageContainer getParentContainer() {
526
527        return m_parentContainer;
528    }
529
530    /**
531     * Opens the group container edit dialog.<p>
532     *
533     * @param dialogTitle the dialog title
534     */
535    protected void openDialog(String dialogTitle) {
536
537        m_editorDialog.setCaption(dialogTitle);
538        int contentHeight = m_dialogContent.getOffsetHeight();
539        m_editorDialog.setMainContent(m_dialogContent);
540        // position dialog and show it
541        if (m_groupContainerPosition != null) {
542            int lefthandSpace = m_groupContainerPosition.getLeft() - Window.getScrollLeft();
543            int righthandSpace = (Window.getClientWidth() + Window.getScrollLeft())
544                - (m_groupContainerPosition.getLeft() + m_groupContainerPosition.getWidth());
545            int requiredWidth = CmsPopup.DEFAULT_WIDTH + 30;
546            int left = m_groupContainerPosition.getLeft();
547            if (requiredWidth > (righthandSpace + m_groupContainerPosition.getWidth())) {
548                left = (Window.getClientWidth() + Window.getScrollLeft()) - requiredWidth;
549            }
550            if (left < Window.getScrollLeft()) {
551                left = 0;
552            }
553            if (lefthandSpace > requiredWidth) {
554                // place left of the group container if there is enough space
555                m_editorDialog.setPopupPosition(
556                    m_groupContainerPosition.getLeft() - requiredWidth,
557                    m_groupContainerPosition.getTop() - 1);
558            } else if ((m_groupContainerPosition.getTop() - Window.getScrollTop()) > (contentHeight
559                + DIALOG_BASE_HEIGHT
560                + 50)) {
561                // else place above if there is enough space
562
563                m_editorDialog.setPopupPosition(
564                    left,
565                    m_groupContainerPosition.getTop() - (contentHeight + DIALOG_BASE_HEIGHT));
566            } else if (righthandSpace > requiredWidth) {
567                // else on the right if there is enough space
568                m_editorDialog.setPopupPosition(
569                    m_groupContainerPosition.getLeft() + m_groupContainerPosition.getWidth() + 20,
570                    m_groupContainerPosition.getTop() - 1);
571            } else {
572                // last resort, place below
573                m_editorDialog.setPopupPosition(
574                    left,
575                    m_groupContainerPosition.getTop() + m_groupContainerPosition.getHeight() + 20);
576            }
577            m_editorDialog.show();
578        } else {
579            // should never happen
580            m_editorDialog.center();
581        }
582        if (!m_controller.getData().isUseClassicEditor()) {
583            for (Widget element : m_groupContainer) {
584                if (element instanceof CmsContainerPageElementPanel) {
585                    ((CmsContainerPageElementPanel)element).initInlineEditor(m_controller);
586                }
587            }
588        }
589    }
590
591    /**
592     * Removes all child container elements.<p>
593     */
594    protected void removeAllChildren() {
595
596        for (int i = getGroupContainerWidget().getWidgetCount() - 1; i >= 0; i--) {
597            Widget widget = getGroupContainerWidget().getWidget(i);
598            if (widget instanceof CmsContainerPageElementPanel) {
599                widget.removeFromParent();
600            }
601        }
602    }
603
604    /**
605     * On click function for save button.<p>
606     */
607    protected abstract void saveEdit();
608
609    /**
610     * Enables or disables the save button.<p>
611     *
612     * @param enabled <code>true</code> to enable the save button
613     * @param disabledMessage the message to display when the button is disabled
614     */
615    protected void setSaveEnabled(boolean enabled, String disabledMessage) {
616
617        if (m_saveButton != null) {
618            if (enabled) {
619                m_saveButton.enable();
620            } else {
621                m_saveButton.disable(disabledMessage);
622            }
623        }
624    }
625}