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