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.acacia.client.CmsEditorBase; 031import org.opencms.acacia.client.I_CmsInlineFormParent; 032import org.opencms.ade.containerpage.client.CmsContainerpageController; 033import org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle; 034import org.opencms.ade.containerpage.shared.CmsElementLockInfo; 035import org.opencms.ade.containerpage.shared.CmsInheritanceInfo; 036import org.opencms.ade.contenteditor.client.CmsContentEditor; 037import org.opencms.gwt.client.I_CmsElementToolbarContext; 038import org.opencms.gwt.client.dnd.I_CmsDraggable; 039import org.opencms.gwt.client.dnd.I_CmsDropTarget; 040import org.opencms.gwt.client.ui.CmsHighlightingBorder; 041import org.opencms.gwt.client.ui.CmsListItemWidget; 042import org.opencms.gwt.client.ui.I_CmsButton; 043import org.opencms.gwt.client.util.CmsDebugLog; 044import org.opencms.gwt.client.util.CmsDomUtil; 045import org.opencms.gwt.client.util.CmsDomUtil.Tag; 046import org.opencms.gwt.client.util.CmsPositionBean; 047import org.opencms.gwt.shared.CmsGwtConstants; 048import org.opencms.gwt.shared.CmsListInfoBean; 049import org.opencms.util.CmsStringUtil; 050import org.opencms.util.CmsUUID; 051 052import java.util.Arrays; 053import java.util.HashMap; 054import java.util.Iterator; 055import java.util.List; 056import java.util.Map; 057import java.util.Map.Entry; 058 059import com.google.common.base.Optional; 060import com.google.common.collect.Lists; 061import com.google.gwt.core.client.JavaScriptObject; 062import com.google.gwt.core.client.Scheduler; 063import com.google.gwt.core.client.Scheduler.ScheduledCommand; 064import com.google.gwt.dom.client.Element; 065import com.google.gwt.dom.client.NodeList; 066import com.google.gwt.dom.client.Style; 067import com.google.gwt.dom.client.Style.Display; 068import com.google.gwt.dom.client.Style.Position; 069import com.google.gwt.dom.client.Style.Unit; 070import com.google.gwt.event.dom.client.ClickEvent; 071import com.google.gwt.event.dom.client.ClickHandler; 072import com.google.gwt.event.dom.client.HasClickHandlers; 073import com.google.gwt.event.shared.HandlerRegistration; 074import com.google.gwt.regexp.shared.MatchResult; 075import com.google.gwt.regexp.shared.RegExp; 076import com.google.gwt.user.client.DOM; 077import com.google.gwt.user.client.Event; 078import com.google.gwt.user.client.Event.NativePreviewEvent; 079import com.google.gwt.user.client.Event.NativePreviewHandler; 080import com.google.gwt.user.client.Timer; 081import com.google.gwt.user.client.ui.AbsolutePanel; 082import com.google.gwt.user.client.ui.IsWidget; 083import com.google.gwt.user.client.ui.RootPanel; 084 085/** 086 * Content element within a container-page.<p> 087 * 088 * @since 8.0.0 089 */ 090public class CmsContainerPageElementPanel extends AbsolutePanel 091implements I_CmsDraggable, HasClickHandlers, I_CmsInlineFormParent { 092 093 /** 094 * Parses CSS classess of the form 'oc-point-TY_LX', where X and Y are strings 095 * of decimal digits possibly preceded by a minus sign.<p> 096 * 097 * The numeric values of Y and X will be available after a successful parse using the 098 * methods getOffsetTop() and getOffsetLeft(). 099 * 100 * This is used to offer the formatter developer some control over the edit point 101 * positioning. 102 */ 103 public static class PointPositioningParser { 104 105 /** Regular expression used to match the special CSS classes. */ 106 private static final RegExp REGEX = RegExp.compile("^oc-point-T(-?[0-9]+)_L(-?[0-9]+)$"); 107 108 /** The left offset. */ 109 private int m_left; 110 111 /** The top offset. */ 112 private int m_top; 113 114 /** 115 * Gets the left offset after a CSS class has successfully been parsed.<p> 116 * 117 * @return the left offset 118 */ 119 int getOffsetLeft() { 120 121 return m_left; 122 } 123 124 /** 125 * Gets the top offset after a CSS class has successfully been parsed.<p> 126 * 127 * @return the top offset 128 */ 129 int getOffsetTop() { 130 131 return m_top; 132 } 133 134 /** 135 * Tries to parse a point positioning instruction from an element's class attribute 136 * and returns true when successful.<p> 137 * 138 * @param cssClass the value of a class attribute 139 * 140 * @return true if a positioning instruction was found 141 */ 142 boolean tryParse(String cssClass) { 143 144 m_left = 0; 145 m_top = 0; 146 if (cssClass == null) { 147 cssClass = ""; 148 } 149 for (String token : cssClass.trim().split(" +")) { 150 if (tryParseSingleClass(token)) { 151 return true; 152 } 153 } 154 return false; 155 } 156 157 /** 158 * Parses a single token from a class attribute.<p> 159 * 160 * @param token the token 161 * @return true if the token was a point positioning instruction 162 */ 163 private boolean tryParseSingleClass(String token) { 164 165 MatchResult result = REGEX.exec(token); 166 if (result != null) { 167 m_top = Integer.parseInt(result.getGroup(1)); 168 m_left = Integer.parseInt(result.getGroup(2)); 169 return true; 170 } else { 171 if (token.startsWith("oc-point")) { 172 CmsDebugLog.consoleLog("Malformed oc-point class: " + token); 173 } 174 return false; 175 } 176 } 177 178 } 179 180 /** The is model group property key. */ 181 public static final String PROP_IS_MODEL_GROUP = "is_model_group"; 182 183 /** The former copy model property. */ 184 public static final String PROP_WAS_MODEL_GROUP = "was_model_group"; 185 186 /** Property used to mark an element as belonging to this widget. */ 187 public static final String PROP_ELEMENT_OBJECT_ID = "element_object_id"; 188 189 /** Highlighting border for this element. */ 190 protected CmsHighlightingBorder m_highlighting; 191 192 /** A flag which indicates whether the height has already been checked. */ 193 private boolean m_checkedHeight; 194 195 /** Flag indicating the the editables are currently being checked. */ 196 private boolean m_checkingEditables; 197 198 /** The elements client id. */ 199 private String m_clientId; 200 201 /** The 'create new' flag. */ 202 private boolean m_createNew; 203 204 /** 205 * Flag which indicates whether the new editor is disabled for this element.<p> 206 */ 207 private boolean m_disableNewEditor; 208 209 /** The direct edit bar instances. */ 210 private Map<Element, CmsListCollectorEditor> m_editables; 211 212 /** The editor click handler registration. */ 213 private HandlerRegistration m_editorClickHandlerRegistration; 214 215 /** The option bar, holding optional function buttons. */ 216 private CmsElementOptionBar m_elementOptionBar; 217 218 /** The element element view. */ 219 private CmsUUID m_elementView; 220 221 /** The overlay for expired elements. */ 222 private Element m_expiredOverlay; 223 224 /** Indicates an edit handler is configured for the element resource type. */ 225 private boolean m_hasEditHandler; 226 227 /** Indicates whether this element has settings to edit. */ 228 private boolean m_hasSettings; 229 230 /** The inheritance info for this element. */ 231 private CmsInheritanceInfo m_inheritanceInfo; 232 233 /** The model group id. */ 234 private CmsUUID m_modelGroupId; 235 236 /** The is new element type. */ 237 private String m_newType; 238 239 /** The registered node insert event handler. */ 240 private JavaScriptObject m_nodeInsertHandler; 241 242 /** The no edit reason, if empty editing is allowed. */ 243 private String m_noEditReason; 244 245 /** The parent drop target. */ 246 private I_CmsDropContainer m_parent; 247 248 /** Parser for point positioning isntructions. */ 249 private PointPositioningParser m_positioningInstructionParser = new PointPositioningParser(); 250 251 /** The drag and drop parent div. */ 252 private Element m_provisionalParent; 253 254 /** Flag indicating if the element resource is currently released and not expired. */ 255 private boolean m_releasedAndNotExpired; 256 257 /** The resource type. */ 258 private String m_resourceType; 259 260 /** The element resource site-path. */ 261 private String m_sitePath; 262 263 /** The sub title. */ 264 private String m_subTitle; 265 266 /** The resource title. */ 267 private String m_title; 268 269 /** 270 * Indicates if the current user has view permissions on the element resource. 271 * Without view permissions, the element can neither be edited, nor moved. 272 **/ 273 private boolean m_viewPermission; 274 275 /** The former copy model status. */ 276 private boolean m_wasModelGroup; 277 /** 278 * Indicates if the current user has write permissions on the element resource. 279 * Without write permissions, the element can not be edited. 280 **/ 281 private boolean m_writePermission; 282 283 /** A random id, which is also stored as a property on the HTML element for this widget. */ 284 private String m_objectId; 285 286 /** The resource type icon CSS classes. */ 287 private String m_iconClasses; 288 289 /** The lock information. */ 290 private CmsElementLockInfo m_lockInfo; 291 292 /** 293 * Constructor.<p> 294 * 295 * @param element the DOM element 296 * @param parent the drag parent 297 * @param clientId the client id 298 * @param sitePath the element site-path 299 * @param noEditReason the no edit reason, if empty, editing is allowed 300 * @param lockInfo the lock information 301 * @param title the resource title 302 * @param subTitle the sub title 303 * @param resourceType the resource type 304 * @param hasSettings should be true if the element has settings which can be edited 305 * @param hasViewPermission indicates if the current user has view permissions on the element resource 306 * @param hasWritePermission indicates if the current user has write permissions on the element resource 307 * @param releasedAndNotExpired <code>true</code> if the element resource is currently released and not expired 308 * @param disableNewEditor flag to disable the new editor for this element 309 * @param hasEditHandler indicates an edit handler is configured for the element resource type 310 * @param modelGroupId the model group id 311 * @param wasModelGroup in case of a former copy model group 312 * @param elementView the element view of the element 313 * @param iconClasses the resource type icon CSS classes 314 */ 315 public CmsContainerPageElementPanel( 316 Element element, 317 I_CmsDropContainer parent, 318 String clientId, 319 String sitePath, 320 String noEditReason, 321 CmsElementLockInfo lockInfo, 322 String title, 323 String subTitle, 324 String resourceType, 325 boolean hasSettings, 326 boolean hasViewPermission, 327 boolean hasWritePermission, 328 boolean releasedAndNotExpired, 329 boolean disableNewEditor, 330 boolean hasEditHandler, 331 CmsUUID modelGroupId, 332 boolean wasModelGroup, 333 CmsUUID elementView, 334 String iconClasses) { 335 336 super(element); 337 m_clientId = clientId; 338 m_objectId = "" + Math.random(); 339 m_sitePath = sitePath; 340 m_title = title; 341 m_subTitle = subTitle; 342 m_resourceType = resourceType; 343 m_noEditReason = noEditReason; 344 m_lockInfo = lockInfo; 345 m_hasSettings = hasSettings; 346 m_parent = parent; 347 m_disableNewEditor = disableNewEditor; 348 m_modelGroupId = modelGroupId; 349 m_wasModelGroup = wasModelGroup; 350 m_hasEditHandler = hasEditHandler; 351 setViewPermission(hasViewPermission); 352 setWritePermission(hasWritePermission); 353 setReleasedAndNotExpired(releasedAndNotExpired); 354 m_elementView = elementView; 355 getElement().setPropertyBoolean(PROP_IS_MODEL_GROUP, modelGroupId != null); 356 getElement().setPropertyBoolean(PROP_WAS_MODEL_GROUP, wasModelGroup); 357 getElement().setPropertyString(PROP_ELEMENT_OBJECT_ID, m_objectId); 358 m_iconClasses = iconClasses; 359 } 360 361 /** 362 * Checks if the element is an overlay for a container page element.<p> 363 * 364 * @param element the element to check 365 * @return true if the element is an overlay 366 */ 367 public static boolean isOverlay(Element element) { 368 369 for (String overlayClass : Arrays.asList( 370 I_CmsLayoutBundle.INSTANCE.containerpageCss().expiredOverlay(), 371 I_CmsElementToolbarContext.ELEMENT_OPTION_BAR_CSS_CLASS)) { 372 if (element.hasClassName(overlayClass)) { 373 return true; 374 } 375 } 376 return false; 377 378 } 379 380 /** 381 * @see com.google.gwt.event.dom.client.HasClickHandlers#addClickHandler(com.google.gwt.event.dom.client.ClickHandler) 382 */ 383 public HandlerRegistration addClickHandler(ClickHandler handler) { 384 385 return addDomHandler(handler, ClickEvent.getType()); 386 } 387 388 /** 389 * @see org.opencms.acacia.client.I_CmsInlineFormParent#adoptWidget(com.google.gwt.user.client.ui.IsWidget) 390 */ 391 public void adoptWidget(IsWidget widget) { 392 393 getChildren().add(widget.asWidget()); 394 adopt(widget.asWidget()); 395 } 396 397 /** 398 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getCursorOffsetDelta() 399 */ 400 public Optional<int[]> getCursorOffsetDelta() { 401 402 return Optional.absent(); 403 } 404 405 /** 406 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getDragHelper(org.opencms.gwt.client.dnd.I_CmsDropTarget) 407 */ 408 public Element getDragHelper(I_CmsDropTarget target) { 409 410 CmsListInfoBean info = new CmsListInfoBean(m_title, m_subTitle, null); 411 info.setResourceType(m_resourceType); 412 info.setBigIconClasses(m_iconClasses); 413 CmsListItemWidget helperWidget = new CmsListItemWidget(info); 414 helperWidget.setWidth("600px"); 415 helperWidget.truncate("ggg", 550); 416 Element helper = helperWidget.getElement(); 417 Element button = DOM.createDiv(); 418 button.addClassName("opencms-icon"); 419 button.addClassName(I_CmsButton.MOVE_SMALL); 420 button.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragHandle()); 421 helper.appendChild(button); 422 helper.addClassName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.generalCss().shadow()); 423 Element parentElement = getElement().getParentElement(); 424 int elementTop = getElement().getAbsoluteTop(); 425 int parentTop = parentElement.getAbsoluteTop(); 426 m_provisionalParent = DOM.createElement(parentElement.getTagName()); 427 RootPanel.getBodyElement().appendChild(m_provisionalParent); 428 m_provisionalParent.addClassName( 429 org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.generalCss().clearStyles()); 430 m_provisionalParent.addClassName( 431 org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.generalCss().opencms()); 432 m_provisionalParent.getStyle().setWidth(parentElement.getOffsetWidth(), Unit.PX); 433 m_provisionalParent.appendChild(helper); 434 Style style = helper.getStyle(); 435 style.setWidth(helper.getOffsetWidth(), Unit.PX); 436 // the dragging class will set position absolute 437 helper.addClassName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().dragging()); 438 style.setTop(elementTop - parentTop, Unit.PX); 439 m_provisionalParent.getStyle().setPosition(Position.ABSOLUTE); 440 m_provisionalParent.getStyle().setTop(parentTop, Unit.PX); 441 m_provisionalParent.getStyle().setLeft(parentElement.getAbsoluteLeft(), Unit.PX); 442 m_provisionalParent.getStyle().setZIndex(I_CmsLayoutBundle.INSTANCE.constants().css().zIndexDND()); 443 444 return helper; 445 } 446 447 /** 448 * Returns the option bar of this element.<p> 449 * 450 * @return the option bar widget 451 */ 452 public CmsElementOptionBar getElementOptionBar() { 453 454 return m_elementOptionBar; 455 } 456 457 /** 458 * Returns the elements element view.<p> 459 * 460 * @return the element view 461 */ 462 public CmsUUID getElementView() { 463 464 return m_elementView; 465 } 466 467 /** 468 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getId() 469 */ 470 public String getId() { 471 472 return m_clientId; 473 } 474 475 /** 476 * Returns the inheritance info for this element.<p> 477 * 478 * @return the inheritance info for this element 479 */ 480 public CmsInheritanceInfo getInheritanceInfo() { 481 482 return m_inheritanceInfo; 483 } 484 485 /** 486 * Gets the lock information. 487 * 488 * @return the lock information 489 */ 490 public CmsElementLockInfo getLockInfo() { 491 492 return m_lockInfo != null ? m_lockInfo : new CmsElementLockInfo(null, false); 493 } 494 495 /** 496 * Returns the model group id.<p> 497 * 498 * @return the model group id 499 */ 500 public CmsUUID getModelGroupId() { 501 502 return m_modelGroupId; 503 } 504 505 /** 506 * Returns the new element type. 507 * 508 * @return the new element type 509 */ 510 public String getNewType() { 511 512 return m_newType; 513 } 514 515 /** 516 * Returns the no edit reason.<p> 517 * 518 * @return the no edit reason 519 */ 520 public String getNoEditReason() { 521 522 return m_noEditReason; 523 } 524 525 /** 526 * Gets the random id identifying this widget. 527 * 528 * <p>The id is also stored in the element_object_id property of the DOM element for this widget. 529 * 530 * @return the random id identifying this widget 531 */ 532 public String getObjectId() { 533 534 return m_objectId; 535 } 536 537 /** 538 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getParentTarget() 539 */ 540 public I_CmsDropContainer getParentTarget() { 541 542 return m_parent; 543 } 544 545 /** 546 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getPlaceholder(org.opencms.gwt.client.dnd.I_CmsDropTarget) 547 */ 548 public Element getPlaceholder(I_CmsDropTarget target) { 549 550 Element placeholder = CmsDomUtil.clone(getElement()); 551 placeholder.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragPlaceholder()); 552 return placeholder; 553 } 554 555 /** 556 * Returns if the element resource is currently released and not expired.<p> 557 * 558 * @return <code>true</code> if the element resource is currently released and not expired 559 */ 560 public boolean getReleasedAndNotExpired() { 561 562 return m_releasedAndNotExpired; 563 } 564 565 /** 566 * Returns the element resource type name.<p> 567 * 568 * @return the resource type name 569 */ 570 public String getResourceType() { 571 572 return m_resourceType; 573 } 574 575 /** 576 * Returns the site-path.<p> 577 * 578 * @return the site-path 579 */ 580 public String getSitePath() { 581 582 return m_sitePath; 583 } 584 585 /** 586 * Returns the structure id of the element.<p> 587 * 588 * @return the structure id of the element 589 */ 590 public CmsUUID getStructureId() { 591 592 if (m_clientId == null) { 593 return null; 594 } 595 return new CmsUUID(CmsContainerpageController.getServerId(m_clientId)); 596 } 597 598 /** 599 * Indicates an edit handler is configured for the element resource type.<p> 600 * 601 * @return indicates an edit handler is configured for the element resource type 602 */ 603 public boolean hasEditHandler() { 604 605 return m_hasEditHandler; 606 } 607 608 /** 609 * Returns whether this element has a model group parent.<p> 610 * 611 * @return <code>true</code> if this element has a model group parent 612 */ 613 public boolean hasModelGroupParent() { 614 615 boolean result = false; 616 Element parent = getElement().getParentElement(); 617 while (parent != null) { 618 if (parent.getPropertyBoolean(PROP_IS_MODEL_GROUP)) { 619 result = true; 620 break; 621 } 622 parent = parent.getParentElement(); 623 } 624 return result; 625 } 626 627 /** 628 * In case the inner HTML contains the reload marker.<p> 629 * 630 * @return <code>true</code> in case the inner HTML contains the reload marker 631 */ 632 public boolean hasReloadMarker() { 633 634 return getElement().getInnerHTML().contains(CmsGwtConstants.FORMATTER_RELOAD_MARKER); 635 } 636 637 /** 638 * Returns true if the element has settings to edit.<p> 639 * 640 * @return true if the element has settings to edit 641 */ 642 public boolean hasSettings() { 643 644 return m_hasSettings; 645 } 646 647 /** 648 * Returns if the current user has view permissions for the element resource.<p> 649 * 650 * @return <code>true</code> if the current user has view permissions for the element resource 651 */ 652 public boolean hasViewPermission() { 653 654 return m_viewPermission; 655 } 656 657 /** 658 * Returns if the user has write permission.<p> 659 * 660 * @return <code>true</code> if the user has write permission 661 */ 662 public boolean hasWritePermission() { 663 664 return m_writePermission; 665 } 666 667 /** 668 * Hides list collector direct edit buttons, if present.<p> 669 */ 670 public void hideEditableListButtons() { 671 672 if (m_editables != null) { 673 for (CmsListCollectorEditor editor : m_editables.values()) { 674 editor.getElement().getStyle().setDisplay(Display.NONE); 675 } 676 } 677 } 678 679 /** 680 * Puts a highlighting border around the element.<p> 681 */ 682 public void highlightElement() { 683 684 CmsPositionBean position = CmsPositionBean.getBoundingClientRect(getElement()); 685 if (m_highlighting == null) { 686 m_highlighting = new CmsHighlightingBorder( 687 position, 688 isNew() || (CmsContainerpageController.get().getData().isModelPage() && isCreateNew()) 689 ? CmsHighlightingBorder.BorderColor.blue 690 : CmsHighlightingBorder.BorderColor.red); 691 RootPanel.get().add(m_highlighting); 692 } else { 693 m_highlighting.setPosition(position); 694 } 695 } 696 697 /** 698 * Initializes the editor click handler.<p> 699 * 700 * @param controller the container page controller instance 701 */ 702 public void initInlineEditor(final CmsContainerpageController controller) { 703 704 if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_noEditReason) 705 && !m_disableNewEditor 706 && CmsContentEditor.setEditable(getElement(), CmsContainerpageController.getServerId(m_clientId), true)) { 707 if (m_editorClickHandlerRegistration != null) { 708 m_editorClickHandlerRegistration.removeHandler(); 709 } 710 m_editorClickHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() { 711 712 public void onPreviewNativeEvent(NativePreviewEvent event) { 713 714 if (event.getTypeInt() == Event.ONCLICK) { 715 // if another content is already being edited, don't start another editor 716 if (controller.isContentEditing()) { 717 return; 718 } 719 Element eventTarget = event.getNativeEvent().getEventTarget().cast(); 720 // check if the event target is a child 721 if (getElement().isOrHasChild(eventTarget)) { 722 Element target = event.getNativeEvent().getEventTarget().cast(); 723 // check if the target closest ancestor drag element is this element 724 Element parentContainerElement = CmsDomUtil.getAncestor( 725 target, 726 I_CmsLayoutBundle.INSTANCE.dragdropCss().dragElement()); 727 if (parentContainerElement == getElement()) { 728 while ((target != null) 729 && !target.getTagName().equalsIgnoreCase("a") 730 && (target != getElement())) { 731 if (CmsContentEditor.isEditable(target)) { 732 CmsEditorBase.markForInlineFocus(target); 733 controller.getHandler().openEditorForElement( 734 CmsContainerPageElementPanel.this, 735 true, 736 isNew()); 737 removeEditorHandler(); 738 event.cancel(); 739 break; 740 } else { 741 target = target.getParentElement(); 742 } 743 } 744 } 745 } 746 } 747 } 748 }); 749 } 750 } 751 752 /** 753 * Checks if this element has 'createNew' status, i.e. will be copied when using this page as a model for a new container page.<p> 754 * 755 * @return true if this element has createNew status 756 */ 757 public boolean isCreateNew() { 758 759 return m_createNew; 760 } 761 762 /** 763 * Returns whether the element is used as a model group.<p> 764 * 765 * @return <code>true</code> if the element is used as a model group 766 */ 767 public boolean isModelGroup() { 768 769 return m_modelGroupId != null; 770 } 771 772 /** 773 * Returns if this is e newly created element.<p> 774 * 775 * @return <code>true</code> if the element is new 776 */ 777 public boolean isNew() { 778 779 return m_newType != null; 780 } 781 782 /** 783 * Returns true if the new content editor is disabled for this element.<p> 784 * 785 * @return true if the new editor is disabled for this element 786 */ 787 public boolean isNewEditorDisabled() { 788 789 return m_disableNewEditor; 790 } 791 792 /** 793 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDragCancel() 794 */ 795 public void onDragCancel() { 796 797 clearDrag(); 798 resetOptionbar(); 799 } 800 801 /** 802 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDrop(org.opencms.gwt.client.dnd.I_CmsDropTarget) 803 */ 804 public void onDrop(I_CmsDropTarget target) { 805 806 clearDrag(); 807 } 808 809 /** 810 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onStartDrag(org.opencms.gwt.client.dnd.I_CmsDropTarget) 811 */ 812 public void onStartDrag(I_CmsDropTarget target) { 813 814 CmsDomUtil.addDisablingOverlay(getElement()); 815 getElement().getStyle().setOpacity(0.7); 816 removeHighlighting(); 817 } 818 819 /** 820 * @see com.google.gwt.user.client.ui.Widget#removeFromParent() 821 */ 822 @Override 823 public void removeFromParent() { 824 825 if (m_highlighting != null) { 826 m_highlighting.removeFromParent(); 827 m_highlighting = null; 828 } 829 super.removeFromParent(); 830 } 831 832 /** 833 * Removes the highlighting border.<p> 834 */ 835 public void removeHighlighting() { 836 837 if (m_highlighting != null) { 838 m_highlighting.removeFromParent(); 839 m_highlighting = null; 840 } 841 } 842 843 /** 844 * Removes the inline editor.<p> 845 */ 846 public void removeInlineEditor() { 847 848 CmsContentEditor.setEditable(getElement(), CmsContainerpageController.getServerId(m_clientId), false); 849 removeEditorHandler(); 850 } 851 852 /** 853 * @see org.opencms.acacia.client.I_CmsInlineFormParent#replaceHtml(java.lang.String) 854 */ 855 public void replaceHtml(String html) { 856 857 // detach all children first 858 while (getChildren().size() > 0) { 859 getChildren().get(getChildren().size() - 1).removeFromParent(); 860 } 861 Element tempDiv = DOM.createDiv(); 862 tempDiv.setInnerHTML(html); 863 getElement().setInnerHTML(tempDiv.getFirstChildElement().getInnerHTML()); 864 } 865 866 /** 867 * Sets the 'create new' status of the element.<p> 868 * 869 * @param createNew the new value for the 'create new' status 870 */ 871 public void setCreateNew(boolean createNew) { 872 873 m_createNew = createNew; 874 } 875 876 /** 877 * Sets the element option bar.<p> 878 * 879 * @param elementOptionBar the element option bar to set 880 */ 881 public void setElementOptionBar(CmsElementOptionBar elementOptionBar) { 882 883 if ((m_elementOptionBar != null) && (getWidgetIndex(m_elementOptionBar) >= 0)) { 884 m_elementOptionBar.removeFromParent(); 885 } 886 m_elementOptionBar = elementOptionBar; 887 if (m_elementOptionBar != null) { 888 getElement().addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragElement()); 889 insert(m_elementOptionBar, 0); 890 updateOptionBarPosition(); 891 } else { 892 getElement().removeClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragElement()); 893 } 894 } 895 896 /** 897 * Sets the element id.<p> 898 * 899 * @param id the id 900 */ 901 public void setId(String id) { 902 903 m_clientId = id; 904 } 905 906 /** 907 * Sets the inheritance info for this element.<p> 908 * 909 * @param inheritanceInfo the inheritance info for this element to set 910 */ 911 public void setInheritanceInfo(CmsInheritanceInfo inheritanceInfo) { 912 913 m_inheritanceInfo = inheritanceInfo; 914 } 915 916 /** 917 * Sets the new-type of the element.<p> 918 * 919 * @param newType the new-type 920 */ 921 public void setNewType(String newType) { 922 923 m_newType = newType; 924 } 925 926 /** 927 * Sets the no edit reason.<p> 928 * 929 * @param noEditReason the no edit reason to set 930 */ 931 public void setNoEditReason(String noEditReason) { 932 933 m_noEditReason = noEditReason; 934 } 935 936 /** 937 * Sets if the element resource is currently released and not expired.<p> 938 * 939 * @param releasedAndNotExpired <code>true</code> if the element resource is currently released and not expired 940 */ 941 public void setReleasedAndNotExpired(boolean releasedAndNotExpired) { 942 943 m_releasedAndNotExpired = releasedAndNotExpired; 944 if (m_releasedAndNotExpired) { 945 removeStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().expired()); 946 if (m_expiredOverlay != null) { 947 m_expiredOverlay.removeFromParent(); 948 m_expiredOverlay = null; 949 } 950 951 } else { 952 addStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().expired()); 953 m_expiredOverlay = DOM.createDiv(); 954 m_expiredOverlay.setTitle("Expired resource"); 955 m_expiredOverlay.addClassName(I_CmsLayoutBundle.INSTANCE.containerpageCss().expiredOverlay()); 956 getElement().appendChild(m_expiredOverlay); 957 } 958 } 959 960 /** 961 * Sets the site path.<p> 962 * 963 * @param sitePath the site path to set 964 */ 965 public void setSitePath(String sitePath) { 966 967 m_sitePath = sitePath; 968 } 969 970 /** 971 * Sets if the current user has view permissions for the element resource.<p> 972 * 973 * @param viewPermission the view permission to set 974 */ 975 public void setViewPermission(boolean viewPermission) { 976 977 m_viewPermission = viewPermission; 978 } 979 980 /** 981 * Sets the user write permission.<p> 982 * 983 * @param writePermission the user write permission to set 984 */ 985 public void setWritePermission(boolean writePermission) { 986 987 m_writePermission = writePermission; 988 } 989 990 /** 991 * Shows list collector direct edit buttons (old direct edit style), if present.<p> 992 */ 993 public void showEditableListButtons() { 994 995 m_checkingEditables = true; 996 if (m_editables == null) { 997 m_editables = new HashMap<Element, CmsListCollectorEditor>(); 998 List<Element> editables = getEditableElements(); 999 if ((editables != null) && (editables.size() > 0)) { 1000 for (Element editable : editables) { 1001 addListCollectorEditorButtons(editable); 1002 } 1003 } 1004 } else { 1005 1006 Iterator<Entry<Element, CmsListCollectorEditor>> it = m_editables.entrySet().iterator(); 1007 while (it.hasNext()) { 1008 Entry<Element, CmsListCollectorEditor> entry = it.next(); 1009 CmsListCollectorEditor editor = entry.getValue(); 1010 if (!editor.isValid()) { 1011 editor.removeFromParent(); 1012 it.remove(); 1013 } else { 1014 if (CmsDomUtil.hasDimension(editor.getElement().getParentElement())) { 1015 editor.setParentHasDimensions(true); 1016 editor.setPosition( 1017 CmsDomUtil.getEditablePosition(entry.getValue().getMarkerTag()), 1018 getElement()); 1019 } else { 1020 editor.setParentHasDimensions(false); 1021 } 1022 } 1023 } 1024 List<Element> editables = getEditableElements(); 1025 if (editables.size() > m_editables.size()) { 1026 for (Element editable : editables) { 1027 if (!m_editables.containsKey(editable)) { 1028 addListCollectorEditorButtons(editable); 1029 } 1030 } 1031 } 1032 } 1033 1034 boolean editableContainer = true; 1035 if (m_parent instanceof CmsContainerPageContainer) { 1036 editableContainer = CmsContainerpageController.get().isContainerEditable(m_parent); 1037 } 1038 for (CmsListCollectorEditor editor : m_editables.values()) { 1039 editor.updateVisibility(editableContainer); 1040 } 1041 1042 m_checkingEditables = false; 1043 resetNodeInsertedHandler(); 1044 } 1045 1046 /** 1047 * Updates the option bar position.<p> 1048 */ 1049 public void updateOptionBarPosition() { 1050 1051 // only if attached to the DOM 1052 if ((m_elementOptionBar != null) && RootPanel.getBodyElement().isOrHasChild(getElement())) { 1053 int absoluteTop = getElement().getAbsoluteTop(); 1054 int absoluteRight = getElement().getAbsoluteRight(); 1055 CmsPositionBean dimensions = CmsPositionBean.getBoundingClientRect(getElement()); 1056 1057 int top = 0; 1058 int right = 0; 1059 int offsetLeft = 0; 1060 int offsetTop = 0; 1061 1062 final Style style = m_elementOptionBar.getElement().getStyle(); 1063 1064 if (m_positioningInstructionParser.tryParse(getElement().getClassName())) { 1065 offsetLeft = m_positioningInstructionParser.getOffsetLeft(); 1066 offsetTop = m_positioningInstructionParser.getOffsetTop(); 1067 } 1068 1069 if (Math.abs(absoluteTop - dimensions.getTop()) > 20) { 1070 absoluteTop = (dimensions.getTop() - absoluteTop) + 2; 1071 top = absoluteTop; 1072 } 1073 if (Math.abs(absoluteRight - dimensions.getLeft() - dimensions.getWidth()) > 20) { 1074 absoluteRight = (absoluteRight - dimensions.getLeft() - dimensions.getWidth()) + 2; 1075 right = absoluteRight; 1076 } 1077 1078 top += offsetTop; 1079 right -= offsetLeft; 1080 1081 if (top != 0) { 1082 style.setTop(top, Unit.PX); 1083 } else { 1084 style.clearTop(); 1085 } 1086 1087 if (right != 0) { 1088 style.setRight(right, Unit.PX); 1089 } else { 1090 style.clearRight(); 1091 } 1092 1093 if (isOptionbarIFrameCollision(absoluteTop, m_elementOptionBar.getCalculatedWidth())) { 1094 style.setPosition(Position.RELATIVE); 1095 int marginLeft = getElement().getClientWidth() - m_elementOptionBar.getCalculatedWidth(); 1096 if (marginLeft > 0) { 1097 style.setMarginLeft(marginLeft, Unit.PX); 1098 } 1099 } else { 1100 style.clearPosition(); 1101 style.clearMarginLeft(); 1102 } 1103 } 1104 } 1105 1106 /** 1107 * Checks for changes in the list collector direct edit content.<p> 1108 */ 1109 protected void checkForEditableChanges() { 1110 1111 if (!m_checkingEditables) { 1112 m_checkingEditables = true; 1113 Timer timer = new Timer() { 1114 1115 @Override 1116 public void run() { 1117 1118 showEditableListButtons(); 1119 } 1120 }; 1121 timer.schedule(500); 1122 } 1123 } 1124 1125 /** 1126 * Gets the editable list elements.<p> 1127 * 1128 * @return the editable list elements 1129 */ 1130 protected List<Element> getEditableElements() { 1131 1132 List<Element> elems = CmsDomUtil.getElementsByClass(CmsGwtConstants.CLASS_EDITABLE, Tag.div, getElement()); 1133 List<Element> result = Lists.newArrayList(); 1134 for (Element currentElem : elems) { 1135 // don't return elements which are contained in nested containers 1136 if (m_parent.getContainerId().equals(getParentContainerId(currentElem))) { 1137 result.add(currentElem); 1138 } 1139 } 1140 return result; 1141 } 1142 1143 /** 1144 * Returns if the list collector direct edit content has changed.<p> 1145 * 1146 * @return <code>true</code> if the list collector direct edit content has changed 1147 */ 1148 protected boolean hasChangedEditables() { 1149 1150 if (m_editables == null) { 1151 return true; 1152 } 1153 for (CmsListCollectorEditor editor : m_editables.values()) { 1154 if (!editor.isValid()) { 1155 return true; 1156 } 1157 } 1158 return getEditableElements().size() > m_editables.size(); 1159 } 1160 1161 /** 1162 * @see com.google.gwt.user.client.ui.Widget#onDetach() 1163 */ 1164 @Override 1165 protected void onDetach() { 1166 1167 super.onDetach(); 1168 removeEditorHandler(); 1169 } 1170 1171 /** 1172 * @see com.google.gwt.user.client.ui.Widget#onLoad() 1173 */ 1174 @Override 1175 protected void onLoad() { 1176 1177 if ((getParentTarget() instanceof CmsContainerPageContainer) 1178 && ((CmsContainerPageContainer)getParentTarget()).isEditable() 1179 && !hasCheckedHeight()) { 1180 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 1181 1182 public void execute() { 1183 1184 CmsContainerPageElementPanel thisElement = CmsContainerPageElementPanel.this; 1185 if (!hasCheckedHeight() && CmsSmallElementsHandler.isSmall(thisElement)) { 1186 CmsContainerpageController.get().getSmallElementsHandler().prepareSmallElement(thisElement); 1187 } 1188 setCheckedHeight(true); 1189 } 1190 }); 1191 } 1192 resetOptionbar(); 1193 } 1194 1195 /** 1196 * Removes the inline editor handler.<p> 1197 */ 1198 protected void removeEditorHandler() { 1199 1200 if (m_editorClickHandlerRegistration != null) { 1201 m_editorClickHandlerRegistration.removeHandler(); 1202 m_editorClickHandlerRegistration = null; 1203 } 1204 } 1205 1206 /** 1207 * Returns if the minimum element height has been checked.<p> 1208 * 1209 * @return <code>true</code> if the minimum element height has been checked 1210 */ 1211 boolean hasCheckedHeight() { 1212 1213 return m_checkedHeight; 1214 } 1215 1216 /** 1217 * Sets the checked height flag.<p> 1218 * 1219 * @param checked the checked height flag 1220 */ 1221 void setCheckedHeight(boolean checked) { 1222 1223 m_checkedHeight = checked; 1224 } 1225 1226 /** 1227 * Adds the collector edit buttons.<p> 1228 * 1229 * @param editable the marker element for an editable list element 1230 */ 1231 private void addListCollectorEditorButtons(Element editable) { 1232 1233 CmsListCollectorEditor editor = new CmsListCollectorEditor(editable, m_clientId); 1234 add(editor, editable.getParentElement()); 1235 if (CmsDomUtil.hasDimension(editable.getParentElement())) { 1236 editor.setParentHasDimensions(true); 1237 editor.setPosition(CmsDomUtil.getEditablePosition(editable), getElement()); 1238 } else { 1239 editor.setParentHasDimensions(false); 1240 } 1241 m_editables.put(editable, editor); 1242 } 1243 1244 /** 1245 * Removes all styling done during drag and drop.<p> 1246 */ 1247 private void clearDrag() { 1248 1249 CmsDomUtil.removeDisablingOverlay(getElement()); 1250 m_elementOptionBar.getElement().removeClassName( 1251 org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.stateCss().cmsHovering()); 1252 getElement().getStyle().clearOpacity(); 1253 getElement().getStyle().clearDisplay(); 1254 updateOptionBarPosition(); 1255 if (m_provisionalParent != null) { 1256 m_provisionalParent.removeFromParent(); 1257 m_provisionalParent = null; 1258 } 1259 } 1260 1261 /** 1262 * Gets the container id of the most deeply nested container containing the given element, or null if no such container can be found.<p> 1263 * 1264 * @param elem the element 1265 * @return the container id of the deepest container containing the element 1266 */ 1267 private String getParentContainerId(Element elem) { 1268 1269 String attr = CmsContainerPageContainer.PROP_CONTAINER_MARKER; 1270 Element lastElem; 1271 do { 1272 String propValue = elem.getPropertyString(attr); 1273 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(propValue)) { 1274 return propValue; 1275 } 1276 lastElem = elem; 1277 elem = elem.getParentElement(); 1278 } while ((elem != null) && (elem != lastElem)); 1279 return null; 1280 } 1281 1282 /** 1283 * Returns if the option bar position collides with any iframe child elements.<p> 1284 * 1285 * @param optionTop the option bar absolute top 1286 * @param optionWidth the option bar width 1287 * 1288 * @return <code>true</code> if there are iframe child elements located no less than 25px below the upper edge of the element 1289 */ 1290 private boolean isOptionbarIFrameCollision(int optionTop, int optionWidth) { 1291 1292 if (RootPanel.getBodyElement().isOrHasChild(getElement())) { 1293 NodeList<Element> frames = getElement().getElementsByTagName(CmsDomUtil.Tag.iframe.name()); 1294 for (int i = 0; i < frames.getLength(); i++) { 1295 int frameTop = frames.getItem(i).getAbsoluteTop(); 1296 int frameHeight = frames.getItem(i).getOffsetHeight(); 1297 int frameRight = frames.getItem(i).getAbsoluteRight(); 1298 if (((frameTop - optionTop) < 25) 1299 && (((frameTop + frameHeight) - optionTop) > 0) 1300 && ((frameRight - getElement().getAbsoluteRight()) < optionWidth)) { 1301 return true; 1302 } 1303 1304 } 1305 } 1306 return false; 1307 } 1308 1309 /** 1310 * Resets the node inserted handler.<p> 1311 */ 1312 private native void resetNodeInsertedHandler()/*-{ 1313 var $this = this; 1314 var element = $this.@org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel::getElement()(); 1315 var handler = $this.@org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel::m_nodeInsertHandler; 1316 if (handler == null) { 1317 handler = function(event) { 1318 $this.@org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel::checkForEditableChanges()(); 1319 }; 1320 $this.@org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel::m_nodeInsertHandler = handler; 1321 } else { 1322 if (element.removeEventLister) { 1323 element.removeEventListener("DOMNodeInserted", handler); 1324 } else if (element.detachEvent) { 1325 // IE specific 1326 element.detachEvent("onDOMNodeInserted", handler); 1327 } 1328 } 1329 if (element.addEventListener) { 1330 element.addEventListener("DOMNodeInserted", handler, false); 1331 } else if (element.attachEvent) { 1332 // IE specific 1333 element.attachEvent("onDOMNodeInserted", handler); 1334 } 1335 }-*/; 1336 1337 /** 1338 * This method removes the option-bar widget from DOM and re-attaches it at it's original position.<p> 1339 * Use to avoid mouse-over and mouse-down malfunction.<p> 1340 */ 1341 private void resetOptionbar() { 1342 1343 if (m_elementOptionBar != null) { 1344 if (getWidgetIndex(m_elementOptionBar) >= 0) { 1345 m_elementOptionBar.removeFromParent(); 1346 } 1347 updateOptionBarPosition(); 1348 insert(m_elementOptionBar, 0); 1349 } 1350 } 1351}