001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ade.containerpage.client.ui; 029 030import org.opencms.ade.containerpage.client.CmsContainerpageDNDController; 031import org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle; 032import org.opencms.ade.containerpage.shared.CmsContainer; 033import org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation; 034import org.opencms.gwt.client.dnd.I_CmsDraggable; 035import org.opencms.gwt.client.dnd.I_CmsDropTarget; 036import org.opencms.gwt.client.ui.CmsHighlightingBorder; 037import org.opencms.gwt.client.util.CmsDebugLog; 038import org.opencms.gwt.client.util.CmsDomUtil; 039import org.opencms.gwt.client.util.CmsDomUtil.Style; 040import org.opencms.gwt.client.util.CmsPositionBean; 041import org.opencms.gwt.shared.CmsGwtConstants; 042import org.opencms.gwt.shared.CmsTemplateContextInfo; 043import org.opencms.util.CmsStringUtil; 044 045import java.util.ArrayList; 046import java.util.HashMap; 047import java.util.Iterator; 048import java.util.List; 049import java.util.Map; 050 051import com.google.gwt.dom.client.Element; 052import com.google.gwt.dom.client.Node; 053import com.google.gwt.dom.client.NodeList; 054import com.google.gwt.dom.client.Style.Display; 055import com.google.gwt.dom.client.Style.Position; 056import com.google.gwt.user.client.ui.ComplexPanel; 057import com.google.gwt.user.client.ui.RootPanel; 058import com.google.gwt.user.client.ui.Widget; 059 060import elemental2.dom.DOMRect; 061import jsinterop.base.Js; 062 063/** 064 * Container page container.<p> 065 * 066 * 067 * 068 * @since 8.0.0 069 */ 070public class CmsContainerPageContainer extends ComplexPanel implements I_CmsDropContainer { 071 072 /** 073 * Helper class for resizing containers in the drag/drop process when an element is dropped into them that is of lower height than the empty container HTML. 074 */ 075 public static class ContainerResizeHelper { 076 077 /** The container. */ 078 private CmsContainerPageContainer m_container; 079 080 /** Minimum height. */ 081 private int m_origHeight; 082 083 /** 084 * Creates a new instance. 085 * 086 * @param container the container 087 */ 088 public ContainerResizeHelper(CmsContainerPageContainer container) { 089 090 m_container = container; 091 092 } 093 094 /** 095 * Initializes the minimum height. 096 */ 097 public void initHeight() { 098 099 int h = m_container.getElement().getOffsetHeight(); 100 m_origHeight = h; 101 } 102 103 /** 104 * Called when the placeholder is shown. 105 * 106 * @param placeholder the placeholder 107 */ 108 public void onShowPlaceholder(Element placeholder) { 109 110 int curHeight = m_container.getElement().getOffsetHeight(); 111 if (curHeight < m_origHeight) { 112 // We want an artificial element to 'blow up' the container to its original size, 113 // but adding a normal element would interfere with drag and drop, so we use the ::after pseudoelement 114 // of the container. We can't just set its style directly, so we modify a stylesheet with 115 // a fixed ID to set the height. 116 m_container.getElement().addClassName(CmsGwtConstants.CLASS_CONTAINER_INFLATED); 117 String styleText = "." + CmsGwtConstants.CLASS_CONTAINER_INFLATED + "::after { "; 118 styleText += "height: " + (m_origHeight - curHeight) + "px;"; 119 styleText += " } "; 120 CmsDomUtil.setStylesheetText("oc-dnd-inflated-container-style", styleText); 121 } 122 } 123 124 /** 125 * Resets the style changes. 126 */ 127 public void reset() { 128 129 m_container.getElement().removeClassName(CmsGwtConstants.CLASS_CONTAINER_INFLATED); 130 } 131 } 132 133 /** 134 * Element position info class.<p> 135 */ 136 protected class ElementPositionInfo { 137 138 /** The DOM element. */ 139 private Element m_element; 140 141 /** The element position bean. */ 142 private CmsPositionBean m_elementPosition; 143 144 /** The float CSS property. */ 145 private String m_float; 146 147 /** Flag indicating the element is positioned absolute. */ 148 private boolean m_isAbsolute; 149 150 /** Flag indicating if the given element is visible. */ 151 private boolean m_isVisible; 152 153 /** 154 * Constructor.<p> 155 * 156 * @param element the DOM element 157 */ 158 public ElementPositionInfo(Element element) { 159 160 m_element = element; 161 String positioning = CmsDomUtil.getCurrentStyle(m_element, Style.position); 162 m_isAbsolute = Position.ABSOLUTE.getCssName().equals(positioning) 163 || Position.FIXED.getCssName().equals(positioning); 164 165 if (!m_isAbsolute) { 166 m_isVisible = !Display.NONE.getCssName().equals(m_element.getStyle().getDisplay()); 167 if (m_isVisible) { 168 m_elementPosition = CmsPositionBean.getBoundingClientRect(element); 169 m_float = CmsDomUtil.getCurrentStyle(m_element, Style.floatCss); 170 } 171 } 172 } 173 174 /** 175 * Returns the DOM element.<p> 176 * 177 * @return the DOM element 178 */ 179 public Element getElement() { 180 181 return m_element; 182 } 183 184 /** 185 * Returns the element position bean.<p> 186 * 187 * @return the element position bean 188 */ 189 public CmsPositionBean getElementPosition() { 190 191 return m_elementPosition; 192 } 193 194 /** 195 * Returns the x distance of the cursor to the element left.<p> 196 * 197 * @param x the cursor x position 198 * @param documentScrollLeft the document scroll left position 199 * 200 * @return the y distance of the cursor to the element top 201 */ 202 public int getRelativeLeft(int x, int documentScrollLeft) { 203 204 return (x + documentScrollLeft) - m_elementPosition.getLeft(); 205 } 206 207 /** 208 * Returns the y distance of the cursor to the element top.<p> 209 * 210 * @param y the cursor y position 211 * @param documentScrollTop the document scroll top position 212 * 213 * @return the y distance of the cursor to the element top 214 */ 215 public int getRelativeTop(int y, int documentScrollTop) { 216 217 return (y + documentScrollTop) - m_elementPosition.getTop(); 218 } 219 220 /** 221 * Returns if the element is positioned absolute.<p> 222 * 223 * @return <code>true</code> if the element is positioned absolute 224 */ 225 public boolean isAbsolute() { 226 227 return m_isAbsolute; 228 } 229 230 /** 231 * Returns if the element is floated.<p> 232 * 233 * @return <code>true</code> if the element is floated 234 */ 235 public boolean isFloating() { 236 237 return isFloatLeft() || isFloatRight(); 238 } 239 240 /** 241 * Returns if the element is floated to the left.<p> 242 * 243 * @return <code>true</code> if the element is floated to the left 244 */ 245 public boolean isFloatLeft() { 246 247 return "left".equals(m_float); 248 } 249 250 /** 251 * Returns if the element is floated to the right.<p> 252 * 253 * @return <code>true</code> if the element is floated to the right 254 */ 255 public boolean isFloatRight() { 256 257 return "right".equals(m_float); 258 } 259 260 /** 261 * Returns if the given element is visible.<p> 262 * 263 * @return <code>true</code> if the given element is visible 264 */ 265 public boolean isVisible() { 266 267 return m_isVisible; 268 } 269 270 } 271 272 /** Name of a special property for the container id. */ 273 public static final String PROP_CONTAINER_MARKER = "opencmsContainerId"; 274 275 /** Static variable for storing the container layout change helper for the current drag/drop process. */ 276 private static ContainerResizeHelper RESIZE_HELPER; 277 278 /** The container data. */ 279 private CmsContainer m_containerData; 280 281 /** The container level. */ 282 private int m_containerLevel; 283 284 /** The list of nested sub containers that are also valid drop targets during the current drag and drop. */ 285 private List<I_CmsDropTarget> m_dnDChildren; 286 287 /** The element position info cache. */ 288 private List<ElementPositionInfo> m_elementPositions; 289 290 /** The element to display in case the container is empty. */ 291 private Element m_emptyContainerElement; 292 293 /** Highlighting border for this container. */ 294 private CmsHighlightingBorder m_highlighting; 295 296 /** The overflowing element. */ 297 private Widget m_overflowingElement; 298 299 /** The cached highlighting position. */ 300 private CmsPositionBean m_ownPosition; 301 302 /** The drag and drop placeholder. */ 303 private Element m_placeholder; 304 305 /** The drag and drop placeholder position index. */ 306 private int m_placeholderIndex = -1; 307 308 /** Flag indicating the current place holder visibility. */ 309 private boolean m_placeholderVisible; 310 311 /** Flag indicating the element positions need to be re-evaluated. */ 312 private boolean m_requiresPositionUpdate = true; 313 314 /** 315 * Constructor.<p> 316 * 317 * @param containerData the container data 318 * @param element the container element 319 */ 320 public CmsContainerPageContainer(CmsContainer containerData, Element element) { 321 322 setElement(element); 323 324 if (!containerData.isSubContainer()) { 325 RootPanel.detachOnWindowClose(this); 326 } 327 m_containerData = containerData; 328 element.setPropertyString(PROP_CONTAINER_MARKER, containerData.getName()); 329 if (m_containerData.isEditable()) { 330 addStyleName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragTarget()); 331 } 332 onAttach(); 333 } 334 335 /** 336 * Clears the static layout change object, resetting it if it's not null. 337 */ 338 public static void clearResizeHelper() { 339 340 if (RESIZE_HELPER != null) { 341 try { 342 RESIZE_HELPER.reset(); 343 } finally { 344 RESIZE_HELPER = null; 345 } 346 } 347 } 348 349 /** 350 * Measures the height of the container's element. 351 * 352 * This sets the overflow-y style property to auto to prevent margin collapsing. 353 * 354 * @param elem the element 355 * @return the height 356 */ 357 public static int measureHeight(Element elem) { 358 359 Map<String, String> props = new HashMap<>(); 360 props.put("overflowY", "auto"); 361 Map<String, String> old = CmsDomUtil.updateStyle(elem.getStyle(), props); 362 int result = elem.getOffsetHeight(); 363 CmsDomUtil.updateStyle(elem.getStyle(), old); 364 return result; 365 366 } 367 368 /** 369 * Creates a new layout helper for resizing containers.<p> 370 * 371 * The previously created layout changes object (if any) will be reset. 372 * 373 * @param container the container 374 * @return the new layout helper 375 */ 376 public static ContainerResizeHelper newResizeHelper(CmsContainerPageContainer container) { 377 378 clearResizeHelper(); 379 RESIZE_HELPER = new ContainerResizeHelper(container); 380 return RESIZE_HELPER; 381 } 382 383 /** 384 * @see com.google.gwt.user.client.ui.Panel#add(com.google.gwt.user.client.ui.Widget) 385 */ 386 @Override 387 public void add(Widget w) { 388 389 add(w, (Element)getElement()); 390 } 391 392 /** 393 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#addDndChild(org.opencms.gwt.client.dnd.I_CmsDropTarget) 394 */ 395 public void addDndChild(I_CmsDropTarget child) { 396 397 if (m_dnDChildren == null) { 398 m_dnDChildren = new ArrayList<I_CmsDropTarget>(); 399 } 400 m_dnDChildren.add(child); 401 } 402 403 /** 404 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#adoptElement(org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel) 405 */ 406 public void adoptElement(CmsContainerPageElementPanel containerElement) { 407 408 assert getElement().equals(containerElement.getElement().getParentElement()); 409 getChildren().add(containerElement); 410 adopt(containerElement); 411 } 412 413 /** 414 * Check if the empty container content should be displayed or removed.<p> 415 */ 416 public void checkEmptyContainers() { 417 418 if (getWidgetCount() == 0) { 419 if (m_emptyContainerElement != null) { 420 m_emptyContainerElement.getStyle().clearDisplay(); 421 } else if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_containerData.getEmptyContainerContent())) { 422 // add empty container element 423 try { 424 m_emptyContainerElement = CmsDomUtil.createElement(m_containerData.getEmptyContainerContent()); 425 getElement().appendChild(m_emptyContainerElement); 426 } catch (Exception e) { 427 CmsDebugLog.getInstance().printLine(e.getMessage()); 428 } 429 } 430 } else if (m_emptyContainerElement != null) { 431 m_emptyContainerElement.removeFromParent(); 432 m_emptyContainerElement = null; 433 } 434 } 435 436 /** 437 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#checkMaxElementsOnEnter() 438 */ 439 public void checkMaxElementsOnEnter() { 440 441 int count = getWidgetCount(); 442 if (count >= m_containerData.getMaxElements()) { 443 Widget overflowElement = null; 444 int index = 0; 445 for (Widget widget : this) { 446 boolean isDummy = widget.getStyleName().contains(CmsTemplateContextInfo.DUMMY_ELEMENT_MARKER); 447 if (!isDummy) { 448 index++; 449 if (index >= m_containerData.getMaxElements()) { 450 if (overflowElement == null) { 451 overflowElement = widget; 452 } 453 } 454 } 455 } 456 if (overflowElement != null) { 457 m_overflowingElement = overflowElement; 458 m_overflowingElement.removeFromParent(); 459 } 460 } 461 if (count == 0) { 462 if (m_emptyContainerElement != null) { 463 m_emptyContainerElement.getStyle().setDisplay(Display.NONE); 464 } 465 } 466 } 467 468 /** 469 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#checkMaxElementsOnLeave() 470 */ 471 public void checkMaxElementsOnLeave() { 472 473 if (m_overflowingElement != null) { 474 add(m_overflowingElement); 475 } 476 if (m_emptyContainerElement != null) { 477 m_emptyContainerElement.getStyle().clearDisplay(); 478 } 479 } 480 481 /** 482 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#checkPosition(int, int, Orientation) 483 */ 484 public boolean checkPosition(int x, int y, Orientation orientation) { 485 486 if (m_ownPosition != null) { 487 // ignore orientation 488 int scrollTop = getElement().getOwnerDocument().getScrollTop(); 489 // use cached position 490 int relativeTop = (y + scrollTop) - m_ownPosition.getTop(); 491 if ((relativeTop > 0) && (m_ownPosition.getHeight() > relativeTop)) { 492 // cursor is inside the height of the element, check horizontal position 493 int scrollLeft = getElement().getOwnerDocument().getScrollLeft(); 494 int relativeLeft = (x + scrollLeft) - m_ownPosition.getLeft(); 495 return (relativeLeft > 0) && (m_ownPosition.getWidth() > relativeLeft); 496 } 497 } 498 return false; 499 } 500 501 /** 502 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#clearDnDChildren() 503 */ 504 public void clearDnDChildren() { 505 506 if (m_dnDChildren != null) { 507 m_dnDChildren.clear(); 508 } 509 } 510 511 /** 512 * Returns all contained drag elements.<p> 513 * 514 * @return the drag elements 515 */ 516 public List<CmsContainerPageElementPanel> getAllDragElements() { 517 518 List<CmsContainerPageElementPanel> elements = new ArrayList<CmsContainerPageElementPanel>(); 519 Iterator<Widget> it = iterator(); 520 while (it.hasNext()) { 521 Widget w = it.next(); 522 if (w instanceof CmsContainerPageElementPanel) { 523 elements.add((CmsContainerPageElementPanel)w); 524 } else { 525 if (CmsDomUtil.hasClass( 526 org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle.INSTANCE.containerpageCss().groupcontainerPlaceholder(), 527 w.getElement())) { 528 CmsDebugLog.getInstance().printLine("Ignoring group container placeholder."); 529 } else { 530 CmsDebugLog.getInstance().printLine( 531 "WARNING: " + w.toString() + " is no instance of CmsDragContainerElement"); 532 } 533 } 534 } 535 return elements; 536 } 537 538 /** 539 * Returns the configured width for this container.<p> 540 * 541 * @return the configured width 542 */ 543 public int getConfiguredWidth() { 544 545 return m_containerData.getWidth(); 546 } 547 548 /** 549 * Returns the container id.<p> 550 * 551 * @return the container id 552 */ 553 public String getContainerId() { 554 555 return m_containerData.getName(); 556 } 557 558 /** 559 * Returns the container level.<p> 560 * 561 * @return the container level 562 */ 563 public int getContainerLevel() { 564 565 return m_containerLevel; 566 } 567 568 /** 569 * Returns the container type.<p> 570 * 571 * @return the container type 572 */ 573 public String getContainerType() { 574 575 return m_containerData.getType(); 576 } 577 578 /** 579 * In case of a former copy model, and a max elements setting of one, the id of the overflowing element is returned.<p> 580 * 581 * @return the overflowing element id or <code>null</code> 582 */ 583 public String getCopyModelReplaceId() { 584 585 String result = null; 586 if ((m_containerData.getMaxElements() == 1) 587 && (m_overflowingElement != null) 588 && (m_overflowingElement instanceof CmsContainerPageElementPanel) 589 && (getFormerModelGroupParent() != null)) { 590 result = ((CmsContainerPageElementPanel)m_overflowingElement).getId(); 591 } 592 return result; 593 } 594 595 /** 596 * @see org.opencms.gwt.client.dnd.I_CmsNestedDropTarget#getDnDChildren() 597 */ 598 public List<I_CmsDropTarget> getDnDChildren() { 599 600 return m_dnDChildren; 601 } 602 603 /** 604 * Returns whether this container has a model group parent.<p> 605 * 606 * @return <code>true</code> if this container has a model group parent 607 */ 608 public Element getFormerModelGroupParent() { 609 610 Element result = null; 611 Element parent = getElement().getParentElement(); 612 while (parent != null) { 613 if (parent.getPropertyBoolean(CmsContainerPageElementPanel.PROP_WAS_MODEL_GROUP)) { 614 result = parent; 615 break; 616 } 617 parent = parent.getParentElement(); 618 } 619 return result; 620 } 621 622 /** 623 * Gets the highlighting widget for the container. 624 * 625 * @return the highlighting widget 626 */ 627 public CmsHighlightingBorder getHighlighting() { 628 629 return m_highlighting; 630 } 631 632 /** 633 * Returns the parent container id.<p> 634 * 635 * @return the container parent id 636 */ 637 public String getParentContainerId() { 638 639 return m_containerData.getParentContainerName(); 640 } 641 642 /** 643 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#getPlaceholderIndex() 644 */ 645 public int getPlaceholderIndex() { 646 647 return m_placeholderIndex; 648 } 649 650 /** 651 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#getPositionInfo() 652 */ 653 public CmsPositionBean getPositionInfo() { 654 655 return m_ownPosition; 656 } 657 658 /** 659 * Returns the settings presets.<p> 660 * 661 * @return the presets 662 */ 663 public Map<String, String> getSettingPresets() { 664 665 return m_containerData.getSettingPresets(); 666 } 667 668 /** 669 * @see org.opencms.gwt.client.dnd.I_CmsNestedDropTarget#hasDnDChildren() 670 */ 671 public boolean hasDnDChildren() { 672 673 return (m_dnDChildren != null) && !m_dnDChildren.isEmpty(); 674 } 675 676 /** 677 * Returns whether this container has a model group parent.<p> 678 * 679 * @return <code>true</code> if this container has a model group parent 680 */ 681 public boolean hasModelGroupParent() { 682 683 boolean result = false; 684 Element parent = getElement(); 685 while (parent != null) { 686 if (parent.getPropertyBoolean(CmsContainerPageElementPanel.PROP_IS_MODEL_GROUP)) { 687 result = true; 688 break; 689 } 690 parent = parent.getParentElement(); 691 } 692 return result; 693 } 694 695 /** 696 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#hideEditableListButtons() 697 */ 698 public void hideEditableListButtons() { 699 700 Iterator<Widget> it = iterator(); 701 while (it.hasNext()) { 702 Widget child = it.next(); 703 if (child instanceof CmsContainerPageElementPanel) { 704 ((CmsContainerPageElementPanel)child).hideEditableListButtons(); 705 } 706 } 707 } 708 709 /** 710 * Puts a highlighting border around the container content.<p> 711 */ 712 public void highlightContainer(boolean addSeparators) { 713 714 highlightContainer(CmsPositionBean.getBoundingClientRect(getElement()), addSeparators); 715 } 716 717 /** 718 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#highlightContainer(org.opencms.gwt.client.util.CmsPositionBean) 719 */ 720 public void highlightContainer(CmsPositionBean positionInfo, boolean addSeparators) { 721 722 // remove any remaining highlighting 723 if (m_highlighting != null) { 724 m_highlighting.removeFromParent(); 725 } 726 // cache the position info, to be used during drag and drop 727 m_ownPosition = positionInfo; 728 m_highlighting = new CmsHighlightingBorder( 729 m_ownPosition.getHeight(), 730 m_ownPosition.getWidth(), 731 m_ownPosition.getLeft(), 732 m_ownPosition.getTop(), 733 CmsHighlightingBorder.BorderColor.red, 734 CmsContainerpageDNDController.HIGHLIGHTING_OFFSET, 735 addSeparators); 736 if (addSeparators) { 737 m_highlighting.setMidpoints(getMidpoints()); 738 } else { 739 // CmsGwtLog.trace("addSeparators = false"); 740 } 741 if (getElement().getOffsetParent() != null) { 742 RootPanel.get().add(m_highlighting); 743 } 744 } 745 746 /** 747 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#insert(com.google.gwt.user.client.ui.Widget, int) 748 */ 749 public void insert(Widget w, int beforeIndex) { 750 751 // in case an option bar as a direct child is present it may disturb the insert order 752 // it needs to be detached first 753 Element optionBar = null; 754 NodeList<Node> children = getElement().getChildNodes(); 755 for (int i = 0; i < children.getLength(); i++) { 756 Node child = children.getItem(i); 757 if ((child.getNodeType() == Node.ELEMENT_NODE) 758 && ((Element)child).hasClassName(I_CmsLayoutBundle.INSTANCE.containerpageCss().optionBar())) { 759 optionBar = (Element)child; 760 optionBar.removeFromParent(); 761 break; 762 } 763 764 } 765 766 insert(w, (Element)getElement(), beforeIndex, true); 767 if (optionBar != null) { 768 getElement().insertFirst(optionBar); 769 } 770 } 771 772 /** 773 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#insertPlaceholder(com.google.gwt.dom.client.Element, int, int, Orientation) 774 */ 775 public void insertPlaceholder(Element placeholder, int x, int y, Orientation orientation) { 776 777 m_placeholder = placeholder; 778 m_placeholderVisible = false; 779 m_placeholder.getStyle().setDisplay(Display.NONE); 780 m_requiresPositionUpdate = true; 781 repositionPlaceholder(x, y, orientation); 782 } 783 784 /** 785 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#isDetailOnly() 786 */ 787 public boolean isDetailOnly() { 788 789 return m_containerData.isDetailOnly(); 790 } 791 792 /** 793 * Returns true if this is a detail view container, being actually used for detail content.<p> 794 * 795 * @return true if this is a detail view container 796 */ 797 public boolean isDetailView() { 798 799 return m_containerData.isDetailView(); 800 } 801 802 /** 803 * Checks if this is a detail view container. 804 * 805 * @return true if this is a detail view container 806 */ 807 public boolean isDetailViewContainer() { 808 809 return m_containerData.isDetailViewContainer(); 810 } 811 812 /** 813 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#isEditable() 814 */ 815 public boolean isEditable() { 816 817 return m_containerData.isEditable(); 818 } 819 820 /** 821 * Checks if the container is showing the empty container element. 822 * 823 * @return true if the empty container element is shown in the container 824 */ 825 public boolean isShowingEmptyContainerElement() { 826 827 return (m_emptyContainerElement != null) && (m_emptyContainerElement.getParentElement() == getElement()); 828 } 829 830 /** 831 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#onConsumeChildren(java.util.List) 832 */ 833 public void onConsumeChildren(List<CmsContainerPageElementPanel> children) { 834 835 // nothing to do 836 } 837 838 /** 839 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#onDrop(org.opencms.gwt.client.dnd.I_CmsDraggable) 840 */ 841 public void onDrop(I_CmsDraggable draggable) { 842 843 m_overflowingElement = null; 844 } 845 846 /** 847 * Refreshes position and dimension of the highlighting border. Call when anything changed during the drag process.<p> 848 */ 849 public void refreshHighlighting() { 850 851 if (m_highlighting != null) { 852 refreshHighlighting(CmsPositionBean.getBoundingClientRect(getElement())); 853 } 854 } 855 856 /** 857 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#refreshHighlighting(org.opencms.gwt.client.util.CmsPositionBean) 858 */ 859 public void refreshHighlighting(CmsPositionBean positionInfo) { 860 861 // cache the position info, to be used during drag and drop 862 m_ownPosition = positionInfo; 863 if (m_highlighting != null) { 864 m_highlighting.setPosition(m_ownPosition); 865 } 866 } 867 868 /** 869 * Removes the highlighting border.<p> 870 */ 871 public void removeHighlighting() { 872 873 if (m_highlighting != null) { 874 m_highlighting.removeFromParent(); 875 m_highlighting = null; 876 } 877 } 878 879 /** 880 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#removePlaceholder() 881 */ 882 public void removePlaceholder() { 883 884 if (m_placeholder != null) { 885 m_placeholder.removeFromParent(); 886 m_placeholder = null; 887 } 888 m_placeholderIndex = -1; 889 m_requiresPositionUpdate = true; 890 891 // check if the empty container content should be displayed or removed 892 checkEmptyContainers(); 893 } 894 895 /** 896 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#repositionPlaceholder(int, int, Orientation) 897 */ 898 public void repositionPlaceholder(int x, int y, Orientation orientation) { 899 900 if (m_requiresPositionUpdate) { 901 updatePositionsList(); 902 } 903 int newPlaceholderIndex = internalRepositionPlaceholder(x, y); 904 m_requiresPositionUpdate = newPlaceholderIndex != m_placeholderIndex; 905 m_placeholderIndex = newPlaceholderIndex; 906 } 907 908 /** 909 * Sets the container level.<p> 910 * 911 * @param level the container level 912 */ 913 public void setContainerLevel(int level) { 914 915 m_containerLevel = level; 916 } 917 918 /** 919 * Sets the empty container element.<p> 920 * 921 * @param emptyContainerElement the empty container element 922 */ 923 public void setEmptyContainerElement(Element emptyContainerElement) { 924 925 m_emptyContainerElement = emptyContainerElement; 926 } 927 928 /** 929 * Measures the height of the container and sets its min-height to that value. 930 * 931 * @return a runnable used to undo the style changes 932 */ 933 public Runnable setMinHeightToCurrentHeight() { 934 935 int h1 = measureHeight(getElement()); 936 Map<String, String> props = new HashMap<>(); 937 props.put("minHeight", h1 + "px"); 938 props.put( 939 "background", 940 "repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(240, 0, 242, 1) 10px, rgba(240, 0, 242, 1) 20px)"); 941 com.google.gwt.dom.client.Style style = getElement().getStyle(); 942 final Map<String, String> oldVals = CmsDomUtil.updateStyle(style, props); 943 return () -> { 944 CmsDomUtil.updateStyle(style, oldVals); 945 }; 946 } 947 948 public void setPlaceholderIndex(int index) { 949 950 m_placeholderIndex = index; 951 } 952 953 /** 954 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#setPlaceholderVisibility(boolean) 955 */ 956 public void setPlaceholderVisibility(boolean visible) { 957 958 if (m_placeholderVisible != visible) { 959 m_placeholderVisible = visible; 960 m_requiresPositionUpdate = true; 961 if (m_placeholderVisible) { 962 m_placeholder.getStyle().clearDisplay(); 963 if (!m_placeholder.hasClassName(CmsGwtConstants.CLASS_PLACEHOLDER_TOO_BIG)) { 964 int height = m_placeholder.getOffsetHeight(); 965 if (height > CmsGwtConstants.MAX_PLACEHOLDER_HEIGHT) { 966 m_placeholder.addClassName(CmsGwtConstants.CLASS_PLACEHOLDER_TOO_BIG); 967 } 968 } 969 if (RESIZE_HELPER != null) { 970 RESIZE_HELPER.onShowPlaceholder(m_placeholder); 971 } 972 } else { 973 m_placeholder.getStyle().setDisplay(Display.NONE); 974 } 975 } 976 } 977 978 /** 979 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#showEditableListButtons() 980 */ 981 public void showEditableListButtons() { 982 983 for (Widget child : this) { 984 if (child instanceof CmsContainerPageElementPanel) { 985 ((CmsContainerPageElementPanel)child).showEditableListButtons(); 986 } 987 } 988 } 989 990 /** 991 * Updates the option bar positions of the child elements.<p> 992 */ 993 public void updateOptionBars() { 994 995 for (Widget widget : this) { 996 if (widget instanceof CmsContainerPageElementPanel) { 997 ((CmsContainerPageElementPanel)widget).updateOptionBarPosition(); 998 } 999 } 1000 } 1001 1002 /** 1003 * @see org.opencms.ade.containerpage.client.ui.I_CmsDropContainer#updatePositionInfo() 1004 */ 1005 public void updatePositionInfo() { 1006 1007 m_ownPosition = CmsPositionBean.getBoundingClientRect(getElement()); 1008 } 1009 1010 /** 1011 * Returns a list of midpoints between container elements as vertical offsets relative to the container top, but only if the container elements are positioned vertically below each other (otherwise the empty list is returned). 1012 * 1013 * @return the list of midpoints between container elements 1014 */ 1015 List<Integer> getMidpoints() { 1016 1017 List<CmsContainerPageElementPanel> elems = getAllDragElements(); 1018 List<Integer> result = new ArrayList<>(); 1019 DOMRect[] rects = new DOMRect[elems.size()]; 1020 elemental2.dom.Element containerElem = Js.cast(getElement()); 1021 double myTop = containerElem.getBoundingClientRect().top; 1022 for (int i = 0; i < getAllDragElements().size(); i++) { 1023 elemental2.dom.Element nativeElem = Js.cast(elems.get(i).getElement()); 1024 rects[i] = nativeElem.getBoundingClientRect(); 1025 } 1026 for (int i = 1; i < rects.length; i++) { 1027 if (rects[i].top <= rects[i - 1].top) { 1028 // return empty list - don't show midpoints if top coordinates not ascending 1029 return result; 1030 } 1031 } 1032 for (int i = 0; i < (rects.length - 1); i++) { 1033 double currentBottom = rects[i].top + rects[i].height; 1034 double nextTop = rects[i + 1].top; 1035 Double middle = Double.valueOf(Math.round((nextTop + currentBottom) / 2)); 1036 result.add(Integer.valueOf((int)(middle.doubleValue() - myTop))); 1037 } 1038 return result; 1039 } 1040 1041 /** 1042 * Repositions the drag and drop placeholder.<p> 1043 * 1044 * @param x the x cursor position 1045 * @param y the y cursor position 1046 * 1047 * @return the placeholder position index 1048 */ 1049 private int internalRepositionPlaceholder(int x, int y) { 1050 1051 int indexCorrection = 0; 1052 int previousTop = 0; 1053 int documentScrollTop = getElement().getOwnerDocument().getScrollTop(); 1054 int documentScrollLeft = getElement().getOwnerDocument().getScrollLeft(); 1055 for (int index = 0; index < m_elementPositions.size(); index++) { 1056 ElementPositionInfo info = m_elementPositions.get(index); 1057 if (info.getElement() == m_placeholder) { 1058 indexCorrection = 1; 1059 } 1060 if (info.isAbsolute() || !info.isVisible()) { 1061 continue; 1062 } 1063 1064 int top = info.getRelativeTop(y, documentScrollTop); 1065 1066 if ((top <= 0) || (top >= info.getElementPosition().getHeight())) { 1067 previousTop = top; 1068 continue; 1069 } 1070 int left = info.getRelativeLeft(x, documentScrollLeft); 1071 if ((left <= 0) || (left >= info.getElementPosition().getWidth())) { 1072 previousTop = top; 1073 continue; 1074 } 1075 boolean floatSort = info.isFloating() && (top != 0) && (top == previousTop); 1076 previousTop = top; 1077 if (info.getElement() != m_placeholder) { 1078 if (floatSort) { 1079 boolean insertBefore = false; 1080 if (left < (info.getElementPosition().getWidth() / 2)) { 1081 if (info.isFloatLeft()) { 1082 insertBefore = true; 1083 } 1084 } else if (info.isFloatRight()) { 1085 insertBefore = true; 1086 } 1087 if (insertBefore) { 1088 getElement().insertBefore(m_placeholder, info.getElement()); 1089 return index - indexCorrection; 1090 } else { 1091 getElement().insertAfter(m_placeholder, info.getElement()); 1092 return (index + 1) - indexCorrection; 1093 } 1094 } else { 1095 if (top < (info.getElementPosition().getHeight() / 2)) { 1096 getElement().insertBefore(m_placeholder, info.getElement()); 1097 return index - indexCorrection; 1098 } else { 1099 getElement().insertAfter(m_placeholder, info.getElement()); 1100 return (index + 1) - indexCorrection; 1101 } 1102 } 1103 } else { 1104 return index; 1105 } 1106 } 1107 1108 // not over any child position 1109 if ((m_placeholderIndex >= 0) && (m_placeholder.getParentElement() == getElement())) { 1110 // element is already attached to this parent and no new position available 1111 // don't do anything 1112 return m_placeholderIndex; 1113 } 1114 int top = CmsDomUtil.getRelativeY(y, getElement()); 1115 int offsetHeight = getElement().getOffsetHeight(); 1116 if ((top >= (offsetHeight / 2))) { 1117 // over top half, insert as first child 1118 getElement().insertFirst(m_placeholder); 1119 return 0; 1120 } 1121 // over bottom half, insert as last child 1122 getElement().appendChild(m_placeholder); 1123 return getElement().getChildCount() - 1; 1124 } 1125 1126 /** 1127 * Updates the element position cache during drag and drop.<p> 1128 */ 1129 private void updatePositionsList() { 1130 1131 CmsDebugLog.getInstance().printLine("Updating positions"); 1132 if (m_elementPositions != null) { 1133 m_elementPositions.clear(); 1134 } else { 1135 m_elementPositions = new ArrayList<ElementPositionInfo>(); 1136 } 1137 for (int index = 0; index < getElement().getChildCount(); index++) { 1138 Node node = getElement().getChild(index); 1139 // in some cases the container element may have an option bar as a direct child, ignore it 1140 if ((node.getNodeType() != Node.ELEMENT_NODE) 1141 || ((Element)node).hasClassName(I_CmsLayoutBundle.INSTANCE.containerpageCss().optionBar())) { 1142 continue; 1143 } 1144 m_elementPositions.add(new ElementPositionInfo((Element)node)); 1145 } 1146 m_requiresPositionUpdate = false; 1147 m_ownPosition = CmsPositionBean.getBoundingClientRect(getElement()); 1148 } 1149}