001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (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.acacia.client.ui; 029 030import org.opencms.acacia.client.CmsAttributeHandler; 031import org.opencms.acacia.client.CmsButtonBarHandler; 032import org.opencms.acacia.client.CmsChoiceMenuEntryBean; 033import org.opencms.acacia.client.CmsEditorBase; 034import org.opencms.acacia.client.CmsValueFocusHandler; 035import org.opencms.acacia.client.I_CmsEntityRenderer; 036import org.opencms.acacia.client.I_CmsWidgetService; 037import org.opencms.acacia.client.css.I_CmsLayoutBundle; 038import org.opencms.acacia.client.widgets.CmsFormWidgetWrapper; 039import org.opencms.acacia.client.widgets.I_CmsEditWidget; 040import org.opencms.acacia.client.widgets.I_CmsFormEditWidget; 041import org.opencms.acacia.client.widgets.I_CmsHasDisplayDirection; 042import org.opencms.acacia.client.widgets.I_CmsHasDisplayDirection.Direction; 043import org.opencms.acacia.shared.CmsEntity; 044import org.opencms.gwt.client.CmsCoreProvider; 045import org.opencms.gwt.client.I_CmsDescendantResizeHandler; 046import org.opencms.gwt.client.I_CmsHasResizeOnShow; 047import org.opencms.gwt.client.dnd.I_CmsDragHandle; 048import org.opencms.gwt.client.dnd.I_CmsDraggable; 049import org.opencms.gwt.client.dnd.I_CmsDropTarget; 050import org.opencms.gwt.client.ui.CmsPushButton; 051import org.opencms.gwt.client.ui.I_CmsButton; 052import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle; 053import org.opencms.gwt.client.util.CmsDomUtil; 054import org.opencms.gwt.client.util.CmsStyleVariable; 055import org.opencms.util.CmsStringUtil; 056 057import java.util.List; 058 059import com.google.common.base.Optional; 060import com.google.gwt.animation.client.Animation; 061import com.google.gwt.core.client.GWT; 062import com.google.gwt.core.client.Scheduler; 063import com.google.gwt.core.client.Scheduler.ScheduledCommand; 064import com.google.gwt.dom.client.DivElement; 065import com.google.gwt.dom.client.Document; 066import com.google.gwt.dom.client.Element; 067import com.google.gwt.dom.client.Node; 068import com.google.gwt.dom.client.SpanElement; 069import com.google.gwt.dom.client.Style; 070import com.google.gwt.dom.client.Style.Display; 071import com.google.gwt.dom.client.Style.Unit; 072import com.google.gwt.event.dom.client.ClickEvent; 073import com.google.gwt.event.dom.client.ClickHandler; 074import com.google.gwt.event.dom.client.FocusEvent; 075import com.google.gwt.event.dom.client.FocusHandler; 076import com.google.gwt.event.dom.client.HasMouseDownHandlers; 077import com.google.gwt.event.dom.client.HasMouseOutHandlers; 078import com.google.gwt.event.dom.client.HasMouseOverHandlers; 079import com.google.gwt.event.dom.client.MouseDownEvent; 080import com.google.gwt.event.dom.client.MouseDownHandler; 081import com.google.gwt.event.dom.client.MouseOutEvent; 082import com.google.gwt.event.dom.client.MouseOutHandler; 083import com.google.gwt.event.dom.client.MouseOverEvent; 084import com.google.gwt.event.dom.client.MouseOverHandler; 085import com.google.gwt.event.logical.shared.HasResizeHandlers; 086import com.google.gwt.event.logical.shared.ValueChangeEvent; 087import com.google.gwt.event.logical.shared.ValueChangeHandler; 088import com.google.gwt.event.shared.HandlerRegistration; 089import com.google.gwt.uibinder.client.UiBinder; 090import com.google.gwt.uibinder.client.UiField; 091import com.google.gwt.uibinder.client.UiHandler; 092import com.google.gwt.user.client.Timer; 093import com.google.gwt.user.client.Window; 094import com.google.gwt.user.client.rpc.AsyncCallback; 095import com.google.gwt.user.client.ui.Composite; 096import com.google.gwt.user.client.ui.FlowPanel; 097import com.google.gwt.user.client.ui.HTML; 098import com.google.gwt.user.client.ui.HTMLPanel; 099import com.google.gwt.user.client.ui.HasWidgets; 100import com.google.gwt.user.client.ui.Label; 101import com.google.gwt.user.client.ui.RootPanel; 102import com.google.gwt.user.client.ui.Widget; 103 104/** 105 * UI object holding an attribute value.<p> 106 */ 107public class CmsAttributeValueView extends Composite 108implements I_CmsDraggable, I_CmsHasResizeOnShow, HasMouseOverHandlers, HasMouseOutHandlers, HasMouseDownHandlers { 109 110 /** 111 * The widget value change handler.<p> 112 */ 113 protected class ChangeHandler implements ValueChangeHandler<String> { 114 115 /** 116 * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent) 117 */ 118 public void onValueChange(ValueChangeEvent<String> event) { 119 120 getHandler().handleValueChange(CmsAttributeValueView.this, event.getValue()); 121 removeValidationMessage(); 122 } 123 } 124 125 /** 126 * Handles showing and hiding the help bubble on mouse over the title label.<p> 127 */ 128 protected class LabelHoverHandler implements MouseOverHandler, MouseOutHandler { 129 130 /** Mouse in timer. */ 131 Timer m_inTimer; 132 133 /** Mouse out timer. */ 134 Timer m_outTimer; 135 136 /** 137 * @see com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google.gwt.event.dom.client.MouseOutEvent) 138 */ 139 public void onMouseOut(MouseOutEvent event) { 140 141 if (m_inTimer != null) { 142 m_inTimer.cancel(); 143 m_inTimer = null; 144 } 145 if (m_outTimer == null) { 146 m_outTimer = new Timer() { 147 148 @Override 149 public void run() { 150 151 toggleLabelHover(false); 152 m_outTimer = null; 153 } 154 }; 155 m_outTimer.schedule(200); 156 } 157 } 158 159 /** 160 * @see com.google.gwt.event.dom.client.MouseOverHandler#onMouseOver(com.google.gwt.event.dom.client.MouseOverEvent) 161 */ 162 public void onMouseOver(MouseOverEvent event) { 163 164 if (m_outTimer != null) { 165 m_outTimer.cancel(); 166 m_outTimer = null; 167 } 168 if (m_inTimer == null) { 169 m_inTimer = new Timer() { 170 171 @Override 172 public void run() { 173 174 toggleLabelHover(true); 175 m_inTimer = null; 176 } 177 }; 178 m_inTimer.schedule(400); 179 } 180 } 181 } 182 183 /** The move handle. */ 184 protected class MoveHandle extends CmsPushButton implements I_CmsDragHandle { 185 186 /** The draggable. */ 187 private CmsAttributeValueView m_draggable; 188 189 /** 190 * Constructor.<p> 191 * 192 * @param draggable the draggable 193 */ 194 MoveHandle(CmsAttributeValueView draggable) { 195 196 setImageClass(I_CmsButton.MOVE_SMALL); 197 setButtonStyle(ButtonStyle.FONT_ICON, null); 198 if (CmsEditorBase.hasDictionary()) { 199 setTitle(CmsEditorBase.getMessageForKey(CmsEditorBase.GUI_VIEW_MOVE_1, getLabel())); 200 } 201 m_draggable = draggable; 202 } 203 204 /** 205 * @see org.opencms.gwt.client.dnd.I_CmsDragHandle#getDraggable() 206 */ 207 public I_CmsDraggable getDraggable() { 208 209 return m_draggable; 210 } 211 212 } 213 214 /** 215 * The UI binder interface.<p> 216 */ 217 interface I_AttributeValueUiBinder extends UiBinder<HTMLPanel, CmsAttributeValueView> { 218 // nothing to do 219 } 220 221 public static final String ATTR_KEY = "data-key"; 222 223 /** The first column compact view mode. */ 224 public static final int COMPACT_MODE_FIRST_COLUMN = 1; 225 226 /** The nested compact view mode. */ 227 public static final int COMPACT_MODE_NESTED = 3; 228 229 /** The second column compact view mode. */ 230 public static final int COMPACT_MODE_SECOND_COLUMN = 2; 231 232 /** The single line compact view mode. */ 233 public static final int COMPACT_MODE_SINGLE_LINE = 4; 234 235 /** The wide compact view mode. */ 236 public static final int COMPACT_MODE_WIDE = 0; 237 238 /** The toolbar height. */ 239 private static final int TOOLBAR_HEIGHT = 52; 240 241 /** Attribute for marking menu bars that are actually menu bars which should open on hovering (rather than just a single button). */ 242 public static final String ATTR_HAS_HOVER = "data-has-hover-menu"; 243 244 /** The UI binder instance. */ 245 private static I_AttributeValueUiBinder uiBinder = GWT.create(I_AttributeValueUiBinder.class); 246 247 /** The add button. */ 248 @UiField 249 protected CmsAttributeChoiceWidget m_addButton; 250 251 /** The attribute choice button. */ 252 @UiField 253 protected CmsAttributeChoiceWidget m_attributeChoice; 254 255 /** The button bar. */ 256 @UiField 257 protected FlowPanel m_buttonBar; 258 259 /** The down button. */ 260 @UiField 261 protected CmsPushButton m_downButton; 262 263 /** The help bubble element. */ 264 @UiField 265 protected DivElement m_helpBubble; 266 267 /** The help bubble close button. */ 268 @UiField 269 protected CmsPushButton m_helpBubbleClose; 270 271 /** The help bubble text element. */ 272 @UiField 273 protected DivElement m_helpBubbleText; 274 275 /** The message text element. */ 276 @UiField 277 protected SpanElement m_messageText; 278 279 /** The move button. */ 280 @UiField(provided = true) 281 protected MoveHandle m_moveButton; 282 283 /** The remove button. */ 284 @UiField 285 protected CmsPushButton m_removeButton; 286 287 /** The up button. */ 288 @UiField 289 protected CmsPushButton m_upButton; 290 291 /** The widget holder elemenet. */ 292 @UiField 293 protected FlowPanel m_widgetHolder; 294 295 /** The currently running animation. */ 296 Animation m_currentAnimation; 297 298 /** The activation mouse down handler registration. */ 299 private HandlerRegistration m_activationHandlerRegistration; 300 301 /** Style variable to enable/disable 'collapsed' style. */ 302 private CmsStyleVariable m_collapsedStyle = new CmsStyleVariable(this); 303 304 /** The compact view style variable. */ 305 private CmsStyleVariable m_compacteModeStyle; 306 307 /** The default widget value. */ 308 private String m_defaultValue; 309 310 /** Flag indicating if drag and drop is enabled for this attribute. */ 311 private boolean m_dragEnabled; 312 313 /** Drag and drop helper element. */ 314 private Element m_dragHelper; 315 316 /** The attribute handler. */ 317 private CmsAttributeHandler m_handler; 318 319 /** Flag indicating a validation error. */ 320 private boolean m_hasError; 321 322 /** Flag indicating if there is a value set for this UI object. */ 323 private boolean m_hasValue; 324 325 /** The help text. */ 326 private String m_help; 327 328 /** Flag indicating this is a representing an attribute choice value. */ 329 private boolean m_isChoice; 330 331 /** Flag indicating that this view represents a simple value. */ 332 private boolean m_isSimpleValue; 333 334 /** The label text. */ 335 private String m_label; 336 337 /** The label mouse in handler registration. */ 338 private HandlerRegistration m_labelOutRegistration; 339 340 /** The label mouse over handler registration. */ 341 private HandlerRegistration m_labelOverRegistration; 342 343 /** The drag and drop place holder element. */ 344 private Element m_placeHolder; 345 346 /** The editing widget. */ 347 private I_CmsFormEditWidget m_widget; 348 349 /** 350 * Constructor.<p> 351 * 352 * @param handler the attribute handler 353 * @param label the attribute label 354 * @param help the attribute help information 355 */ 356 public CmsAttributeValueView(CmsAttributeHandler handler, String label, String help) { 357 358 // important: provide the move button before binding the widget UI 359 m_moveButton = new MoveHandle(this); 360 initWidget(uiBinder.createAndBindUi(this)); 361 m_handler = handler; 362 m_handler.registerAttributeValue(this); 363 if (!CmsCoreProvider.isTouchOnly()) { 364 m_moveButton.addMouseDownHandler(m_handler.getDNDHandler()); 365 } 366 m_label = label; 367 m_help = help; 368 if (m_help == null) { 369 m_helpBubble.getStyle().setDisplay(Display.NONE); 370 m_help = ""; 371 } 372 generateLabel(); 373 m_helpBubbleText.setInnerHTML(m_help); 374 addStyleName(formCss().emptyValue()); 375 m_compacteModeStyle = new CmsStyleVariable(this); 376 m_compacteModeStyle.setValue(formCss().defaultView()); 377 initHighlightingHandler(); 378 initButtons(); 379 m_buttonBar.addStyleName(CmsButtonBarHandler.HOVERABLE_MARKER); 380 381 m_buttonBar.addDomHandler(CmsButtonBarHandler.INSTANCE, MouseOverEvent.getType()); 382 m_buttonBar.addDomHandler(CmsButtonBarHandler.INSTANCE, MouseOutEvent.getType()); 383 m_collapsedStyle.setValue(formCss().uncollapsed()); 384 385 } 386 387 /** 388 * Adds a new choice choice selection menu.<p> 389 * 390 * @param widgetService the widget service to use for labels 391 * @param menuEntry the menu entry bean for the choice 392 */ 393 public void addChoice(I_CmsWidgetService widgetService, final CmsChoiceMenuEntryBean menuEntry) { 394 395 AsyncCallback<CmsChoiceMenuEntryBean> selectHandler = new AsyncCallback<CmsChoiceMenuEntryBean>() { 396 397 public void onFailure(Throwable caught) { 398 399 // will not be called 400 401 } 402 403 public void onSuccess(CmsChoiceMenuEntryBean selectedEntry) { 404 405 m_attributeChoice.hide(); 406 selectChoice(selectedEntry.getPath()); 407 } 408 }; 409 410 m_attributeChoice.addChoice(widgetService, menuEntry, selectHandler); 411 m_isChoice = true; 412 } 413 414 /** 415 * @see com.google.gwt.event.dom.client.HasMouseDownHandlers#addMouseDownHandler(com.google.gwt.event.dom.client.MouseDownHandler) 416 */ 417 public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) { 418 419 return addDomHandler(handler, MouseDownEvent.getType()); 420 } 421 422 /** 423 * @see com.google.gwt.event.dom.client.HasMouseOutHandlers#addMouseOutHandler(com.google.gwt.event.dom.client.MouseOutHandler) 424 */ 425 public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) { 426 427 return addDomHandler(handler, MouseOutEvent.getType()); 428 } 429 430 /** 431 * @see com.google.gwt.event.dom.client.HasMouseOverHandlers#addMouseOverHandler(com.google.gwt.event.dom.client.MouseOverHandler) 432 */ 433 public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) { 434 435 return addDomHandler(handler, MouseOverEvent.getType()); 436 } 437 438 public FlowPanel getButtonBar() { 439 440 return m_buttonBar; 441 } 442 443 /** 444 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getCursorOffsetDelta() 445 */ 446 public Optional<int[]> getCursorOffsetDelta() { 447 448 return Optional.absent(); 449 } 450 451 /** 452 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getDragHelper(org.opencms.gwt.client.dnd.I_CmsDropTarget) 453 */ 454 public Element getDragHelper(I_CmsDropTarget target) { 455 456 closeHelpBubble(null); 457 // using the widget element as the drag helper also to avoid cloning issues on input fields 458 m_dragHelper = getElement(); 459 Element parentElement = getElement().getParentElement(); 460 if (parentElement == null) { 461 parentElement = target.getElement(); 462 } 463 int elementTop = getElement().getAbsoluteTop(); 464 int parentTop = parentElement.getAbsoluteTop(); 465 Style style = m_dragHelper.getStyle(); 466 style.setWidth(m_dragHelper.getOffsetWidth(), Unit.PX); 467 // the dragging class will set position absolute 468 style.setTop(elementTop - parentTop, Unit.PX); 469 m_dragHelper.addClassName(formCss().dragHelper()); 470 return m_dragHelper; 471 } 472 473 /** 474 * Returns the attribute handler.<p> 475 * 476 * @return the attribute handler 477 */ 478 public CmsAttributeHandler getHandler() { 479 480 return m_handler; 481 } 482 483 /** 484 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getId() 485 */ 486 public String getId() { 487 488 String id = getElement().getId(); 489 if ((id == null) || "".equals(id)) { 490 id = Document.get().createUniqueId(); 491 getElement().setId(id); 492 } 493 return id; 494 } 495 496 /** 497 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getParentTarget() 498 */ 499 public I_CmsDropTarget getParentTarget() { 500 501 return (I_CmsDropTarget)getParent(); 502 } 503 504 /** 505 * Gets the parent attribute value view, or null if none exists.<p> 506 * 507 * @return the parent attribute value view 508 */ 509 public CmsAttributeValueView getParentView() { 510 511 Widget ancestor = getParent(); 512 while ((ancestor != null) && !(ancestor instanceof CmsAttributeValueView)) { 513 ancestor = ancestor.getParent(); 514 } 515 return (CmsAttributeValueView)ancestor; 516 } 517 518 /** 519 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getPlaceholder(org.opencms.gwt.client.dnd.I_CmsDropTarget) 520 */ 521 public Element getPlaceholder(I_CmsDropTarget target) { 522 523 m_placeHolder = CmsDomUtil.clone(getElement()); 524 removeDragHelperStyles(m_placeHolder); 525 m_placeHolder.addClassName(formCss().dragPlaceholder()); 526 return m_placeHolder; 527 } 528 529 /** 530 * Returns the attribute value index.<p> 531 * 532 * @return the attribute value index 533 */ 534 public int getValueIndex() { 535 536 int result = 0; 537 Node previousSibling = getElement().getPreviousSibling(); 538 while (previousSibling != null) { 539 result++; 540 previousSibling = previousSibling.getPreviousSibling(); 541 } 542 return result; 543 } 544 545 /** 546 * Returns the editing widget.<p> 547 * 548 * @return the editing widget or <code>null</code> if not available 549 */ 550 public I_CmsEditWidget getValueWidget() { 551 552 return m_widget; 553 } 554 555 /** 556 * Returns if there is a value set for this attribute.<p> 557 * 558 * @return <code>true</code> if there is a value set for this attribute 559 */ 560 public boolean hasValue() { 561 562 return m_hasValue && ((m_widget == null) || m_widget.isActive()); 563 } 564 565 /** 566 * Hides the button bar.<p> 567 */ 568 public void hideAllButtons() { 569 570 m_buttonBar.getElement().getStyle().setDisplay(Display.NONE); 571 } 572 573 /** 574 * Returns if drag and drop is enabled for this attribute.<p> 575 * 576 * @return <code>true</code> if drag and drop is enabled for this attribute 577 */ 578 public boolean isDragEnabled() { 579 580 return m_dragEnabled; 581 } 582 583 /** 584 * Returns if this view represents a simple value.<p> 585 * 586 * @return <code>true</code> if this view represents a simple value 587 */ 588 public boolean isSimpleValue() { 589 590 return m_isSimpleValue; 591 } 592 593 /** 594 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDragCancel() 595 */ 596 public void onDragCancel() { 597 598 clearDrag(); 599 } 600 601 /** 602 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDrop(org.opencms.gwt.client.dnd.I_CmsDropTarget) 603 */ 604 public void onDrop(I_CmsDropTarget target) { 605 606 clearDrag(); 607 } 608 609 /** 610 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onStartDrag(org.opencms.gwt.client.dnd.I_CmsDropTarget) 611 */ 612 public void onStartDrag(I_CmsDropTarget target) { 613 614 // nothing to do 615 } 616 617 /** 618 * Checks if the attribute value view's widget "owns" the given element.<p> 619 * 620 * @param element the element to check 621 * @return true if the widget owns the element 622 */ 623 public boolean owns(Element element) { 624 625 return (m_widget != null) && m_widget.owns(element); 626 } 627 628 /** 629 * Removes any present error message.<p> 630 */ 631 public void removeValidationMessage() { 632 633 if (m_hasError) { 634 m_messageText.setInnerText(""); 635 removeStyleName(formCss().hasError()); 636 removeStyleName(formCss().hasWarning()); 637 m_hasError = false; 638 } 639 640 } 641 642 /** 643 * Removes the value.<p> 644 */ 645 public void removeValue() { 646 647 if (!isSimpleValue()) { 648 m_hasValue = false; 649 m_widgetHolder.clear(); 650 generateLabel(); 651 } else { 652 // only deactivate the widget and restore the default value 653 m_widget.setActive(false); 654 if (m_widget.shouldSetDefaultWhenDisabled() && (m_defaultValue != null)) { 655 m_widget.setValue(m_defaultValue); 656 } else { 657 m_widget.setValue(""); 658 } 659 addActivationHandler(); 660 } 661 addStyleName(formCss().emptyValue()); 662 removeValidationMessage(); 663 } 664 665 /** 666 * @see org.opencms.gwt.client.I_CmsHasResizeOnShow#resizeOnShow() 667 */ 668 public void resizeOnShow() { 669 670 // call resize on all children implementing org.opencms.acacia.client.ui.I_HasResizeOnShow 671 if (hasValue()) { 672 if (isSimpleValue()) { 673 if (m_widget instanceof I_CmsHasResizeOnShow) { 674 ((I_CmsHasResizeOnShow)m_widget).resizeOnShow(); 675 } 676 } else { 677 for (Widget panel : m_widgetHolder) { 678 if (panel instanceof HasWidgets.ForIsWidget) { 679 for (Widget w : (HasWidgets.ForIsWidget)panel) { 680 if (w instanceof I_CmsHasResizeOnShow) { 681 ((I_CmsHasResizeOnShow)w).resizeOnShow(); 682 } 683 } 684 } 685 } 686 } 687 } 688 } 689 690 /** 691 * Sets the value widget active and removes the inactive view styling.<p> 692 */ 693 public void setActive() { 694 695 if (m_widget != null) { 696 m_widget.setActive(true); 697 removeStyleName(formCss().emptyValue()); 698 } 699 } 700 701 /** 702 * Enables or disables the "collapsed" style, which is used for choice elements to reduce the nesting level visually.<p> 703 * 704 * @param collapsed true if the view should be set to 'collapsed' 705 */ 706 public void setCollapsed(boolean collapsed) { 707 708 m_collapsedStyle.setValue(collapsed ? formCss().collapsed() : formCss().uncollapsed()); 709 } 710 711 /** 712 * Sets the compact view mode.<p> 713 * 714 * @param mode the mode to set 715 */ 716 public void setCompactMode(int mode) { 717 718 switch (mode) { 719 case COMPACT_MODE_FIRST_COLUMN: 720 m_compacteModeStyle.setValue(formCss().firstColumn()); 721 break; 722 case COMPACT_MODE_SECOND_COLUMN: 723 m_compacteModeStyle.setValue(formCss().secondColumn()); 724 break; 725 case COMPACT_MODE_NESTED: 726 m_compacteModeStyle.setValue(formCss().compactView()); 727 break; 728 case COMPACT_MODE_SINGLE_LINE: 729 m_compacteModeStyle.setValue(formCss().singleLine()); 730 break; 731 default: 732 733 } 734 updateWidth(); 735 } 736 737 /** 738 * Shows a validation error message.<p> 739 * 740 * @param message the error message 741 */ 742 public void setErrorMessage(String message) { 743 744 m_messageText.setInnerHTML(message); 745 addStyleName(formCss().hasError()); 746 m_hasError = true; 747 } 748 749 /** 750 * Sets the value entity.<p> 751 * 752 * @param renderer the entity renderer 753 * @param value the value entity 754 */ 755 public void setValueEntity(I_CmsEntityRenderer renderer, CmsEntity value) { 756 757 if (m_hasValue) { 758 throw new RuntimeException("Value has already been set"); 759 } 760 m_hasValue = true; 761 m_isSimpleValue = false; 762 FlowPanel entityPanel = new FlowPanel(); 763 m_widgetHolder.add(entityPanel); 764 int index = getValueIndex(); 765 m_handler.ensureHandlers(index); 766 renderer.renderForm(value, entityPanel, m_handler, index); 767 removeStyleName(formCss().emptyValue()); 768 } 769 770 /** 771 * Sets the value widget.<p> 772 * 773 * @param widget the widget 774 * @param value the value 775 * @param defaultValue the default attribute value 776 * @param active <code>true</code> if the widget should be activated 777 */ 778 public void setValueWidget(I_CmsFormEditWidget widget, String value, String defaultValue, boolean active) { 779 780 if (m_hasValue) { 781 throw new RuntimeException("Value has already been set"); 782 } 783 m_defaultValue = defaultValue; 784 m_hasValue = true; 785 m_isSimpleValue = true; 786 m_widget = widget; 787 if (CmsAttributeHandler.hasResizeHandler() && (m_widget instanceof HasResizeHandlers)) { 788 ((HasResizeHandlers)m_widget).addResizeHandler(CmsAttributeHandler.getResizeHandler()); 789 } 790 m_widgetHolder.clear(); 791 m_widget.setWidgetInfo(m_label, m_help); 792 if (active) { 793 m_widget.setValue(value, false); 794 } else if (m_widget.shouldSetDefaultWhenDisabled()) { 795 m_widget.setValue(defaultValue, false); 796 } else { 797 m_widget.setValue(""); 798 } 799 m_widgetHolder.add(m_widget); 800 m_widget.setName(getHandler().getAttributeName()); 801 m_widget.addValueChangeHandler(new ChangeHandler()); 802 m_widget.addFocusHandler(new FocusHandler() { 803 804 public void onFocus(FocusEvent event) { 805 806 CmsValueFocusHandler.getInstance().setFocus(CmsAttributeValueView.this); 807 activateWidget(); 808 } 809 }); 810 m_widget.setActive(active); 811 if (!active) { 812 addActivationHandler(); 813 } else { 814 removeStyleName(formCss().emptyValue()); 815 } 816 addStyleName(formCss().simpleValue()); 817 818 if (m_widget instanceof CmsFormWidgetWrapper) { 819 addLabelHoverHandler(((CmsFormWidgetWrapper)m_widget).getLabel()); 820 } else { 821 removeLabelHoverHandler(); 822 } 823 } 824 825 /** 826 * Shows a validation warning message.<p> 827 * 828 * @param message the warning message 829 */ 830 public void setWarningMessage(String message) { 831 832 m_messageText.setInnerText(message); 833 addStyleName(formCss().hasWarning()); 834 m_hasError = true; 835 } 836 837 /** 838 * Shows the button bar.<p> 839 */ 840 public void showButtons() { 841 842 m_buttonBar.getElement().getStyle().clearDisplay(); 843 } 844 845 /** 846 * Tells the attribute value view to change its display state between focused/unfocused (this doesn't actually change the focus).<p> 847 * 848 * @param focusOn <code>true</code> to change the display state to 'focused' 849 */ 850 public void toggleFocus(boolean focusOn) { 851 852 if (!isAttached()) { 853 // can happen if values get actively deleted 854 return; 855 } 856 if (focusOn) { 857 addStyleName(formCss().focused()); 858 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_help) || m_hasError) { 859 m_helpBubble.getStyle().clearDisplay(); 860 if (shouldDisplayTooltipAbove()) { 861 addStyleName(formCss().displayAbove()); 862 } else { 863 removeStyleName(formCss().displayAbove()); 864 } 865 } else { 866 m_helpBubble.getStyle().setDisplay(Display.NONE); 867 } 868 } else { 869 removeStyleName(formCss().focused()); 870 if (m_widget != null) { 871 if (m_handler.hasSingleOptionalValue()) { 872 if (m_handler.getWidgetService().shouldRemoveLastValueAfterUnfocus(m_widget)) { 873 m_handler.removeAttributeValue(this); 874 } 875 } 876 } 877 } 878 } 879 880 /** 881 * Updates the visibility of the add, remove, up and down buttons.<p> 882 * 883 * @param hasAddButton <code>true</code> if the add button should be visible 884 * @param hasRemoveButton <code>true</code> if the remove button should be visible 885 * @param hasSortButtons <code>true</code> if the sort buttons should be visible 886 */ 887 public void updateButtonVisibility(boolean hasAddButton, boolean hasRemoveButton, boolean hasSortButtons) { 888 889 boolean hasOnlyDelete = !hasAddButton && hasRemoveButton && !hasSortButtons; 890 boolean hasHoverMenu = !hasOnlyDelete; 891 892 m_buttonBar.getElement().setAttribute(ATTR_HAS_HOVER, "" + hasHoverMenu); 893 894 if (hasAddButton && m_isChoice) { 895 m_attributeChoice.getElement().getStyle().clearDisplay(); 896 } else { 897 m_attributeChoice.getElement().getStyle().setDisplay(Display.NONE); 898 } 899 if (hasAddButton && !m_isChoice) { 900 m_addButton.getElement().getStyle().clearDisplay(); 901 } else { 902 m_addButton.getElement().getStyle().setDisplay(Display.NONE); 903 } 904 905 if (hasRemoveButton) { 906 m_removeButton.getElement().getStyle().clearDisplay(); 907 } else { 908 m_removeButton.getElement().getStyle().setDisplay(Display.NONE); 909 } 910 if (hasSortButtons && (getValueIndex() != 0)) { 911 m_upButton.getElement().getStyle().clearDisplay(); 912 } else { 913 m_upButton.getElement().getStyle().setDisplay(Display.NONE); 914 } 915 if (hasSortButtons && (getElement().getNextSibling() != null)) { 916 m_downButton.getElement().getStyle().clearDisplay(); 917 } else { 918 m_downButton.getElement().getStyle().setDisplay(Display.NONE); 919 } 920 if (hasSortButtons) { 921 m_moveButton.addStyleName(I_CmsLayoutBundle.INSTANCE.form().moveHandle()); 922 if (CmsEditorBase.hasDictionary()) { 923 m_moveButton.setTitle(CmsEditorBase.getMessageForKey(CmsEditorBase.GUI_VIEW_MOVE_1, m_label)); 924 } 925 } else { 926 m_moveButton.setTitle(""); 927 m_moveButton.removeStyleName(I_CmsLayoutBundle.INSTANCE.form().moveHandle()); 928 } 929 m_dragEnabled = hasSortButtons; 930 if (!hasAddButton && !hasRemoveButton && !hasSortButtons) { 931 // hide the button bar if no button is visible 932 m_buttonBar.getElement().getStyle().setDisplay(Display.NONE); 933 } else { 934 // show the button bar 935 m_buttonBar.getElement().getStyle().clearDisplay(); 936 if (hasSortButtons || (hasAddButton && hasRemoveButton)) { 937 // set multi button mode 938 m_buttonBar.addStyleName(formCss().multiButtonBar()); 939 m_moveButton.getElement().getStyle().clearDisplay(); 940 } else { 941 m_moveButton.getElement().getStyle().setDisplay(Display.NONE); 942 m_buttonBar.removeStyleName(formCss().multiButtonBar()); 943 } 944 } 945 } 946 947 /** 948 * Adds a new attribute value.<p> 949 */ 950 protected void addNewAttributeValue() { 951 952 if ((m_widget != null) && !m_widget.isActive()) { 953 activateWidget(); 954 } else { 955 m_handler.addNewAttributeValue(this); 956 } 957 onResize(); 958 } 959 960 /** 961 * Handles the click event to close the help bubble.<p> 962 * 963 * @param event the click event 964 */ 965 @UiHandler("m_helpBubbleClose") 966 protected void closeHelpBubble(ClickEvent event) { 967 968 addStyleName(formCss().closedBubble()); 969 } 970 971 /** 972 * Returns the attribute label.<p> 973 * 974 * @return the attribute label 975 */ 976 protected String getLabel() { 977 978 return m_label; 979 } 980 981 /** 982 * Handles the click event to move the attribute value down.<p> 983 * 984 * @param event the click event 985 */ 986 @UiHandler("m_downButton") 987 protected void moveAttributeValueDown(ClickEvent event) { 988 989 m_handler.moveAttributeValueDown(this); 990 } 991 992 /** 993 * Handles the click event to move the attribute value up.<p> 994 * 995 * @param event the click event 996 */ 997 @UiHandler("m_upButton") 998 protected void moveAttributeValueUp(ClickEvent event) { 999 1000 m_handler.moveAttributeValueUp(this); 1001 } 1002 1003 /** 1004 * @see com.google.gwt.user.client.ui.Composite#onAttach() 1005 */ 1006 @Override 1007 protected void onAttach() { 1008 1009 super.onAttach(); 1010 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 1011 1012 public void execute() { 1013 1014 updateWidth(); 1015 } 1016 }); 1017 } 1018 1019 /** 1020 * Call when content changes.<p> 1021 */ 1022 protected void onResize() { 1023 1024 Widget parent = getParent(); 1025 while (parent != null) { 1026 if (parent instanceof I_CmsDescendantResizeHandler) { 1027 ((I_CmsDescendantResizeHandler)parent).onResizeDescendant(); 1028 break; 1029 } 1030 parent = parent.getParent(); 1031 } 1032 } 1033 1034 /** 1035 * Handles the click event to remove the attribute value.<p> 1036 * 1037 * @param event the click event 1038 */ 1039 @UiHandler("m_removeButton") 1040 1041 protected void removeAttributeValue(ClickEvent event) { 1042 1043 m_handler.removeAttributeValue(this); 1044 onResize(); 1045 } 1046 1047 /** 1048 * Selects the attribute choice.<p> 1049 * 1050 * @param choicePath the choice attribute path 1051 */ 1052 protected void selectChoice(List<String> choicePath) { 1053 1054 m_handler.addNewChoiceAttributeValue(this, choicePath); 1055 } 1056 1057 /** 1058 * Activates the value widget if prNamet.<p> 1059 */ 1060 void activateWidget() { 1061 1062 if (m_activationHandlerRegistration != null) { 1063 m_activationHandlerRegistration.removeHandler(); 1064 m_activationHandlerRegistration = null; 1065 } 1066 if ((m_widget != null) && !m_widget.isActive()) { 1067 m_widget.setActive(true); 1068 if ((m_defaultValue != null) && (m_defaultValue.trim().length() > 0)) { 1069 m_widget.setValue(m_defaultValue, true); 1070 } 1071 m_handler.updateButtonVisisbility(); 1072 removeStyleName(formCss().emptyValue()); 1073 } 1074 } 1075 1076 /** 1077 * Toggles the label hover CSS class.<p> 1078 * 1079 * @param hovered <code>true</code> in case the 1080 */ 1081 void toggleLabelHover(boolean hovered) { 1082 1083 boolean hover = hovered && (m_hasError || CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_help)); 1084 if (hover) { 1085 addStyleName(formCss().labelHover()); 1086 if (shouldDisplayTooltipAbove()) { 1087 addStyleName(formCss().displayAbove()); 1088 } else { 1089 removeStyleName(formCss().displayAbove()); 1090 } 1091 } else { 1092 removeStyleName(formCss().labelHover()); 1093 } 1094 CmsValueFocusHandler.getInstance().hideHelpBubbles( 1095 RootPanel.get(), 1096 hover || !CmsCoreProvider.get().isShowEditorHelp()); 1097 } 1098 1099 /** 1100 * Updates the widget width according to the compact mode setting.<p> 1101 */ 1102 void updateWidth() { 1103 1104 if (formCss().firstColumn().equals(m_compacteModeStyle.getValue())) { 1105 int width = getElement().getParentElement().getOffsetWidth() - formCss().SECOND_COLUMN_WIDTH(); 1106 // if width could not be evaluated, fall back to a 'save' value 1107 if (width < 0) { 1108 width = 400; 1109 } 1110 getElement().getStyle().setWidth(width, Unit.PX); 1111 } else { 1112 getElement().getStyle().clearWidth(); 1113 } 1114 } 1115 1116 /** 1117 * Adds a mouse down handler to activate the editing widget.<p> 1118 */ 1119 private void addActivationHandler() { 1120 1121 if (m_activationHandlerRegistration == null) { 1122 m_activationHandlerRegistration = addMouseDownHandler(new MouseDownHandler() { 1123 1124 public void onMouseDown(MouseDownEvent event) { 1125 1126 // only act on click if not inside the button bar 1127 if (!m_buttonBar.getElement().isOrHasChild((Node)event.getNativeEvent().getEventTarget().cast())) { 1128 activateWidget(); 1129 } 1130 } 1131 }); 1132 } 1133 } 1134 1135 /** 1136 * Adds the label hover mouse handler.<p> 1137 * 1138 * @param label the label widget 1139 */ 1140 private void addLabelHoverHandler(Label label) { 1141 1142 removeLabelHoverHandler(); 1143 LabelHoverHandler hoverHandler = new LabelHoverHandler(); 1144 m_labelOutRegistration = label.addMouseOutHandler(hoverHandler); 1145 m_labelOverRegistration = label.addMouseOverHandler(hoverHandler); 1146 } 1147 1148 /** 1149 * Called when a drag operation for this widget is stopped.<p> 1150 */ 1151 private void clearDrag() { 1152 1153 if (m_dragHelper != null) { 1154 removeDragHelperStyles(m_dragHelper); 1155 m_dragHelper = null; 1156 } 1157 // preventing issue where mouse out was never triggered after drag and drop 1158 m_moveButton.clearHoverState(); 1159 CmsButtonBarHandler.INSTANCE.closeAll(); 1160 } 1161 1162 /** 1163 * Returns the CSS bundle for the form editor.<p> 1164 * 1165 * @return the form CSS bundle 1166 */ 1167 private I_CmsLayoutBundle.I_Style formCss() { 1168 1169 return I_CmsLayoutBundle.INSTANCE.form(); 1170 } 1171 1172 /** 1173 * Generates the attribute label.<p> 1174 */ 1175 private void generateLabel() { 1176 1177 HTML labelWidget = new HTML("<div class=\"" + formCss().label() + "\">" + m_label + "</div>"); 1178 addLabelHoverHandler(labelWidget); 1179 m_widgetHolder.add(labelWidget); 1180 } 1181 1182 /** 1183 * Initializes the button styling.<p> 1184 */ 1185 private void initButtons() { 1186 1187 m_addButton.addChoice( 1188 m_handler.getWidgetService(), 1189 new CmsChoiceMenuEntryBean(m_handler.getAttributeName()), 1190 new AsyncCallback<CmsChoiceMenuEntryBean>() { 1191 1192 public void onFailure(Throwable caught) { 1193 1194 // will not be called 1195 1196 } 1197 1198 public void onSuccess(CmsChoiceMenuEntryBean selectedEntry) { 1199 1200 // nothing to do 1201 } 1202 }); 1203 m_addButton.addDomHandler(new ClickHandler() { 1204 1205 public void onClick(ClickEvent event) { 1206 1207 m_addButton.hide(); 1208 addNewAttributeValue(); 1209 event.preventDefault(); 1210 event.stopPropagation(); 1211 1212 } 1213 }, ClickEvent.getType()); 1214 1215 m_removeButton.setImageClass(I_CmsButton.CUT_SMALL); 1216 m_removeButton.setButtonStyle(ButtonStyle.FONT_ICON, null); 1217 m_removeButton.setHideFromTabNav(true); 1218 1219 m_upButton.setImageClass(I_CmsButton.EDIT_UP_SMALL); 1220 m_upButton.setButtonStyle(ButtonStyle.FONT_ICON, null); 1221 m_upButton.setHideFromTabNav(true); 1222 1223 m_downButton.setImageClass(I_CmsButton.EDIT_DOWN_SMALL); 1224 m_downButton.setButtonStyle(ButtonStyle.FONT_ICON, null); 1225 m_downButton.setHideFromTabNav(true); 1226 1227 m_helpBubbleClose.setImageClass(I_CmsButton.DELETE_SMALL); 1228 m_helpBubbleClose.setButtonStyle(ButtonStyle.FONT_ICON, null); 1229 m_helpBubbleClose.setHideFromTabNav(true); 1230 if (CmsEditorBase.hasDictionary()) { 1231 m_addButton.setTitle(CmsEditorBase.getMessageForKey(CmsEditorBase.GUI_VIEW_ADD_1, m_label)); 1232 m_attributeChoice.setTitle(CmsEditorBase.getMessageForKey(CmsEditorBase.GUI_CHOICE_ADD_CHOICE_1, m_label)); 1233 m_removeButton.setTitle(CmsEditorBase.getMessageForKey(CmsEditorBase.GUI_VIEW_DELETE_1, m_label)); 1234 m_helpBubbleClose.setTitle(CmsEditorBase.getMessageForKey(CmsEditorBase.GUI_VIEW_CLOSE_0)); 1235 m_upButton.setTitle(CmsEditorBase.getMessageForKey(CmsEditorBase.GUI_VIEW_MOVE_UP_0, m_label)); 1236 m_downButton.setTitle(CmsEditorBase.getMessageForKey(CmsEditorBase.GUI_VIEW_MOVE_DOWN_0, m_label)); 1237 } 1238 } 1239 1240 /** 1241 * Initializes the highlighting handler.<p> 1242 */ 1243 private void initHighlightingHandler() { 1244 1245 addMouseOverHandler(CmsValueFocusHandler.getInstance()); 1246 addMouseOutHandler(CmsValueFocusHandler.getInstance()); 1247 addMouseDownHandler(CmsValueFocusHandler.getInstance()); 1248 } 1249 1250 /** 1251 * Removes the drag helper styles from the given element.<p> 1252 * 1253 * @param helper the helper element 1254 */ 1255 private void removeDragHelperStyles(Element helper) { 1256 1257 Style style = helper.getStyle(); 1258 style.clearTop(); 1259 style.clearLeft(); 1260 style.clearPosition(); 1261 style.clearWidth(); 1262 helper.removeClassName(formCss().dragHelper()); 1263 } 1264 1265 /** 1266 * Removes the label hover mouse handler.<p> 1267 */ 1268 private void removeLabelHoverHandler() { 1269 1270 if (m_labelOverRegistration != null) { 1271 m_labelOverRegistration.removeHandler(); 1272 m_labelOverRegistration = null; 1273 } 1274 if (m_labelOutRegistration != null) { 1275 m_labelOutRegistration.removeHandler(); 1276 m_labelOutRegistration = null; 1277 } 1278 } 1279 1280 /** 1281 * Returns if the help bubble should be displayed above the value field.<p> 1282 * 1283 * @return <code>true</code> if the help bubble should be displayed above 1284 */ 1285 private boolean shouldDisplayTooltipAbove() { 1286 1287 boolean displayAbove; 1288 if (isSimpleValue()) { 1289 Direction direction = Direction.none; 1290 if (m_widget instanceof I_CmsHasDisplayDirection) { 1291 direction = ((I_CmsHasDisplayDirection)m_widget).getDisplayingDirection(); 1292 } 1293 switch (direction) { 1294 case above: 1295 displayAbove = false; 1296 break; 1297 case below: 1298 case none: 1299 default: 1300 displayAbove = true; 1301 break; 1302 } 1303 } else { 1304 displayAbove = true; 1305 } 1306 1307 m_helpBubble.getStyle().setDisplay(Display.BLOCK); 1308 int bubbleHeight = m_helpBubble.getOffsetHeight(); 1309 m_helpBubble.getStyle().clearDisplay(); 1310 1311 Element widgetElement; 1312 if (m_widget != null) { 1313 widgetElement = m_widget.asWidget().getElement(); 1314 } else { 1315 return true; 1316 } 1317 1318 // Calculate top position for the popup 1319 int top = widgetElement.getAbsoluteTop(); 1320 1321 int windowTop = Window.getScrollTop(); 1322 int windowBottom = Window.getScrollTop() + Window.getClientHeight(); 1323 int distanceFromWindowTop = top - windowTop - TOOLBAR_HEIGHT; 1324 1325 int distanceToWindowBottom = windowBottom - (top + widgetElement.getOffsetHeight()); 1326 if (displayAbove 1327 && ((distanceFromWindowTop < bubbleHeight) && (distanceToWindowBottom > distanceFromWindowTop))) { 1328 // in case there is too little space above, and there is more below, change direction 1329 displayAbove = false; 1330 } else if (!displayAbove 1331 && ((distanceToWindowBottom < bubbleHeight) && (distanceFromWindowTop > distanceToWindowBottom)) 1332 && !m_hasError) { 1333 // in case there is too little space below, and there is more above, change direction 1334 // (exception for m_hasError: when displaying a validation error for a widget that displays a popup above it, disregard the 1335 // available space under it, because if we display the message below, the page expands so the user can scroll down to read it, which is less 1336 // bad than the message covering a popup required to use the widget). 1337 displayAbove = true; 1338 } 1339 return displayAbove; 1340 } 1341}