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