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.contenteditor.client; 029 030import org.opencms.acacia.client.CmsAttributeHandler; 031import org.opencms.acacia.client.CmsEditorBase; 032import org.opencms.acacia.client.CmsUndoRedoHandler; 033import org.opencms.acacia.client.CmsUndoRedoHandler.UndoRedoState; 034import org.opencms.acacia.client.CmsValidationContext; 035import org.opencms.acacia.client.CmsValueFocusHandler; 036import org.opencms.acacia.client.I_CmsEntityRenderer; 037import org.opencms.acacia.client.I_CmsInlineFormParent; 038import org.opencms.acacia.client.entity.CmsEntityBackend; 039import org.opencms.acacia.shared.CmsEntity; 040import org.opencms.acacia.shared.CmsEntityAttribute; 041import org.opencms.acacia.shared.CmsEntityChangeEvent; 042import org.opencms.acacia.shared.CmsEntityChangeEvent.ChangeType; 043import org.opencms.acacia.shared.CmsTabInfo; 044import org.opencms.acacia.shared.CmsType; 045import org.opencms.acacia.shared.CmsValidationResult; 046import org.opencms.ade.containerpage.client.CmsContainerpageController; 047import org.opencms.ade.contenteditor.client.css.I_CmsLayoutBundle; 048import org.opencms.ade.contenteditor.shared.CmsComplexWidgetData; 049import org.opencms.ade.contenteditor.shared.CmsContentDefinition; 050import org.opencms.ade.contenteditor.shared.CmsEditHandlerData; 051import org.opencms.ade.contenteditor.shared.CmsSaveResult; 052import org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService; 053import org.opencms.ade.contenteditor.shared.rpc.I_CmsContentServiceAsync; 054import org.opencms.ade.contenteditor.widgetregistry.client.WidgetRegistry; 055import org.opencms.ade.publish.client.CmsPublishDialog; 056import org.opencms.ade.publish.shared.CmsPublishOptions; 057import org.opencms.gwt.client.CmsCoreProvider; 058import org.opencms.gwt.client.I_CmsEditableData; 059import org.opencms.gwt.client.rpc.CmsRpcAction; 060import org.opencms.gwt.client.rpc.CmsRpcPrefetcher; 061import org.opencms.gwt.client.ui.CmsConfirmDialog; 062import org.opencms.gwt.client.ui.CmsErrorDialog; 063import org.opencms.gwt.client.ui.CmsInfoHeader; 064import org.opencms.gwt.client.ui.CmsModelSelectDialog; 065import org.opencms.gwt.client.ui.CmsNotification; 066import org.opencms.gwt.client.ui.CmsNotification.Type; 067import org.opencms.gwt.client.ui.CmsPushButton; 068import org.opencms.gwt.client.ui.CmsToggleButton; 069import org.opencms.gwt.client.ui.CmsToolbar; 070import org.opencms.gwt.client.ui.I_CmsButton; 071import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle; 072import org.opencms.gwt.client.ui.I_CmsButton.Size; 073import org.opencms.gwt.client.ui.I_CmsConfirmDialogHandler; 074import org.opencms.gwt.client.ui.I_CmsModelSelectHandler; 075import org.opencms.gwt.client.ui.input.CmsSelectBox; 076import org.opencms.gwt.client.util.CmsDebugLog; 077import org.opencms.gwt.client.util.CmsDomUtil; 078import org.opencms.gwt.client.util.I_CmsSimpleCallback; 079import org.opencms.gwt.shared.CmsGwtConstants; 080import org.opencms.gwt.shared.CmsListInfoBean; 081import org.opencms.util.CmsStringUtil; 082import org.opencms.util.CmsUUID; 083 084import java.util.ArrayList; 085import java.util.Collection; 086import java.util.Collections; 087import java.util.HashMap; 088import java.util.HashSet; 089import java.util.List; 090import java.util.Map; 091import java.util.Map.Entry; 092import java.util.Set; 093 094import com.google.gwt.core.client.GWT; 095import com.google.gwt.core.client.JavaScriptObject; 096import com.google.gwt.core.client.Scheduler; 097import com.google.gwt.core.client.Scheduler.ScheduledCommand; 098import com.google.gwt.dom.client.Element; 099import com.google.gwt.dom.client.NodeList; 100import com.google.gwt.dom.client.Style; 101import com.google.gwt.dom.client.Style.Overflow; 102import com.google.gwt.dom.client.Style.Position; 103import com.google.gwt.dom.client.Style.Unit; 104import com.google.gwt.dom.client.Style.VerticalAlign; 105import com.google.gwt.event.dom.client.ClickEvent; 106import com.google.gwt.event.dom.client.ClickHandler; 107import com.google.gwt.event.dom.client.KeyCodes; 108import com.google.gwt.event.logical.shared.CloseEvent; 109import com.google.gwt.event.logical.shared.CloseHandler; 110import com.google.gwt.event.logical.shared.SelectionEvent; 111import com.google.gwt.event.logical.shared.SelectionHandler; 112import com.google.gwt.event.logical.shared.ValueChangeEvent; 113import com.google.gwt.event.logical.shared.ValueChangeHandler; 114import com.google.gwt.event.shared.HandlerRegistration; 115import com.google.gwt.user.client.Command; 116import com.google.gwt.user.client.Event; 117import com.google.gwt.user.client.Event.NativePreviewEvent; 118import com.google.gwt.user.client.Event.NativePreviewHandler; 119import com.google.gwt.user.client.Window; 120import com.google.gwt.user.client.Window.ClosingEvent; 121import com.google.gwt.user.client.Window.ClosingHandler; 122import com.google.gwt.user.client.rpc.AsyncCallback; 123import com.google.gwt.user.client.rpc.SerializationException; 124import com.google.gwt.user.client.rpc.ServiceDefTarget; 125import com.google.gwt.user.client.ui.FlowPanel; 126import com.google.gwt.user.client.ui.Label; 127import com.google.gwt.user.client.ui.PopupPanel; 128import com.google.gwt.user.client.ui.RootPanel; 129import com.google.gwt.user.client.ui.SimplePanel; 130import com.google.gwt.user.client.ui.TextBox; 131 132/** 133 * The content editor.<p> 134 */ 135public final class CmsContentEditor extends CmsEditorBase { 136 137 /** 138 * CmsEntity change handler to watch for changes within given scopes and call the editor change handlers accordingly.<p> 139 */ 140 class EditorChangeHandler implements ValueChangeHandler<CmsEntity> { 141 142 /** The scope values. */ 143 Map<String, String> m_scopeValues; 144 145 private Set<String> m_changedScopes = new HashSet<>(); 146 147 /** The change handler registration. */ 148 private HandlerRegistration m_handlerRegistration; 149 150 /** The observed entity. */ 151 private CmsEntity m_observerdEntity; 152 153 /** 154 * Constructor.<p> 155 * 156 * @param entity the entity to observe 157 * @param changeScopes the value scopes to watch for changes 158 */ 159 public EditorChangeHandler(CmsEntity entity, Collection<String> changeScopes) { 160 161 m_observerdEntity = entity; 162 m_handlerRegistration = entity.addValueChangeHandler(this); 163 m_scopeValues = new HashMap<String, String>(); 164 for (String scope : changeScopes) { 165 m_scopeValues.put(scope, CmsContentDefinition.getValueForPath(m_observerdEntity, scope)); 166 } 167 } 168 169 /** 170 * Removes this observer from the entities change handler registration and clears registered listeners.<p> 171 */ 172 public void clear() { 173 174 if (m_handlerRegistration != null) { 175 m_handlerRegistration.removeHandler(); 176 m_handlerRegistration = null; 177 } 178 m_scopeValues.clear(); 179 m_observerdEntity = null; 180 } 181 182 /** 183 * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent) 184 */ 185 public void onValueChange(ValueChangeEvent<CmsEntity> event) { 186 187 final CmsEntity entity = event.getValue(); 188 if (event instanceof CmsEntityChangeEvent) { 189 190 CmsEntityChangeEvent.ChangeType type = ((CmsEntityChangeEvent)event).getChangeType(); 191 for (String scope : m_scopeValues.keySet()) { 192 String scopeValue = CmsContentDefinition.getValueForPath(entity, scope); 193 String previousValue = m_scopeValues.get(scope); 194 if (((scopeValue != null) && !scopeValue.equals(previousValue)) 195 || ((scopeValue == null) && (previousValue != null))) { 196 if (type == ChangeType.change) { 197 m_changedScopes.add(scope); 198 } 199 m_scopeValues.put(scope, scopeValue); 200 } 201 } 202 203 } 204 Scheduler.get().scheduleFinally(new ScheduledCommand() { 205 206 public void execute() { 207 208 if (m_changedScopes.size() > 0) { 209 Set<String> changedScopesCopy = new HashSet<>(m_changedScopes); 210 m_changedScopes.clear(); 211 if (!changedScopesCopy.isEmpty()) { 212 callEditorChangeHandlers(changedScopesCopy); 213 } 214 } 215 } 216 }); 217 } 218 } 219 220 /** CSS marker class added to the html element when the editor is active. */ 221 public static final String EDITOR_MARKER_CLASS = "opencms-editor-active"; 222 223 /** The add change listener method name. */ 224 private static final String ADD_CHANGE_LISTENER_METHOD = "cmsAddEntityChangeListener"; 225 226 /** The entity id selector prefix. */ 227 private static final String ENTITY_ID_SELECTOR_PREFIX = "[" + CmsGwtConstants.ATTR_DATA_ID + "*=\""; 228 229 /** The entity id selector suffix. */ 230 private static final String ENTITY_ID_SELECTOR_SUFFIX = "\"]"; 231 232 /** The editable field selector. */ 233 private static final String FIELD_SELECTOR = "[" + CmsGwtConstants.ATTR_DATA_FIELD + "*=\"opencms://\"]"; 234 235 /** The get current entity method name. */ 236 private static final String GET_CURRENT_ENTITY_METHOD = "cmsGetCurrentEntity"; 237 238 /** The in-line editor instance. */ 239 private static CmsContentEditor INSTANCE; 240 241 /** Flag indicating that an AJAX call for the editor change handler is running. */ 242 protected boolean m_callingChangeHandlers; 243 244 /** The current content locale. */ 245 protected String m_locale; 246 247 /** The on close call back. */ 248 protected I_CmsEditorCloseHandler m_onClose; 249 250 /** The edit tool-bar. */ 251 protected CmsToolbar m_toolbar; 252 253 /** The container element client id if available. */ 254 String m_clientId; 255 256 /** The editor context. */ 257 CmsEditorContext m_context; 258 259 /** Indicates if any settings attributes have been changed. */ 260 boolean m_hasChangedSettings; 261 262 /** Value of the auto-unlock option from the configuration. */ 263 private boolean m_autoUnlock; 264 265 /** The available locales. */ 266 private Map<String, String> m_availableLocales; 267 268 /** The form editing base panel. */ 269 private FlowPanel m_basePanel; 270 271 /** The cancel button. */ 272 private CmsPushButton m_cancelButton; 273 274 /** The id's of the changed entities. */ 275 private Set<String> m_changedEntityIds; 276 277 /** Changed scopes which still haven't been sent to the editor change handlers. */ 278 private Set<String> m_changedScopes = new HashSet<String>(); 279 280 /** The window closing handler registration. */ 281 private HandlerRegistration m_closingHandlerRegistration; 282 283 /** The content info panel. */ 284 private CmsInfoHeader m_contentInfoHeader; 285 286 /** The locales present within the edited content. */ 287 private Set<String> m_contentLocales; 288 289 /** The copy locale button. */ 290 private CmsPushButton m_copyLocaleButton; 291 292 /** The loaded content definitions by locale. */ 293 private Map<String, CmsContentDefinition> m_definitions; 294 295 /** The entities to delete. */ 296 private Set<String> m_deletedEntities; 297 298 /** The delete locale button. */ 299 private CmsPushButton m_deleteLocaleButton; 300 301 /** Flag indicating the resource needs to removed on cancel. */ 302 private boolean m_deleteOnCancel; 303 304 /** The entity value change handler calling configured editor change handlers. */ 305 private EditorChangeHandler m_editorChangeHandler; 306 307 /** The entity observer instance. */ 308 private CmsEntityObserver m_entityObserver; 309 310 /** The hide help bubbles button. */ 311 private CmsToggleButton m_hideHelpBubblesButton; 312 313 /** The resource icon classes. */ 314 private String m_iconClasses; 315 316 /** Flag which indicate whether the directedit parameter was set to true when loading the editor. */ 317 private boolean m_isDirectEdit; 318 319 /** Flag indicating the editor was opened as the stand alone version, not from within any other module. */ 320 private boolean m_isStandAlone; 321 322 /** The locale select box. */ 323 private CmsSelectBox m_localeSelect; 324 325 /** The open form button. */ 326 private CmsPushButton m_openFormButton; 327 328 /** The page info panel. */ 329 private CmsInfoHeader m_pageInfoHeader; 330 331 /** The event preview handler registration. */ 332 private HandlerRegistration m_previewHandlerRegistration; 333 334 /** The publish button. */ 335 private CmsPushButton m_publishButton; 336 337 /** The redo button. */ 338 private CmsPushButton m_redoButton; 339 340 /** The registered entity id's. */ 341 private Set<String> m_registeredEntities; 342 343 /** The resource type name. */ 344 private String m_resourceTypeName; 345 346 /** The save button. */ 347 private CmsPushButton m_saveButton; 348 349 /** The save and exit button. */ 350 private CmsPushButton m_saveExitButton; 351 352 /** The content service. */ 353 private I_CmsContentServiceAsync m_service; 354 355 /** The resource site path. */ 356 private String m_sitePath; 357 358 /** The tab informations for this form. */ 359 private List<CmsTabInfo> m_tabInfos; 360 361 /** The resource title. */ 362 private String m_title; 363 364 /** The undo button. */ 365 private CmsPushButton m_undoButton; 366 367 /** The undo redo event handler registration. */ 368 private HandlerRegistration m_undoRedoHandlerRegistration; 369 370 /** 371 * Constructor.<p> 372 */ 373 private CmsContentEditor() { 374 375 super((I_CmsContentServiceAsync)GWT.create(I_CmsContentService.class), new CmsDefaultWidgetService()); 376 m_service = (I_CmsContentServiceAsync)super.getService(); 377 String serviceUrl = CmsCoreProvider.get().link("org.opencms.ade.contenteditor.CmsContentService.gwt"); 378 ((ServiceDefTarget)m_service).setServiceEntryPoint(serviceUrl); 379 getWidgetService().setWidgetFactories(WidgetRegistry.getInstance().getWidgetFactories()); 380 for (I_CmsEntityRenderer renderer : WidgetRegistry.getInstance().getRenderers()) { 381 getWidgetService().addRenderer(renderer); 382 } 383 // set the acacia editor message bundle 384 setDictionary(Messages.get().getDictionary()); 385 I_CmsLayoutBundle.INSTANCE.editorCss().ensureInjected(); 386 m_changedEntityIds = new HashSet<String>(); 387 m_registeredEntities = new HashSet<String>(); 388 m_availableLocales = new HashMap<String, String>(); 389 m_contentLocales = new HashSet<String>(); 390 m_deletedEntities = new HashSet<String>(); 391 m_definitions = new HashMap<String, CmsContentDefinition>(); 392 addValidationChangeHandler(new ValueChangeHandler<CmsValidationContext>() { 393 394 public void onValueChange(ValueChangeEvent<CmsValidationContext> event) { 395 396 handleValidationChange(event.getValue()); 397 } 398 }); 399 } 400 401 /** 402 * Adds an entity change listener.<p> 403 * 404 * @param changeListener the change listener 405 * @param changeScope the change scope 406 */ 407 public static void addEntityChangeListener(I_CmsEntityChangeListener changeListener, String changeScope) { 408 409 CmsDebugLog.getInstance().printLine("trying to ad change listener for scope: " + changeScope); 410 if ((INSTANCE == null) || (INSTANCE.m_entityObserver == null)) { 411 CmsDebugLog.getInstance().printLine("handling external registration"); 412 if (isObserverExported()) { 413 CmsDebugLog.getInstance().printLine("registration is available"); 414 try { 415 addNativeListener(changeListener, changeScope); 416 } catch (Exception e) { 417 418 CmsDebugLog.getInstance().printLine( 419 "Exception occured during listener registration" + e.getMessage()); 420 } 421 } else { 422 throw new RuntimeException("Editor is not initialized yet."); 423 } 424 } else { 425 INSTANCE.m_entityObserver.addEntityChangeListener(changeListener, changeScope); 426 } 427 } 428 429 /** 430 * Gets the client id for the editable element.<p> 431 * 432 * @param editableData the editable element 433 * 434 * @return the client id for the editable element 435 */ 436 public static String getClientIdForEditable(final I_CmsEditableData editableData) { 437 438 return ((editableData.getElementName() != null) 439 && editableData.getElementName().startsWith(editableData.getStructureId().toString())) 440 ? editableData.getElementName() 441 : editableData.getStructureId().toString(); 442 } 443 444 /** 445 * Returns the currently edited entity.<p> 446 * 447 * @return the currently edited entity 448 */ 449 public static CmsEntity getEntity() { 450 451 if ((INSTANCE == null) || (INSTANCE.m_entityObserver == null)) { 452 CmsDebugLog.getInstance().printLine("handling external registration"); 453 if (isObserverExported()) { 454 return CmsEntityBackend.createFromNativeWrapper(nativeGetEntity()); 455 } else { 456 throw new RuntimeException("Editor is not initialized yet."); 457 } 458 } else { 459 return INSTANCE.getCurrentEntity(); 460 } 461 } 462 463 /** 464 * Returns the in-line editor instance.<p> 465 * 466 * @return the in-line editor instance 467 */ 468 public static CmsContentEditor getInstance() { 469 470 if (INSTANCE == null) { 471 INSTANCE = new CmsContentEditor(); 472 } 473 return INSTANCE; 474 } 475 476 /** 477 * Returns if the given element or it's descendants are inline editable.<p> 478 * 479 * @param element the element 480 * 481 * @return <code>true</code> if the element has editable descendants 482 */ 483 public static boolean hasEditable(Element element) { 484 485 NodeList<Element> children = CmsDomUtil.querySelectorAll(FIELD_SELECTOR, element); 486 return (children != null) && (children.getLength() > 0); 487 } 488 489 /** 490 * Checks whether the given element is annotated for inline editing.<p> 491 * 492 * @param element the element to check 493 * 494 * @return <code>true</code> if the given element is annotated for inline editing 495 */ 496 public static boolean isEditable(Element element) { 497 498 String field = element.getAttribute(CmsGwtConstants.ATTR_DATA_FIELD); 499 return (field != null) && field.contains("opencms://"); 500 } 501 502 /** 503 * Replaces the id's within data-oc-id attributes of the given element and all it's children.<p> 504 * 505 * @param element the element 506 * @param oldId the old id 507 * @param newId the new id 508 */ 509 public static void replaceResourceIds(Element element, String oldId, String newId) { 510 511 NodeList<Element> children = CmsDomUtil.querySelectorAll( 512 FIELD_SELECTOR + ENTITY_ID_SELECTOR_PREFIX + oldId + ENTITY_ID_SELECTOR_SUFFIX, 513 element); 514 if (children.getLength() > 0) { 515 for (int i = 0; i < children.getLength(); i++) { 516 Element child = children.getItem(i); 517 String idData = child.getAttribute(CmsGwtConstants.ATTR_DATA_ID); 518 idData = idData.replace(oldId, newId); 519 child.setAttribute(CmsGwtConstants.ATTR_DATA_ID, idData); 520 } 521 } 522 } 523 524 /** 525 * Sets all annotated child elements editable.<p> 526 * 527 * @param element the element 528 * @param serverId the editable resource structure id 529 * @param editable <code>true</code> to enable editing 530 * 531 * @return <code>true</code> if the element had editable elements 532 */ 533 public static boolean setEditable(Element element, String serverId, boolean editable) { 534 535 I_CmsLayoutBundle.INSTANCE.editorCss().ensureInjected(); 536 NodeList<Element> children = CmsDomUtil.querySelectorAll( 537 FIELD_SELECTOR + ENTITY_ID_SELECTOR_PREFIX + serverId + ENTITY_ID_SELECTOR_SUFFIX, 538 element); 539 if (children.getLength() > 0) { 540 for (int i = 0; i < children.getLength(); i++) { 541 Element child = children.getItem(i); 542 if (editable) { 543 child.addClassName(I_CmsLayoutBundle.INSTANCE.editorCss().inlineEditable()); 544 } else { 545 child.removeClassName(I_CmsLayoutBundle.INSTANCE.editorCss().inlineEditable()); 546 } 547 } 548 return true; 549 } 550 return false; 551 } 552 553 /** 554 * Adds the change listener.<p> 555 * 556 * @param changeListener the change listener 557 * @param changeScope the change scope 558 */ 559 static native void addNativeListener(I_CmsEntityChangeListener changeListener, String changeScope)/*-{ 560 var instance = changeListener; 561 var nat = { 562 onChange : function(entity) { 563 var cmsEntity = @org.opencms.acacia.client.entity.CmsEntityBackend::createFromNativeWrapper(Lcom/google/gwt/core/client/JavaScriptObject;)(entity); 564 instance.@org.opencms.ade.contenteditor.client.I_CmsEntityChangeListener::onEntityChange(Lorg/opencms/acacia/shared/CmsEntity;)(cmsEntity); 565 } 566 } 567 var method = $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::ADD_CHANGE_LISTENER_METHOD]; 568 if (typeof method == 'function') { 569 method(nat, changeScope); 570 } 571 }-*/; 572 573 /** 574 * Checks whether the add entity change listener method has been exported.<p> 575 * 576 * @return <code>true</code> if the add entity change listener method has been exported 577 */ 578 private static native boolean isObserverExported()/*-{ 579 var method = $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::ADD_CHANGE_LISTENER_METHOD]; 580 if (typeof method == 'function') { 581 return true; 582 } else { 583 return false; 584 } 585 }-*/; 586 587 /** 588 * Returns the current entity.<p> 589 * 590 * @return the current entity 591 */ 592 private static native JavaScriptObject nativeGetEntity()/*-{ 593 return $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::GET_CURRENT_ENTITY_METHOD] 594 (); 595 }-*/; 596 597 /** 598 * Closes the editor.<p> 599 * May be used from outside the editor module.<p> 600 */ 601 public void closeEditor() { 602 603 if (m_saveButton != null) { 604 if (m_saveButton.isEnabled()) { 605 CmsConfirmDialog dialog = new CmsConfirmDialog( 606 org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TITLE_0), 607 Messages.get().key(Messages.GUI_CONFIRM_LEAVING_EDITOR_0)); 608 dialog.setHandler(new I_CmsConfirmDialogHandler() { 609 610 public void onClose() { 611 612 cancelEdit(); 613 } 614 615 public void onOk() { 616 617 saveAndExit(); 618 } 619 }); 620 dialog.center(); 621 } else { 622 cancelEdit(); 623 } 624 } 625 } 626 627 /** 628 * Bypasses a focus bug in IE which can happen if the user opens the HTML code editor from the WYSIWYG editor.<p> 629 * 630 * The next time they open the editor form from the same container page, the user may be unable to focus on any input 631 * fields. To prevent this, we create a dummy input field outside the visible screen region and focus it when opening 632 * the editor. 633 */ 634 public void fixFocus() { 635 636 TextBox invisibleTextBox = new TextBox(); 637 Style style = invisibleTextBox.getElement().getStyle(); 638 style.setPosition(Position.FIXED); 639 style.setLeft(-99999, Unit.PX); 640 style.setTop(-99999, Unit.PX); 641 m_basePanel.add(invisibleTextBox); 642 // base panel is already attached at this point, so we can just set the focus 643 invisibleTextBox.setFocus(true); 644 } 645 646 /** 647 * @see org.opencms.acacia.client.CmsEditorBase#getService() 648 */ 649 @Override 650 public I_CmsContentServiceAsync getService() { 651 652 return m_service; 653 } 654 655 /** 656 * Loads the content definition for the given entity and executes the callback on success.<p> 657 * 658 * @param entityId the entity id 659 * @param editedEntity the currently edited entity 660 * @param callback the callback 661 */ 662 public void loadDefinition( 663 final String entityId, 664 final CmsEntity editedEntity, 665 final I_CmsSimpleCallback<CmsContentDefinition> callback) { 666 667 CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() { 668 669 @Override 670 public void execute() { 671 672 start(0, true); 673 getService().loadDefinition( 674 entityId, 675 m_clientId, 676 editedEntity, 677 getSkipPaths(), 678 m_context.getSettingPresets(), 679 this); 680 } 681 682 @Override 683 protected void onResponse(final CmsContentDefinition result) { 684 685 registerContentDefinition(result); 686 WidgetRegistry.getInstance().registerExternalWidgets( 687 result.getExternalWidgetConfigurations(), 688 new Command() { 689 690 public void execute() { 691 692 stop(false); 693 callback.execute(result); 694 } 695 }); 696 } 697 }; 698 action.execute(); 699 } 700 701 /** 702 * Loads the content definition for the given entity and executes the callback on success.<p> 703 * 704 * @param entityId the entity id 705 * @param newLink the new link 706 * @param modelFileId the model file id 707 * @param postCreateHandler the post-create handler class name (optional) 708 * @param mode the content creation mode 709 * @param mainLocale the main language to copy in case the element language node does not exist yet 710 * @param editHandlerData the data for the edit handler, if one is used to create a new content; null otherwise 711 * @param settingPresets the presets for container element settings 712 * @param editorStylesheet the path for the editor style sheet (may be null) 713 * @param callback the callback 714 */ 715 public void loadInitialDefinition( 716 final String entityId, 717 final String newLink, 718 final CmsUUID modelFileId, 719 final String postCreateHandler, 720 final String mode, 721 final String mainLocale, 722 final CmsEditHandlerData editHandlerData, 723 Map<String, String> settingPresets, 724 String editorStylesheet, 725 final I_CmsSimpleCallback<CmsContentDefinition> callback) { 726 727 CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() { 728 729 @Override 730 public void execute() { 731 732 start(0, true); 733 getService().loadInitialDefinition( 734 entityId, 735 m_clientId, 736 newLink, 737 modelFileId, 738 CmsCoreProvider.get().getUri(), 739 mainLocale, 740 postCreateHandler, 741 mode, 742 editHandlerData, 743 settingPresets, 744 editorStylesheet, 745 this); 746 } 747 748 @Override 749 protected void onResponse(final CmsContentDefinition result) { 750 751 if (result.isModelInfo()) { 752 stop(false); 753 callback.execute(result); 754 } else { 755 registerContentDefinition(result); 756 WidgetRegistry.getInstance().registerExternalWidgets( 757 result.getExternalWidgetConfigurations(), 758 new Command() { 759 760 public void execute() { 761 762 stop(false); 763 callback.execute(result); 764 } 765 }); 766 } 767 } 768 }; 769 action.execute(); 770 } 771 772 /** 773 * Loads the content definition for the given entity and executes the callback on success.<p> 774 * 775 * @param entityId the entity id 776 * @param editedEntity the currently edited entity 777 * @param callback the callback 778 */ 779 public void loadNewDefinition( 780 final String entityId, 781 final CmsEntity editedEntity, 782 final I_CmsSimpleCallback<CmsContentDefinition> callback) { 783 784 final Map<String, String> presets = new HashMap<>(); 785 if ((m_context != null) && (m_context.getSettingPresets() != null)) { 786 presets.putAll(m_context.getSettingPresets()); 787 } 788 CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() { 789 790 @Override 791 public void execute() { 792 793 start(0, true); 794 getService().loadNewDefinition(entityId, m_clientId, editedEntity, getSkipPaths(), presets, this); 795 } 796 797 @Override 798 protected void onResponse(final CmsContentDefinition result) { 799 800 registerContentDefinition(result); 801 WidgetRegistry.getInstance().registerExternalWidgets( 802 result.getExternalWidgetConfigurations(), 803 new Command() { 804 805 public void execute() { 806 807 stop(false); 808 callback.execute(result); 809 } 810 }); 811 } 812 }; 813 action.execute(); 814 } 815 816 /** 817 * Opens the content editor dialog.<p> 818 * 819 * @param context the editor context 820 * @param locale the content locale 821 * @param elementId the element id 822 * @param clientId the container element client id if available 823 * @param newLink the new link 824 * @param modelFileId the model file id 825 * @param postCreateHandler the post-create handler class (optional) 826 * @param mode the content creation mode 827 * @param mainLocale the main language to copy in case the element language node does not exist yet 828 * @param editHandlerData the edit handler data, if we are using an edit handler to create a new element; null otherwise 829 * @param onClose the command executed on dialog close 830 */ 831 public void openFormEditor( 832 final CmsEditorContext context, 833 final String locale, 834 String elementId, 835 String clientId, 836 final String newLink, 837 final CmsUUID modelFileId, 838 final String postCreateHandler, 839 final String mode, 840 final String mainLocale, 841 final CmsEditHandlerData editHandlerData, 842 final I_CmsEditorCloseHandler onClose) { 843 844 m_onClose = onClose; 845 m_clientId = clientId; 846 initEventPreviewHandler(); 847 final CmsUUID structureId = new CmsUUID(elementId); 848 849 I_CmsSimpleCallback<Boolean> callback = new I_CmsSimpleCallback<Boolean>() { 850 851 public void execute(Boolean arg) { 852 853 if (arg.booleanValue()) { 854 loadInitialDefinition( 855 CmsContentDefinition.uuidToEntityId(structureId, locale), 856 newLink, 857 modelFileId, 858 mode, 859 postCreateHandler, 860 mainLocale, 861 editHandlerData, 862 context.getSettingPresets(), 863 context.getEditorStylesheet(), 864 new I_CmsSimpleCallback<CmsContentDefinition>() { 865 866 public void execute(CmsContentDefinition contentDefinition) { 867 868 if (contentDefinition.isModelInfo()) { 869 openModelSelectDialog(context, contentDefinition); 870 } else { 871 initEditor(context, contentDefinition, null, false, null); 872 } 873 } 874 }); 875 } else { 876 showLockedResourceMessage(); 877 } 878 879 } 880 }; 881 // make sure the resource is locked, if we are not creating a new one 882 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(newLink)) { 883 callback.execute(Boolean.TRUE); 884 } else { 885 CmsCoreProvider.get().lock(structureId, callback); 886 } 887 } 888 889 /** 890 * Renders the in-line editor for the given element.<p> 891 * 892 * @param context the editor context 893 * @param elementId the element id 894 * @param locale the content locale 895 * @param panel the element panel 896 * @param mainLocale the main language to copy in case the element language node does not exist yet 897 * @param loadTime the time when the requested resource was loaded 898 * @param onClose the command to execute on close 899 */ 900 public void openInlineEditor( 901 final CmsEditorContext context, 902 CmsUUID elementId, 903 String locale, 904 final I_CmsInlineFormParent panel, 905 final String mainLocale, 906 long loadTime, 907 I_CmsEditorCloseHandler onClose) { 908 909 initEventPreviewHandler(); 910 final String entityId = CmsContentDefinition.uuidToEntityId(elementId, locale); 911 m_locale = locale; 912 m_onClose = onClose; 913 CmsCoreProvider.get().lock(elementId, loadTime, new I_CmsSimpleCallback<Boolean>() { 914 915 public void execute(Boolean arg) { 916 917 if (arg.booleanValue()) { 918 loadInitialDefinition( 919 entityId, 920 null, 921 null, 922 null, 923 null, 924 mainLocale, 925 null, 926 Collections.emptyMap(), 927 context.getEditorStylesheet(), 928 new I_CmsSimpleCallback<CmsContentDefinition>() { 929 930 public void execute(CmsContentDefinition contentDefinition) { 931 932 initEditor(context, contentDefinition, panel, true, mainLocale); 933 } 934 }); 935 } else { 936 showLockedResourceMessage(); 937 } 938 } 939 }); 940 } 941 942 /** 943 * Opens the form based editor. Used within the stand alone acacia/editor.jsp.<p> 944 * 945 * @param context the editor context 946 */ 947 public void openStandAloneFormEditor(final CmsEditorContext context) { 948 949 initEventPreviewHandler(); 950 final CmsContentDefinition definition; 951 try { 952 definition = (CmsContentDefinition)CmsRpcPrefetcher.getSerializedObjectFromDictionary( 953 getService(), 954 I_CmsContentService.DICT_CONTENT_DEFINITION); 955 } catch (SerializationException e) { 956 RootPanel.get().add(new Label(e.getMessage())); 957 return; 958 } 959 m_isStandAlone = true; 960 if (definition.isModelInfo()) { 961 openModelSelectDialog(context, definition); 962 } else { 963 CmsCoreProvider.get().lock( 964 CmsContentDefinition.entityIdToUuid(definition.getEntityId()), 965 new I_CmsSimpleCallback<Boolean>() { 966 967 public void execute(Boolean arg) { 968 969 if (arg.booleanValue()) { 970 971 registerContentDefinition(definition); 972 // register all external widgets 973 WidgetRegistry.getInstance().registerExternalWidgets( 974 definition.getExternalWidgetConfigurations(), 975 new Command() { 976 977 public void execute() { 978 979 initEditor(context, definition, null, false, null); 980 } 981 }); 982 983 } else { 984 showLockedResourceMessage(); 985 } 986 } 987 }); 988 } 989 } 990 991 /** 992 * Registers a deep copy of the source entity with the given target entity id.<p> 993 * 994 * @param sourceEntityId the source entity id 995 * @param targetEntityId the target entity id 996 */ 997 public void registerClonedEntity(String sourceEntityId, String targetEntityId) { 998 999 CmsEntityBackend.getInstance().getEntity(sourceEntityId).createDeepCopy(targetEntityId); 1000 } 1001 1002 /** 1003 * Registers the given content definition.<p> 1004 * 1005 * @param definition the content definition 1006 */ 1007 public void registerContentDefinition(CmsContentDefinition definition) { 1008 1009 getWidgetService().addConfigurations(definition.getConfigurations()); 1010 CmsType baseType = definition.getTypes().get(definition.getEntityTypeName()); 1011 m_entityBackend.registerTypes(baseType, definition.getTypes()); 1012 for (CmsEntity entity : definition.getEntities().values()) { 1013 CmsEntity previousValue = m_entityBackend.getEntity(entity.getId()); 1014 if (previousValue != null) { 1015 m_entityBackend.changeEntityContentValues(previousValue, entity); 1016 } else { 1017 m_entityBackend.registerEntity(entity); 1018 m_registeredEntities.add(entity.getId()); 1019 } 1020 } 1021 for (Map.Entry<String, CmsComplexWidgetData> entry : definition.getComplexWidgetData().entrySet()) { 1022 String attrName = entry.getKey(); 1023 CmsComplexWidgetData widgetData = entry.getValue(); 1024 getWidgetService().registerComplexWidgetAttribute( 1025 attrName, 1026 widgetData.getRendererName(), 1027 widgetData.getConfiguration()); 1028 } 1029 } 1030 1031 /** 1032 * Saves the given entities.<p> 1033 * 1034 * @param clearOnSuccess <code>true</code> to clear the VIE instance on success 1035 * @param callback the call back command 1036 */ 1037 public void saveAndDeleteEntities(final boolean clearOnSuccess, final I_CmsSimpleCallback<Boolean> callback) { 1038 1039 final CmsEntity entity = m_entityBackend.getEntity(m_entityId); 1040 saveAndDeleteEntities(entity, new ArrayList<String>(m_deletedEntities), clearOnSuccess, callback); 1041 } 1042 1043 /** 1044 * Saves the given entities.<p> 1045 * 1046 * @param lastEditedEntity the last edited entity 1047 * @param deletedEntites the deleted entity id's 1048 * @param clearOnSuccess <code>true</code> to clear the VIE instance on success 1049 * @param callback the call back command 1050 */ 1051 public void saveAndDeleteEntities( 1052 final CmsEntity lastEditedEntity, 1053 final List<String> deletedEntites, 1054 final boolean clearOnSuccess, 1055 final I_CmsSimpleCallback<Boolean> callback) { 1056 1057 CmsRpcAction<CmsSaveResult> asyncCallback = new CmsRpcAction<CmsSaveResult>() { 1058 1059 @Override 1060 public void execute() { 1061 1062 start(200, true); 1063 getService().saveAndDeleteEntities( 1064 lastEditedEntity, 1065 m_clientId, 1066 deletedEntites, 1067 getSkipPaths(), 1068 m_locale, 1069 clearOnSuccess, 1070 this); 1071 } 1072 1073 @Override 1074 protected void onResponse(CmsSaveResult result) { 1075 1076 stop(false); 1077 if ((result != null) && result.hasErrors()) { 1078 showValidationErrorDialog(result.getValidationResult()); 1079 } else { 1080 callback.execute(Boolean.valueOf((result != null) && result.isHasChangedSettings())); 1081 if (clearOnSuccess) { 1082 destroyForm(true); 1083 } 1084 } 1085 } 1086 }; 1087 asyncCallback.execute(); 1088 } 1089 1090 /** 1091 * Saves a value in an Xml content.<p> 1092 * 1093 * @param contentId the structure id of the content 1094 * @param contentPath the xpath for which to set the value 1095 * @param locale the locale for which to set the value 1096 * @param value the new value 1097 * @param asyncCallback the callback for the result 1098 */ 1099 public void saveValue( 1100 final String contentId, 1101 final String contentPath, 1102 final String locale, 1103 final String value, 1104 final AsyncCallback<String> asyncCallback) { 1105 1106 CmsRpcAction<String> action = new CmsRpcAction<String>() { 1107 1108 @Override 1109 public void execute() { 1110 1111 start(0, false); 1112 getService().saveValue(contentId, contentPath, locale, value, this); 1113 1114 } 1115 1116 @Override 1117 protected void onResponse(String result) { 1118 1119 stop(false); 1120 asyncCallback.onSuccess(result); 1121 1122 } 1123 }; 1124 action.execute(); 1125 } 1126 1127 /** 1128 * Sets the show editor help flag to the user session.<p> 1129 * 1130 * @param show the show editor help flag 1131 */ 1132 public void setShowEditorHelp(final boolean show) { 1133 1134 CmsCoreProvider.get().setShowEditorHelp(show); 1135 } 1136 1137 /** 1138 * Removes the given entity from the entity VIE store.<p> 1139 * 1140 * @param entityId the entity id 1141 */ 1142 public void unregistereEntity(String entityId) { 1143 1144 CmsEntityBackend.getInstance().removeEntity(entityId); 1145 } 1146 1147 /** 1148 * @see org.opencms.acacia.client.CmsEditorBase#clearEditor() 1149 */ 1150 @Override 1151 protected void clearEditor() { 1152 1153 super.clearEditor(); 1154 m_context = null; 1155 if (m_undoRedoHandlerRegistration != null) { 1156 m_undoRedoHandlerRegistration.removeHandler(); 1157 } 1158 if (m_toolbar != null) { 1159 m_toolbar.removeFromParent(); 1160 m_toolbar = null; 1161 } 1162 m_cancelButton = null; 1163 m_localeSelect = null; 1164 m_deleteLocaleButton = null; 1165 m_copyLocaleButton = null; 1166 m_openFormButton = null; 1167 m_saveButton = null; 1168 m_onClose = null; 1169 m_locale = null; 1170 if (m_basePanel != null) { 1171 m_basePanel.removeFromParent(); 1172 m_basePanel = null; 1173 } 1174 if (m_entityObserver != null) { 1175 m_entityObserver.clear(); 1176 m_entityObserver = null; 1177 } 1178 m_changedEntityIds.clear(); 1179 m_registeredEntities.clear(); 1180 m_availableLocales.clear(); 1181 m_contentLocales.clear(); 1182 m_deletedEntities.clear(); 1183 m_definitions.clear(); 1184 m_title = null; 1185 m_sitePath = null; 1186 m_resourceTypeName = null; 1187 if (m_closingHandlerRegistration != null) { 1188 m_closingHandlerRegistration.removeHandler(); 1189 m_closingHandlerRegistration = null; 1190 } 1191 if (m_isStandAlone) { 1192 closeEditorWidow(); 1193 } else { 1194 RootPanel.getBodyElement().getParentElement().getStyle().clearOverflow(); 1195 } 1196 if (m_previewHandlerRegistration != null) { 1197 m_previewHandlerRegistration.removeHandler(); 1198 m_previewHandlerRegistration = null; 1199 } 1200 CmsDomUtil.getHtmlElement().removeClassName(EDITOR_MARKER_CLASS); 1201 } 1202 1203 /** 1204 * Gets the editor context.<p> 1205 * 1206 * @return the editor context 1207 */ 1208 protected CmsEditorContext getContext() { 1209 1210 return m_context; 1211 } 1212 1213 /** 1214 * @see org.opencms.acacia.client.CmsEditorBase#getContextUri() 1215 */ 1216 @Override 1217 protected String getContextUri() { 1218 1219 return CmsCoreProvider.get().getUri(); 1220 } 1221 1222 /** 1223 * Gets the entity id.<p> 1224 * 1225 * @return the entity id 1226 */ 1227 protected String getEntityId() { 1228 1229 return m_entityId; 1230 } 1231 1232 /** 1233 * @see org.opencms.acacia.client.CmsEditorBase#getHtmlContextInfo() 1234 */ 1235 @Override 1236 protected String getHtmlContextInfo() { 1237 1238 return m_context.getHtmlContextInfo(); 1239 } 1240 1241 /** 1242 * Returns the paths to be skipped when synchronizing locale independent fields.<p> 1243 * 1244 * @return the paths to be skipped when synchronizing locale independent fields 1245 */ 1246 protected Collection<String> getSkipPaths() { 1247 1248 return ((CmsDefaultWidgetService)getWidgetService()).getSkipPaths(); 1249 } 1250 1251 /** 1252 * Adds a content definition to the internal store.<p> 1253 * 1254 * @param definition the definition to add 1255 */ 1256 void addContentDefinition(CmsContentDefinition definition) { 1257 1258 m_definitions.put(definition.getLocale(), definition); 1259 m_contentLocales.add(definition.getLocale()); 1260 } 1261 1262 /** 1263 * Calls the editor change handlers.<p> 1264 * 1265 * @param changedScopes the changed content value scopes 1266 */ 1267 void callEditorChangeHandlers(final Set<String> changedScopes) { 1268 1269 m_changedScopes.addAll(changedScopes); 1270 if (!m_callingChangeHandlers && (m_changedScopes.size() > 0)) { 1271 m_callingChangeHandlers = true; 1272 final Set<String> scopesToSend = new HashSet<String>(m_changedScopes); 1273 m_changedScopes.clear(); 1274 final CmsEntity entity = m_entityBackend.getEntity(m_entityId); 1275 final org.opencms.acacia.shared.CmsEntity currentState = entity.createDeepCopy(m_entityId); 1276 CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() { 1277 1278 @Override 1279 public void execute() { 1280 1281 start(200, true); 1282 getService().callEditorChangeHandlers( 1283 getEntityId(), 1284 currentState, 1285 getSkipPaths(), 1286 scopesToSend, 1287 this); 1288 } 1289 1290 @Override 1291 public void onFailure(Throwable t) { 1292 1293 m_callingChangeHandlers = false; 1294 super.onFailure(t); 1295 1296 } 1297 1298 @Override 1299 protected void onResponse(CmsContentDefinition result) { 1300 1301 m_callingChangeHandlers = false; 1302 stop(false); 1303 updateEditorValues(currentState, result.getEntity()); 1304 callEditorChangeHandlers(new HashSet<String>()); 1305 } 1306 }; 1307 action.execute(); 1308 } 1309 } 1310 1311 /** 1312 * Cancels the editing process.<p> 1313 */ 1314 void cancelEdit() { 1315 1316 // store the scroll position 1317 int scrollTop = RootPanel.getBodyElement().getOwnerDocument().getScrollTop(); 1318 setEditorState(false); 1319 unlockResource(); 1320 if (m_onClose != null) { 1321 m_onClose.onClose(m_hasChangedSettings, /*publishDialog=*/false); 1322 } 1323 destroyForm(true); 1324 clearEditor(); 1325 // restore the scroll position 1326 RootPanel.getBodyElement().getOwnerDocument().setScrollTop(scrollTop); 1327 } 1328 1329 /** 1330 * Checks if the content is valid and sends a notification if not.<p> 1331 * This will not trigger a validation run, but will use the latest state.<p> 1332 * 1333 * @return <code>true</code> in case there are no validation issues 1334 */ 1335 boolean checkValidation() { 1336 1337 boolean result; 1338 if (m_changedEntityIds.isEmpty()) { 1339 result = true; 1340 } else if (m_saveButton.isEnabled()) { 1341 result = true; 1342 } else { 1343 result = false; 1344 CmsNotification.get().send(Type.ERROR, m_saveButton.getDisabledReason()); 1345 } 1346 return result; 1347 } 1348 1349 /** 1350 * Asks the user to confirm resetting all changes.<p> 1351 */ 1352 void confirmCancel() { 1353 1354 if (m_saveButton.isEnabled()) { 1355 CmsConfirmDialog dialog = new CmsConfirmDialog( 1356 org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TITLE_0), 1357 org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TEXT_0)); 1358 dialog.setHandler(new I_CmsConfirmDialogHandler() { 1359 1360 public void onClose() { 1361 1362 // nothing to do 1363 } 1364 1365 public void onOk() { 1366 1367 cancelEdit(); 1368 } 1369 }); 1370 dialog.center(); 1371 } else { 1372 cancelEdit(); 1373 } 1374 } 1375 1376 /** 1377 * Opens the confirm delete locale dialog.<p> 1378 */ 1379 void confirmDeleteLocale() { 1380 1381 CmsConfirmDialog dialog = new CmsConfirmDialog( 1382 Messages.get().key(Messages.GUI_CONFIRM_DELETE_LOCALE_TITLE_0), 1383 Messages.get().key(Messages.GUI_CONFIRM_DELETE_LOCALE_TEXT_0)); 1384 dialog.setHandler(new I_CmsConfirmDialogHandler() { 1385 1386 public void onClose() { 1387 1388 // nothing to do 1389 } 1390 1391 public void onOk() { 1392 1393 deleteCurrentLocale(); 1394 } 1395 }); 1396 dialog.center(); 1397 } 1398 1399 /** 1400 * Copies the current entity values to the given locales.<p> 1401 * 1402 * @param targetLocales the target locales 1403 */ 1404 void copyLocales(final Set<String> targetLocales) { 1405 1406 final CmsEntity entity = m_entityBackend.getEntity(m_entityId); 1407 CmsRpcAction<Void> action = new CmsRpcAction<Void>() { 1408 1409 @Override 1410 public void execute() { 1411 1412 start(200, true); 1413 getService().copyLocale(targetLocales, entity, this); 1414 } 1415 1416 @Override 1417 protected void onResponse(Void result) { 1418 1419 stop(false); 1420 } 1421 }; 1422 action.execute(); 1423 for (String targetLocale : targetLocales) { 1424 String targetId = getIdForLocale(targetLocale); 1425 if (!m_entityId.equals(targetId)) { 1426 if (m_registeredEntities.contains(targetId)) { 1427 unregistereEntity(targetId); 1428 } 1429 registerClonedEntity(m_entityId, targetId); 1430 m_registeredEntities.add(targetId); 1431 m_changedEntityIds.add(targetId); 1432 m_contentLocales.add(targetLocale); 1433 m_deletedEntities.remove(targetId); 1434 enableSave(); 1435 } 1436 } 1437 initLocaleSelect(); 1438 } 1439 1440 /** 1441 * Deferrers the save action to the end of the browser event queue.<p> 1442 */ 1443 void deferSave() { 1444 1445 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 1446 1447 public void execute() { 1448 1449 save(); 1450 } 1451 }); 1452 } 1453 1454 /** 1455 * Deferrers the save and exit action to the end of the browser event queue.<p> 1456 */ 1457 void deferSaveAndExit() { 1458 1459 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 1460 1461 public void execute() { 1462 1463 saveAndExit(); 1464 } 1465 }); 1466 } 1467 1468 /** 1469 * Deletes the current locale.<p> 1470 */ 1471 void deleteCurrentLocale() { 1472 1473 // there has to remain at least one content locale 1474 if (m_contentLocales.size() > 1) { 1475 String deletedLocale = m_locale; 1476 m_contentLocales.remove(deletedLocale); 1477 m_registeredEntities.remove(m_entityId); 1478 m_changedEntityIds.remove(m_entityId); 1479 m_deletedEntities.add(m_entityId); 1480 getValidationHandler().getValidationContext().removeEntityId(m_entityId); 1481 unregistereEntity(m_entityId); 1482 enableSave(); 1483 String nextLocale = null; 1484 if (m_registeredEntities.isEmpty()) { 1485 nextLocale = m_contentLocales.iterator().next(); 1486 } else { 1487 nextLocale = CmsContentDefinition.getLocaleFromId(m_registeredEntities.iterator().next()); 1488 } 1489 switchLocale(nextLocale); 1490 } 1491 } 1492 1493 /** 1494 * Disables the save buttons with the given message.<p> 1495 * 1496 * @param message the disabled message 1497 */ 1498 void disableSave(String message) { 1499 1500 m_saveButton.disable(message); 1501 m_saveExitButton.disable(message); 1502 } 1503 1504 /** 1505 * Leaves the editor saving the content if necessary.<p> 1506 */ 1507 void exitWithSaving() { 1508 1509 if (checkValidation()) { 1510 if (m_saveExitButton.isEnabled()) { 1511 saveAndExit(); 1512 } else { 1513 cancelEdit(); 1514 } 1515 } 1516 } 1517 1518 /** 1519 * Handles validation changes.<p> 1520 * 1521 * @param validationContext the changed validation context 1522 */ 1523 void handleValidationChange(CmsValidationContext validationContext) { 1524 1525 if (validationContext.hasValidationErrors()) { 1526 String locales = ""; 1527 for (String id : validationContext.getInvalidEntityIds()) { 1528 1529 locales += "\n"; 1530 1531 String locale = CmsContentDefinition.getLocaleFromId(id); 1532 if (m_availableLocales.containsKey(locale)) { 1533 locales += m_availableLocales.get(locale); 1534 locales += ": " + validationContext.getInvalidFields(id); 1535 } 1536 } 1537 disableSave(Messages.get().key(Messages.GUI_TOOLBAR_VALIDATION_ERRORS_1, locales)); 1538 } else if (!m_changedEntityIds.isEmpty()) { 1539 enableSave(); 1540 } 1541 } 1542 1543 /** 1544 * Hides the editor help bubbles.<p> 1545 * 1546 * @param hide <code>true</code> to hide the help bubbles 1547 */ 1548 void hideHelpBubbles(boolean hide) { 1549 1550 setShowEditorHelp(!hide); 1551 CmsValueFocusHandler.getInstance().hideHelpBubbles(RootPanel.get(), hide); 1552 if (!hide) { 1553 m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_SHOWN_0)); 1554 } else { 1555 m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_HIDDEN_0)); 1556 } 1557 } 1558 1559 /** 1560 * Initializes the editor.<p> 1561 * 1562 * @param context the editor context 1563 * @param contentDefinition the content definition 1564 * @param formParent the inline form parent panel, used for inline editing only 1565 * @param inline <code>true</code> to render the editor for inline editing 1566 * @param mainLocale the main language to copy in case the element language node does not exist yet 1567 */ 1568 void initEditor( 1569 CmsEditorContext context, 1570 CmsContentDefinition contentDefinition, 1571 I_CmsInlineFormParent formParent, 1572 boolean inline, 1573 String mainLocale) { 1574 1575 m_context = context; 1576 m_locale = contentDefinition.getLocale(); 1577 m_entityId = contentDefinition.getEntityId(); 1578 m_deleteOnCancel = contentDefinition.isDeleteOnCancel(); 1579 m_autoUnlock = contentDefinition.isAutoUnlock(); 1580 m_isDirectEdit = contentDefinition.isDirectEdit(); 1581 CmsDomUtil.getHtmlElement().addClassName(EDITOR_MARKER_CLASS); 1582 1583 initClosingHandler(); 1584 setContentDefinition(contentDefinition); 1585 initToolbar(); 1586 if (inline && (formParent != null)) { 1587 if ((mainLocale != null) 1588 && (CmsDomUtil.querySelector( 1589 "[" + CmsGwtConstants.ATTR_DATA_ID + "^='" + m_entityId + "']", 1590 formParent.getElement()) == null)) { 1591 // in case a main locale is given and there are not any HTML elements attributed to the current entity id, 1592 // check if the content was rendered for the main locale 1593 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(m_entityId); 1594 String mainLocaleEntityId = CmsContentDefinition.uuidToEntityId(structureId, mainLocale); 1595 NodeList<Element> elements = CmsDomUtil.querySelectorAll( 1596 "[" + CmsGwtConstants.ATTR_DATA_ID + "^='" + mainLocaleEntityId + "']", 1597 formParent.getElement()); 1598 if (elements.getLength() > 0) { 1599 for (int i = 0; i < elements.getLength(); i++) { 1600 Element element = elements.getItem(i); 1601 element.setAttribute( 1602 CmsGwtConstants.ATTR_DATA_ID, 1603 element.getAttribute(CmsGwtConstants.ATTR_DATA_ID).replace(mainLocaleEntityId, m_entityId)); 1604 } 1605 } 1606 1607 } 1608 initEditOverlay(formParent.getElement()); 1609 addOverlayClickHandler(new ClickHandler() { 1610 1611 public void onClick(ClickEvent event) { 1612 1613 exitWithSaving(); 1614 } 1615 }); 1616 m_hideHelpBubblesButton.setVisible(false); 1617 setNativeResourceInfo(m_sitePath, m_locale); 1618 initEntityObserver(); 1619 if (m_definitions.get(m_locale).hasEditorChangeHandlers()) { 1620 initEditorChangeHandlers(m_definitions.get(m_locale).getEditorChangeScopes()); 1621 } 1622 renderInlineEntity(m_entityId, formParent); 1623 } else { 1624 initFormPanel(); 1625 renderFormContent(); 1626 fixFocus(); 1627 } 1628 if (contentDefinition.isPerformedAutocorrection()) { 1629 CmsNotification.get().send( 1630 CmsNotification.Type.NORMAL, 1631 Messages.get().key(Messages.GUI_WARN_INVALID_XML_STRUCTURE_0)); 1632 setChanged(); 1633 } 1634 } 1635 1636 /** 1637 * Initializes the editor change handler.<p> 1638 * 1639 * @param changeScopes the scopes to watch for changes 1640 */ 1641 void initEditorChangeHandlers(Collection<String> changeScopes) { 1642 1643 if (m_editorChangeHandler != null) { 1644 m_editorChangeHandler.clear(); 1645 } 1646 m_editorChangeHandler = new EditorChangeHandler(getEntity(), changeScopes); 1647 } 1648 1649 /** 1650 * Initializes the entity observer.<p> 1651 */ 1652 void initEntityObserver() { 1653 1654 if (m_entityObserver != null) { 1655 m_entityObserver.clear(); 1656 } 1657 m_entityObserver = new CmsEntityObserver(getCurrentEntity()); 1658 exportObserver(); 1659 } 1660 1661 /** 1662 * Opens the form based editor.<p> 1663 */ 1664 void initFormPanel() { 1665 1666 removeEditOverlays(); 1667 m_openFormButton.setVisible(false); 1668 m_saveButton.setVisible(true); 1669 m_hideHelpBubblesButton.setVisible(true); 1670 m_undoButton.setVisible(true); 1671 m_redoButton.setVisible(true); 1672 m_basePanel = new FlowPanel(); 1673 m_basePanel.addStyleName(I_CmsLayoutBundle.INSTANCE.editorCss().basePanel()); 1674 m_basePanel.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().opencms()); 1675 // insert base panel before the tool bar to keep the tool bar visible 1676 RootPanel.get().add(m_basePanel); 1677 if (m_isStandAlone) { 1678 RootPanel.getBodyElement().addClassName(I_CmsLayoutBundle.INSTANCE.editorCss().standAloneEditor()); 1679 } else { 1680 RootPanel.getBodyElement().getParentElement().getStyle().setOverflow(Overflow.HIDDEN); 1681 } 1682 } 1683 1684 /** 1685 * Opens the copy locale dialog.<p> 1686 */ 1687 void openCopyLocaleDialog() { 1688 1689 CmsCopyLocaleDialog dialog = new CmsCopyLocaleDialog( 1690 m_availableLocales, 1691 m_contentLocales, 1692 m_locale, 1693 m_definitions.get(m_locale).hasSynchronizedElements(), 1694 this); 1695 dialog.center(); 1696 } 1697 1698 /** 1699 * Opens the model file select dialog.<p> 1700 * 1701 * @param context the editor context 1702 * @param definition the content definition 1703 */ 1704 void openModelSelectDialog(final CmsEditorContext context, final CmsContentDefinition definition) { 1705 1706 I_CmsModelSelectHandler handler = new I_CmsModelSelectHandler() { 1707 1708 public void onModelSelect(CmsUUID modelStructureId) { 1709 1710 if (modelStructureId == null) { 1711 modelStructureId = CmsUUID.getNullUUID(); 1712 } 1713 openFormEditor( 1714 context, 1715 definition.getLocale(), 1716 definition.getReferenceResourceId().toString(), 1717 m_clientId, 1718 definition.getNewLink(), 1719 modelStructureId, 1720 null, 1721 null, 1722 null, 1723 1724 null, 1725 m_onClose); 1726 } 1727 }; 1728 String title = org.opencms.gwt.client.Messages.get().key( 1729 org.opencms.gwt.client.Messages.GUI_MODEL_SELECT_TITLE_0); 1730 String message = org.opencms.gwt.client.Messages.get().key( 1731 org.opencms.gwt.client.Messages.GUI_MODEL_SELECT_MESSAGE_0); 1732 CmsModelSelectDialog dialog = new CmsModelSelectDialog(handler, definition.getModelInfos(), title, message); 1733 dialog.center(); 1734 } 1735 1736 /** 1737 * Previews the native event to enable keyboard short cuts.<p> 1738 * 1739 * @param event the event 1740 */ 1741 void previewNativeEvent(NativePreviewEvent event) { 1742 1743 Event nativeEvent = Event.as(event.getNativeEvent()); 1744 if (event.getTypeInt() == Event.ONKEYDOWN) { 1745 int keyCode = nativeEvent.getKeyCode(); 1746 if (nativeEvent.getCtrlKey() || nativeEvent.getMetaKey()) { 1747 // look for short cuts 1748 if (nativeEvent.getShiftKey()) { 1749 if (keyCode == KeyCodes.KEY_S) { 1750 1751 exitWithSaving(); 1752 nativeEvent.preventDefault(); 1753 nativeEvent.stopPropagation(); 1754 } else if (keyCode == KeyCodes.KEY_X) { 1755 confirmCancel(); 1756 nativeEvent.preventDefault(); 1757 nativeEvent.stopPropagation(); 1758 } 1759 } else if (keyCode == KeyCodes.KEY_S) { 1760 if (checkValidation()) { 1761 save(); 1762 } 1763 nativeEvent.preventDefault(); 1764 nativeEvent.stopPropagation(); 1765 } 1766 1767 } 1768 } 1769 } 1770 1771 /** 1772 * Renders the form content.<p> 1773 */ 1774 void renderFormContent() { 1775 1776 initLocaleSelect(); 1777 setNativeResourceInfo(m_sitePath, m_locale); 1778 m_contentInfoHeader = new CmsInfoHeader(m_title, null, m_sitePath, m_locale, m_iconClasses); 1779 m_basePanel.add(m_contentInfoHeader); 1780 SimplePanel content = new SimplePanel(); 1781 content.setStyleName(org.opencms.acacia.client.css.I_CmsLayoutBundle.INSTANCE.form().formParent()); 1782 m_basePanel.add(content); 1783 initEntityObserver(); 1784 if (m_definitions.get(m_locale).hasEditorChangeHandlers()) { 1785 initEditorChangeHandlers(m_definitions.get(m_locale).getEditorChangeScopes()); 1786 } 1787 renderEntityForm(m_entityId, m_tabInfos, content, m_basePanel.getElement()); 1788 if ((m_clientId != null) && (getFormTabs() != null)) { 1789 1790 CmsListInfoBean pageInfo = CmsContainerpageController.get().getData().getPageInfo(); 1791 m_pageInfoHeader = new CmsInfoHeader( 1792 pageInfo.getTitle(), 1793 null, 1794 pageInfo.getSubTitle(), 1795 CmsContainerpageController.get().getData().getLocale(), 1796 pageInfo.getBigIconClasses()); 1797 updateInfoHeader(CmsContentDefinition.SETTINGS_TAB_ID.equals(getFormTabs().getSelectedId())); 1798 getFormTabs().addSelectionHandler(new SelectionHandler<Integer>() { 1799 1800 public void onSelection(SelectionEvent<Integer> event) { 1801 1802 updateInfoHeader(CmsContentDefinition.SETTINGS_TAB_ID.equals(getFormTabs().getSelectedId())); 1803 } 1804 }); 1805 } 1806 } 1807 1808 /** 1809 * Saves the content and closes the editor.<p> 1810 */ 1811 void save() { 1812 1813 saveAndDeleteEntities(false, new I_CmsSimpleCallback<Boolean>() { 1814 1815 public void execute(Boolean hasChangedSettings) { 1816 1817 setSaved(); 1818 setUnchanged(); 1819 m_hasChangedSettings = m_hasChangedSettings || hasChangedSettings.booleanValue(); 1820 } 1821 }); 1822 } 1823 1824 /** 1825 * Saves the content and closes the editor.<p> 1826 */ 1827 void saveAndExit() { 1828 1829 boolean unlock = shouldUnlockAutomatically(); 1830 // store the scroll position 1831 final int scrollTop = RootPanel.getBodyElement().getOwnerDocument().getScrollTop(); 1832 saveAndDeleteEntities(unlock, new I_CmsSimpleCallback<Boolean>() { 1833 1834 public void execute(final Boolean hasChangedSettings) { 1835 1836 setSaved(); 1837 if (m_onClose != null) { 1838 m_onClose.onClose( 1839 m_hasChangedSettings || hasChangedSettings.booleanValue(), 1840 /*publishDialog=*/false); 1841 } 1842 clearEditor(); 1843 // restore the scroll position 1844 RootPanel.getBodyElement().getOwnerDocument().setScrollTop(scrollTop); 1845 } 1846 }); 1847 } 1848 1849 /** 1850 * Sets the has changed flag and enables the save button.<p> 1851 */ 1852 void setChanged() { 1853 1854 enableSave(); 1855 m_changedEntityIds.add(m_entityId); 1856 m_deletedEntities.remove(m_entityId); 1857 updateOverlayPosition(); 1858 setEditorState(true); 1859 } 1860 1861 /** 1862 * Sets the content definition.<p> 1863 * 1864 * @param definition the content definition 1865 */ 1866 void setContentDefinition(CmsContentDefinition definition) { 1867 1868 if (m_availableLocales.isEmpty()) { 1869 // only set the locales when initially setting the content definition 1870 m_availableLocales.putAll(definition.getAvailableLocales()); 1871 m_contentLocales.addAll(definition.getContentLocales()); 1872 } else { 1873 m_contentLocales.add(definition.getLocale()); 1874 } 1875 m_title = definition.getTitle(); 1876 m_sitePath = definition.getSitePath(); 1877 m_resourceTypeName = definition.getResourceType(); 1878 m_registeredEntities.add(definition.getEntityId()); 1879 m_tabInfos = definition.getTabInfos(); 1880 m_iconClasses = definition.getIconClasses(); 1881 addContentDefinition(definition); 1882 CmsDefaultWidgetService service = (CmsDefaultWidgetService)getWidgetService(); 1883 service.addConfigurations(definition.getConfigurations()); 1884 service.setSyncValues(definition.getSyncValues()); 1885 service.setSkipPaths(definition.getSkipPaths()); 1886 addEntityChangeHandler(definition.getEntityId(), new ValueChangeHandler<CmsEntity>() { 1887 1888 public void onValueChange(ValueChangeEvent<CmsEntity> event) { 1889 1890 setChanged(); 1891 } 1892 }); 1893 } 1894 1895 /** 1896 * Removes the delete on cancel flag for new resources.<p> 1897 */ 1898 void setSaved() { 1899 1900 setEditorState(false); 1901 m_deleteOnCancel = false; 1902 } 1903 1904 /** 1905 * Call after save.<p> 1906 */ 1907 void setUnchanged() { 1908 1909 m_changedEntityIds.clear(); 1910 m_deletedEntities.clear(); 1911 disableSave(Messages.get().key(Messages.GUI_TOOLBAR_NOTHING_CHANGED_0)); 1912 } 1913 1914 /** 1915 * Enables and disabler the undo redo buttons according to the state.<p> 1916 * 1917 * @param state the undo redo state 1918 */ 1919 void setUndoRedoState(UndoRedoState state) { 1920 1921 if (state.hasUndo()) { 1922 m_undoButton.enable(); 1923 } else { 1924 m_undoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_DISABLED_0)); 1925 } 1926 if (state.hasRedo()) { 1927 m_redoButton.enable(); 1928 } else { 1929 m_redoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_REDO_DISABLED_0)); 1930 } 1931 } 1932 1933 /** 1934 * Returns true if the edited resource should be unlocked automatically after pressing Save/Exit.<p> 1935 * 1936 * @return true if the edited resource should be unlocked automatically 1937 */ 1938 boolean shouldUnlockAutomatically() { 1939 1940 if (m_isStandAlone) { 1941 if (m_isDirectEdit) { 1942 // Classic direct edit case - always unlock 1943 return true; 1944 } else { 1945 // Workplace case - determined by configuration 1946 return m_autoUnlock; 1947 } 1948 } 1949 // Container page case - always unlock 1950 return true; 1951 } 1952 1953 /** 1954 * Shows the locked resource error message.<p> 1955 */ 1956 void showLockedResourceMessage() { 1957 1958 CmsErrorDialog dialog = new CmsErrorDialog( 1959 Messages.get().key(Messages.ERR_RESOURCE_ALREADY_LOCKED_BY_OTHER_USER_0), 1960 null); 1961 dialog.addCloseHandler(new CloseHandler<PopupPanel>() { 1962 1963 public void onClose(CloseEvent<PopupPanel> event) { 1964 1965 cancelEdit(); 1966 } 1967 }); 1968 dialog.center(); 1969 } 1970 1971 /** 1972 * Shows the validation error dialog.<p> 1973 * 1974 * @param validationResult the validation result 1975 */ 1976 void showValidationErrorDialog(CmsValidationResult validationResult) { 1977 1978 if (validationResult.getErrors().keySet().contains(m_entityId)) { 1979 getValidationHandler().displayValidation(m_entityId, validationResult); 1980 } 1981 String errorLocales = ""; 1982 for (String entityId : validationResult.getErrors().keySet()) { 1983 String locale = CmsContentDefinition.getLocaleFromId(entityId); 1984 errorLocales += m_availableLocales.get(locale) + ", "; 1985 } 1986 // remove trailing ',' 1987 errorLocales = errorLocales.substring(0, errorLocales.length() - 2); 1988 CmsErrorDialog dialog = new CmsErrorDialog( 1989 Messages.get().key(Messages.GUI_VALIDATION_ERROR_1, errorLocales), 1990 null); 1991 dialog.center(); 1992 } 1993 1994 /** 1995 * Switches to the selected locale. Will save changes first.<p> 1996 * 1997 * @param locale the locale to switch to 1998 */ 1999 void switchLocale(final String locale) { 2000 2001 if (locale.equals(m_locale)) { 2002 return; 2003 } 2004 final Integer oldTabIndex = getTabIndex(); 2005 m_locale = locale; 2006 m_basePanel.clear(); 2007 destroyForm(false); 2008 final CmsEntity entity = m_entityBackend.getEntity(m_entityId); 2009 m_entityId = getIdForLocale(locale); 2010 // if the content does not contain the requested locale yet, a new node will be created 2011 final boolean addedNewLocale = !m_contentLocales.contains(locale); 2012 if (m_registeredEntities.contains(m_entityId)) { 2013 unregistereEntity(m_entityId); 2014 } 2015 if (addedNewLocale) { 2016 loadNewDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() { 2017 2018 public void execute(final CmsContentDefinition contentDefinition) { 2019 2020 setContentDefinition(contentDefinition); 2021 renderFormContent(); 2022 if (oldTabIndex != null) { 2023 if (oldTabIndex.intValue() < getFormTabs().getTabCount()) { 2024 getFormTabs().selectTab(oldTabIndex.intValue()); 2025 } 2026 } 2027 setChanged(); 2028 2029 } 2030 }); 2031 } else { 2032 loadDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() { 2033 2034 public void execute(CmsContentDefinition contentDefinition) { 2035 2036 setContentDefinition(contentDefinition); 2037 renderFormContent(); 2038 if (oldTabIndex != null) { 2039 if (oldTabIndex.intValue() < getFormTabs().getTabCount()) { 2040 getFormTabs().selectTab(oldTabIndex.intValue()); 2041 } 2042 } 2043 } 2044 2045 }); 2046 } 2047 } 2048 2049 /** 2050 * Synchronizes the locale independent fields to the other locales.<p> 2051 */ 2052 void synchronizeCurrentLocale() { 2053 2054 m_basePanel.clear(); 2055 destroyForm(false); 2056 CmsEntity entity = m_entityBackend.getEntity(m_entityId); 2057 m_entityId = getIdForLocale(m_locale); 2058 ((CmsDefaultWidgetService)getWidgetService()).setSkipPaths(Collections.<String> emptyList()); 2059 loadDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() { 2060 2061 public void execute(CmsContentDefinition contentDefinition) { 2062 2063 setContentDefinition(contentDefinition); 2064 renderFormContent(); 2065 setChanged(); 2066 } 2067 }); 2068 } 2069 2070 /** 2071 * Unlocks the edited resource.<p> 2072 */ 2073 void unlockResource() { 2074 2075 if (!shouldUnlockAutomatically()) { 2076 return; 2077 } 2078 if (m_entityId != null) { 2079 final CmsUUID structureId = CmsContentDefinition.entityIdToUuid(m_entityId); 2080 if (m_deleteOnCancel) { 2081 CmsRpcAction<Void> action = new CmsRpcAction<Void>() { 2082 2083 @Override 2084 public void execute() { 2085 2086 CmsCoreProvider.getVfsService().syncDeleteResource(structureId, this); 2087 } 2088 2089 @Override 2090 protected void onResponse(Void result) { 2091 2092 // nothing to do 2093 } 2094 }; 2095 action.executeSync(); 2096 } else { 2097 CmsCoreProvider.get().unlock(structureId); 2098 } 2099 } 2100 } 2101 2102 /** 2103 * Updates the editor values.<p> 2104 * 2105 * @param previous the previous entity state 2106 * @param updated the updated entity state 2107 */ 2108 void updateEditorValues(CmsEntity previous, CmsEntity updated) { 2109 2110 if (!m_isDirectEdit && updated.getId().equals(m_entityId)) { 2111 // only apply the changes to the same locale entity 2112 updateEditorValues(previous, updated, getEntity(), Collections.<String> emptyList()); 2113 } 2114 } 2115 2116 /** 2117 * Updates the info header.<p> 2118 * 2119 * @param isSettingsTabSelected <code>true</code> if the element settings tab is selected 2120 */ 2121 void updateInfoHeader(boolean isSettingsTabSelected) { 2122 2123 m_basePanel.remove(0); 2124 m_basePanel.insert(isSettingsTabSelected ? m_pageInfoHeader : m_contentInfoHeader, 0); 2125 } 2126 2127 /** 2128 * Adds the change listener to the observer.<p> 2129 * 2130 * @param changeListener the change listener 2131 * @param changeScope the change scope 2132 */ 2133 private void addChangeListener(JavaScriptObject changeListener, String changeScope) { 2134 2135 try { 2136 System.out.println("Adding native listener for scope " + changeScope); 2137 m_entityObserver.addEntityChangeListener(new CmsEntityChangeListenerWrapper(changeListener), changeScope); 2138 } catch (Exception e) { 2139 2140 CmsDebugLog.getInstance().printLine("Exception occured during listener registration" + e.getMessage()); 2141 } 2142 } 2143 2144 /** 2145 * Changes a simple type entity value.<p> 2146 * 2147 * @param attributeName the attribute name 2148 * @param index the value index 2149 * @param value the value 2150 * @param parentPathElements the parent path elements 2151 */ 2152 private void changeSimpleValue(String attributeName, int index, String value, List<String> parentPathElements) { 2153 2154 CmsAttributeHandler handler = getAttributeHandler(attributeName, parentPathElements); 2155 handler.changeValue(value, index); 2156 } 2157 2158 /** 2159 * Closes the editor.<p> 2160 */ 2161 private native void closeEditorWidow() /*-{ 2162 if ($wnd.top.cms_ade_closeEditorDialog) { 2163 $wnd.top.cms_ade_closeEditorDialog(); 2164 } else if ($wnd.parent.parent.cms_ade_closeEditorDialog) { 2165 $wnd.parent.parent.cms_ade_closeEditorDialog(); 2166 } else { 2167 var backlink = $wnd[@org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService::PARAM_BACKLINK]; 2168 if (backlink) { 2169 $wnd.top.location.href = backlink; 2170 } 2171 } 2172 }-*/; 2173 2174 /** 2175 * Creates a push button for the edit tool-bar.<p> 2176 * 2177 * @param title the button title 2178 * @param imageClass the image class 2179 * 2180 * @return the button 2181 */ 2182 private CmsPushButton createButton(String title, String imageClass) { 2183 2184 CmsPushButton result = new CmsPushButton(); 2185 result.setTitle(title); 2186 result.setImageClass(imageClass); 2187 result.setButtonStyle(ButtonStyle.FONT_ICON, null); 2188 result.setSize(Size.big); 2189 return result; 2190 } 2191 2192 /** 2193 * Enables the save buttons.<p> 2194 */ 2195 private void enableSave() { 2196 2197 m_saveButton.enable(); 2198 m_saveExitButton.enable(); 2199 } 2200 2201 /** 2202 * Exports the add entity change listener method.<p> 2203 */ 2204 private native void exportObserver()/*-{ 2205 var self = this; 2206 $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::ADD_CHANGE_LISTENER_METHOD] = function( 2207 listener, scope) { 2208 var wrapper = { 2209 onChange : listener.onChange 2210 } 2211 self.@org.opencms.ade.contenteditor.client.CmsContentEditor::addChangeListener(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;)(wrapper, scope); 2212 } 2213 $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::GET_CURRENT_ENTITY_METHOD] = function() { 2214 return new $wnd.acacia.CmsEntityWrapper( 2215 self.@org.opencms.ade.contenteditor.client.CmsContentEditor::getCurrentEntity()()); 2216 } 2217 }-*/; 2218 2219 /** 2220 * Returns the attribute handler for the given attribute.<p> 2221 * 2222 * @param attributeName the attribute name 2223 * @param parentPathElements the parent path elements 2224 * 2225 * @return the attribute handler 2226 */ 2227 private CmsAttributeHandler getAttributeHandler(String attributeName, List<String> parentPathElements) { 2228 2229 List<String> childPathElements = new ArrayList<String>(parentPathElements); 2230 childPathElements.add(attributeName); 2231 CmsAttributeHandler handler = getRootAttributeHandler().getHandlerByPath( 2232 childPathElements.toArray(new String[childPathElements.size()])); 2233 return handler; 2234 } 2235 2236 /** 2237 * Returns the entity id for the given locale.<p> 2238 * 2239 * @param locale the locale 2240 * 2241 * @return the entity id 2242 */ 2243 private String getIdForLocale(String locale) { 2244 2245 return CmsContentDefinition.uuidToEntityId(CmsContentDefinition.entityIdToUuid(m_entityId), locale); 2246 } 2247 2248 /** 2249 * Returns the selected tab index, or null if there are no tabs. 2250 * 2251 * @return the selected tab index or null 2252 */ 2253 private Integer getTabIndex() { 2254 2255 if (getFormTabs() != null) { 2256 return Integer.valueOf(getFormTabs().getSelectedIndex()); 2257 } 2258 return null; 2259 } 2260 2261 /** 2262 * Initializes the window closing handler to ensure the resource will be unlocked when leaving the editor.<p> 2263 */ 2264 private void initClosingHandler() { 2265 2266 m_closingHandlerRegistration = Window.addWindowClosingHandler(new ClosingHandler() { 2267 2268 /** 2269 * @see com.google.gwt.user.client.Window.ClosingHandler#onWindowClosing(com.google.gwt.user.client.Window.ClosingEvent) 2270 */ 2271 public void onWindowClosing(ClosingEvent event) { 2272 2273 unlockResource(); 2274 } 2275 }); 2276 } 2277 2278 /** 2279 * Initializes the event preview handler.<p> 2280 */ 2281 private void initEventPreviewHandler() { 2282 2283 m_previewHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() { 2284 2285 public void onPreviewNativeEvent(NativePreviewEvent event) { 2286 2287 previewNativeEvent(event); 2288 } 2289 }); 2290 } 2291 2292 /** 2293 * Initializes the locale selector.<p> 2294 */ 2295 private void initLocaleSelect() { 2296 2297 if (m_availableLocales.size() < 2) { 2298 return; 2299 } 2300 Map<String, String> selectOptions = new HashMap<String, String>(); 2301 for (Entry<String, String> localeEntry : m_availableLocales.entrySet()) { 2302 if (m_contentLocales.contains(localeEntry.getKey())) { 2303 selectOptions.put(localeEntry.getKey(), localeEntry.getValue()); 2304 } else { 2305 selectOptions.put(localeEntry.getKey(), localeEntry.getValue() + " [-]"); 2306 } 2307 } 2308 if (m_localeSelect == null) { 2309 m_localeSelect = new CmsSelectBox(selectOptions); 2310 m_toolbar.insertRight(m_localeSelect, 1); 2311 m_localeSelect.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().inlineBlock()); 2312 m_localeSelect.getElement().getStyle().setWidth(100, Unit.PX); 2313 m_localeSelect.getElement().getStyle().setVerticalAlign(VerticalAlign.MIDDLE); 2314 m_localeSelect.addValueChangeHandler(new ValueChangeHandler<String>() { 2315 2316 public void onValueChange(ValueChangeEvent<String> event) { 2317 2318 switchLocale(event.getValue()); 2319 } 2320 }); 2321 } else { 2322 m_localeSelect.setItems(selectOptions); 2323 } 2324 m_localeSelect.setFormValueAsString(m_locale); 2325 if (m_deleteLocaleButton == null) { 2326 m_deleteLocaleButton = createButton( 2327 Messages.get().key(Messages.GUI_TOOLBAR_DELETE_LOCALE_0), 2328 "opencms-icon-remove-locale"); 2329 m_deleteLocaleButton.addClickHandler(new ClickHandler() { 2330 2331 public void onClick(ClickEvent event) { 2332 2333 confirmDeleteLocale(); 2334 } 2335 }); 2336 m_toolbar.insertRight(m_deleteLocaleButton, 2); 2337 } 2338 if (m_contentLocales.size() > 1) { 2339 m_deleteLocaleButton.enable(); 2340 } else { 2341 m_deleteLocaleButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_CANT_DELETE_LAST_LOCALE_0)); 2342 } 2343 if (m_copyLocaleButton == null) { 2344 m_copyLocaleButton = createButton( 2345 I_CmsButton.ButtonData.COPY_LOCALE_BUTTON.getTitle(), 2346 I_CmsButton.ButtonData.COPY_LOCALE_BUTTON.getIconClass()); 2347 m_copyLocaleButton.addClickHandler(new ClickHandler() { 2348 2349 public void onClick(ClickEvent event) { 2350 2351 openCopyLocaleDialog(); 2352 } 2353 }); 2354 m_toolbar.insertRight(m_copyLocaleButton, 3); 2355 } 2356 2357 } 2358 2359 /** 2360 * Generates the button bar displayed beneath the editable fields.<p> 2361 */ 2362 private void initToolbar() { 2363 2364 m_toolbar = new CmsToolbar(); 2365 m_toolbar.setAppTitle(Messages.get().key(Messages.GUI_CONTENT_EDITOR_TITLE_0)); 2366 m_publishButton = createButton( 2367 I_CmsButton.ButtonData.PUBLISH_BUTTON.getTitle(), 2368 I_CmsButton.ButtonData.PUBLISH_BUTTON.getIconClass()); 2369 m_toolbar.addLeft(m_publishButton); 2370 m_publishButton.addClickHandler(new ClickHandler() { 2371 2372 public void onClick(ClickEvent event) { 2373 2374 boolean unlock = shouldUnlockAutomatically(); 2375 2376 saveAndDeleteEntities(unlock, new I_CmsSimpleCallback<Boolean>() { 2377 2378 public void execute(final Boolean hasChangedSeetings) { 2379 2380 setSaved(); 2381 HashMap<String, String> params = new HashMap<String, String>( 2382 getContext().getPublishParameters()); 2383 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(getEntityId()); 2384 params.put(CmsPublishOptions.PARAM_CONTENT, "" + structureId); 2385 params.put(CmsPublishOptions.PARAM_START_WITH_CURRENT_PAGE, ""); 2386 CmsPublishDialog.showPublishDialog(params, new CloseHandler<PopupPanel>() { 2387 2388 public void onClose(CloseEvent<PopupPanel> closeEvent) { 2389 2390 if (m_onClose != null) { 2391 m_onClose.onClose( 2392 m_hasChangedSettings || hasChangedSeetings.booleanValue(), 2393 /*publishDialog=*/true); 2394 } 2395 clearEditor(); 2396 } 2397 }, new Runnable() { 2398 2399 public void run() { 2400 2401 // ignore 2402 } 2403 2404 }, null); 2405 2406 } 2407 }); 2408 2409 } 2410 }); 2411 m_saveExitButton = createButton( 2412 Messages.get().key(Messages.GUI_TOOLBAR_SAVE_AND_EXIT_0), 2413 "opencms-icon-save-exit"); 2414 m_saveExitButton.addClickHandler(new ClickHandler() { 2415 2416 public void onClick(ClickEvent event) { 2417 2418 deferSaveAndExit(); 2419 } 2420 }); 2421 m_toolbar.addLeft(m_saveExitButton); 2422 m_saveButton = createButton( 2423 Messages.get().key(Messages.GUI_TOOLBAR_SAVE_0), 2424 I_CmsButton.ButtonData.SAVE_BUTTON.getIconClass()); 2425 m_saveButton.addClickHandler(new ClickHandler() { 2426 2427 public void onClick(ClickEvent event) { 2428 2429 deferSave(); 2430 } 2431 }); 2432 m_saveButton.setVisible(false); 2433 m_toolbar.addLeft(m_saveButton); 2434 disableSave(Messages.get().key(Messages.GUI_TOOLBAR_NOTHING_CHANGED_0)); 2435 m_undoButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_0), "opencms-icon-undo"); 2436 m_undoButton.addClickHandler(new ClickHandler() { 2437 2438 public void onClick(ClickEvent event) { 2439 2440 if (CmsUndoRedoHandler.getInstance().isIntitalized()) { 2441 CmsUndoRedoHandler.getInstance().undo(); 2442 } 2443 } 2444 }); 2445 m_undoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_DISABLED_0)); 2446 m_undoButton.setVisible(false); 2447 m_toolbar.addLeft(m_undoButton); 2448 m_redoButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_REDO_0), "opencms-icon-redo"); 2449 m_redoButton.addClickHandler(new ClickHandler() { 2450 2451 public void onClick(ClickEvent event) { 2452 2453 if (CmsUndoRedoHandler.getInstance().isIntitalized()) { 2454 CmsUndoRedoHandler.getInstance().redo(); 2455 } 2456 } 2457 }); 2458 m_redoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_REDO_DISABLED_0)); 2459 m_redoButton.setVisible(false); 2460 m_toolbar.addLeft(m_redoButton); 2461 2462 m_undoRedoHandlerRegistration = CmsUndoRedoHandler.getInstance().addValueChangeHandler( 2463 new ValueChangeHandler<CmsUndoRedoHandler.UndoRedoState>() { 2464 2465 public void onValueChange(ValueChangeEvent<UndoRedoState> event) { 2466 2467 setUndoRedoState(event.getValue()); 2468 } 2469 }); 2470 m_openFormButton = createButton( 2471 Messages.get().key(Messages.GUI_TOOLBAR_OPEN_FORM_0), 2472 I_CmsButton.ButtonData.EDIT.getIconClass()); 2473 m_openFormButton.addClickHandler(new ClickHandler() { 2474 2475 public void onClick(ClickEvent event) { 2476 2477 initFormPanel(); 2478 renderFormContent(); 2479 } 2480 }); 2481 m_toolbar.addLeft(m_openFormButton); 2482 2483 m_hideHelpBubblesButton = new CmsToggleButton(); 2484 2485 m_hideHelpBubblesButton.setImageClass(I_CmsButton.ButtonData.TOGGLE_HELP.getIconClass()); 2486 m_hideHelpBubblesButton.setButtonStyle(ButtonStyle.FONT_ICON, null); 2487 m_hideHelpBubblesButton.setSize(Size.big); 2488 m_hideHelpBubblesButton.addClickHandler(new ClickHandler() { 2489 2490 public void onClick(ClickEvent event) { 2491 2492 CmsToggleButton button = (CmsToggleButton)event.getSource(); 2493 hideHelpBubbles(!button.isDown()); 2494 } 2495 }); 2496 m_hideHelpBubblesButton.setDown(CmsCoreProvider.get().isShowEditorHelp()); 2497 CmsValueFocusHandler.getInstance().hideHelpBubbles(RootPanel.get(), !CmsCoreProvider.get().isShowEditorHelp()); 2498 if (!CmsCoreProvider.get().isShowEditorHelp()) { 2499 m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_HIDDEN_0)); 2500 } else { 2501 m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_SHOWN_0)); 2502 } 2503 m_toolbar.addRight(m_hideHelpBubblesButton); 2504 2505 m_cancelButton = createButton( 2506 Messages.get().key(Messages.GUI_TOOLBAR_RESET_0), 2507 I_CmsButton.ButtonData.RESET_BUTTON.getIconClass()); 2508 m_cancelButton.addClickHandler(new ClickHandler() { 2509 2510 public void onClick(ClickEvent event) { 2511 2512 confirmCancel(); 2513 } 2514 }); 2515 m_toolbar.addRight(m_cancelButton); 2516 RootPanel.get().add(m_toolbar); 2517 } 2518 2519 /** 2520 * Sets the editor state.<p> 2521 * 2522 * @param changed if the content has been changed 2523 */ 2524 private native void setEditorState(boolean changed)/*-{ 2525 if (typeof $wnd.cmsSetEditorChangedState === 'function') { 2526 $wnd.cmsSetEditorChangedState(changed); 2527 } 2528 }-*/; 2529 2530 /** 2531 * Sets the resource info to native window context variables.<p> 2532 * 2533 * @param sitePath the site path 2534 * @param locale the content locale 2535 */ 2536 private native void setNativeResourceInfo(String sitePath, String locale)/*-{ 2537 $wnd._editResource = sitePath; 2538 $wnd._editLanguage = locale; 2539 }-*/; 2540 2541 /** 2542 * Updates the editor values according to the given entity.<p> 2543 * 2544 * @param previous the previous entity state 2545 * @param updated the updated entity state 2546 * @param target the target entity 2547 * @param parentPathElements the parent path elements 2548 */ 2549 private void updateEditorValues( 2550 CmsEntity previous, 2551 CmsEntity updated, 2552 CmsEntity target, 2553 List<String> parentPathElements) { 2554 2555 for (String attributeName : m_entityBackend.getType(target.getTypeName()).getAttributeNames()) { 2556 CmsAttributeHandler handler = getAttributeHandler(attributeName, parentPathElements); 2557 if (handler == null) { 2558 // non visible attribute, skip it 2559 continue; 2560 } 2561 if (previous.hasAttribute(attributeName) 2562 && updated.hasAttribute(attributeName) 2563 && target.hasAttribute(attributeName)) { 2564 CmsEntityAttribute updatedAttribute = updated.getAttribute(attributeName); 2565 CmsEntityAttribute previousAttribute = previous.getAttribute(attributeName); 2566 CmsEntityAttribute targetAttribute = target.getAttribute(attributeName); 2567 if (updatedAttribute.isSimpleValue()) { 2568 if ((updatedAttribute.getValueCount() == previousAttribute.getValueCount()) 2569 && (updatedAttribute.getValueCount() == targetAttribute.getValueCount())) { 2570 for (int i = 0; i < updatedAttribute.getValueCount(); i++) { 2571 if (!updatedAttribute.getSimpleValues().get(i).equals( 2572 previousAttribute.getSimpleValues().get(i)) 2573 && previousAttribute.getSimpleValues().get(i).equals( 2574 targetAttribute.getSimpleValues().get(i))) { 2575 2576 changeSimpleValue( 2577 attributeName, 2578 i, 2579 updatedAttribute.getSimpleValues().get(i), 2580 parentPathElements); 2581 } 2582 } 2583 } else { 2584 if (targetAttribute.getValueCount() == previousAttribute.getValueCount()) { 2585 // only act, if the value count has not been altered while executing the server request 2586 if (updatedAttribute.getValueCount() > previousAttribute.getValueCount()) { 2587 // new values have been added 2588 for (int i = 0; i < updatedAttribute.getValueCount(); i++) { 2589 if (i >= previousAttribute.getSimpleValues().size()) { 2590 handler.addNewAttributeValue(updatedAttribute.getSimpleValues().get(i)); 2591 } else if (!updatedAttribute.getSimpleValues().get(i).equals( 2592 previousAttribute.getSimpleValues().get(i)) 2593 && previousAttribute.getSimpleValues().get(i).equals( 2594 targetAttribute.getSimpleValues().get(i))) { 2595 changeSimpleValue( 2596 attributeName, 2597 i, 2598 updatedAttribute.getSimpleValues().get(i), 2599 parentPathElements); 2600 } 2601 } 2602 } else { 2603 // values have been removed 2604 for (int i = previousAttribute.getValueCount() - 1; i >= 0; i--) { 2605 if (i >= updatedAttribute.getSimpleValues().size()) { 2606 handler.removeAttributeValue(i); 2607 } else if (!updatedAttribute.getSimpleValues().get(i).equals( 2608 previousAttribute.getSimpleValues().get(i)) 2609 && previousAttribute.getSimpleValues().get(i).equals( 2610 targetAttribute.getSimpleValues().get(i))) { 2611 changeSimpleValue( 2612 attributeName, 2613 i, 2614 updatedAttribute.getSimpleValues().get(i), 2615 parentPathElements); 2616 } 2617 } 2618 } 2619 } 2620 } 2621 } else { 2622 if (targetAttribute.getValueCount() == previousAttribute.getValueCount()) { 2623 // only act, if the value count has not been altered while executing the server request 2624 if (updatedAttribute.getValueCount() > previousAttribute.getValueCount()) { 2625 // new values have been added 2626 for (int i = 0; i < updatedAttribute.getValueCount(); i++) { 2627 if (i >= previousAttribute.getSimpleValues().size()) { 2628 handler.addNewAttributeValue( 2629 m_entityBackend.registerEntity( 2630 updatedAttribute.getComplexValues().get(i), 2631 true)); 2632 } else { 2633 List<String> childPathElements = new ArrayList<String>(parentPathElements); 2634 childPathElements.add(attributeName + "[" + i + "]"); 2635 updateEditorValues( 2636 previousAttribute.getComplexValues().get(i), 2637 updatedAttribute.getComplexValues().get(i), 2638 targetAttribute.getComplexValues().get(i), 2639 childPathElements); 2640 } 2641 } 2642 } else { 2643 // values have been removed 2644 for (int i = previousAttribute.getValueCount() - 1; i >= 0; i--) { 2645 if (i >= updatedAttribute.getValueCount()) { 2646 2647 handler.removeAttributeValue(i); 2648 } else { 2649 List<String> childPathElements = new ArrayList<String>(parentPathElements); 2650 childPathElements.add(attributeName + "[" + i + "]"); 2651 updateEditorValues( 2652 previousAttribute.getComplexValues().get(i), 2653 updatedAttribute.getComplexValues().get(i), 2654 targetAttribute.getComplexValues().get(i), 2655 childPathElements); 2656 } 2657 } 2658 } 2659 } 2660 } 2661 } else if (previous.hasAttribute(attributeName) && target.hasAttribute(attributeName)) { 2662 for (int i = target.getAttribute(attributeName).getValueCount() - 1; i >= 0; i--) { 2663 handler.removeAttributeValue(i); 2664 } 2665 2666 } else if (!previous.hasAttribute(attributeName) 2667 && !target.hasAttribute(attributeName) 2668 && updated.hasAttribute(attributeName)) // 2669 { 2670 CmsEntityAttribute updatedAttribute = updated.getAttribute(attributeName); 2671 for (int i = 0; i < updatedAttribute.getValueCount(); i++) { 2672 if (updatedAttribute.isSimpleValue()) { 2673 handler.addNewAttributeValue(updatedAttribute.getSimpleValues().get(i)); 2674 } else { 2675 handler.addNewAttributeValue( 2676 m_entityBackend.registerEntity(updatedAttribute.getComplexValues().get(i), true)); 2677 } 2678 } 2679 } 2680 } 2681 } 2682}