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;
029
030import org.opencms.ade.containerpage.client.CmsContainerpageEvent.EventType;
031import org.opencms.ade.containerpage.client.ui.CmsConfirmRemoveDialog;
032import org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer;
033import org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel;
034import org.opencms.ade.containerpage.client.ui.CmsGroupContainerElementPanel;
035import org.opencms.ade.containerpage.client.ui.CmsRemovedElementDeletionDialog;
036import org.opencms.ade.containerpage.client.ui.CmsReuseInfoDialog;
037import org.opencms.ade.containerpage.client.ui.CmsSmallElementsHandler;
038import org.opencms.ade.containerpage.client.ui.I_CmsDropContainer;
039import org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle;
040import org.opencms.ade.containerpage.client.ui.groupeditor.A_CmsGroupEditor;
041import org.opencms.ade.containerpage.client.ui.groupeditor.CmsGroupContainerEditor;
042import org.opencms.ade.containerpage.client.ui.groupeditor.CmsInheritanceContainerEditor;
043import org.opencms.ade.containerpage.shared.CmsCntPageData;
044import org.opencms.ade.containerpage.shared.CmsCntPageData.ElementDeleteMode;
045import org.opencms.ade.containerpage.shared.CmsContainer;
046import org.opencms.ade.containerpage.shared.CmsContainerElement;
047import org.opencms.ade.containerpage.shared.CmsContainerElementData;
048import org.opencms.ade.containerpage.shared.CmsContainerPageGalleryData;
049import org.opencms.ade.containerpage.shared.CmsContainerPageRpcContext;
050import org.opencms.ade.containerpage.shared.CmsCreateElementData;
051import org.opencms.ade.containerpage.shared.CmsDialogOptionsAndInfo;
052import org.opencms.ade.containerpage.shared.CmsElementSettingsConfig;
053import org.opencms.ade.containerpage.shared.CmsElementViewInfo;
054import org.opencms.ade.containerpage.shared.CmsGroupContainer;
055import org.opencms.ade.containerpage.shared.CmsGroupContainerSaveResult;
056import org.opencms.ade.containerpage.shared.CmsInheritanceContainer;
057import org.opencms.ade.containerpage.shared.CmsRemovedElementStatus;
058import org.opencms.ade.containerpage.shared.CmsReuseInfo;
059import org.opencms.ade.containerpage.shared.rpc.I_CmsContainerpageService;
060import org.opencms.ade.containerpage.shared.rpc.I_CmsContainerpageServiceAsync;
061import org.opencms.ade.contenteditor.client.CmsContentEditor;
062import org.opencms.gwt.client.CmsCoreProvider;
063import org.opencms.gwt.client.I_CmsElementToolbarContext;
064import org.opencms.gwt.client.dnd.CmsCompositeDNDController;
065import org.opencms.gwt.client.dnd.CmsDNDHandler;
066import org.opencms.gwt.client.dnd.I_CmsDNDController;
067import org.opencms.gwt.client.rpc.CmsRpcAction;
068import org.opencms.gwt.client.rpc.CmsRpcPrefetcher;
069import org.opencms.gwt.client.ui.CmsErrorDialog;
070import org.opencms.gwt.client.ui.CmsNotification;
071import org.opencms.gwt.client.ui.CmsNotification.Type;
072import org.opencms.gwt.client.ui.contextmenu.CmsEmbeddedAction;
073import org.opencms.gwt.client.util.CmsDebugLog;
074import org.opencms.gwt.client.util.CmsDomUtil;
075import org.opencms.gwt.client.util.I_CmsSimpleCallback;
076import org.opencms.gwt.shared.CmsContextMenuEntryBean;
077import org.opencms.gwt.shared.CmsCoreData.AdeContext;
078import org.opencms.gwt.shared.CmsGalleryContainerInfo;
079import org.opencms.gwt.shared.CmsGwtConstants;
080import org.opencms.gwt.shared.CmsGwtLog;
081import org.opencms.gwt.shared.CmsListInfoBean;
082import org.opencms.gwt.shared.CmsTemplateContextInfo;
083import org.opencms.gwt.shared.I_CmsAutoBeanFactory;
084import org.opencms.gwt.shared.I_CmsUnlockData;
085import org.opencms.gwt.shared.rpc.I_CmsCoreServiceAsync;
086import org.opencms.util.CmsDefaultSet;
087import org.opencms.util.CmsStringUtil;
088import org.opencms.util.CmsUUID;
089
090import java.util.ArrayList;
091import java.util.Collection;
092import java.util.HashMap;
093import java.util.HashSet;
094import java.util.Iterator;
095import java.util.List;
096import java.util.Map;
097import java.util.Map.Entry;
098import java.util.Set;
099
100import com.google.common.base.Optional;
101import com.google.common.collect.Lists;
102import com.google.gwt.core.client.GWT;
103import com.google.gwt.dom.client.AnchorElement;
104import com.google.gwt.dom.client.Element;
105import com.google.gwt.dom.client.EventTarget;
106import com.google.gwt.event.dom.client.KeyCodes;
107import com.google.gwt.event.logical.shared.ResizeEvent;
108import com.google.gwt.event.logical.shared.ResizeHandler;
109import com.google.gwt.event.logical.shared.ValueChangeEvent;
110import com.google.gwt.event.logical.shared.ValueChangeHandler;
111import com.google.gwt.user.client.Command;
112import com.google.gwt.user.client.Event;
113import com.google.gwt.user.client.Event.NativePreviewEvent;
114import com.google.gwt.user.client.Event.NativePreviewHandler;
115import com.google.gwt.user.client.History;
116import com.google.gwt.user.client.Timer;
117import com.google.gwt.user.client.Window;
118import com.google.gwt.user.client.rpc.AsyncCallback;
119import com.google.gwt.user.client.rpc.SerializationException;
120import com.google.gwt.user.client.rpc.ServiceDefTarget;
121import com.google.gwt.user.client.ui.RootPanel;
122import com.google.gwt.user.client.ui.Widget;
123import com.google.web.bindery.autobean.shared.AutoBean;
124import com.google.web.bindery.autobean.shared.AutoBeanCodex;
125
126import elemental2.core.JsObject;
127import elemental2.dom.CustomEvent;
128import elemental2.dom.CustomEventInit;
129import elemental2.dom.DomGlobal;
130import elemental2.webstorage.WebStorageWindow;
131import jsinterop.annotations.JsPackage;
132import jsinterop.annotations.JsProperty;
133import jsinterop.annotations.JsType;
134import jsinterop.base.Js;
135import jsinterop.base.JsPropertyMap;
136
137/**
138 * Data provider for the container-page editor. All data concerning the container-page is requested and maintained by this provider.<p>
139 *
140 * @since 8.0.0
141 */
142public final class CmsContainerpageController {
143
144    /**
145     * Enum which is used to control how elements are removed from the page.<p>
146     */
147    public enum ElementRemoveMode {
148        /** Reference checks are performed and the user is asked for confirmation whether they really want to remove the element before the page is saved. */
149        confirmRemove,
150
151        /** Reference checks are only performed after the page or group has been saved. */
152        saveAndCheckReferences,
153
154        /** Element is just removed, no checks are performed. */
155        silent;
156    }
157
158    /**
159     * Wrapper for the event details object for custom page editor events.
160     */
161    @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "?")
162    public interface I_EventDetails {
163
164        /**
165         * Gets the HTML element for the container itself.
166         *
167         * @return the HTML element for the container
168         */
169        @JsProperty
170        elemental2.dom.Element getContainer();
171
172        /**
173         * Gets the HTML element for the page element.
174         *
175         * @return the HTML element for the page element
176         */
177        @JsProperty
178        elemental2.dom.Element getElement();
179
180        /**
181         * The 'placeholder' state of the element.
182         *
183         * @return the 'placedholder' state
184         */
185        @JsProperty
186        boolean getIsPlaceholder();
187
188        /**
189         * Gets the replaced element.
190         *
191         * @return the replaced element
192         */
193        @JsProperty
194        elemental2.dom.Element getReplacedElement();
195
196        /**
197         * Gets the type.
198         *
199         * @return the type
200         */
201        @JsProperty
202        String getType();
203
204        /**
205         * Sets the HTML element for the container.
206         *
207         * @param container the HTML element for the container
208         */
209        @JsProperty
210        void setContainer(elemental2.dom.Element container);
211
212        /**
213         * Sets the HTML element for the page element.
214         * @param element the HTML element for the page element
215         */
216        @JsProperty
217        void setElement(elemental2.dom.Element element);
218
219        /**
220         * Sets the 'placeholder' (i.e. 'new') state
221         *
222         * @param isPlaceholder true if the element was new
223         */
224        @JsProperty
225        void setIsPlaceholder(boolean isPlaceholder);
226
227        /**
228         * Sets the replaced element.
229         *
230         * @param element the replaced element
231         */
232        @JsProperty
233        void setReplacedElement(elemental2.dom.Element element);
234
235        /**
236         * Sets the type.
237         *
238         * @param type the type
239         */
240        @JsProperty
241        void setType(String type);
242
243    }
244
245    /**
246     * Visitor interface used to process the current container content on the page.<p>
247     */
248    public static interface I_PageContentVisitor {
249
250        /**
251         * This method is called before a container is processed.<p>
252         *
253         * If the method returns false, the container will be skipped.<p>
254         *
255         * @param name the container name
256         * @param container the container data object
257         *
258         * @return true if the container should be processed, true if it should be skipped
259         */
260        boolean beginContainer(String name, CmsContainer container);
261
262        /**
263         * This method is called after all elements of a container have been processed.<p>
264         */
265        void endContainer();
266
267        /**
268         * This method is called for each element of a container.<p>
269         *
270         * @param element the container element
271         */
272        void handleElement(CmsContainerPageElementPanel element);
273    }
274
275    /**
276     * Interface for classes that should be notified when container page elements are reloaded.
277     */
278    public interface I_ReloadHandler {
279
280        /**
281         * Called after all other onReload calls for the current operation.
282         * */
283        void finish();
284
285        /**
286         * Is called when an element has been replaced on the page.
287         *
288         * @param oldElement the old element
289         * @param newElement the replacement element that is currently on the page
290         */
291        void onReload(CmsContainerPageElementPanel oldElement, CmsContainerPageElementPanel newElement);
292    }
293
294    /**
295     * This visitor implementation checks whether there are other elements in the current page
296     * which correspond to the same VFS resource as a given container element.
297     */
298    public static class ReferenceCheckVisitor implements I_PageContentVisitor {
299
300        /** The element for which we want to check whether there are other references to the same resource. */
301        private CmsContainerPageElementPanel m_elementPanel;
302
303        /** True if other references have been found. */
304        private boolean m_hasReferences;
305
306        /** The structure id of the element. */
307        private String m_structureId;
308
309        /**
310         * Creates a new instance.<p>
311         *
312         * @param elementPanel the element for which we want to check if there are other references
313         */
314        public ReferenceCheckVisitor(CmsContainerPageElementPanel elementPanel) {
315
316            m_elementPanel = elementPanel;
317            m_structureId = getServerId(elementPanel.getId());
318        }
319
320        /**
321         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#beginContainer(java.lang.String, org.opencms.ade.containerpage.shared.CmsContainer)
322         */
323        public boolean beginContainer(String name, CmsContainer container) {
324
325            return !container.isDetailView();
326        }
327
328        /**
329         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#endContainer()
330         */
331        public void endContainer() {
332
333            // do nothing
334        }
335
336        /**
337         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#handleElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)
338         */
339        public void handleElement(CmsContainerPageElementPanel element) {
340
341            if (element != m_elementPanel) {
342                String id = getServerId(element.getId());
343                if (m_structureId.equals(id)) {
344                    m_hasReferences = true;
345                }
346            }
347        }
348
349        /**
350         * Checks if other references have been found.<p>
351         *
352         * @return true if other references have been found
353         */
354        public boolean hasReferences() {
355
356            return m_hasReferences;
357        }
358
359    }
360
361    /**
362     * Visitor implementation which is used to gather the container contents for saving.<p>
363     */
364    protected class PageStateVisitor implements I_PageContentVisitor {
365
366        /** The current container name. */
367        protected String m_containerName;
368
369        /** The contaienr which is currently being processed. */
370        protected CmsContainer m_currentContainer;
371
372        /** The list of collected containers. */
373        protected List<CmsContainer> m_resultContainers = new ArrayList<CmsContainer>();
374
375        /** The list of elements of the currently processed container which have already been processed. */
376        List<CmsContainerElement> m_currentElements;
377
378        /**
379         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#beginContainer(java.lang.String, org.opencms.ade.containerpage.shared.CmsContainer)
380         */
381        public boolean beginContainer(String name, CmsContainer container) {
382
383            m_currentContainer = container;
384            m_containerName = name;
385            m_currentElements = new ArrayList<CmsContainerElement>();
386            return true;
387        }
388
389        /**
390         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#endContainer()
391         */
392        public void endContainer() {
393
394            CmsContainer container = new CmsContainer(
395                m_containerName,
396                m_currentContainer.getType(),
397                null,
398                m_currentContainer.getWidth(),
399                m_currentContainer.getMaxElements(),
400                m_currentContainer.isDetailViewContainer(),
401                m_currentContainer.isDetailView(),
402                true,
403                m_currentElements,
404                m_currentContainer.getParentContainerName(),
405                m_currentContainer.getParentInstanceId(),
406                m_currentContainer.getSettingPresets());
407            container.setDetailOnly(m_currentContainer.isDetailOnly());
408            container.setRootContainer(isRootContainer(m_currentContainer));
409            m_resultContainers.add(container);
410        }
411
412        /**
413         * Gets the list of collected containers.<p>
414         *
415         * @return the list of containers
416         */
417        public List<CmsContainer> getContainers() {
418
419            return m_resultContainers;
420        }
421
422        /**
423         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#handleElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)
424         */
425        public void handleElement(CmsContainerPageElementPanel elementWidget) {
426
427            CmsContainerElement element = new CmsContainerElement();
428            element.setClientId(elementWidget.getId());
429            element.setResourceType(elementWidget.getNewType());
430            element.setCreateNew(elementWidget.isCreateNew());
431            element.setModelGroupId(elementWidget.getModelGroupId());
432            element.setSitePath(elementWidget.getSitePath());
433            element.setNewEditorDisabled(elementWidget.isNewEditorDisabled());
434            m_currentElements.add(element);
435        }
436
437    }
438
439    /**
440     * Visitor implementation which is used to gather the container contents for saving.<p>
441     */
442    protected class SaveDataVisitor implements I_PageContentVisitor {
443
444        /** The current container name. */
445        protected String m_containerName;
446
447        /** The contaienr which is currently being processed. */
448        protected CmsContainer m_currentContainer;
449
450        /** The list of collected containers. */
451        protected List<CmsContainer> m_resultContainers = new ArrayList<CmsContainer>();
452
453        /** The list of elements of the currently processed container which have already been processed. */
454        List<CmsContainerElement> m_currentElements;
455
456        /**
457         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#beginContainer(java.lang.String, org.opencms.ade.containerpage.shared.CmsContainer)
458         */
459        public boolean beginContainer(String name, CmsContainer container) {
460
461            if (container.isDetailView() || ((getData().getDetailId() != null) && !container.isDetailOnly())) {
462                m_currentContainer = null;
463                return false;
464
465            } else {
466                m_currentContainer = container;
467                m_containerName = name;
468                m_currentElements = new ArrayList<CmsContainerElement>();
469                return true;
470            }
471        }
472
473        /**
474         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#endContainer()
475         */
476        public void endContainer() {
477
478            CmsContainer container = new CmsContainer(
479                m_containerName,
480                m_currentContainer.getType(),
481                null,
482                m_currentContainer.getWidth(),
483                m_currentContainer.getMaxElements(),
484                m_currentContainer.isDetailViewContainer(),
485                m_currentContainer.isDetailView(),
486                true,
487                m_currentElements,
488                m_currentContainer.getParentContainerName(),
489                m_currentContainer.getParentInstanceId(),
490                m_currentContainer.getSettingPresets());
491
492            container.setRootContainer(isRootContainer(m_currentContainer));
493            m_resultContainers.add(container);
494        }
495
496        /**
497         * Gets the list of collected containers.<p>
498         *
499         * @return the list of containers
500         */
501        public List<CmsContainer> getContainers() {
502
503            return m_resultContainers;
504        }
505
506        /**
507         * @see org.opencms.ade.containerpage.client.CmsContainerpageController.I_PageContentVisitor#handleElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)
508         */
509        public void handleElement(CmsContainerPageElementPanel elementWidget) {
510
511            CmsContainerElement element = new CmsContainerElement();
512            element.setClientId(elementWidget.getId());
513            element.setResourceType(elementWidget.getNewType());
514            element.setCreateNew(elementWidget.isCreateNew());
515            element.setModelGroupId(elementWidget.getModelGroupId());
516            element.setSitePath(elementWidget.getSitePath());
517            element.setNewEditorDisabled(elementWidget.isNewEditorDisabled());
518            m_currentElements.add(element);
519        }
520
521    }
522
523    /**
524     * A type which indicates the locking status of the currently edited container page.<p>
525     */
526    enum LockStatus {
527        /** Locking the resource failed. */
528        failed,
529
530        /** The resource could be successfully locked. */
531        locked,
532
533        /** Locking the resource has not been tried. */
534        unknown
535    }
536
537    /**
538     * A RPC action implementation used to request the data for container-page elements.<p>
539     */
540    private class MultiElementAction extends CmsRpcAction<Map<String, CmsContainerElementData>> {
541
542        /** Call-back executed on response. */
543        private I_CmsSimpleCallback<Map<String, CmsContainerElementData>> m_callback;
544
545        /** The requested client id's. */
546        private Set<String> m_clientIds;
547
548        /**
549        "         * Constructor.<p>
550         *
551         * @param clientIds the client id's
552         * @param callback the call-back
553         */
554        public MultiElementAction(
555            Set<String> clientIds,
556            I_CmsSimpleCallback<Map<String, CmsContainerElementData>> callback) {
557
558            super();
559            m_clientIds = clientIds;
560            m_callback = callback;
561        }
562
563        /**
564         * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
565         */
566        @Override
567        public void execute() {
568
569            Map<String, CmsContainerElementData> result = new HashMap<String, CmsContainerElementData>();
570            List<String> neededIds = new ArrayList<String>();
571            Iterator<String> it = m_clientIds.iterator();
572            while (it.hasNext()) {
573                String clientId = it.next();
574                if (m_elements.containsKey(clientId)) {
575                    result.put(clientId, m_elements.get(clientId));
576                } else {
577                    neededIds.add(clientId);
578                }
579            }
580            if (neededIds.size() == 0) {
581                m_callback.execute(result);
582            } else {
583                getContainerpageService().getElementsData(
584                    getData().getRpcContext(),
585                    getData().getDetailId(),
586                    getRequestParams(),
587                    m_clientIds,
588                    getPageState(),
589                    false,
590                    null,
591                    getLocale(),
592                    this);
593            }
594
595        }
596
597        /**
598         * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
599         */
600        @Override
601        protected void onResponse(Map<String, CmsContainerElementData> result) {
602
603            if (result != null) {
604                addElements(result);
605                Map<String, CmsContainerElementData> elements = new HashMap<String, CmsContainerElementData>();
606                Iterator<String> it = m_clientIds.iterator();
607                while (it.hasNext()) {
608                    String clientId = it.next();
609                    elements.put(clientId, m_elements.get(clientId));
610                }
611                m_callback.execute(elements);
612            }
613        }
614
615    }
616
617    /**
618     * A RPC action implementation used to reload the data for a container-page element.<p>
619     */
620    private class ReloadElementAction extends CmsRpcAction<Map<String, CmsContainerElementData>> {
621
622        /** The requested client id's. */
623        private Set<String> m_clientIds;
624
625        /** The callback to execute after the reload. */
626        private I_ReloadHandler m_reloadHandler;
627
628        /**
629         * Constructor.<p>
630         *
631         * @param clientIds the client id's to reload
632         * @param reloadHandler  the callback to call after the elements have been replaced
633         */
634        public ReloadElementAction(Set<String> clientIds, I_ReloadHandler reloadHandler) {
635
636            super();
637            m_clientIds = clientIds;
638            m_reloadHandler = reloadHandler;
639        }
640
641        /**
642         * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
643         */
644        @Override
645        public void execute() {
646
647            getContainerpageService().getElementsData(
648                getData().getRpcContext(),
649                getData().getDetailId(),
650                getRequestParams(),
651                m_clientIds,
652                getPageState(),
653                false,
654                null,
655                getLocale(),
656                this);
657        }
658
659        /**
660         * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
661         */
662        @Override
663        protected void onResponse(Map<String, CmsContainerElementData> result) {
664
665            if (result == null) {
666                return;
667            }
668            addElements(result);
669            Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> it = getAllDragElements().iterator();
670            boolean reloadMarkerFound = false;
671            while (it.hasNext()) {
672                org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel containerElement = it.next();
673                if (!m_clientIds.contains(containerElement.getId())) {
674                    continue;
675                }
676                try {
677                    CmsContainerPageElementPanel replacer = replaceContainerElement(
678                        containerElement,
679                        result.get(containerElement.getId()));
680                    if (replacer.getElement().getInnerHTML().contains(CmsGwtConstants.FORMATTER_RELOAD_MARKER)) {
681                        reloadMarkerFound = true;
682                    }
683                    m_reloadHandler.onReload(containerElement, replacer);
684                } catch (Exception e) {
685                    CmsDebugLog.getInstance().printLine("trying to replace");
686                    CmsDebugLog.getInstance().printLine(e.getLocalizedMessage());
687                }
688
689            }
690            if (isGroupcontainerEditing()) {
691                getGroupEditor().updateBackupElements(result);
692                getGroupcontainer().refreshHighlighting();
693            } else {
694                if (reloadMarkerFound) {
695                    CmsContainerpageController.get().reloadPage();
696                } else {
697                    long loadTime = result.values().iterator().next().getLoadTime();
698                    setLoadTime(Long.valueOf(loadTime));
699                }
700            }
701            m_handler.updateClipboard(result);
702            resetEditButtons();
703            CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.elementEdited));
704            if (m_reloadHandler != null) {
705                m_reloadHandler.finish();
706            }
707        }
708    }
709
710    /**
711     * A RPC action implementation used to request the data for a single container-page element.<p>
712     */
713    private class SingleElementAction extends CmsRpcAction<Map<String, CmsContainerElementData>> {
714
715        /** Always copy createNew elements in case reading data for a clipboard element used as a copy group. */
716        private boolean m_alwaysCopy;
717
718        /** Call-back executed on response. */
719        private I_CmsSimpleCallback<CmsContainerElementData> m_callback;
720        /** The requested client id. */
721        private String m_clientId;
722
723        /** If this action was triggered by drag and drop from a container, this should contain the id of the origin container. */
724        private String m_dndContainer;
725
726        /**
727         * Constructor.<p>
728         *
729         * @param clientId the client id
730         * @param callback the call-back
731         * @param alwaysCopy <code>true</code> in case reading data for a clipboard element used as a copy group
732         */
733        public SingleElementAction(
734            String clientId,
735            boolean alwaysCopy,
736            I_CmsSimpleCallback<CmsContainerElementData> callback) {
737
738            super();
739            m_clientId = clientId;
740            m_callback = callback;
741            m_alwaysCopy = alwaysCopy;
742        }
743
744        /**
745         * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
746         */
747        @Override
748        public void execute() {
749
750            List<String> clientIds = new ArrayList<String>();
751            clientIds.add(m_clientId);
752            getContainerpageService().getElementsData(
753                getData().getRpcContext(),
754                getData().getDetailId(),
755                getRequestParams(),
756                clientIds,
757                getPageState(),
758                m_alwaysCopy,
759                m_dndContainer,
760                getLocale(),
761                this);
762        }
763
764        /**
765         * Sets the origin container for the drag and drop case.<p>
766         *
767         * @param containerId the origin container name
768         */
769        public void setDndContainer(String containerId) {
770
771            m_dndContainer = containerId;
772        }
773
774        /**
775         * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
776         */
777        @Override
778        protected void onResponse(Map<String, CmsContainerElementData> result) {
779
780            if (result != null) {
781                addElements(result);
782                long loadTime = result.get(m_clientId).getLoadTime();
783                setLoadTime(Long.valueOf(loadTime));
784                m_callback.execute(result.get(m_clientId));
785            }
786        }
787    }
788
789    /** The client side id/setting-hash seperator. */
790    public static final String CLIENT_ID_SEPERATOR = "#";
791
792    /** Custom event name. */
793    public static final String EVENT_DRAG = "ocDrag";
794
795    /** Custom event name. */
796    public static final String EVENT_DRAG_FINISHED = "ocDragFinished";
797
798    /** Custom event name. */
799    public static final String EVENT_DRAG_STARTED = "ocDragStarted";
800
801    /** Custom event name. */
802    public static final String EVENT_ELEMENT_ADDED = "ocElementAdded";
803
804    /** Custom event name. */
805    public static final String EVENT_ELEMENT_DELETED = "ocElementDeleted";
806
807    /** Custom event name. */
808    public static final String EVENT_ELEMENT_EDITED = "ocElementEdited";
809
810    /** Custom event name. */
811    public static final String EVENT_ELEMENT_EDITED_CONTENT = "ocElementEditedContent";
812
813    /** Custom event name. */
814    public static final String EVENT_ELEMENT_EDITED_SETTINGS = "ocElementEditedSettings";
815
816    /** Custom event name. */
817    public static final String EVENT_ELEMENT_MODIFIED = "ocElementModified";
818
819    /** Custom event name. */
820    public static final String EVENT_ELEMENT_MOVED = "ocElementMoved";
821
822    /** CSS class on the body to signal whether we are currently editing a group container. */
823    public static final String OC_EDITING_GROUPCONTAINER = "oc-editing-groupcontainer";
824
825    /** Parameter name. */
826    public static final String PARAM_REMOVEMODE = "removemode";
827
828    /** Instance of the data provider. */
829    private static CmsContainerpageController INSTANCE;
830
831    /** The container element data. All requested elements will be cached here.*/
832    protected Map<String, CmsContainerElementData> m_elements;
833
834    /** The new element data by resource type name. */
835    protected Map<String, CmsContainerElementData> m_newElements;
836
837    /** The gallery data update timer. */
838    Timer m_galleryUpdateTimer;
839
840    /** The currently editing group-container editor. */
841    A_CmsGroupEditor m_groupEditor;
842
843    /** The container-page handler. */
844    CmsContainerpageHandler m_handler;
845
846    /** The drag targets within this page. */
847    Map<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> m_targetContainers;
848
849    /** The UUIDs for which reuse status has already been checked. */
850    private Set<CmsUUID> m_checkedReuse = new HashSet<>();
851
852    /** The container page drag and drop controller. */
853    private I_CmsDNDController m_cntDndController;
854
855    /** The container-page RPC service. */
856    private I_CmsContainerpageServiceAsync m_containerpageService;
857
858    /** The container-page util instance. */
859    private CmsContainerpageUtil m_containerpageUtil;
860
861    /** The container data. */
862    private Map<String, CmsContainer> m_containers;
863
864    /** The XML content editor handler. */
865    private CmsContentEditorHandler m_contentEditorHandler;
866
867    /** The core RPC service instance. */
868    private I_CmsCoreServiceAsync m_coreSvc;
869
870    /** The current edit container level. */
871    private int m_currentEditLevel = -1;
872
873    /** The prefetched data. */
874    private CmsCntPageData m_data;
875
876    /** The DND controller. */
877    private CmsCompositeDNDController m_dndController;
878
879    /** The drag and drop handler. */
880    private CmsDNDHandler m_dndHandler;
881
882    /** Edit button position timer. */
883    private Timer m_editButtonsPositionTimer;
884
885    /** The current element view. */
886    private CmsElementViewInfo m_elementView;
887
888    /** Flag indicating that a content element is being edited. */
889    private boolean m_isContentEditing;
890
891    /** The container page load time. */
892    private long m_loadTime;
893
894    /** The lock error message. */
895    private String m_lockErrorMessage;
896
897    /** The current lock status for the page. */
898    private LockStatus m_lockStatus = LockStatus.unknown;
899
900    /** The max container level. */
901    private int m_maxContainerLevel;
902
903    /** The model group base element id. */
904    private String m_modelGroupElementId;
905
906    /** The browser location at the time the containerpage controller was initialized. */
907    private String m_originalUrl;
908
909    /** Flag if the container-page has changed. */
910    private boolean m_pageChanged;
911
912    /** The publish lock checker. */
913    private CmsPublishLockChecker m_publishLockChecker = new CmsPublishLockChecker(this);
914
915    /** Timer to handle window resize. */
916    private Timer m_resizeTimer;
917
918    /** Handler for small elements. */
919    private CmsSmallElementsHandler m_smallElementsHandler;
920
921    /** Alternative preview handler for events. */
922    private NativePreviewHandler m_previewHandler;
923
924    /**
925     * Constructor.<p>
926     */
927    public CmsContainerpageController() {
928
929        m_originalUrl = Window.Location.getHref();
930        INSTANCE = this;
931        try {
932            m_data = (CmsCntPageData)CmsRpcPrefetcher.getSerializedObjectFromDictionary(
933                getContainerpageService(),
934                CmsCntPageData.DICT_NAME);
935            m_elementView = m_data.getElementView();
936            m_modelGroupElementId = m_data.getModelGroupElementId();
937            m_loadTime = m_data.getLoadTime();
938            try {
939                WebStorageWindow window = WebStorageWindow.of(DomGlobal.window);
940                for (Map.Entry<String, String> entry : m_data.getSessionStorageData().entrySet()) {
941                    window.sessionStorage.setItem(entry.getKey(), entry.getValue());
942                }
943            } catch (Exception e) {
944                CmsGwtLog.log("can't use webstorage API");
945            }
946        } catch (SerializationException e) {
947            CmsErrorDialog.handleException(
948                new Exception(
949                    "Deserialization of page data failed. This may be caused by expired java-script resources, please clear your browser cache and try again.",
950                    e));
951        }
952        m_smallElementsHandler = new CmsSmallElementsHandler(getContainerpageService());
953        if (m_data != null) {
954            m_smallElementsHandler.setEditSmallElements(m_data.isEditSmallElementsInitially(), false);
955
956            m_data.setRpcContext(
957                new CmsContainerPageRpcContext(
958                    CmsCoreProvider.get().getStructureId(),
959                    m_data.getTemplateContextInfo().getCurrentContext()));
960        }
961
962    }
963
964    /**
965     * Returns the data provider instance.<p>
966     *
967     * @return the data provider
968     */
969    public static CmsContainerpageController get() {
970
971        if (INSTANCE == null) {
972            CmsDebugLog.getInstance().printLine("WARNING: The data provider has not been initialized!");
973            return null;
974        }
975        return INSTANCE;
976    }
977
978    /**
979     * Returns the current URI.<p>
980     *
981     * @return the current URI
982     */
983    public static String getCurrentUri() {
984
985        return CmsCoreProvider.get().getUri();
986
987    }
988
989    /**
990     * Returns the server id for a given client element id.<p>
991     *
992     * @param clientId the client id including an optional element settings hash
993     *
994     * @return the server id
995     */
996    public static String getServerId(String clientId) {
997
998        String serverId = clientId;
999        if (clientId.contains(CLIENT_ID_SEPERATOR)) {
1000            serverId = clientId.substring(0, clientId.lastIndexOf(CLIENT_ID_SEPERATOR));
1001        }
1002        return serverId;
1003    }
1004
1005    /**
1006     * Checks whether element removal should be confirmed.<p>
1007     *
1008     * @return true if element removal should be confirmed
1009     */
1010    public static boolean isConfirmRemove() {
1011
1012        Map<String, String> params = CmsCoreProvider.get().getAdeParameters();
1013        String removeMode = params.get(PARAM_REMOVEMODE);
1014        return (removeMode == null) || removeMode.equals("confirm");
1015    }
1016
1017    /**
1018     * Adds a handler for container page events.<p>
1019     *
1020     * @param handler the handler to add
1021     */
1022    public void addContainerpageEventHandler(I_CmsContainerpageEventHandler handler) {
1023
1024        CmsCoreProvider.get().getEventBus().addHandler(CmsContainerpageEvent.TYPE, handler);
1025    }
1026
1027    /**
1028     * Adds an element specified by it's id to the favorite list.<p>
1029     *
1030     * @param clientId the element id
1031     */
1032    public void addToFavoriteList(final String clientId) {
1033
1034        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
1035
1036            /**
1037             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1038             */
1039            @Override
1040            public void execute() {
1041
1042                getContainerpageService().addToFavoriteList(getData().getRpcContext(), clientId, this);
1043            }
1044
1045            /**
1046             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1047             */
1048            @Override
1049            protected void onResponse(Void result) {
1050
1051                CmsNotification.get().send(
1052                    Type.NORMAL,
1053                    Messages.get().key(Messages.GUI_NOTIFICATION_ADD_TO_FAVORITES_0));
1054            }
1055        };
1056        action.execute();
1057    }
1058
1059    /**
1060     * Adds an element specified by it's id to the recent list.<p>
1061     *
1062     * @param clientId the element id
1063     * @param nextAction the action to execute after the element has been added
1064     */
1065    public void addToRecentList(final String clientId, final Runnable nextAction) {
1066
1067        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
1068
1069            /**
1070             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1071             */
1072            @Override
1073            public void execute() {
1074
1075                getContainerpageService().addToRecentList(getData().getRpcContext(), clientId, this);
1076            }
1077
1078            /**
1079             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1080             */
1081            @Override
1082            protected void onResponse(Void result) {
1083
1084                if (nextAction != null) {
1085                    nextAction.run();
1086                }
1087            }
1088        };
1089        action.execute();
1090    }
1091
1092    /**
1093     * Checks whether GWT widgets are available for all fields of a content.<p>
1094     *
1095     * @param structureId the structure id of the content
1096     * @param resultCallback the callback for the result
1097     */
1098    public void checkNewWidgetsAvailable(final CmsUUID structureId, final AsyncCallback<Boolean> resultCallback) {
1099
1100        CmsRpcAction<Boolean> action = new CmsRpcAction<Boolean>() {
1101
1102            @Override
1103            public void execute() {
1104
1105                start(200, false);
1106                getContainerpageService().checkNewWidgetsAvailable(structureId, this);
1107            }
1108
1109            @Override
1110            protected void onResponse(Boolean result) {
1111
1112                stop(false);
1113                resultCallback.onSuccess(result);
1114            }
1115
1116            // empty
1117        };
1118        action.execute();
1119
1120    }
1121
1122    /**
1123     * Checks reuse status of a container element, then shows a warning dialog if it is reused, and finally executes an action.
1124     *
1125     * @param elementWidget the container element for which to check the reuse status
1126     * @param action the action to execute after the reuse warning dialog is closed
1127     */
1128    public void checkReuse(CmsContainerPageElementPanel elementWidget, Runnable action) {
1129
1130        Runnable wrappedAction = () -> {
1131            m_checkedReuse.add(elementWidget.getStructureId());
1132            action.run();
1133        };
1134        if (elementWidget.isReused()
1135            && CmsCoreProvider.get().isWarnWhenEditingReusedElement()
1136            && !m_checkedReuse.contains(elementWidget.getStructureId())) {
1137            CmsUUID id = elementWidget.getStructureId();
1138            CmsUUID detailId = CmsContainerpageController.get().getData().getDetailId();
1139            CmsUUID pageId = CmsCoreProvider.get().getStructureId();
1140            CmsRpcAction<CmsReuseInfo> rpc = new CmsRpcAction<CmsReuseInfo>() {
1141
1142                @Override
1143                public void execute() {
1144
1145                    start(0, true);
1146                    getContainerpageService().getReuseInfo(pageId, detailId, id, this);
1147                }
1148
1149                @Override
1150                protected void onResponse(CmsReuseInfo result) {
1151
1152                    stop(false);
1153                    if (result.getCount() == 0) {
1154                        wrappedAction.run();
1155                    } else {
1156                        CmsReuseInfoDialog dialog = new CmsReuseInfoDialog(result, status -> {
1157                            if (status.booleanValue()) {
1158                                wrappedAction.run();
1159                            }
1160                        });
1161                        dialog.show();
1162                        // without delayed center(), positioning is inconsistent
1163                        Timer timer = new Timer() {
1164
1165                            @Override
1166                            public void run() {
1167
1168                                dialog.center();
1169                            }
1170                        };
1171                        timer.schedule(100);
1172                    }
1173                }
1174            };
1175            rpc.execute();
1176        } else {
1177            action.run();
1178        }
1179    }
1180
1181    /**
1182     * Checks for container elements that are no longer present within the DOM.<p>
1183     */
1184    public void cleanUpContainers() {
1185
1186        List<String> removed = new ArrayList<String>();
1187        for (Entry<String, CmsContainerPageContainer> entry : m_targetContainers.entrySet()) {
1188            if (!RootPanel.getBodyElement().isOrHasChild(entry.getValue().getElement())) {
1189                removed.add(entry.getKey());
1190            }
1191        }
1192        for (String containerId : removed) {
1193            m_targetContainers.remove(containerId);
1194            m_containers.remove(containerId);
1195        }
1196        if (removed.size() > 0) {
1197            scheduleGalleryUpdate();
1198        }
1199    }
1200
1201    /**
1202     * Copies an element and asynchronously returns the structure id of the copy.<p>
1203     *
1204     * @param id the element id
1205     * @param callback the callback for the result
1206     */
1207    public void copyElement(final String id, final I_CmsSimpleCallback<CmsUUID> callback) {
1208
1209        CmsRpcAction<CmsUUID> action = new CmsRpcAction<CmsUUID>() {
1210
1211            @Override
1212            public void execute() {
1213
1214                start(200, false);
1215                getContainerpageService().copyElement(
1216                    CmsCoreProvider.get().getStructureId(),
1217                    new CmsUUID(id),
1218                    getData().getLocale(),
1219                    this);
1220            }
1221
1222            @Override
1223            protected void onResponse(CmsUUID result) {
1224
1225                stop(false);
1226                callback.execute(result);
1227            }
1228
1229        };
1230        action.execute();
1231    }
1232
1233    /**
1234     * Creates a new resource for crag container elements with the status new and opens the content editor.<p>
1235     *
1236     * @param element the container element
1237     * @param inline <code>true</code> to open the inline editor for the given element if available
1238     */
1239    public void createAndEditNewElement(
1240        final org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element,
1241        final boolean inline) {
1242
1243        if (!element.isNew()) {
1244            return;
1245        }
1246
1247        final CmsContainer container = m_containers.get(element.getParentTarget().getContainerId());
1248
1249        m_handler.showPageOverlay();
1250        CmsRpcAction<CmsCreateElementData> action = new CmsRpcAction<CmsCreateElementData>() {
1251
1252            /**
1253             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1254             */
1255            @Override
1256            public void execute() {
1257
1258                getContainerpageService().checkCreateNewElement(
1259                    CmsCoreProvider.get().getStructureId(),
1260                    getData().getDetailId(),
1261                    element.getId(),
1262                    element.getNewType(),
1263                    container,
1264                    getLocale(),
1265                    this);
1266
1267            }
1268
1269            /**
1270             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1271             */
1272            @Override
1273            protected void onResponse(CmsCreateElementData result) {
1274
1275                if (result.needsModelSelection()) {
1276                    getHandler().openModelResourceSelect(element, result.getModelResources());
1277                } else {
1278                    openEditorForNewElement(element, result.getCreatedElement(), inline);
1279                }
1280            }
1281        };
1282        action.execute();
1283    }
1284
1285    /**
1286     * Creates a new resource for drag container elements with the status new and opens the content editor.<p>
1287     *
1288     * @param element the container element
1289     * @param modelResourceStructureId the model resource structure id
1290     */
1291    public void createAndEditNewElement(
1292        final org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element,
1293        final CmsUUID modelResourceStructureId) {
1294
1295        CmsRpcAction<CmsContainerElement> action = new CmsRpcAction<CmsContainerElement>() {
1296
1297            @Override
1298            public void execute() {
1299
1300                getContainerpageService().createNewElement(
1301                    CmsCoreProvider.get().getStructureId(),
1302                    getData().getDetailId(),
1303                    element.getId(),
1304                    element.getNewType(),
1305                    modelResourceStructureId,
1306                    getLocale(),
1307                    this);
1308
1309            }
1310
1311            @Override
1312            protected void onResponse(CmsContainerElement result) {
1313
1314                openEditorForNewElement(element, result, false);
1315
1316            }
1317        };
1318        action.execute();
1319    }
1320
1321    /**
1322     * Creates the details object for a custom page editor event based on the given container page element.
1323     *
1324     * @param widget a container page element
1325     * @return the event details object
1326     */
1327    public I_EventDetails createEventDetails(CmsContainerPageElementPanel widget) {
1328
1329        final Element parentContainerElement = CmsDomUtil.getAncestor(
1330            widget.getElement(),
1331            CmsContainerElement.CLASS_CONTAINER);
1332
1333        I_EventDetails details = Js.cast(new JsObject());
1334        details.setIsPlaceholder(widget.isNew());
1335        details.setElement(Js.cast(widget.getElement()));
1336        details.setContainer(Js.cast(parentContainerElement));
1337        return details;
1338    }
1339
1340    /**
1341     * Creates a new element.<p>
1342     *
1343     * @param element the widget belonging to the element which is currently in memory only
1344     * @param callback the callback to call with the result
1345     */
1346    public void createNewElement(
1347        final CmsContainerPageElementPanel element,
1348        final AsyncCallback<CmsContainerElement> callback) {
1349
1350        CmsRpcAction<CmsContainerElement> action = new CmsRpcAction<CmsContainerElement>() {
1351
1352            @Override
1353            public void execute() {
1354
1355                getContainerpageService().createNewElement(
1356                    CmsCoreProvider.get().getStructureId(),
1357                    getData().getDetailId(),
1358                    element.getId(),
1359                    element.getNewType(),
1360                    null,
1361                    getLocale(),
1362                    this);
1363
1364            }
1365
1366            @Override
1367            protected void onResponse(CmsContainerElement result) {
1368
1369                callback.onSuccess(result);
1370            }
1371        };
1372        action.execute();
1373    }
1374
1375    /**
1376     * Deletes an element from the VFS, removes it from all containers and the client side cache.<p>
1377     *
1378     * @param elementId the element to delete
1379     * @param relatedElementId related element to reload after the element has been deleted
1380     */
1381    public void deleteElement(String elementId, final String relatedElementId) {
1382
1383        elementId = getServerId(elementId);
1384        removeContainerElements(elementId);
1385        addToRecentList(elementId, null);
1386        reloadElements(new String[] {relatedElementId}, () -> {/*do nothing*/});
1387    }
1388
1389    /**
1390     * Disables the inline editing for all content elements but the given one.<p>
1391     *
1392     * @param notThisOne the content element not to disable
1393     */
1394    public void disableInlineEditing(CmsContainerPageElementPanel notThisOne) {
1395
1396        removeEditButtonsPositionTimer();
1397        if (isGroupcontainerEditing()) {
1398            for (Widget element : m_groupEditor.getGroupContainerWidget()) {
1399                if ((element instanceof CmsContainerPageElementPanel) && (element != notThisOne)) {
1400                    ((CmsContainerPageElementPanel)element).removeInlineEditor();
1401                }
1402            }
1403        } else {
1404            for (org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer container : m_targetContainers.values()) {
1405                for (Widget element : container) {
1406                    if ((element instanceof CmsContainerPageElementPanel) && (element != notThisOne)) {
1407                        ((CmsContainerPageElementPanel)element).removeInlineEditor();
1408                    }
1409                }
1410            }
1411        }
1412    }
1413
1414    /**
1415     * Enables the favorites editing drag and drop controller.<p>
1416     *
1417     * @param enable if <code>true</code> favorites editing will enabled, otherwise disabled
1418     * @param dndController the favorites editing drag and drop controller
1419     */
1420    public void enableFavoriteEditing(boolean enable, I_CmsDNDController dndController) {
1421
1422        if (m_dndHandler.isDragging()) {
1423            // never switch drag and drop controllers while dragging
1424            return;
1425        }
1426        if (enable) {
1427            m_dndHandler.setController(dndController);
1428        } else {
1429            m_dndHandler.setController(m_cntDndController);
1430        }
1431
1432    }
1433
1434    /**
1435     * Replaces all element instances of the original element with the new element within the former copy model.<p>
1436     *
1437     * @param originalElementId the original element id
1438     * @param modelGroupParent the model group parent element
1439     * @param elementData the replace element data
1440     */
1441    public void executeCopyModelReplace(
1442        String originalElementId,
1443        Element modelGroupParent,
1444        CmsContainerElementData elementData) {
1445
1446        String serverId = getServerId(originalElementId);
1447        for (CmsContainerPageContainer cont : m_targetContainers.values()) {
1448            if (modelGroupParent.isOrHasChild(cont.getElement())) {
1449                // look for instances of the original element
1450                for (Widget child : cont) {
1451                    if ((child instanceof CmsContainerPageElementPanel)
1452                        && ((CmsContainerPageElementPanel)child).getId().startsWith(serverId)) {
1453                        CmsContainerPageElementPanel replacer = null;
1454                        String elementContent = elementData.getContents().get(cont.getContainerId());
1455                        if ((elementContent != null) && (elementContent.trim().length() > 0)) {
1456                            try {
1457                                replacer = getContainerpageUtil().createElement(elementData, cont, false);
1458                                cont.insert(replacer, cont.getWidgetIndex(child));
1459                                child.removeFromParent();
1460                                initializeSubContainers(replacer);
1461                            } catch (Exception e) {
1462                                //ignore
1463                            }
1464                        }
1465                    }
1466                }
1467            }
1468        }
1469    }
1470
1471    /**
1472     * Fires an event on the core event bus.<p>
1473     *
1474     * @param event the event to fire
1475     */
1476    public void fireEvent(CmsContainerpageEvent event) {
1477
1478        CmsCoreProvider.get().getEventBus().fireEvent(event);
1479
1480    }
1481
1482    /**
1483     * Returns all drag elements of the page.<p>
1484     *
1485     * @return the drag elements
1486     */
1487    public List<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> getAllDragElements() {
1488
1489        List<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> result = new ArrayList<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel>();
1490        Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> it = m_targetContainers.values().iterator();
1491        while (it.hasNext()) {
1492            result.addAll(it.next().getAllDragElements());
1493        }
1494        if (isGroupcontainerEditing()) {
1495            Iterator<Widget> itSub = m_groupEditor.getGroupContainerWidget().iterator();
1496            while (itSub.hasNext()) {
1497                Widget w = itSub.next();
1498                if (w instanceof org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel) {
1499                    result.add((org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)w);
1500                }
1501            }
1502        }
1503        return result;
1504    }
1505
1506    /**
1507     * Returns the data for the requested element, or <code>null</code> if the element has not been cached yet.<p>
1508     *
1509     * @param clientId the element id
1510     *
1511     * @return the element data
1512     */
1513    public CmsContainerElementData getCachedElement(String clientId) {
1514
1515        if (m_elements.containsKey(clientId)) {
1516            return m_elements.get(clientId);
1517        }
1518        return null;
1519    }
1520
1521    /**
1522     * Returns the data for the requested element, or <code>null</code> if the element has not been cached yet.<p>
1523     *
1524     * @param resourceTypeName the element resource type
1525     *
1526     * @return the element data
1527     */
1528    public CmsContainerElementData getCachedNewElement(String resourceTypeName) {
1529
1530        if (m_newElements.containsKey(resourceTypeName)) {
1531            return m_newElements.get(resourceTypeName);
1532        }
1533        return null;
1534    }
1535
1536    /**
1537     * Returns the container data of container with the given name.
1538     *
1539     * @param containerName the container name
1540     *
1541     * @return the container data
1542     */
1543    public CmsContainer getContainer(String containerName) {
1544
1545        return m_containers.get(containerName);
1546    }
1547
1548    /**
1549     * Gets the container element widget to which the given element belongs, or Optional.absent if none could be found.<p>
1550     *
1551     * @param element the element for which the container element widget should be found
1552     *
1553     * @return the container element widget, or Optional.absent if none can be found
1554     */
1555    public Optional<CmsContainerPageElementPanel> getContainerElementWidgetForElement(Element element) {
1556
1557        final Element parentContainerElement = CmsDomUtil.getAncestor(
1558            element,
1559            I_CmsLayoutBundle.INSTANCE.dragdropCss().dragElement());
1560        if (parentContainerElement == null) {
1561            return Optional.absent();
1562        }
1563        final List<CmsContainerPageElementPanel> result = Lists.newArrayList();
1564        processPageContent(new I_PageContentVisitor() {
1565
1566            public boolean beginContainer(String name, CmsContainer container) {
1567
1568                // we don't need to look into the container if we have already found our container element
1569                return result.isEmpty();
1570            }
1571
1572            public void endContainer() {
1573
1574                // do nothing
1575            }
1576
1577            public void handleElement(CmsContainerPageElementPanel current) {
1578
1579                if ((current.getElement() == parentContainerElement) && result.isEmpty()) {
1580                    result.add(current);
1581                }
1582            }
1583        });
1584        if (result.isEmpty()) {
1585            return Optional.absent();
1586        } else {
1587            return Optional.fromNullable(result.get(0));
1588        }
1589    }
1590
1591    /**
1592     * Gets the container info to send to the gallery service.
1593     *
1594     * @return the container info to send to the gallery service
1595     */
1596    public CmsGalleryContainerInfo getContainerInfoForGalleries() {
1597
1598        if (m_targetContainers != null) {
1599            HashSet<CmsGalleryContainerInfo.Item> items = new HashSet<>();
1600            for (CmsContainerPageContainer cont : m_targetContainers.values()) {
1601                items.add(new CmsGalleryContainerInfo.Item(cont.getContainerType(), cont.getConfiguredWidth()));
1602            }
1603            return new CmsGalleryContainerInfo(items);
1604        }
1605        return null;
1606    }
1607
1608    /**
1609     * Returns the container-page RPC service.<p>
1610     *
1611     * @return the container-page service
1612     */
1613    public I_CmsContainerpageServiceAsync getContainerpageService() {
1614
1615        if (m_containerpageService == null) {
1616            m_containerpageService = GWT.create(I_CmsContainerpageService.class);
1617            String serviceUrl = CmsCoreProvider.get().link("org.opencms.ade.containerpage.CmsContainerpageService.gwt");
1618            ((ServiceDefTarget)m_containerpageService).setServiceEntryPoint(serviceUrl);
1619        }
1620        return m_containerpageService;
1621    }
1622
1623    /**
1624     * Returns the {@link org.opencms.ade.containerpage.client.CmsContainerpageUtil}.<p>
1625     *
1626     * @return the containerpage-util
1627     */
1628    public CmsContainerpageUtil getContainerpageUtil() {
1629
1630        return m_containerpageUtil;
1631    }
1632
1633    /**
1634     * Returns the containers.<p>
1635     *
1636     * @return the containers
1637     */
1638    public Map<String, CmsContainer> getContainers() {
1639
1640        return m_containers;
1641    }
1642
1643    /**
1644     * Returns the container drag target by name (HTML id attribute).<p>
1645     *
1646     * @param containerName the container name
1647     * @return the drag target
1648     */
1649    public org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer getContainerTarget(String containerName) {
1650
1651        return m_targetContainers.get(containerName);
1652    }
1653
1654    /**
1655     * Returns a map of the container drag targets.<p>
1656     *
1657     * @return the drag targets
1658     */
1659    public Map<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> getContainerTargets() {
1660
1661        Map<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> result = new HashMap<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer>();
1662        for (Entry<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> entry : m_targetContainers.entrySet()) {
1663            if (entry.getValue().isEditable()
1664                && (!isDetailPage() || (entry.getValue().isDetailOnly() || entry.getValue().isDetailView()))) {
1665                result.put(entry.getKey(), entry.getValue());
1666            }
1667        }
1668        return result;
1669    }
1670
1671    /**
1672     * Returns the type of container with the given name.<p>
1673     *
1674     * @param containerName the container name
1675     *
1676     * @return the container type
1677     */
1678    public String getContainerType(String containerName) {
1679
1680        return getContainer(containerName).getType();
1681    }
1682
1683    /**
1684     * Returns the XML content editor handler.<p>
1685     *
1686     * @return the XML content editor handler
1687     */
1688    public CmsContentEditorHandler getContentEditorHandler() {
1689
1690        return m_contentEditorHandler;
1691    }
1692
1693    /**
1694     * Returns the prefetched data.<p>
1695     *
1696     * @return the prefetched data
1697     */
1698    public CmsCntPageData getData() {
1699
1700        return m_data;
1701    }
1702
1703    /**
1704     * Returns the delete options for the given content element.<p>
1705     *
1706     * @param clientId the content id
1707     * @param callback the callback to execute
1708     */
1709    public void getDeleteOptions(final String clientId, final I_CmsSimpleCallback<CmsDialogOptionsAndInfo> callback) {
1710
1711        CmsRpcAction<CmsDialogOptionsAndInfo> action = new CmsRpcAction<CmsDialogOptionsAndInfo>() {
1712
1713            @Override
1714            public void execute() {
1715
1716                getContainerpageService().getDeleteOptions(
1717                    clientId,
1718                    getData().getRpcContext().getPageStructureId(),
1719                    getData().getRequestParams(),
1720                    this);
1721            }
1722
1723            @Override
1724            protected void onResponse(CmsDialogOptionsAndInfo result) {
1725
1726                callback.execute(result);
1727            }
1728        };
1729        action.execute();
1730    }
1731
1732    /**
1733     * Gets the DND controller.<p>
1734     *
1735     * @return the DND controller
1736     */
1737    public CmsCompositeDNDController getDndController() {
1738
1739        return m_dndController;
1740    }
1741
1742    /**
1743     * Returns the drag and drop handler.<p>
1744     *
1745     * @return the drag and drop handler
1746     */
1747    public CmsDNDHandler getDndHandler() {
1748
1749        return m_dndHandler;
1750    }
1751
1752    /**
1753     * Returns the edit options for the given content element.<p>
1754     *
1755     * @param clientId the content id
1756     * @param isListElement in case a list element, not a container element is about to be edited
1757     * @param callback the callback to execute
1758     */
1759    public void getEditOptions(
1760        final String clientId,
1761        final boolean isListElement,
1762        final I_CmsSimpleCallback<CmsDialogOptionsAndInfo> callback) {
1763
1764        CmsRpcAction<CmsDialogOptionsAndInfo> action = new CmsRpcAction<CmsDialogOptionsAndInfo>() {
1765
1766            @Override
1767            public void execute() {
1768
1769                getContainerpageService().getEditOptions(
1770                    clientId,
1771                    getData().getRpcContext().getPageStructureId(),
1772                    getData().getRequestParams(),
1773                    isListElement,
1774                    this);
1775            }
1776
1777            @Override
1778            protected void onResponse(CmsDialogOptionsAndInfo result) {
1779
1780                callback.execute(result);
1781            }
1782        };
1783        action.execute();
1784    }
1785
1786    /**
1787     * Requests the data for a container element specified by the client id for drag and drop from a container. The data will be provided to the given call-back function.<p>
1788     *
1789     * @param clientId the element id
1790     * @param containerId the id of the container from which the element is being dragged
1791     * @param alwaysCopy <code>true</code> in case reading data for a clipboard element used as a copy group
1792     * @param callback the call-back to execute with the requested data
1793     */
1794    public void getElementForDragAndDropFromContainer(
1795        final String clientId,
1796        final String containerId,
1797        boolean alwaysCopy,
1798        final I_CmsSimpleCallback<CmsContainerElementData> callback) {
1799
1800        SingleElementAction action = new SingleElementAction(clientId, alwaysCopy, callback);
1801        action.setDndContainer(containerId);
1802        action.execute();
1803    }
1804
1805    /**
1806     * Requests the data for container elements specified by the client id. The data will be provided to the given call-back function.<p>
1807     *
1808     * @param clientIds the element id's
1809     * @param callback the call-back to execute with the requested data
1810     */
1811    public void getElements(Set<String> clientIds, I_CmsSimpleCallback<Map<String, CmsContainerElementData>> callback) {
1812
1813        MultiElementAction action = new MultiElementAction(clientIds, callback);
1814        action.execute();
1815    }
1816
1817    /**
1818     * Requests the element settings config data for a container element specified by the client id. The data will be provided to the given call-back function.<p>
1819     *
1820     * @param clientId the element id
1821     * @param containerId the parent container id
1822     * @param callback the call-back to execute with the requested data
1823     */
1824    public void getElementSettingsConfig(
1825        final String clientId,
1826        final String containerId,
1827        final I_CmsSimpleCallback<CmsElementSettingsConfig> callback) {
1828
1829        CmsRpcAction<CmsElementSettingsConfig> action = new CmsRpcAction<CmsElementSettingsConfig>() {
1830
1831            /**
1832             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1833             */
1834            @Override
1835            public void execute() {
1836
1837                start(100, true);
1838                getContainerpageService().getElementSettingsConfig(
1839                    getData().getRpcContext(),
1840                    clientId,
1841                    containerId,
1842                    getPageState(),
1843                    getLocale(),
1844                    this);
1845
1846            }
1847
1848            /**
1849             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1850             */
1851            @Override
1852            protected void onResponse(CmsElementSettingsConfig result) {
1853
1854                if (result != null) {
1855                    callback.execute(result);
1856                }
1857                stop(false);
1858            }
1859        };
1860        action.execute();
1861    }
1862
1863    /**
1864     * Returns the current element view.<p>
1865     *
1866     * @return the current element view
1867     */
1868    public CmsElementViewInfo getElementView() {
1869
1870        return m_elementView;
1871    }
1872
1873    /**
1874     * Retrieves a container element with a given set of settings.<p>
1875     *
1876     * @param clientId the id of the container element
1877     * @param settings the set of settings
1878     *
1879     * @param callback the callback which should be executed when the element has been loaded
1880     */
1881    public void getElementWithSettings(
1882        final String clientId,
1883        final Map<String, String> settings,
1884        final I_CmsSimpleCallback<CmsContainerElementData> callback) {
1885
1886        CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
1887
1888            /**
1889             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1890             */
1891            @Override
1892            public void execute() {
1893
1894                start(200, false);
1895                getContainerpageService().getElementWithSettings(
1896                    getData().getRpcContext(),
1897                    getData().getDetailId(),
1898                    getRequestParams(),
1899                    clientId,
1900                    settings,
1901                    getPageState(),
1902                    getLocale(),
1903                    this);
1904
1905            }
1906
1907            /**
1908             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1909             */
1910            @Override
1911            protected void onResponse(CmsContainerElementData result) {
1912
1913                stop(false);
1914                if (result != null) {
1915                    // cache the loaded element
1916                    m_elements.put(result.getClientId(), result);
1917                }
1918                callback.execute(result);
1919            }
1920
1921        };
1922        action.execute();
1923
1924    }
1925
1926    /**
1927     * Returns the group-container element being edited.<p>
1928     *
1929     * @return the group-container
1930     */
1931    public CmsGroupContainerElementPanel getGroupcontainer() {
1932
1933        return m_groupEditor.getGroupContainerWidget();
1934    }
1935
1936    /**
1937     * Returns the id of the currently edited group-container.<p>
1938     *
1939     * @return the group-container id, or <code>null</code> if no editing is taking place
1940     */
1941    public String getGroupcontainerId() {
1942
1943        if (m_groupEditor != null) {
1944            return m_groupEditor.getGroupContainerWidget().getContainerId();
1945        }
1946        return null;
1947    }
1948
1949    /**
1950     * Returns the container-page handler.<p>
1951     *
1952     * @return the container-page handler
1953     */
1954    public CmsContainerpageHandler getHandler() {
1955
1956        return m_handler;
1957    }
1958
1959    /**
1960     * Returns the time off page load.<p>
1961     *
1962     * @return the time stamp
1963     */
1964    public long getLoadTime() {
1965
1966        return m_loadTime;
1967    }
1968
1969    /**
1970     * Gets the lock error message.<p>
1971     *
1972     * @return the lock error message
1973     */
1974    public String getLockErrorMessage() {
1975
1976        return m_lockErrorMessage;
1977    }
1978
1979    /**
1980     * Returns the model group base element id.<p>
1981     *
1982     * @return the model group base element id
1983     */
1984    public String getModelGroupElementId() {
1985
1986        return m_modelGroupElementId;
1987    }
1988
1989    /**
1990     * Collects all container elements which are model groups.<p>
1991     *
1992     * @return the list of model group container elements
1993     */
1994    public List<CmsContainerPageElementPanel> getModelGroups() {
1995
1996        final List<CmsContainerPageElementPanel> result = Lists.newArrayList();
1997
1998        processPageContent(new I_PageContentVisitor() {
1999
2000            public boolean beginContainer(String name, CmsContainer container) {
2001
2002                return true;
2003            }
2004
2005            public void endContainer() {
2006
2007                // do nothing
2008            }
2009
2010            public void handleElement(CmsContainerPageElementPanel element) {
2011
2012                if (element.isModelGroup()) {
2013                    result.add(element);
2014                }
2015            }
2016        });
2017        return result;
2018    }
2019
2020    /**
2021     * Returns the element data for a resource type representing a new element.<p>
2022     *
2023     * @param resourceType the resource type name
2024     * @param callback the callback to execute with the new element data
2025     */
2026    public void getNewElement(final String resourceType, final I_CmsSimpleCallback<CmsContainerElementData> callback) {
2027
2028        CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
2029
2030            @Override
2031            public void execute() {
2032
2033                getContainerpageService().getNewElementData(
2034                    getData().getRpcContext(),
2035                    getData().getDetailId(),
2036                    getRequestParams(),
2037                    resourceType,
2038                    getPageState(),
2039                    getLocale(),
2040                    this);
2041            }
2042
2043            @Override
2044            protected void onResponse(CmsContainerElementData result) {
2045
2046                m_elements.put(result.getClientId(), result);
2047                callback.execute(result);
2048            }
2049        };
2050        action.execute();
2051    }
2052
2053    /**
2054     * Fetches the options for creating a new element from the edit handler.<p>
2055     *
2056     * @param clientId the client id of the element
2057     * @param callback the callback which is called with the result
2058     */
2059    public void getNewOptions(final String clientId, final I_CmsSimpleCallback<CmsDialogOptionsAndInfo> callback) {
2060
2061        CmsRpcAction<CmsDialogOptionsAndInfo> action = new CmsRpcAction<CmsDialogOptionsAndInfo>() {
2062
2063            @Override
2064            public void execute() {
2065
2066                getContainerpageService().getNewOptions(
2067                    clientId,
2068                    getData().getRpcContext().getPageStructureId(),
2069                    getData().getRequestParams(),
2070                    this);
2071            }
2072
2073            @Override
2074            protected void onResponse(CmsDialogOptionsAndInfo result) {
2075
2076                callback.execute(result);
2077            }
2078        };
2079
2080        action.execute();
2081    }
2082
2083    /**
2084     * Produces the "return code", which is needed to return to the current page from the sitemap.<p>
2085     *
2086     * @return the return code
2087     */
2088    public String getReturnCode() {
2089
2090        CmsUUID ownId = CmsCoreProvider.get().getStructureId();
2091        CmsUUID detailId = m_data.getDetailId();
2092        if (detailId != null) {
2093            return "" + ownId + ":" + detailId;
2094        } else {
2095            return "" + ownId;
2096        }
2097    }
2098
2099    /**
2100     * Returns the deserialized element data.<p>
2101     *
2102     * @param data the data to deserialize
2103     *
2104     * @return the container element
2105     * @throws SerializationException if deserialization fails
2106     */
2107    public CmsContainer getSerializedContainer(String data) throws SerializationException {
2108
2109        return (CmsContainer)CmsRpcPrefetcher.getSerializedObjectFromString(getContainerpageService(), data);
2110    }
2111
2112    /**
2113     * Returns the deserialized element data.<p>
2114     *
2115     * @param data the data to deserialize
2116     *
2117     * @return the container element
2118     * @throws SerializationException if deserialization fails
2119     */
2120    public CmsContainerElement getSerializedElement(String data) throws SerializationException {
2121
2122        return (CmsContainerElement)CmsRpcPrefetcher.getSerializedObjectFromString(getContainerpageService(), data);
2123    }
2124
2125    /**
2126     * Gets the handler for small elements.<p>
2127     *
2128     * @return the small elements handler
2129     */
2130    public CmsSmallElementsHandler getSmallElementsHandler() {
2131
2132        return m_smallElementsHandler;
2133    }
2134
2135    /**
2136     * Gets the view with the given id.<p>
2137     *
2138     * @param value the view id as a string
2139     *
2140     * @return the view with the given id, or null if no such view is available
2141     */
2142    public CmsElementViewInfo getView(String value) {
2143
2144        for (CmsElementViewInfo info : m_data.getElementViews()) {
2145            if (info.getElementViewId().toString().equals(value)) {
2146                return info;
2147            }
2148        }
2149        return null;
2150    }
2151
2152    /**
2153     * Handler that gets called when the template context setting of an element was changed by the user.<p>
2154     *
2155     * @param element the element whose template context setting was changed
2156     *
2157     * @param newValue the new value of the setting
2158     */
2159    public void handleChangeTemplateContext(final CmsContainerPageElementPanel element, final String newValue) {
2160
2161        if (CmsStringUtil.isEmptyOrWhitespaceOnly(newValue) || CmsTemplateContextInfo.EMPTY_VALUE.equals(newValue)) {
2162            if (CmsInheritanceContainerEditor.getInstance() != null) {
2163                CmsInheritanceContainerEditor.getInstance().removeElement(element);
2164            } else {
2165                removeElement(element, ElementRemoveMode.silent);
2166            }
2167        }
2168    }
2169
2170    /**
2171     * Asks the user for confirmation before removing a container page element.<p>
2172     *
2173     * @param element the element for which the user should confirm the removal
2174     */
2175    public void handleConfirmRemove(final CmsContainerPageElementPanel element) {
2176
2177        if (element.isNew()) {
2178            I_EventDetails details = createEventDetails(element);
2179            element.removeFromParent();
2180            cleanUpContainers();
2181            setPageChanged();
2182            sendElementDeleted(details);
2183            return;
2184        }
2185        checkElementReferences(element, new AsyncCallback<CmsRemovedElementStatus>() {
2186
2187            public void onFailure(Throwable caught) {
2188
2189                // ignore, will never be executed
2190
2191            }
2192
2193            public void onSuccess(CmsRemovedElementStatus status) {
2194
2195                boolean showDeleteCheckbox = status.isDeletionCandidate();
2196                ElementDeleteMode deleteMode = status.getElementDeleteMode();
2197                if (deleteMode == null) {
2198                    deleteMode = getData().getDeleteMode();
2199                }
2200                CmsConfirmRemoveDialog removeDialog = new CmsConfirmRemoveDialog(
2201                    status.getElementInfo(),
2202                    showDeleteCheckbox,
2203                    deleteMode,
2204                    new AsyncCallback<Boolean>() {
2205
2206                        public void onFailure(Throwable caught) {
2207
2208                            element.removeHighlighting();
2209                        }
2210
2211                        public void onSuccess(Boolean shouldDeleteResource) {
2212
2213                            Runnable[] nextActions = new Runnable[] {};
2214
2215                            if (shouldDeleteResource.booleanValue()) {
2216                                final CmsRpcAction<Void> deleteAction = new CmsRpcAction<Void>() {
2217
2218                                    @Override
2219                                    public void execute() {
2220
2221                                        start(200, true);
2222
2223                                        CmsUUID id = new CmsUUID(getServerId(element.getId()));
2224                                        CmsCoreProvider.getVfsService().deleteResource(id, this);
2225                                    }
2226
2227                                    @Override
2228                                    public void onResponse(Void result) {
2229
2230                                        stop(true);
2231                                    }
2232                                };
2233                                nextActions = new Runnable[] {new Runnable() {
2234
2235                                    public void run() {
2236
2237                                        deleteAction.execute();
2238                                    }
2239                                }};
2240                            }
2241                            I_CmsDropContainer container = element.getParentTarget();
2242                            I_EventDetails details = createEventDetails(element);
2243                            element.removeFromParent();
2244                            if (container instanceof CmsContainerPageContainer) {
2245                                ((CmsContainerPageContainer)container).checkEmptyContainers();
2246                            }
2247                            cleanUpContainers();
2248                            sendElementDeleted(details);
2249                            setPageChanged(nextActions);
2250                        }
2251                    });
2252                removeDialog.center();
2253            }
2254
2255        });
2256    }
2257
2258    /**
2259     * Calls the edit handler to handle the delete action.<p>
2260     *
2261     * @param clientId the content client id
2262     * @param deleteOption the selected delete option
2263     * @param callback the callback to execute after the delete
2264     */
2265    public void handleDelete(
2266        final String clientId,
2267        final String deleteOption,
2268        final I_CmsSimpleCallback<Void> callback) {
2269
2270        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
2271
2272            @Override
2273            public void execute() {
2274
2275                getContainerpageService().handleDelete(
2276                    clientId,
2277                    deleteOption,
2278                    getData().getRpcContext().getPageStructureId(),
2279                    getData().getRequestParams(),
2280                    this);
2281            }
2282
2283            @Override
2284            protected void onResponse(Void result) {
2285
2286                if (callback != null) {
2287                    callback.execute(result);
2288                }
2289            }
2290        };
2291        action.execute();
2292    }
2293
2294    /**
2295     * Returns if the selection button is active.<p>
2296     *
2297     * @return <code>true</code> if the selection button is active
2298     */
2299    public boolean hasActiveSelection() {
2300
2301        return m_handler.hasActiveSelection();
2302    }
2303
2304    /**
2305     * Returns if the page has changed.<p>
2306     *
2307     * @return <code>true</code> if the page has changed
2308     */
2309    public boolean hasPageChanged() {
2310
2311        return m_pageChanged;
2312    }
2313
2314    /**
2315     * Hides list collector direct edit buttons, if present.<p>
2316     */
2317    public void hideEditableListButtons() {
2318
2319        removeEditButtonsPositionTimer();
2320        for (org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer container : m_targetContainers.values()) {
2321            container.hideEditableListButtons();
2322        }
2323    }
2324
2325    /**
2326     * Initializes the controller.<p>
2327     *
2328     * @param handler the container-page handler
2329     * @param dndHandler the drag and drop handler
2330     * @param contentEditorHandler the XML content editor handler
2331     * @param containerpageUtil the container-page utility
2332     */
2333    public void init(
2334        CmsContainerpageHandler handler,
2335        CmsDNDHandler dndHandler,
2336        CmsContentEditorHandler contentEditorHandler,
2337        CmsContainerpageUtil containerpageUtil) {
2338
2339        if (getData().getDetailId() != null) {
2340            CmsEmbeddedAction.PARAMS.put(CmsGwtConstants.PARAM_ADE_DETAIL_ID, "" + getData().getDetailId());
2341        }
2342
2343        Window.addResizeHandler(new ResizeHandler() {
2344
2345            public void onResize(ResizeEvent event) {
2346
2347                CmsContainerpageController.this.onResize();
2348            }
2349        });
2350        m_containerpageUtil = containerpageUtil;
2351        m_handler = handler;
2352        m_contentEditorHandler = contentEditorHandler;
2353        m_dndHandler = dndHandler;
2354        m_cntDndController = m_dndHandler.getController();
2355
2356        m_elements = new HashMap<String, CmsContainerElementData>();
2357        m_newElements = new HashMap<String, CmsContainerElementData>();
2358        m_containers = new HashMap<String, CmsContainer>();
2359        if (m_data == null) {
2360            m_handler.m_editor.disableEditing(Messages.get().key(Messages.ERR_READING_CONTAINER_PAGE_DATA_0));
2361            CmsErrorDialog dialog = new CmsErrorDialog(
2362                Messages.get().key(Messages.ERR_READING_CONTAINER_PAGE_DATA_0),
2363                null);
2364            dialog.center();
2365            return;
2366        }
2367        // ensure any embedded flash players are set opaque so UI elements may be placed above them
2368        CmsDomUtil.fixFlashZindex(RootPanel.getBodyElement());
2369        m_targetContainers = m_containerpageUtil.consumeContainers(m_containers, RootPanel.getBodyElement());
2370        updateContainerLevelInfo();
2371        resetEditButtons();
2372        Event.addNativePreviewHandler(new NativePreviewHandler() {
2373
2374            public void onPreviewNativeEvent(NativePreviewEvent event) {
2375
2376                previewNativeEvent(event);
2377            }
2378        });
2379        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_data.getNoEditReason())) {
2380            m_handler.m_editor.disableEditing(m_data.getNoEditReason());
2381        } else {
2382            checkLockInfo();
2383        }
2384
2385        // initialize the browser history handler
2386        History.addValueChangeHandler(new ValueChangeHandler<String>() {
2387
2388            public void onValueChange(ValueChangeEvent<String> event) {
2389
2390                String historyToken = event.getValue();
2391                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(historyToken)) {
2392                    getContentEditorHandler().openEditorForHistory(historyToken);
2393                } else {
2394                    getContentEditorHandler().closeContentEditor();
2395                }
2396            }
2397        });
2398        AsyncCallback<Void> doNothing = new AsyncCallback<Void>() {
2399
2400            public void onFailure(Throwable caught) {
2401
2402                // nothing to do
2403            }
2404
2405            public void onSuccess(Void result) {
2406
2407                // nothing to do
2408            }
2409        };
2410        getContainerpageService().setLastPage(CmsCoreProvider.get().getStructureId(), m_data.getDetailId(), doNothing);
2411
2412        // check if there is already a history item available
2413        String historyToken = History.getToken();
2414        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(historyToken)) {
2415            m_contentEditorHandler.openEditorForHistory(historyToken);
2416        }
2417
2418        updateGalleryData(false, null);
2419        addContainerpageEventHandler(event -> {
2420            updateDetailPreviewStyles();
2421        });
2422        updateDetailPreviewStyles();
2423        updateButtonsForCurrentView();
2424        startPublishLockCheck();
2425    }
2426
2427    /**
2428     * Checks for element sub containers.<p>
2429     *
2430     * @param containerElement the container element
2431     */
2432    public void initializeSubContainers(CmsContainerPageElementPanel containerElement) {
2433
2434        int containerCount = m_targetContainers.size();
2435        m_targetContainers.putAll(m_containerpageUtil.consumeContainers(m_containers, containerElement.getElement()));
2436        updateContainerLevelInfo();
2437        if (m_targetContainers.size() > containerCount) {
2438            // in case new containers have been added, the gallery data needs to be updated
2439            scheduleGalleryUpdate();
2440        }
2441    }
2442
2443    /**
2444     * Returns if the given container is editable.<p>
2445     *
2446     * @param dragParent the parent container
2447     *
2448     * @return <code>true</code> if the given container is editable
2449     */
2450    public boolean isContainerEditable(I_CmsDropContainer dragParent) {
2451
2452        boolean isSubElement = dragParent instanceof CmsGroupContainerElementPanel;
2453        boolean isContainerEditable = dragParent.isEditable()
2454            && (isSubElement || !isDetailPage() || dragParent.isDetailView() || dragParent.isDetailOnly());
2455        return isContainerEditable;
2456    }
2457
2458    /**
2459     * Returns the flag indicating that a content element is being edited.<p>
2460     *
2461     * @return the flag indicating that a content element is being edited
2462     */
2463    public boolean isContentEditing() {
2464
2465        return m_isContentEditing;
2466    }
2467
2468    /**
2469     * Returns if this page displays a detail view.<p>
2470     *
2471     * @return <code>true</code> if this page displays a detail view
2472     */
2473    public boolean isDetailPage() {
2474
2475        return m_data.getDetailId() != null;
2476    }
2477
2478    /**
2479     * Checks if the page editing features should be disabled.<p>
2480     *
2481     * @return true if the page editing features should be disabled
2482     */
2483    public boolean isEditingDisabled() {
2484
2485        return (m_data == null)
2486            || CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_data.getNoEditReason())
2487            || (m_lockStatus == LockStatus.failed);
2488    }
2489
2490    /**
2491     * Returns if a group-container is currently being edited.<p>
2492     *
2493     * @return <code>true</code> if a group-container is being edited
2494     */
2495    public boolean isGroupcontainerEditing() {
2496
2497        return m_groupEditor != null;
2498    }
2499
2500    /**
2501     * Checks whether the given element should be inline editable.<p>
2502     *
2503     * @param element the element
2504     * @param dragParent the element parent
2505     *
2506     * @return <code>true</code> if the element should be inline editable
2507     */
2508    public boolean isInlineEditable(CmsContainerPageElementPanel element, I_CmsDropContainer dragParent) {
2509
2510        CmsUUID elemView = element.getElementView();
2511        return !getData().isUseClassicEditor()
2512            && CmsStringUtil.isEmptyOrWhitespaceOnly(element.getNoEditReason())
2513            && hasActiveSelection()
2514            && matchRootView(elemView)
2515            && isContainerEditable(dragParent)
2516            && matchesCurrentEditLevel(dragParent)
2517            && (getData().isModelGroup() || !element.hasModelGroupParent())
2518            && (!(dragParent instanceof CmsGroupContainerElementPanel) || isGroupcontainerEditing());
2519    }
2520
2521    /**
2522     * Method to leave the page without saving.<p>
2523     *
2524     * @param targetUri the new URI to call
2525     */
2526    public void leaveUnsaved(String targetUri) {
2527
2528        setPageChanged(false, true);
2529        Window.Location.assign(targetUri);
2530    }
2531
2532    /**
2533     * Loads the context menu entries.<p>
2534     *
2535     * @param structureId the structure id of the resource to get the context menu entries for
2536     * @param context the ade context (sitemap or containerpae)
2537     */
2538    public void loadContextMenu(final CmsUUID structureId, final AdeContext context) {
2539
2540        /** The RPC menu action for the container page dialog. */
2541        CmsRpcAction<List<CmsContextMenuEntryBean>> menuAction = new CmsRpcAction<List<CmsContextMenuEntryBean>>() {
2542
2543            /**
2544            * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2545            */
2546            @Override
2547            public void execute() {
2548
2549                Map<String, String> params = new HashMap<>();
2550                if (getData().getDetailId() != null) {
2551                    params.put(CmsGwtConstants.PARAM_ADE_DETAIL_ID, "" + getData().getDetailId());
2552                }
2553                getCoreService().getContextMenuEntries(structureId, context, params, this);
2554            }
2555
2556            /**
2557            * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2558            */
2559            @Override
2560            public void onResponse(List<CmsContextMenuEntryBean> menuBeans) {
2561
2562                m_handler.insertContextMenu(menuBeans, structureId);
2563            }
2564        };
2565        menuAction.execute();
2566
2567    }
2568
2569    /**
2570     * Loads the favorite list and adds the elements to the favorite list widget of the tool-bar menu.<p>
2571     *
2572     * @param callback the call-back to execute with the result data
2573     */
2574    public void loadFavorites(final I_CmsSimpleCallback<List<CmsContainerElementData>> callback) {
2575
2576        CmsRpcAction<List<CmsContainerElementData>> action = new CmsRpcAction<List<CmsContainerElementData>>() {
2577
2578            /**
2579             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2580             */
2581            @Override
2582            public void execute() {
2583
2584                start(200, true);
2585                getContainerpageService().getFavoriteList(
2586                    CmsCoreProvider.get().getStructureId(),
2587                    getData().getDetailId(),
2588                    getPageState(),
2589                    getLocale(),
2590                    this);
2591            }
2592
2593            /**
2594             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2595             */
2596            @Override
2597            protected void onResponse(List<CmsContainerElementData> result) {
2598
2599                stop(false);
2600                addElements(result);
2601                callback.execute(result);
2602            }
2603        };
2604        action.execute();
2605    }
2606
2607    /**
2608     * Loads the recent list and adds the elements to the recent list widget of the tool-bar menu.<p>
2609     *
2610     * @param callback the call-back to execute with the result data
2611     */
2612    public void loadRecent(final I_CmsSimpleCallback<List<CmsContainerElementData>> callback) {
2613
2614        CmsRpcAction<List<CmsContainerElementData>> action = new CmsRpcAction<List<CmsContainerElementData>>() {
2615
2616            /**
2617             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2618             */
2619            @Override
2620            public void execute() {
2621
2622                start(200, true);
2623                getContainerpageService().getRecentList(
2624                    CmsCoreProvider.get().getStructureId(),
2625                    getData().getDetailId(),
2626                    getPageState(),
2627                    getLocale(),
2628                    this);
2629            }
2630
2631            /**
2632             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2633             */
2634            @Override
2635            protected void onResponse(List<CmsContainerElementData> result) {
2636
2637                stop(false);
2638                addElements(result);
2639                callback.execute(result);
2640            }
2641        };
2642        action.execute();
2643    }
2644
2645    /**
2646     * Locks the container-page.<p>
2647     *
2648     * @param callback the callback to execute
2649     */
2650    public void lockContainerpage(final I_CmsSimpleCallback<Boolean> callback) {
2651
2652        if (m_lockStatus == LockStatus.locked) {
2653            callback.execute(Boolean.TRUE);
2654        } else if (m_lockStatus == LockStatus.failed) {
2655            callback.execute(Boolean.FALSE);
2656        } else {
2657            I_CmsSimpleCallback<String> call = new I_CmsSimpleCallback<String>() {
2658
2659                public void execute(String lockError) {
2660
2661                    if (lockError == null) {
2662                        onLockSuccess();
2663                        callback.execute(Boolean.TRUE);
2664                    } else {
2665                        onLockFail(lockError);
2666                        callback.execute(Boolean.FALSE);
2667                    }
2668                }
2669            };
2670
2671            if (getData().getDetailContainerPage() != null) {
2672                CmsCoreProvider.get().lockOrReturnError(getData().getDetailContainerPage(), getLoadTime(), call);
2673            } else {
2674                CmsCoreProvider.get().lockOrReturnError(CmsCoreProvider.get().getStructureId(), getLoadTime(), call);
2675            }
2676        }
2677    }
2678
2679    /**
2680     * Returns true if the view with the given view id and the current view have the same root view.<p>
2681     *
2682     * @param viewIdFromElement the id of a view
2683     * @return true if the root view of the id matches the root view of the current view
2684     */
2685    public boolean matchRootView(CmsUUID viewIdFromElement) {
2686
2687        if (viewIdFromElement == null) {
2688            viewIdFromElement = CmsUUID.getNullUUID();
2689        }
2690        CmsElementViewInfo viewFromElement = getView(viewIdFromElement.toString());
2691        return (viewFromElement != null) && viewFromElement.getRootViewId().equals(m_elementView.getRootViewId());
2692    }
2693
2694    /**
2695     * This method should be called when locking the page has failed.<p>
2696     *
2697     * @param lockError the locking information
2698     */
2699    public void onLockFail(String lockError) {
2700
2701        m_lockStatus = LockStatus.failed;
2702        m_handler.onLockFail(lockError);
2703    }
2704
2705    /**
2706     * This method should be called when locking the page has succeeded.<p>
2707     *
2708     */
2709    public void onLockSuccess() {
2710
2711        assert m_lockStatus == LockStatus.unknown;
2712        m_lockStatus = LockStatus.locked;
2713    }
2714
2715    /**
2716     * Handler which is executed when the window closes.<p>
2717     */
2718    public void onWindowClose() {
2719
2720        // causes synchronous RPC call
2721        unlockContainerpage();
2722    }
2723
2724    /**
2725     * Calls the edit handler to prepare the given content element for editing.<p>
2726     *
2727     * @param clientId the element id
2728     * @param editOption the selected edit option
2729     * @param callback the callback to execute
2730     */
2731    public void prepareForEdit(
2732        final String clientId,
2733        final String editOption,
2734        final I_CmsSimpleCallback<CmsUUID> callback) {
2735
2736        CmsRpcAction<CmsUUID> action = new CmsRpcAction<CmsUUID>() {
2737
2738            @Override
2739            public void execute() {
2740
2741                getContainerpageService().prepareForEdit(
2742                    clientId,
2743                    editOption,
2744                    getData().getRpcContext().getPageStructureId(),
2745                    getData().getRequestParams(),
2746                    this);
2747            }
2748
2749            @Override
2750            protected void onResponse(CmsUUID result) {
2751
2752                callback.execute(result);
2753            }
2754        };
2755        action.execute();
2756    }
2757
2758    /**
2759     * Reinitializes the buttons in the container element menus.<p>
2760     */
2761    public void reinitializeButtons() {
2762
2763        if (isGroupcontainerEditing()) {
2764            m_groupEditor.reinitializeButtons();
2765        } else {
2766            List<CmsContainerPageElementPanel> elemWidgets = getAllContainerPageElements(true);
2767
2768            for (CmsContainerPageElementPanel elemWidget : elemWidgets) {
2769                if (requiresOptionBar(elemWidget, elemWidget.getParentTarget())) {
2770                    getContainerpageUtil().addOptionBar(elemWidget);
2771                } else {
2772                    // otherwise remove any present option bar
2773                    elemWidget.setElementOptionBar(null);
2774                }
2775                elemWidget.showEditableListButtons();
2776            }
2777        }
2778    }
2779
2780    /**
2781     * Re-initializes the inline editing.<p>
2782     */
2783    public void reInitInlineEditing() {
2784
2785        removeEditButtonsPositionTimer();
2786        if ((m_targetContainers == null) || getData().isUseClassicEditor()) {
2787            // if the target containers are not initialized yet or classic editor is set, don't do anything
2788            return;
2789        }
2790        if (isGroupcontainerEditing()) {
2791            for (Widget element : m_groupEditor.getGroupContainerWidget()) {
2792                if (((element instanceof CmsContainerPageElementPanel)
2793                    && isInlineEditable(
2794                        (CmsContainerPageElementPanel)element,
2795                        m_groupEditor.getGroupContainerWidget()))) {
2796                    ((CmsContainerPageElementPanel)element).initInlineEditor(this);
2797                }
2798            }
2799        } else {
2800            for (CmsContainerPageContainer container : m_targetContainers.values()) {
2801                // first remove inline editors
2802                for (Widget element : container) {
2803                    if ((element instanceof CmsContainerPageElementPanel)) {
2804                        ((CmsContainerPageElementPanel)element).removeInlineEditor();
2805                    }
2806                }
2807
2808                // add inline editors only on suitable elements
2809                if (isContainerEditable(container) && matchesCurrentEditLevel(container)) {
2810                    for (Widget element : container) {
2811                        if ((element instanceof CmsContainerPageElementPanel)
2812                            && isInlineEditable((CmsContainerPageElementPanel)element, container)) {
2813                            ((CmsContainerPageElementPanel)element).initInlineEditor(this);
2814                        }
2815                    }
2816                }
2817            }
2818        }
2819    }
2820
2821    /**
2822     * Reloads the content for the given elements and related elements.
2823     *
2824     * @param ids the element ids
2825     * @param callback the callback to execute after the reload
2826     */
2827    public void reloadElements(Collection<String> ids, I_ReloadHandler callback) {
2828
2829        Set<String> related = new HashSet<String>();
2830        for (String id : ids) {
2831            related.addAll(getRelatedElementIds(id));
2832        }
2833        if (!related.isEmpty()) {
2834            ReloadElementAction action = new ReloadElementAction(related, callback);
2835            action.execute();
2836        }
2837    }
2838
2839    /**
2840     * Reloads the content for the given elements and related elements.
2841     *
2842     * @param ids the element ids
2843     * @param callback the callback to execute after the reload
2844     */
2845    public void reloadElements(Collection<String> ids, Runnable callback) {
2846
2847        reloadElements(ids, new I_ReloadHandler() {
2848
2849            public void finish() {
2850
2851                if (callback != null) {
2852                    callback.run();
2853                }
2854            }
2855
2856            public void onReload(CmsContainerPageElementPanel a, CmsContainerPageElementPanel b) {
2857
2858                // ignore
2859            }
2860        });
2861
2862    }
2863
2864    /**
2865     * Reloads the content for the given element and all related elements.<p>
2866     *
2867     * Call this if the element content has changed.<p>
2868     *
2869     * @param ids the element ids
2870     * @param callback the callback to execute after the reload
2871     */
2872    public void reloadElements(String[] ids, I_ReloadHandler callback) {
2873
2874        Set<String> related = new HashSet<String>();
2875        for (int i = 0; i < ids.length; i++) {
2876            related.addAll(getRelatedElementIds(ids[i]));
2877        }
2878        if (!related.isEmpty()) {
2879            ReloadElementAction action = new ReloadElementAction(related, callback);
2880            action.execute();
2881        } else {
2882            callback.finish();
2883        }
2884    }
2885
2886    /**
2887     * Reloads the content for the given element and all related elements.<p>
2888     *
2889     * Call this if the element content has changed.<p>
2890     *
2891     * @param ids the element ids
2892     * @param callback the callback to execute after the reload
2893     */
2894    public void reloadElements(String[] ids, Runnable callback) {
2895
2896        reloadElements(ids, new I_ReloadHandler() {
2897
2898            @Override
2899            public void finish() {
2900
2901                if (callback != null) {
2902                    callback.run();
2903                }
2904            }
2905
2906            @Override
2907            public void onReload(CmsContainerPageElementPanel oldElement, CmsContainerPageElementPanel newElement) {
2908
2909            }
2910        });
2911
2912    }
2913
2914    /**
2915     * Reloads a container page element with a new set of settings.<p>
2916     *
2917     * @param elementWidget the widget of the container page element which should be reloaded
2918     * @param clientId the id of the container page element which should be reloaded
2919     * @param settings the new set of settings
2920     * @param reloadHandler the handler to call with the reloaded element
2921     */
2922    public void reloadElementWithSettings(
2923        final org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel elementWidget,
2924        final String clientId,
2925        final Map<String, String> settings,
2926        final I_ReloadHandler reloadHandler) {
2927
2928        final I_CmsSimpleCallback<CmsContainerElementData> callback = new I_CmsSimpleCallback<CmsContainerElementData>() {
2929
2930            public void execute(CmsContainerElementData newElement) {
2931
2932                try {
2933                    final CmsContainerPageElementPanel replacement = replaceContainerElement(elementWidget, newElement);
2934                    resetEditButtons();
2935                    addToRecentList(newElement.getClientId(), null);
2936                    reloadHandler.onReload(elementWidget, replacement);
2937                    reloadHandler.finish();
2938                } catch (Exception e) {
2939                    // should never happen
2940                    CmsDebugLog.getInstance().printLine(e.getLocalizedMessage());
2941                }
2942            }
2943        };
2944
2945        if (!isGroupcontainerEditing()) {
2946
2947            lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
2948
2949                public void execute(Boolean arg) {
2950
2951                    if (arg.booleanValue()) {
2952                        CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
2953
2954                            @Override
2955                            public void execute() {
2956
2957                                start(500, true);
2958                                getContainerpageService().saveElementSettings(
2959                                    getData().getRpcContext(),
2960                                    getData().getDetailId(),
2961                                    getRequestParams(),
2962                                    clientId,
2963                                    settings,
2964                                    getPageState(),
2965                                    getLocale(),
2966                                    this);
2967                            }
2968
2969                            @Override
2970                            protected void onResponse(CmsContainerElementData result) {
2971
2972                                stop(false);
2973                                CmsContainerpageController.get().fireEvent(
2974                                    new CmsContainerpageEvent(EventType.pageSaved));
2975                                setPageChanged(false, false);
2976                                if (result != null) {
2977                                    // cache the loaded element
2978                                    m_elements.put(result.getClientId(), result);
2979                                    setLoadTime(Long.valueOf(result.getLoadTime()));
2980                                }
2981                                callback.execute(result);
2982                            }
2983                        };
2984                        action.execute();
2985                    }
2986                }
2987            });
2988
2989        } else {
2990            getElementWithSettings(clientId, settings, callback);
2991        }
2992    }
2993
2994    /**
2995     * Reloads the page.<p>
2996     */
2997    public void reloadPage() {
2998
2999        Timer timer = new Timer() {
3000
3001            @Override
3002            public void run() {
3003
3004                Window.Location.reload();
3005            }
3006        };
3007
3008        timer.schedule(150);
3009
3010    }
3011
3012    /**
3013     * Removes the given container element from its parent container.<p>
3014     *
3015     * @param dragElement the element to remove
3016     */
3017    public void removeElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel dragElement) {
3018
3019        ElementRemoveMode removeMode = isConfirmRemove()
3020        ? ElementRemoveMode.confirmRemove
3021        : ElementRemoveMode.saveAndCheckReferences;
3022        removeElement(dragElement, removeMode);
3023    }
3024
3025    /**
3026     * Removes the given container element from its parent container.<p>
3027     *
3028     * @param dragElement the element to remove
3029     * @param removeMode the remove mode
3030     */
3031    public void removeElement(
3032        org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel dragElement,
3033        ElementRemoveMode removeMode) {
3034
3035        if (isGroupcontainerEditing()) {
3036            dragElement.removeFromParent();
3037            if (!getGroupcontainer().iterator().hasNext()) {
3038                // group-container is empty, mark it
3039                getGroupcontainer().addStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().emptyGroupContainer());
3040            }
3041            getGroupcontainer().refreshHighlighting();
3042        } else {
3043            final String id = dragElement.getId();
3044            if (id != null) {
3045                addToRecentList(id, null);
3046            }
3047
3048            I_CmsDropContainer container = dragElement.getParentTarget();
3049            switch (removeMode) {
3050                case saveAndCheckReferences:
3051                    I_EventDetails details = createEventDetails(dragElement);
3052                    dragElement.removeFromParent();
3053                    if (container instanceof CmsContainerPageContainer) {
3054                        ((CmsContainerPageContainer)container).checkEmptyContainers();
3055                    }
3056                    cleanUpContainers();
3057                    Runnable checkReferencesAction = new Runnable() {
3058
3059                        public void run() {
3060
3061                            checkReferencesToRemovedElement(id);
3062                        }
3063                    };
3064                    sendElementDeleted(details);
3065                    setPageChanged(checkReferencesAction);
3066                    break;
3067                case confirmRemove:
3068                    handleConfirmRemove(dragElement);
3069                    break;
3070                case silent:
3071                default:
3072                    I_EventDetails details2 = createEventDetails(dragElement);
3073                    dragElement.removeFromParent();
3074                    if (container instanceof CmsContainerPageContainer) {
3075                        ((CmsContainerPageContainer)container).checkEmptyContainers();
3076                    }
3077                    cleanUpContainers();
3078                    sendElementDeleted(details2);
3079                    setPageChanged();
3080                    break;
3081            }
3082        }
3083    }
3084
3085    /**
3086     * Replaces the given drag-element with the given container element.<p>
3087     *
3088     * @param containerElement the container element to replace
3089     * @param elementData the new element data
3090     *
3091     * @return the container element which replaced the old one
3092     *
3093     * @throws Exception if something goes wrong
3094     */
3095    public CmsContainerPageElementPanel replaceContainerElement(
3096        CmsContainerPageElementPanel containerElement,
3097        CmsContainerElementData elementData)
3098    throws Exception {
3099
3100        I_CmsDropContainer parentContainer = containerElement.getParentTarget();
3101        String containerId = parentContainer.getContainerId();
3102        CmsContainerPageElementPanel replacer = null;
3103        String elementContent = elementData.getContents().get(containerId);
3104        if ((elementContent != null) && (elementContent.trim().length() > 0)) {
3105            replacer = getContainerpageUtil().createElement(elementData, parentContainer, false);
3106
3107            if (containerElement.isNew()) {
3108                // if replacing element data has the same structure id, keep the 'new' state by setting the new type property
3109                // this should only be the case when editing settings of a new element that has not been created in the VFS yet
3110                String id = getServerId(containerElement.getId());
3111                if (elementData.getClientId().startsWith(id)) {
3112                    replacer.setNewType(containerElement.getNewType());
3113                }
3114            }
3115            replacer.setCreateNew(containerElement.isCreateNew());
3116            // replacer.setModelGroup(containerElement.isModelGroup());
3117            if (isGroupcontainerEditing() && (containerElement.getInheritanceInfo() != null)) {
3118                // in case of inheritance container editing, keep the inheritance info
3119                replacer.setInheritanceInfo(containerElement.getInheritanceInfo());
3120                // set the proper element options
3121                CmsInheritanceContainerEditor.getInstance().setOptionBar(replacer);
3122            }
3123            parentContainer.insert(replacer, parentContainer.getWidgetIndex(containerElement));
3124            containerElement.removeFromParent();
3125            initializeSubContainers(replacer);
3126        }
3127        cleanUpContainers();
3128        return replacer;
3129    }
3130
3131    /**
3132     * Replaces the given element with another content while keeping it's settings.<p>
3133     *
3134     * @param elementWidget the element to replace
3135     * @param elementId the id of the replacing content
3136     * @param callback  the callback to execute after the element is replaced
3137     */
3138    public void replaceElement(
3139        final CmsContainerPageElementPanel elementWidget,
3140        final String elementId,
3141        I_ReloadHandler handler) {
3142
3143        final CmsRpcAction<CmsContainerElementData> action = new CmsRpcAction<CmsContainerElementData>() {
3144
3145            @Override
3146            public void execute() {
3147
3148                start(500, true);
3149                getContainerpageService().replaceElement(
3150                    getData().getRpcContext(),
3151                    getData().getDetailId(),
3152                    getRequestParams(),
3153                    elementWidget.getId(),
3154                    elementId,
3155                    getPageState(),
3156                    getLocale(),
3157                    this);
3158            }
3159
3160            @Override
3161            protected void onResponse(CmsContainerElementData result) {
3162
3163                stop(false);
3164
3165                if (result != null) {
3166                    // cache the loaded element
3167                    m_elements.put(result.getClientId(), result);
3168                    try {
3169                        CmsContainerPageElementPanel replacer = replaceContainerElement(elementWidget, result);
3170                        resetEditButtons();
3171                        addToRecentList(result.getClientId(), null);
3172                        setPageChanged(new Runnable() {
3173
3174                            public void run() {
3175
3176                                if (handler != null) {
3177                                    handler.onReload(elementWidget, replacer);
3178                                    handler.finish();
3179                                }
3180                            }
3181                        });
3182                    } catch (Exception e) {
3183                        // should never happen
3184                        CmsDebugLog.getInstance().printLine(e.getLocalizedMessage());
3185                    }
3186                }
3187            }
3188        };
3189
3190        if (!isGroupcontainerEditing()) {
3191
3192            lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
3193
3194                public void execute(Boolean arg) {
3195
3196                    if (arg.booleanValue()) {
3197                        action.execute();
3198                    }
3199                }
3200            });
3201
3202        } else {
3203            action.execute();
3204        }
3205    }
3206
3207    /**
3208     * Replaces the given element with another content while keeping it's settings.<p>
3209     *
3210     * @param elementWidget the element to replace
3211     * @param elementId the id of the replacing content
3212     * @param callback  the callback to execute after the element is replaced
3213     */
3214    public void replaceElement(
3215        final CmsContainerPageElementPanel elementWidget,
3216        final String elementId,
3217        Runnable callback) {
3218
3219        replaceElement(elementWidget, elementId, new I_ReloadHandler() {
3220
3221            @Override
3222            public void finish() {
3223
3224                if (callback != null) {
3225                    callback.run();
3226                }
3227            }
3228
3229            @Override
3230            public void onReload(CmsContainerPageElementPanel oldElement, CmsContainerPageElementPanel newElement) {
3231
3232                // do nothing
3233            }
3234        });
3235
3236    }
3237
3238    /**
3239     * Checks whether the given element should display the option bar.<p>
3240     *
3241     * @param element the element
3242     * @param dragParent the element parent
3243     *
3244     * @return <code>true</code> if the given element should display the option bar
3245     */
3246    public boolean requiresOptionBar(CmsContainerPageElementPanel element, I_CmsDropContainer dragParent) {
3247
3248        return element.hasViewPermission()
3249            && (!element.hasModelGroupParent() || getData().isModelGroup())
3250            && (matchRootView(element.getElementView())
3251                || isGroupcontainerEditing()
3252                || shouldShowModelgroupOptionBar(element))
3253            && isContainerEditable(dragParent)
3254            && matchesCurrentEditLevel(dragParent);
3255    }
3256
3257    /**
3258     * Resets all edit buttons an there positions.<p>
3259     */
3260    public void resetEditButtons() {
3261
3262        removeEditButtonsPositionTimer();
3263        m_editButtonsPositionTimer = new Timer() {
3264
3265            /** Timer run counter. */
3266            private int m_timerRuns;
3267
3268            @Override
3269            public void run() {
3270
3271                for (org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer container : m_targetContainers.values()) {
3272                    container.showEditableListButtons();
3273                    container.updateOptionBars();
3274                }
3275                if (m_timerRuns > 3) {
3276                    cancel();
3277                }
3278                m_timerRuns++;
3279            }
3280        };
3281        m_editButtonsPositionTimer.scheduleRepeating(100);
3282    }
3283
3284    /**
3285     * Resets the container-page.<p>
3286     */
3287    public void resetPage() {
3288
3289        setPageChanged(false, true);
3290        Window.Location.reload();
3291    }
3292
3293    /**
3294     * Method to save and leave the page.<p>
3295     *
3296     * @param leaveCommand the command to execute to leave the page
3297     */
3298    public void saveAndLeave(final Command leaveCommand) {
3299
3300        if (hasPageChanged()) {
3301            CmsRpcAction<Long> action = new CmsRpcAction<Long>() {
3302
3303                /**
3304                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3305                 */
3306                @Override
3307                public void execute() {
3308
3309                    if (getData().getDetailContainerPage() != null) {
3310                        getContainerpageService().saveDetailContainers(
3311                            getData().getDetailId(),
3312                            getData().getDetailContainerPage(),
3313                            getPageContent(),
3314                            this);
3315                    } else {
3316                        getContainerpageService().saveContainerpage(
3317                            CmsCoreProvider.get().getStructureId(),
3318                            getPageContent(),
3319                            this);
3320                    }
3321                }
3322
3323                /**
3324                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3325                 */
3326                @Override
3327                protected void onResponse(Long result) {
3328
3329                    setLoadTime(result);
3330                    CmsNotification.get().send(Type.NORMAL, Messages.get().key(Messages.GUI_NOTIFICATION_PAGE_SAVED_0));
3331                    CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.pageSaved));
3332                    setPageChanged(false, true);
3333                    leaveCommand.execute();
3334                }
3335            };
3336            action.execute();
3337        }
3338    }
3339
3340    /**
3341     * Method to save and leave the page.<p>
3342     *
3343     * @param targetUri the new URI to call
3344     */
3345    public void saveAndLeave(final String targetUri) {
3346
3347        if (hasPageChanged()) {
3348            CmsRpcAction<Long> action = new CmsRpcAction<Long>() {
3349
3350                /**
3351                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3352                 */
3353                @Override
3354                public void execute() {
3355
3356                    if (getData().getDetailContainerPage() != null) {
3357                        getContainerpageService().saveDetailContainers(
3358                            getData().getDetailId(),
3359                            getData().getDetailContainerPage(),
3360                            getPageContent(),
3361                            this);
3362                    } else {
3363                        getContainerpageService().saveContainerpage(
3364                            CmsCoreProvider.get().getStructureId(),
3365                            getPageContent(),
3366                            this);
3367                    }
3368                }
3369
3370                /**
3371                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3372                 */
3373                @Override
3374                protected void onResponse(Long result) {
3375
3376                    setLoadTime(result);
3377                    CmsNotification.get().send(Type.NORMAL, Messages.get().key(Messages.GUI_NOTIFICATION_PAGE_SAVED_0));
3378                    CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.pageSaved));
3379                    setPageChanged(false, true);
3380                    Window.Location.assign(targetUri);
3381                }
3382            };
3383            action.execute();
3384        }
3385    }
3386
3387    /**
3388     * Saves the clipboard tab  index selected by the user.<p>
3389     *
3390     * @param tabIndex the tab index
3391     */
3392    public void saveClipboardTab(final int tabIndex) {
3393
3394        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
3395
3396            @Override
3397            public void execute() {
3398
3399                start(1, false);
3400                getContainerpageService().saveClipboardTab(tabIndex, this);
3401            }
3402
3403            @Override
3404            protected void onResponse(Void result) {
3405
3406                stop(false);
3407            }
3408        };
3409        action.execute();
3410    }
3411
3412    /**
3413     * Saves the current state of the container-page.<p>
3414     *
3415     * @param afterSaveActions the actions to execute after saving
3416     */
3417    public void saveContainerpage(final Runnable... afterSaveActions) {
3418
3419        if (hasPageChanged()) {
3420            final CmsRpcAction<Long> action = new CmsRpcAction<Long>() {
3421
3422                /**
3423                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3424                 */
3425                @Override
3426                public void execute() {
3427
3428                    start(500, true);
3429                    if (getData().getDetailContainerPage() != null) {
3430                        getContainerpageService().saveDetailContainers(
3431                            getData().getDetailId(),
3432                            getData().getDetailContainerPage(),
3433                            getPageContent(),
3434                            this);
3435                    } else {
3436                        getContainerpageService().saveContainerpage(
3437                            CmsCoreProvider.get().getStructureId(),
3438                            getPageContent(),
3439                            this);
3440                    }
3441                }
3442
3443                /**
3444                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3445                 */
3446                @Override
3447                protected void onResponse(Long result) {
3448
3449                    setLoadTime(result);
3450                    stop(false);
3451                    setPageChanged(false, false);
3452                    CmsContainerpageController.get().fireEvent(new CmsContainerpageEvent(EventType.pageSaved));
3453                    for (Runnable afterSaveAction : afterSaveActions) {
3454                        afterSaveAction.run();
3455                    }
3456                }
3457            };
3458            if (getData().getDetailContainerPage() != null) {
3459                action.execute();
3460            } else {
3461                lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
3462
3463                    public void execute(Boolean arg) {
3464
3465                        if (arg.booleanValue()) {
3466                            action.execute();
3467                        }
3468                    }
3469                });
3470            }
3471        }
3472    }
3473
3474    /**
3475     * Saves the favorite list.<p>
3476     *
3477     * @param clientIds the client id's of the list's elements
3478     */
3479    public void saveFavoriteList(final List<String> clientIds) {
3480
3481        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
3482
3483            /**
3484             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3485             */
3486            @Override
3487            public void execute() {
3488
3489                getContainerpageService().saveFavoriteList(clientIds, CmsCoreProvider.get().getUri(), this);
3490            }
3491
3492            /**
3493             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3494             */
3495            @Override
3496            protected void onResponse(Void result) {
3497
3498                CmsNotification.get().send(
3499                    Type.NORMAL,
3500                    Messages.get().key(Messages.GUI_NOTIFICATION_FAVORITES_SAVED_0));
3501            }
3502        };
3503        action.execute();
3504    }
3505
3506    /**
3507     * Saves the group-container.<p>
3508     *
3509     * @param groupContainer the group-container data to save
3510     * @param groupContainerElement the group-container widget
3511     */
3512    public void saveGroupcontainer(
3513        final CmsGroupContainer groupContainer,
3514        final CmsGroupContainerElementPanel groupContainerElement) {
3515
3516        if (getGroupcontainer() != null) {
3517            CmsRpcAction<CmsGroupContainerSaveResult> action = new CmsRpcAction<CmsGroupContainerSaveResult>() {
3518
3519                /**
3520                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3521                 */
3522                @Override
3523                public void execute() {
3524
3525                    start(0, true);
3526                    getContainerpageService().saveGroupContainer(
3527                        getData().getRpcContext(),
3528                        getData().getDetailId(),
3529                        getRequestParams(),
3530                        groupContainer,
3531                        getPageState(),
3532                        getLocale(),
3533                        this);
3534                }
3535
3536                /**
3537                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3538                 */
3539                @Override
3540                protected void onResponse(CmsGroupContainerSaveResult saveResult) {
3541
3542                    stop(false);
3543                    Map<String, CmsContainerElementData> elementData = saveResult.getElementData();
3544                    m_elements.putAll(elementData);
3545                    try {
3546                        replaceContainerElement(groupContainerElement, elementData.get(groupContainerElement.getId()));
3547                    } catch (Exception e) {
3548                        CmsDebugLog.getInstance().printLine("Error replacing group container element");
3549                    }
3550                    addToRecentList(groupContainerElement.getId(), null);
3551                    CmsNotification.get().send(
3552                        Type.NORMAL,
3553                        Messages.get().key(Messages.GUI_NOTIFICATION_GROUP_CONTAINER_SAVED_0));
3554                    List<CmsRemovedElementStatus> removedElements = saveResult.getRemovedElements();
3555                    for (CmsRemovedElementStatus removedElement : removedElements) {
3556                        askWhetherRemovedElementShouldBeDeleted(removedElement);
3557                    }
3558
3559                }
3560            };
3561            action.execute();
3562
3563        }
3564    }
3565
3566    /**
3567     * Saves the inheritance container.<p>
3568     *
3569     * @param inheritanceContainer the inheritance container data to save
3570     * @param groupContainerElement the group container widget
3571     */
3572    public void saveInheritContainer(
3573        final CmsInheritanceContainer inheritanceContainer,
3574        final CmsGroupContainerElementPanel groupContainerElement) {
3575
3576        if (getGroupcontainer() != null) {
3577            CmsRpcAction<Map<String, CmsContainerElementData>> action = new CmsRpcAction<Map<String, CmsContainerElementData>>() {
3578
3579                /**
3580                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
3581                 */
3582                @Override
3583                public void execute() {
3584
3585                    start(0, true);
3586                    getContainerpageService().saveInheritanceContainer(
3587                        CmsCoreProvider.get().getStructureId(),
3588                        getData().getDetailId(),
3589                        inheritanceContainer,
3590                        getPageState(),
3591                        getLocale(),
3592                        this);
3593                }
3594
3595                /**
3596                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
3597                 */
3598                @Override
3599                protected void onResponse(Map<String, CmsContainerElementData> result) {
3600
3601                    stop(false);
3602                    m_elements.putAll(result);
3603                    try {
3604                        replaceContainerElement(groupContainerElement, result.get(groupContainerElement.getId()));
3605                    } catch (Exception e) {
3606                        CmsDebugLog.getInstance().printLine("Error replacing group container element");
3607                    }
3608                    addToRecentList(groupContainerElement.getId(), null);
3609                    CmsNotification.get().send(
3610                        Type.NORMAL,
3611                        Messages.get().key(Messages.GUI_NOTIFICATION_INHERITANCE_CONTAINER_SAVED_0));
3612
3613                }
3614            };
3615            action.execute();
3616
3617        }
3618    }
3619
3620    /**
3621     * Sends the ocDragFinished event.
3622     *
3623     * @param dragElement the drag element
3624     * @param target the target element
3625     * @param isNew true if the dragged element is new
3626     */
3627    public void sendDragFinished(Element dragElement, Element target, boolean isNew) {
3628
3629        I_EventDetails details = Js.cast(new JsObject());
3630        if (dragElement != null) {
3631            details.setElement(Js.cast(dragElement));
3632        }
3633        if (target != null) {
3634            final Element parentContainerElement = CmsDomUtil.getAncestor(target, CmsContainerElement.CLASS_CONTAINER);
3635            details.setContainer(Js.cast(parentContainerElement));
3636        }
3637        if (isNew) {
3638            details.setIsPlaceholder(isNew);
3639        }
3640        sendEvent(EVENT_DRAG_FINISHED, details);
3641        I_EventDetails details2 = copyEventDetails(details);
3642        details2.setType("finish");
3643        sendEvent(EVENT_DRAG, details2);
3644    }
3645
3646    /**
3647     * Sends the ocDragStarted event.
3648     *
3649     * @param isNew true if the user started dragging a new element
3650     */
3651    public void sendDragStarted(boolean isNew) {
3652
3653        I_EventDetails details = Js.cast(new JsObject());
3654        details.setIsPlaceholder(isNew);
3655        sendEvent(EVENT_DRAG_STARTED, details);
3656        I_EventDetails details2 = copyEventDetails(details);
3657        details2.setType("start");
3658        sendEvent(EVENT_DRAG, details2);
3659    }
3660
3661    /**
3662     * Sends the custom 'ocElementAdded' event for added container elements.
3663     *
3664     * @param widget the container element
3665     */
3666    public void sendElementAdded(CmsContainerPageElementPanel widget) {
3667
3668        if (widget != null) {
3669            I_EventDetails details = createEventDetails(widget);
3670            sendEvent(EVENT_ELEMENT_ADDED, details);
3671            sendEvent(EVENT_ELEMENT_MODIFIED, addTypeToDetails(details, "add"));
3672        }
3673    }
3674
3675    /**
3676     * Sends the custom 'ocElementDeleted' event for a deleted container element.
3677     *
3678     * @param details the event details
3679     */
3680    public void sendElementDeleted(I_EventDetails details) {
3681
3682        sendEvent(EVENT_ELEMENT_DELETED, details);
3683        sendEvent(EVENT_ELEMENT_MODIFIED, addTypeToDetails(details, "delete"));
3684    }
3685
3686    /**
3687     * Sends the custom 'ocElementEditedContent' event for an edited container element.
3688     *
3689     * @param widget the container element
3690     * @param replacedElement the element that was formerly on the page before the user edited it
3691     * @param wasNew true if the element was a new element before editing
3692     */
3693    public void sendElementEditedContent(
3694        CmsContainerPageElementPanel widget,
3695        elemental2.dom.Element replacedElement,
3696        boolean wasNew) {
3697
3698        if (widget != null) {
3699            I_EventDetails details = createEventDetails(widget);
3700            details.setReplacedElement(replacedElement);
3701            details.setIsPlaceholder(wasNew);
3702            sendEvent(EVENT_ELEMENT_EDITED_CONTENT, details);
3703            sendEvent(EVENT_ELEMENT_EDITED, details);
3704            sendEvent(EVENT_ELEMENT_MODIFIED, addTypeToDetails(details, "editContent"));
3705        }
3706    }
3707
3708    /**
3709     * Sends the custom 'ocElementEditedSettings' event for an edited container element.
3710     *
3711     * @param widget the container element
3712     * @param replacedWidget the former container element that has now been replaced with 'widget'
3713     */
3714    public void sendElementEditedSettings(
3715        CmsContainerPageElementPanel widget,
3716        CmsContainerPageElementPanel replacedWidget) {
3717
3718        if (widget != null) {
3719            I_EventDetails details = createEventDetails(widget);
3720            details.setReplacedElement(Js.cast(replacedWidget.getElement()));
3721            sendEvent(EVENT_ELEMENT_EDITED_SETTINGS, details);
3722            sendEvent(EVENT_ELEMENT_EDITED, details);
3723            sendEvent(EVENT_ELEMENT_MODIFIED, addTypeToDetails(details, "editSettings"));
3724        }
3725    }
3726
3727    /**
3728     * Sends the custom 'ocElementMoved' event for a moved container element.
3729     *
3730     * @param widget the container element
3731     */
3732    public void sendElementMoved(CmsContainerPageElementPanel widget) {
3733
3734        if (widget != null) {
3735            I_EventDetails details = createEventDetails(widget);
3736            sendEvent(EVENT_ELEMENT_MOVED, details);
3737            sendEvent(EVENT_ELEMENT_MODIFIED, addTypeToDetails(details, "move"));
3738        }
3739    }
3740
3741    /**
3742     * Sets the flag indicating that a content element is being edited.<p>
3743     *
3744     * @param isContentEditing the flag indicating that a content element is being edited
3745     */
3746    public void setContentEditing(boolean isContentEditing) {
3747
3748        if (m_groupEditor != null) {
3749            if (isContentEditing) {
3750                m_groupEditor.hidePopup();
3751            } else {
3752                m_groupEditor.showPopup();
3753            }
3754        }
3755        m_isContentEditing = isContentEditing;
3756    }
3757
3758    /**
3759     * Sets the DND controller.<p>
3760     *
3761     * @param dnd the new DND controller
3762     */
3763    public void setDndController(CmsCompositeDNDController dnd) {
3764
3765        m_dndController = dnd;
3766    }
3767
3768    /**
3769     * Sets the element view.<p>
3770     *
3771     * @param viewInfo the element view
3772     * @param nextAction the action to execute after setting the view
3773     */
3774    public void setElementView(CmsElementViewInfo viewInfo, Runnable nextAction) {
3775
3776        if (viewInfo != null) {
3777            m_elementView = viewInfo;
3778
3779            CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
3780
3781                @SuppressWarnings("synthetic-access")
3782                @Override
3783                public void execute() {
3784
3785                    getContainerpageService().setElementView(m_elementView.getElementViewId(), this);
3786                }
3787
3788                @Override
3789                protected void onResponse(Void result) {
3790
3791                    // nothing to do
3792                }
3793            };
3794            action.execute();
3795
3796            m_currentEditLevel = -1;
3797            reinitializeButtons();
3798            updateButtonsForCurrentView();
3799            reInitInlineEditing();
3800            updateGalleryData(true, nextAction);
3801        }
3802    }
3803
3804    /**
3805     * Sets the model group base element id.<p>
3806     *
3807     * @param modelGroupElementId the model group base element id
3808     */
3809    public void setModelGroupElementId(String modelGroupElementId) {
3810
3811        m_modelGroupElementId = modelGroupElementId;
3812    }
3813
3814    /**
3815     * Marks the page as changed.<p>
3816     *
3817     * @param nextActions the actions to perform after the page has been marked as changed
3818     */
3819    public void setPageChanged(Runnable... nextActions) {
3820
3821        if (!isGroupcontainerEditing()) {
3822            // the container page will be saved immediately
3823            m_pageChanged = true;
3824            saveContainerpage(nextActions);
3825        }
3826    }
3827
3828    /**
3829     * Sets an alternative event preview handler.
3830     *
3831     * @param handler the alternative preview handler to use
3832     */
3833    public void setPreviewHandler(NativePreviewHandler handler) {
3834        m_previewHandler = handler;
3835    }
3836
3837    /**
3838     * Method to determine whether a container element should be shown in the current template context.<p>
3839     *
3840     * @param elementData the element data
3841     *
3842     * @return true if the element should be shown
3843     */
3844    public boolean shouldShowInContext(CmsContainerElementData elementData) {
3845
3846        CmsTemplateContextInfo contextInfo = getData().getTemplateContextInfo();
3847        if (contextInfo.getCurrentContext() == null) {
3848            return true;
3849        }
3850        CmsDefaultSet<String> allowedContexts = contextInfo.getAllowedContexts().get(elementData.getResourceType());
3851        if ((allowedContexts != null) && !allowedContexts.contains(contextInfo.getCurrentContext())) {
3852            return false;
3853        }
3854
3855        String settingValue = elementData.getSettings().get(CmsTemplateContextInfo.SETTING);
3856        return (settingValue == null) || settingValue.contains(contextInfo.getCurrentContext());
3857    }
3858
3859    /**
3860     * Tells the controller that group-container editing has started.<p>
3861     *
3862     * @param groupContainer the group container
3863     * @param isElementGroup <code>true</code> if the group container is an element group and not an inheritance group
3864     */
3865    public void startEditingGroupcontainer(
3866        final CmsGroupContainerElementPanel groupContainer,
3867        final boolean isElementGroup) {
3868
3869        removeEditButtonsPositionTimer();
3870        I_CmsSimpleCallback<Boolean> callback = new I_CmsSimpleCallback<Boolean>() {
3871
3872            public void execute(Boolean arg) {
3873
3874                if (arg.booleanValue()) {
3875                    if (isElementGroup) {
3876                        m_groupEditor = CmsGroupContainerEditor.openGroupcontainerEditor(
3877                            groupContainer,
3878                            CmsContainerpageController.this,
3879                            m_handler);
3880                    } else {
3881                        m_groupEditor = CmsInheritanceContainerEditor.openInheritanceContainerEditor(
3882                            groupContainer,
3883                            CmsContainerpageController.this,
3884                            m_handler);
3885                    }
3886                } else {
3887                    CmsNotification.get().send(
3888                        Type.WARNING,
3889                        Messages.get().key(Messages.GUI_NOTIFICATION_UNABLE_TO_LOCK_0));
3890                }
3891            }
3892        };
3893        RootPanel.get().addStyleName(OC_EDITING_GROUPCONTAINER);
3894        if ((m_groupEditor == null) && (groupContainer.isNew())) {
3895            callback.execute(Boolean.TRUE);
3896        } else {
3897            lockContainerpage(callback);
3898        }
3899
3900    }
3901
3902    /**
3903     * Starts the publish lock check.
3904     */
3905    public void startPublishLockCheck() {
3906
3907        Set<CmsUUID> elementIds = new HashSet<>();
3908        processPageContent(new I_PageContentVisitor() {
3909
3910            public boolean beginContainer(String name, CmsContainer container) {
3911
3912                return true;
3913            }
3914
3915            public void endContainer() {
3916
3917                // do nothing
3918            }
3919
3920            public void handleElement(CmsContainerPageElementPanel element) {
3921
3922                if (element.hasWritePermission() && element.getLockInfo().isPublishLock()) {
3923                    CmsUUID structureId = element.getStructureId();
3924                    if (structureId != null) {
3925                        elementIds.add(structureId);
3926                    }
3927                }
3928            }
3929        });
3930
3931        m_publishLockChecker.addIdsToCheck(elementIds);
3932    }
3933
3934    /**
3935     * Tells the controller that group-container editing has stopped.<p>
3936     */
3937    public void stopEditingGroupcontainer() {
3938
3939        m_groupEditor = null;
3940        RootPanel.get().removeStyleName(OC_EDITING_GROUPCONTAINER);
3941    }
3942
3943    /**
3944     * Unlocks the given resource.<p>
3945     *
3946     * @param structureId the structure id of the resource to unlock
3947     *
3948     * @return <code>true</code> if the resource was unlocked successfully
3949     */
3950    public boolean unlockResource(CmsUUID structureId) {
3951
3952        return CmsCoreProvider.get().unlock(structureId);
3953    }
3954
3955    /**
3956     * Updates he
3957     */
3958    public void updateButtonsForCurrentView() {
3959
3960        String nonDefaultViewClass = I_CmsLayoutBundle.INSTANCE.containerpageCss().nonDefaultView();
3961        CmsUUID viewId = getElementView().getRootViewId();
3962        if (viewId.isNullUUID()) {
3963            RootPanel.get().removeStyleName(nonDefaultViewClass);
3964        } else {
3965            RootPanel.get().addStyleName(nonDefaultViewClass);
3966        }
3967    }
3968
3969    /**
3970     * Adds the given element data to the element cache.<p>
3971     *
3972     * @param elements the element data
3973     */
3974    protected void addElements(List<CmsContainerElementData> elements) {
3975
3976        for (CmsContainerElementData element : elements) {
3977            m_elements.put(element.getClientId(), element);
3978        }
3979    }
3980
3981    /**
3982     * Adds the given element data to the element cache.<p>
3983     *
3984     * @param elements the element data
3985     */
3986    protected void addElements(Map<String, CmsContainerElementData> elements) {
3987
3988        for (CmsContainerElementData element : elements.values()) {
3989            m_elements.put(element.getClientId(), element);
3990        }
3991    }
3992
3993    /**
3994     * Asks the user whether an element which has been removed should be deleted.<p>
3995     *
3996     * @param status the status of the removed element
3997     */
3998    protected void askWhetherRemovedElementShouldBeDeleted(final CmsRemovedElementStatus status) {
3999
4000        CmsRemovedElementDeletionDialog dialog = new CmsRemovedElementDeletionDialog(status);
4001        dialog.center();
4002    }
4003
4004    /**
4005     * Checks that a removed can be possibly deleted and if so, asks the user if it should be deleted.<p>
4006     *
4007     * @param id the client id of the element
4008     */
4009    protected void checkReferencesToRemovedElement(final String id) {
4010
4011        if (id != null) {
4012            //NOTE: We only use an RPC call here to check for references on the server side. If, at a later point, we decide
4013            //to add a save button again, this will have to be changed, because then we have to consider client-side state.
4014            CmsRpcAction<CmsRemovedElementStatus> getStatusAction = new CmsRpcAction<CmsRemovedElementStatus>() {
4015
4016                @Override
4017                public void execute() {
4018
4019                    start(200, true);
4020                    getContainerpageService().getRemovedElementStatus(id, null, this);
4021                }
4022
4023                @Override
4024                public void onResponse(final CmsRemovedElementStatus status) {
4025
4026                    stop(false);
4027                    if (status.isDeletionCandidate()) {
4028                        askWhetherRemovedElementShouldBeDeleted(status);
4029
4030                    }
4031                }
4032
4033            };
4034            getStatusAction.execute();
4035
4036        }
4037    }
4038
4039    /**
4040     * Disables option and toolbar buttons.<p>
4041     */
4042    protected void deactivateOnClosing() {
4043
4044        removeEditButtonsPositionTimer();
4045        m_handler.deactivateCurrentButton();
4046        m_handler.disableToolbarButtons();
4047    }
4048
4049    /**
4050     * Helper method to get all current container page elements.<p>
4051     *
4052     * @param includeGroupContents true if the contents of group containers should also be included
4053     *
4054     * @return the list of current container page elements
4055     */
4056    protected List<CmsContainerPageElementPanel> getAllContainerPageElements(boolean includeGroupContents) {
4057
4058        List<CmsContainerPageElementPanel> elemWidgets = new ArrayList<CmsContainerPageElementPanel>();
4059        for (Entry<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> entry : CmsContainerpageController.get().getContainerTargets().entrySet()) {
4060            Iterator<Widget> elIt = entry.getValue().iterator();
4061            while (elIt.hasNext()) {
4062                try {
4063                    org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel elementWidget = (org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel)elIt.next();
4064                    elemWidgets.add(elementWidget);
4065                    if (includeGroupContents && (elementWidget instanceof CmsGroupContainerElementPanel)) {
4066                        List<CmsContainerPageElementPanel> groupChildren = ((CmsGroupContainerElementPanel)elementWidget).getGroupChildren();
4067                        elemWidgets.addAll(groupChildren);
4068                    }
4069                } catch (ClassCastException e) {
4070                    // no proper container element, skip it (this should never happen!)
4071                    CmsDebugLog.getInstance().printLine(
4072                        "WARNING: there is an inappropriate element within a container");
4073                }
4074            }
4075        }
4076        return elemWidgets;
4077    }
4078
4079    /**
4080     * Returns the core RPC service.<p>
4081     *
4082     * @return the core service
4083     */
4084    protected I_CmsCoreServiceAsync getCoreService() {
4085
4086        if (m_coreSvc == null) {
4087            m_coreSvc = CmsCoreProvider.getService();
4088        }
4089        return m_coreSvc;
4090    }
4091
4092    /**
4093     * Returns the currently active group editor.<p>
4094     *
4095     * @return the currently active group editor
4096     */
4097    protected A_CmsGroupEditor getGroupEditor() {
4098
4099        return m_groupEditor;
4100    }
4101
4102    /**
4103     * Returns the content locale.<p>
4104     *
4105     * @return the content locale
4106     */
4107    protected String getLocale() {
4108
4109        return m_data.getLocale();
4110    }
4111
4112    /**
4113     * Gets the page content for purposes of saving.<p>
4114     *
4115     * @return the page content
4116     */
4117    protected List<CmsContainer> getPageContent() {
4118
4119        SaveDataVisitor visitor = new SaveDataVisitor();
4120        processPageContent(visitor);
4121        return visitor.getContainers();
4122
4123    }
4124
4125    /**
4126     * Returns the containers of the page in their current state.<p>
4127     *
4128     * @return the containers of the page
4129     */
4130    protected List<CmsContainer> getPageState() {
4131
4132        PageStateVisitor visitor = new PageStateVisitor();
4133        processPageContent(visitor);
4134        return visitor.getContainers();
4135    }
4136
4137    /**
4138     * Returns the request parameters of the displayed container-page.<p>
4139     *
4140     * @return the request parameters
4141     */
4142    protected String getRequestParams() {
4143
4144        return m_data.getRequestParams();
4145    }
4146
4147    /**
4148     * Checks if any of the containers are nested containers.<p>
4149     *
4150     * @return true if there are nested containers
4151     */
4152    protected boolean hasNestedContainers() {
4153
4154        boolean hasNestedContainers = false;
4155        for (CmsContainer container : m_containers.values()) {
4156            if (container.getParentContainerName() != null) {
4157                hasNestedContainers = true;
4158                break;
4159            }
4160        }
4161        return hasNestedContainers;
4162    }
4163
4164    /**
4165     * Returns whether the given container is considered a root container.<p>
4166     *
4167     * @param container the container to check
4168     *
4169     * @return <code>true</code> if the given container is a root container
4170     */
4171    protected boolean isRootContainer(CmsContainer container) {
4172
4173        boolean isRoot = false;
4174        if (!container.isSubContainer()) {
4175            isRoot = true;
4176        } else if (container.isDetailOnly()) {
4177            CmsContainer parent = getContainer(container.getParentContainerName());
4178            isRoot = (parent != null) && !parent.isDetailOnly();
4179        }
4180        return isRoot;
4181    }
4182
4183    /**
4184     * Opens the editor for the newly created element.<p>
4185     *
4186     * @param element the container element
4187     * @param newElementData the new element data
4188     * @param inline <code>true</code> to open the inline editor for the given element if available
4189     */
4190    protected void openEditorForNewElement(
4191        org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element,
4192        CmsContainerElement newElementData,
4193        boolean inline) {
4194
4195        String oldId = element.getNewType();
4196        element.setNewType(null);
4197        if (inline) {
4198            String newId = getServerId(newElementData.getClientId());
4199            CmsContentEditor.replaceResourceIds(element.getElement(), oldId, newId);
4200        }
4201        element.setId(newElementData.getClientId());
4202        element.setSitePath(newElementData.getSitePath());
4203        if (!isGroupcontainerEditing()) {
4204            setPageChanged();
4205        }
4206        getHandler().hidePageOverlay();
4207        getHandler().openEditorForElement(element, inline, true);
4208    }
4209
4210    /**
4211    * Previews events. Shows the leaving page dialog, if the page has changed and an anchor has been clicked.<p>
4212    * Also triggers an element view change on 'Ctrl+E'.<p>
4213    *
4214    * @param event the native event
4215    */
4216    protected void previewNativeEvent(NativePreviewEvent event) {
4217        if (m_previewHandler != null) {
4218            m_previewHandler.onPreviewNativeEvent(event);
4219            return;
4220        }
4221
4222        Event nativeEvent = Event.as(event.getNativeEvent());
4223
4224        if ((nativeEvent.getTypeInt() == Event.ONCLICK) && hasPageChanged()) {
4225            EventTarget target = nativeEvent.getEventTarget();
4226            if (!Element.is(target)) {
4227                return;
4228            }
4229            Element element = Element.as(target);
4230            element = CmsDomUtil.getAncestor(element, CmsDomUtil.Tag.a);
4231            if (element == null) {
4232                return;
4233            }
4234            AnchorElement anc = AnchorElement.as(element);
4235            final String uri = anc.getHref();
4236
4237            // avoid to abort events for date-picker widgets
4238            if (CmsStringUtil.isEmptyOrWhitespaceOnly(uri)
4239                || (CmsDomUtil.getAncestor(element, "x-date-picker") != null)) {
4240                return;
4241            }
4242            nativeEvent.preventDefault();
4243            nativeEvent.stopPropagation();
4244            m_handler.leavePage(uri);
4245        }
4246        if (event.getTypeInt() == Event.ONKEYDOWN) {
4247            int keyCode = nativeEvent.getKeyCode();
4248            if ((keyCode == KeyCodes.KEY_F5) && hasPageChanged()) {
4249                // user pressed F5
4250                nativeEvent.preventDefault();
4251                nativeEvent.stopPropagation();
4252                m_handler.leavePage(Window.Location.getHref());
4253            }
4254            if (nativeEvent.getCtrlKey() || nativeEvent.getMetaKey()) {
4255                // look for short cuts
4256                if (keyCode == KeyCodes.KEY_E) {
4257                    if (nativeEvent.getShiftKey()) {
4258                        circleContainerEditLayers();
4259                    } else {
4260                        openNextElementView();
4261                    }
4262                    nativeEvent.preventDefault();
4263                    nativeEvent.stopPropagation();
4264                }
4265            }
4266        }
4267    }
4268
4269    /**
4270     * Iterates over all the container contents and calls a visitor object with the visited containers/elements as parameters.
4271     *
4272     * @param visitor the visitor which the container elements should be passed to
4273     */
4274    protected void processPageContent(I_PageContentVisitor visitor) {
4275
4276        for (Entry<String, org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer> entry : m_targetContainers.entrySet()) {
4277
4278            CmsContainer cnt = m_containers.get(entry.getKey());
4279            if (visitor.beginContainer(entry.getKey(), cnt)) {
4280                Iterator<Widget> elIt = entry.getValue().iterator();
4281                while (elIt.hasNext()) {
4282                    try {
4283                        CmsContainerPageElementPanel elementWidget = (CmsContainerPageElementPanel)elIt.next();
4284                        visitor.handleElement(elementWidget);
4285                    } catch (ClassCastException e) {
4286                        // no proper container element, skip it (this should never happen!)
4287                        CmsDebugLog.getInstance().printLine(
4288                            "WARNING: there is an inappropriate element within a container");
4289                    }
4290                }
4291                visitor.endContainer();
4292            }
4293        }
4294    }
4295
4296    /**
4297     * Removes all container elements with the given id from all containers and the client side cache.<p>
4298     *
4299     * @param resourceId the resource id
4300     */
4301    protected void removeContainerElements(String resourceId) {
4302
4303        boolean changed = false;
4304        Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> it = getAllDragElements().iterator();
4305        while (it.hasNext()) {
4306            org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel containerElement = it.next();
4307            if (resourceId.startsWith(containerElement.getId())) {
4308                containerElement.removeFromParent();
4309                changed = true;
4310            }
4311        }
4312        for (String elementId : m_elements.keySet()) {
4313            if (elementId.startsWith(resourceId)) {
4314                m_elements.remove(elementId);
4315            }
4316        }
4317        if (changed) {
4318            setPageChanged();
4319        }
4320    }
4321
4322    /**
4323     * Schedules an update of the gallery data according to the current element view and the editable containers.<p>
4324     */
4325    protected void scheduleGalleryUpdate() {
4326
4327        // only if not already scheduled
4328        if (m_galleryUpdateTimer == null) {
4329            m_galleryUpdateTimer = new Timer() {
4330
4331                @Override
4332                public void run() {
4333
4334                    m_galleryUpdateTimer = null;
4335                    updateGalleryData(false, null);
4336                }
4337            };
4338            m_galleryUpdateTimer.schedule(50);
4339        }
4340    }
4341
4342    /**
4343     * Sets the page changed flag and initializes the window closing handler if necessary.<p>
4344     *
4345     * @param changed if <code>true</code> the page has changed
4346     * @param unlock if <code>true</code> the page will be unlocked for unchanged pages
4347     */
4348    protected void setPageChanged(boolean changed, boolean unlock) {
4349
4350        if (changed) {
4351            if (!m_pageChanged) {
4352                m_pageChanged = changed;
4353                lockContainerpage(new I_CmsSimpleCallback<Boolean>() {
4354
4355                    public void execute(Boolean arg) {
4356
4357                        // nothing to do
4358                    }
4359                });
4360            }
4361        } else {
4362            m_pageChanged = changed;
4363            if (unlock) {
4364                unlockContainerpage();
4365            }
4366        }
4367    }
4368
4369    /**
4370     * Asynchronously unlocks the container page.
4371     */
4372    protected void unlockContainerpage() {
4373
4374        I_CmsAutoBeanFactory factory = CmsCoreProvider.AUTO_BEAN_FACTORY;
4375        AutoBean<I_CmsUnlockData> unlockParams = factory.unlockData();
4376        unlockParams.as().setPageId("" + CmsCoreProvider.get().getStructureId());
4377        if (getData().getDetailId() != null) {
4378            unlockParams.as().setDetailId("" + getData().getDetailId());
4379        }
4380        unlockParams.as().setLocale(CmsCoreProvider.get().getLocale());
4381        String url = CmsCoreProvider.get().link("/handleBuiltinService" + CmsGwtConstants.HANDLER_UNLOCK_PAGE);
4382        sendBeacon(url, AutoBeanCodex.encode(unlockParams).getPayload());
4383    }
4384
4385    /**
4386     * Returns the pages of editable containers.<p>
4387     *
4388     * @return the containers
4389     */
4390    List<CmsContainer> getEditableContainers() {
4391
4392        List<CmsContainer> containers = new ArrayList<CmsContainer>();
4393        for (CmsContainer container : m_containers.values()) {
4394            if ((m_targetContainers.get(container.getName()) != null)
4395                && isContainerEditable(m_targetContainers.get(container.getName()))) {
4396                containers.add(container);
4397            }
4398        }
4399        return containers;
4400    }
4401
4402    /**
4403     * Handles a window resize to reset highlighting and the edit button positions.<p>
4404     */
4405    void handleResize() {
4406
4407        m_resizeTimer = null;
4408        resetEditButtons();
4409    }
4410
4411    /**
4412     * Call on window resize.<p>
4413     */
4414    void onResize() {
4415
4416        if (!isGroupcontainerEditing() && (m_resizeTimer == null)) {
4417            m_resizeTimer = new Timer() {
4418
4419                @Override
4420                public void run() {
4421
4422                    handleResize();
4423                }
4424            };
4425            m_resizeTimer.schedule(300);
4426        }
4427    }
4428
4429    /**
4430     * Sets the load time.<p>
4431     *
4432     * @param time the time to set
4433     */
4434    void setLoadTime(Long time) {
4435
4436        if (time != null) {
4437            m_loadTime = time.longValue();
4438        }
4439    }
4440
4441    /**
4442     * Updates the gallery data according to the current element view and the editable containers.<p>
4443     * This method should only be called from the gallery update timer to avoid unnecessary requests.<p>
4444     *
4445     * @param viewChanged <code>true</code> in case the element view changed
4446     * @param nextAction the action to execute after updating the gallery data
4447     */
4448    void updateGalleryData(final boolean viewChanged, final Runnable nextAction) {
4449
4450        CmsRpcAction<CmsContainerPageGalleryData> dataAction = new CmsRpcAction<CmsContainerPageGalleryData>() {
4451
4452            @Override
4453            public void execute() {
4454
4455                getContainerpageService().getGalleryDataForPage(
4456                    getEditableContainers(),
4457                    getElementView().getElementViewId(),
4458                    CmsCoreProvider.get().getUri(),
4459                    getData().getDetailId(),
4460                    getData().getLocale(),
4461                    CmsContainerpageController.get().getData().getTemplateContextInfo(),
4462                    this);
4463            }
4464
4465            @Override
4466            protected void onResponse(CmsContainerPageGalleryData result) {
4467
4468                m_handler.m_editor.getAdd().updateGalleryData(result, viewChanged);
4469                if (nextAction != null) {
4470                    nextAction.run();
4471                }
4472            }
4473        };
4474        dataAction.execute();
4475    }
4476
4477    /**
4478     * Creates a copy of the event details and adds a 'type' field with the specified value to the result.
4479     *
4480     * @param details the details to copy
4481     * @param type the type to add
4482     * @return the copy with the added type
4483     */
4484    private I_EventDetails addTypeToDetails(I_EventDetails details, String type) {
4485
4486        I_EventDetails result = copyEventDetails(details);
4487        result.setType(type);
4488        return result;
4489    }
4490
4491    /**
4492     * Checks whether there are other references to a given container page element.<p>
4493     *
4494     * @param element the element to check
4495     * @param callback the callback which will be called with the result of the check (true if there are other references)
4496     */
4497    private void checkElementReferences(
4498        final CmsContainerPageElementPanel element,
4499        final AsyncCallback<CmsRemovedElementStatus> callback) {
4500
4501        ReferenceCheckVisitor visitor = new ReferenceCheckVisitor(element);
4502        processPageContent(visitor);
4503        if (visitor.hasReferences()) {
4504            // Don't need to ask the server because we already know we have other references in the same page
4505            CmsRpcAction<CmsListInfoBean> infoAction = new CmsRpcAction<CmsListInfoBean>() {
4506
4507                @Override
4508                public void execute() {
4509
4510                    start(200, true);
4511                    CmsCoreProvider.getVfsService().getPageInfo(new CmsUUID(getServerId(element.getId())), this);
4512                }
4513
4514                @Override
4515                protected void onResponse(CmsListInfoBean result) {
4516
4517                    stop(false);
4518                    callback.onSuccess(new CmsRemovedElementStatus(null, result, false, null));
4519                }
4520            };
4521            infoAction.execute();
4522        } else {
4523            CmsRpcAction<CmsRemovedElementStatus> getStatusAction = new CmsRpcAction<CmsRemovedElementStatus>() {
4524
4525                @Override
4526                public void execute() {
4527
4528                    start(200, true);
4529                    getContainerpageService().getRemovedElementStatus(
4530                        element.getId(),
4531                        CmsCoreProvider.get().getStructureId(),
4532                        this);
4533                }
4534
4535                @Override
4536                public void onResponse(final CmsRemovedElementStatus status) {
4537
4538                    stop(false);
4539                    callback.onSuccess(status);
4540                }
4541
4542            };
4543            getStatusAction.execute();
4544
4545        }
4546    }
4547
4548    /**
4549     * Checks if the page was locked by another user at load time.<p>
4550     */
4551    private void checkLockInfo() {
4552
4553        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getData().getLockInfo())) {
4554            CmsNotification.get().send(Type.ERROR, getData().getLockInfo());
4555            m_lockStatus = LockStatus.failed;
4556            m_handler.m_editor.disableEditing(getData().getLockInfo());
4557        }
4558    }
4559
4560    /**
4561     * Selects the next container edit level.<p>
4562     */
4563    private void circleContainerEditLayers() {
4564
4565        if (m_isContentEditing || isGroupcontainerEditing() || (m_maxContainerLevel == 0)) {
4566            return;
4567        }
4568        boolean hasEditables = false;
4569        int previousLevel = m_currentEditLevel;
4570        String message = "";
4571        while (!hasEditables) {
4572            if (m_currentEditLevel == m_maxContainerLevel) {
4573                m_currentEditLevel = -1;
4574                message = Messages.get().key(Messages.GUI_SWITCH_EDIT_LEVEL_ALL_1, m_elementView.getTitle());
4575            } else {
4576                m_currentEditLevel++;
4577                message = Messages.get().key(Messages.GUI_SWITCH_EDIT_LEVEL_1, Integer.valueOf(m_currentEditLevel));
4578            }
4579            reinitializeButtons();
4580            hasEditables = !CmsDomUtil.getElementsByClass(
4581                I_CmsElementToolbarContext.ELEMENT_OPTION_BAR_CSS_CLASS).isEmpty();
4582        }
4583        if (previousLevel != m_currentEditLevel) {
4584            CmsNotification.get().send(Type.NORMAL, message);
4585        }
4586    }
4587
4588    /**
4589     * Copies an event details object.
4590     *
4591     * @param original the original event details
4592     * @return the copied object
4593     */
4594    private I_EventDetails copyEventDetails(I_EventDetails original) {
4595
4596        JsPropertyMap<Object> originalMap = Js.cast(original);
4597        JsPropertyMap<Object> result = Js.cast(new JsObject());
4598        originalMap.forEach(key -> {
4599            result.set(key, originalMap.get(key));
4600        });
4601        return Js.cast(result);
4602    }
4603
4604    /**
4605     * Returns all element id's related to the given one.<p>
4606     *
4607     * @param id the element id
4608     * @return the related id's
4609     */
4610    private Set<String> getRelatedElementIds(String id) {
4611
4612        Set<String> result = new HashSet<String>();
4613        if (id != null) {
4614            result.add(id);
4615            String serverId = getServerId(id);
4616
4617            Iterator<String> it = m_elements.keySet().iterator();
4618            while (it.hasNext()) {
4619                String elId = it.next();
4620                if (elId.startsWith(serverId)) {
4621                    result.add(elId);
4622                }
4623            }
4624
4625            Iterator<org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel> itEl = getAllDragElements().iterator();
4626            while (itEl.hasNext()) {
4627                org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel element = itEl.next();
4628                if (element.getId().startsWith(serverId)) {
4629                    result.add(element.getId());
4630                }
4631            }
4632        }
4633        return result;
4634    }
4635
4636    /**
4637     * Checks whether the given container matches the current edit level.<p>
4638     *
4639     * @param container the container to check
4640     *
4641     * @return <code>true</code> if the given container matches the current edit level
4642     */
4643    private boolean matchesCurrentEditLevel(I_CmsDropContainer container) {
4644
4645        boolean result = !(container instanceof CmsContainerPageContainer)
4646            || (m_currentEditLevel == -1)
4647            || (m_currentEditLevel == ((CmsContainerPageContainer)container).getContainerLevel());
4648        return result;
4649    }
4650
4651    /**
4652     * Opens the next available root element view.<p>
4653     */
4654    private void openNextElementView() {
4655
4656        List<CmsElementViewInfo> views = getData().getElementViews();
4657        if (views.size() > 1) {
4658            CmsUUID current = m_elementView.getRootViewId();
4659
4660            // look for the current view index
4661            int currentIndex = -1;
4662            for (int i = 0; i < views.size(); i++) {
4663                CmsElementViewInfo view = views.get(i);
4664                if (view.isRoot() && current.equals(view.getElementViewId())) {
4665                    currentIndex = i;
4666                    break;
4667                }
4668            }
4669            if (currentIndex != -1) {
4670                CmsElementViewInfo target = null;
4671                // look for the next root view
4672                for (int i = currentIndex + 1; i < views.size(); i++) {
4673                    CmsElementViewInfo view = views.get(i);
4674                    if (view.isRoot()) {
4675                        target = view;
4676                        break;
4677                    }
4678                }
4679                if (target == null) {
4680                    // start at the beginning
4681                    for (int i = 0; i < currentIndex; i++) {
4682                        CmsElementViewInfo view = views.get(i);
4683                        if (view.isRoot()) {
4684                            target = view;
4685                            break;
4686                        }
4687                    }
4688                }
4689                if (target != null) {
4690                    final String viewName = target.getTitle();
4691                    Runnable action = new Runnable() {
4692
4693                        public void run() {
4694
4695                            CmsNotification.get().send(
4696                                Type.NORMAL,
4697                                Messages.get().key(Messages.GUI_SWITCH_ELEMENT_VIEW_NOTIFICATION_1, viewName));
4698                        }
4699                    };
4700                    setElementView(target, action);
4701                }
4702            }
4703        }
4704    }
4705
4706    /**
4707     * Removes the edit buttons position timer.<p>
4708     */
4709    private void removeEditButtonsPositionTimer() {
4710
4711        if (m_editButtonsPositionTimer != null) {
4712            m_editButtonsPositionTimer.cancel();
4713            m_editButtonsPositionTimer = null;
4714        }
4715    }
4716
4717    /**
4718     * Calls the browser's sendBeacon function.
4719     *
4720     * @param url the URL to send the data to
4721     * @param data the data to send
4722     */
4723    private native void sendBeacon(String url, String data) /*-{
4724        $wnd.navigator.sendBeacon(url, data);
4725    }-*/;
4726
4727    /**
4728     * Creates a custom event and dispatches it to the document.
4729     *
4730     * @param type the event type
4731     * @param details the event details
4732     */
4733    private void sendEvent(String type, I_EventDetails details) {
4734
4735        CustomEventInit<I_EventDetails> init = Js.cast(CustomEventInit.create());
4736        init.setBubbles(false);
4737        init.setCancelable(false);
4738        init.setDetail(details);
4739        CustomEvent<I_EventDetails> event1 = new elemental2.dom.CustomEvent<I_EventDetails>(type, init);
4740        CustomEvent<I_EventDetails> event = event1;
4741        // fire event in a timer so the page editor is not blocked by the event handler reacting to it
4742        Timer timer = new Timer() {
4743
4744            @Override
4745            public void run() {
4746
4747                DomGlobal.document.documentElement.dispatchEvent(event);
4748            }
4749        };
4750        timer.schedule(0);
4751    }
4752
4753    /**
4754     * Checks whether given element is a model group and it's option bar edit points should be visible.<p>
4755     *
4756     * @param element the element to check
4757     *
4758     * @return <code>true</code> in case the current page is not a model group page,
4759     *  the given element is a model group and it is inside a view visible to the current user
4760     */
4761    private boolean shouldShowModelgroupOptionBar(CmsContainerPageElementPanel element) {
4762
4763        if (!getData().isModelGroup() && element.isModelGroup()) {
4764            for (CmsElementViewInfo info : getData().getElementViews()) {
4765                if (info.getElementViewId().equals(element.getElementView())) {
4766                    return true;
4767                }
4768            }
4769        }
4770
4771        return false;
4772    }
4773
4774    /**
4775     * Updates the container level info on the present containers.<p>
4776     */
4777    private void updateContainerLevelInfo() {
4778
4779        Map<String, CmsContainerPageContainer> containers = new HashMap<String, CmsContainerPageContainer>();
4780        List<CmsContainerPageContainer> temp = new ArrayList<CmsContainerPageContainer>(m_targetContainers.values());
4781        m_maxContainerLevel = 0;
4782        boolean progress = true;
4783        while (!temp.isEmpty() && progress) {
4784            int size = containers.size();
4785            Iterator<CmsContainerPageContainer> it = temp.iterator();
4786            while (it.hasNext()) {
4787                CmsContainerPageContainer container = it.next();
4788                int level = -1;
4789                if (CmsStringUtil.isEmptyOrWhitespaceOnly(container.getParentContainerId())) {
4790                    level = 0;
4791                } else if (containers.containsKey(container.getParentContainerId())) {
4792                    level = containers.get(container.getParentContainerId()).getContainerLevel() + 1;
4793                }
4794                if (level > -1) {
4795                    container.setContainerLevel(level);
4796                    containers.put(container.getContainerId(), container);
4797                    it.remove();
4798                    if (level > m_maxContainerLevel) {
4799                        m_maxContainerLevel = level;
4800                    }
4801                }
4802            }
4803            progress = containers.size() > size;
4804        }
4805    }
4806
4807    /**
4808     * Sets the oc-detail-preview class on first container elements of an appropriate type in detail containers,
4809     * if we are currently not showing a detail content.
4810     */
4811    private void updateDetailPreviewStyles() {
4812
4813        Set<String> detailTypes = getData().getDetailTypes();
4814        if ((getData().getDetailId() != null) || detailTypes.isEmpty()) {
4815            return;
4816        }
4817        for (Element elem : CmsDomUtil.getElementsByClass(CmsGwtConstants.CLASS_DETAIL_PREVIEW)) {
4818            elem.removeClassName(CmsGwtConstants.CLASS_DETAIL_PREVIEW);
4819        }
4820        boolean defaultDetailPage = detailTypes.contains(CmsGwtConstants.DEFAULT_DETAILPAGE_TYPE);
4821
4822        processPageContent(new I_PageContentVisitor() {
4823
4824            boolean m_isdetail = false;
4825
4826            public boolean beginContainer(String name, CmsContainer container) {
4827
4828                m_isdetail = container.isDetailViewContainer();
4829                return true;
4830            }
4831
4832            public void endContainer() {
4833
4834                // do nothing
4835            }
4836
4837            public void handleElement(CmsContainerPageElementPanel element) {
4838
4839                if (m_isdetail && (defaultDetailPage || detailTypes.contains(element.getResourceType()))) {
4840                    element.addStyleName(CmsGwtConstants.CLASS_DETAIL_PREVIEW);
4841                }
4842            }
4843        });
4844
4845    }
4846}