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 m_context = context; 849 850 I_CmsSimpleCallback<Boolean> callback = new I_CmsSimpleCallback<Boolean>() { 851 852 public void execute(Boolean arg) { 853 854 if (arg.booleanValue()) { 855 loadInitialDefinition( 856 CmsContentDefinition.uuidToEntityId(structureId, locale), 857 newLink, 858 modelFileId, 859 mode, 860 postCreateHandler, 861 mainLocale, 862 editHandlerData, 863 context.getSettingPresets(), 864 context.getEditorStylesheet(), 865 new I_CmsSimpleCallback<CmsContentDefinition>() { 866 867 public void execute(CmsContentDefinition contentDefinition) { 868 869 if (contentDefinition.isModelInfo()) { 870 openModelSelectDialog(context, contentDefinition); 871 } else { 872 initEditor(context, contentDefinition, null, false, null); 873 } 874 } 875 }); 876 } else { 877 showLockedResourceMessage(); 878 } 879 880 } 881 }; 882 // make sure the resource is locked, if we are not creating a new one 883 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(newLink)) { 884 callback.execute(Boolean.TRUE); 885 } else { 886 CmsCoreProvider.get().lock(structureId, callback); 887 } 888 } 889 890 /** 891 * Renders the in-line editor for the given element.<p> 892 * 893 * @param context the editor context 894 * @param elementId the element id 895 * @param locale the content locale 896 * @param panel the element panel 897 * @param mainLocale the main language to copy in case the element language node does not exist yet 898 * @param loadTime the time when the requested resource was loaded 899 * @param onClose the command to execute on close 900 */ 901 public void openInlineEditor( 902 final CmsEditorContext context, 903 CmsUUID elementId, 904 String locale, 905 final I_CmsInlineFormParent panel, 906 final String mainLocale, 907 long loadTime, 908 I_CmsEditorCloseHandler onClose) { 909 910 initEventPreviewHandler(); 911 m_context = context; 912 final String entityId = CmsContentDefinition.uuidToEntityId(elementId, locale); 913 m_locale = locale; 914 m_onClose = onClose; 915 CmsCoreProvider.get().lock(elementId, loadTime, new I_CmsSimpleCallback<Boolean>() { 916 917 public void execute(Boolean arg) { 918 919 if (arg.booleanValue()) { 920 loadInitialDefinition( 921 entityId, 922 null, 923 null, 924 null, 925 null, 926 mainLocale, 927 null, 928 Collections.emptyMap(), 929 context.getEditorStylesheet(), 930 new I_CmsSimpleCallback<CmsContentDefinition>() { 931 932 public void execute(CmsContentDefinition contentDefinition) { 933 934 initEditor(context, contentDefinition, panel, true, mainLocale); 935 } 936 }); 937 } else { 938 showLockedResourceMessage(); 939 } 940 } 941 }); 942 } 943 944 /** 945 * Opens the form based editor. Used within the stand alone acacia/editor.jsp.<p> 946 * 947 * @param context the editor context 948 */ 949 public void openStandAloneFormEditor(final CmsEditorContext context) { 950 951 initEventPreviewHandler(); 952 final CmsContentDefinition definition; 953 try { 954 definition = (CmsContentDefinition)CmsRpcPrefetcher.getSerializedObjectFromDictionary( 955 getService(), 956 I_CmsContentService.DICT_CONTENT_DEFINITION); 957 } catch (SerializationException e) { 958 RootPanel.get().add(new Label(e.getMessage())); 959 return; 960 } 961 context.setReusedElement(definition.isReusedElement()); 962 m_isStandAlone = true; 963 if (definition.isModelInfo()) { 964 openModelSelectDialog(context, definition); 965 } else { 966 CmsCoreProvider.get().lock( 967 CmsContentDefinition.entityIdToUuid(definition.getEntityId()), 968 new I_CmsSimpleCallback<Boolean>() { 969 970 public void execute(Boolean arg) { 971 972 if (arg.booleanValue()) { 973 974 registerContentDefinition(definition); 975 // register all external widgets 976 WidgetRegistry.getInstance().registerExternalWidgets( 977 definition.getExternalWidgetConfigurations(), 978 new Command() { 979 980 public void execute() { 981 982 initEditor(context, definition, null, false, null); 983 } 984 }); 985 986 } else { 987 showLockedResourceMessage(); 988 } 989 } 990 }); 991 } 992 } 993 994 /** 995 * Registers a deep copy of the source entity with the given target entity id.<p> 996 * 997 * @param sourceEntityId the source entity id 998 * @param targetEntityId the target entity id 999 */ 1000 public void registerClonedEntity(String sourceEntityId, String targetEntityId) { 1001 1002 CmsEntityBackend.getInstance().getEntity(sourceEntityId).createDeepCopy(targetEntityId); 1003 } 1004 1005 /** 1006 * Registers the given content definition.<p> 1007 * 1008 * @param definition the content definition 1009 */ 1010 public void registerContentDefinition(CmsContentDefinition definition) { 1011 1012 getWidgetService().addConfigurations(definition.getConfigurations()); 1013 CmsType baseType = definition.getTypes().get(definition.getEntityTypeName()); 1014 m_entityBackend.registerTypes(baseType, definition.getTypes()); 1015 for (CmsEntity entity : definition.getEntities().values()) { 1016 CmsEntity previousValue = m_entityBackend.getEntity(entity.getId()); 1017 if (previousValue != null) { 1018 m_entityBackend.changeEntityContentValues(previousValue, entity); 1019 } else { 1020 m_entityBackend.registerEntity(entity); 1021 m_registeredEntities.add(entity.getId()); 1022 } 1023 } 1024 for (Map.Entry<String, CmsComplexWidgetData> entry : definition.getComplexWidgetData().entrySet()) { 1025 String attrName = entry.getKey(); 1026 CmsComplexWidgetData widgetData = entry.getValue(); 1027 getWidgetService().registerComplexWidgetAttribute( 1028 attrName, 1029 widgetData.getRendererName(), 1030 widgetData.getConfiguration()); 1031 } 1032 } 1033 1034 /** 1035 * Saves the given entities.<p> 1036 * 1037 * @param clearOnSuccess <code>true</code> to clear the VIE instance on success 1038 * @param callback the call back command 1039 */ 1040 public void saveAndDeleteEntities(final boolean clearOnSuccess, final I_CmsSimpleCallback<Boolean> callback) { 1041 1042 final CmsEntity entity = m_entityBackend.getEntity(m_entityId); 1043 saveAndDeleteEntities(entity, new ArrayList<String>(m_deletedEntities), clearOnSuccess, callback); 1044 } 1045 1046 /** 1047 * Saves the given entities.<p> 1048 * 1049 * @param lastEditedEntity the last edited entity 1050 * @param deletedEntites the deleted entity id's 1051 * @param clearOnSuccess <code>true</code> to clear the VIE instance on success 1052 * @param callback the call back command 1053 */ 1054 public void saveAndDeleteEntities( 1055 final CmsEntity lastEditedEntity, 1056 final List<String> deletedEntites, 1057 final boolean clearOnSuccess, 1058 final I_CmsSimpleCallback<Boolean> callback) { 1059 1060 CmsRpcAction<CmsSaveResult> asyncCallback = new CmsRpcAction<CmsSaveResult>() { 1061 1062 @Override 1063 public void execute() { 1064 1065 start(200, true); 1066 getService().saveAndDeleteEntities( 1067 lastEditedEntity, 1068 m_clientId, 1069 deletedEntites, 1070 getSkipPaths(), 1071 m_locale, 1072 clearOnSuccess, 1073 this); 1074 } 1075 1076 @Override 1077 protected void onResponse(CmsSaveResult result) { 1078 1079 stop(false); 1080 if ((result != null) && result.hasErrors()) { 1081 showValidationErrorDialog(result.getValidationResult()); 1082 } else { 1083 callback.execute(Boolean.valueOf((result != null) && result.isHasChangedSettings())); 1084 if (clearOnSuccess) { 1085 destroyForm(true); 1086 } 1087 } 1088 } 1089 }; 1090 asyncCallback.execute(); 1091 } 1092 1093 /** 1094 * Saves a value in an Xml content.<p> 1095 * 1096 * @param contentId the structure id of the content 1097 * @param contentPath the xpath for which to set the value 1098 * @param locale the locale for which to set the value 1099 * @param value the new value 1100 * @param asyncCallback the callback for the result 1101 */ 1102 public void saveValue( 1103 final String contentId, 1104 final String contentPath, 1105 final String locale, 1106 final String value, 1107 final AsyncCallback<String> asyncCallback) { 1108 1109 CmsRpcAction<String> action = new CmsRpcAction<String>() { 1110 1111 @Override 1112 public void execute() { 1113 1114 start(0, false); 1115 getService().saveValue(contentId, contentPath, locale, value, this); 1116 1117 } 1118 1119 @Override 1120 protected void onResponse(String result) { 1121 1122 stop(false); 1123 asyncCallback.onSuccess(result); 1124 1125 } 1126 }; 1127 action.execute(); 1128 } 1129 1130 /** 1131 * Sets the show editor help flag to the user session.<p> 1132 * 1133 * @param show the show editor help flag 1134 */ 1135 public void setShowEditorHelp(final boolean show) { 1136 1137 CmsCoreProvider.get().setShowEditorHelp(show); 1138 } 1139 1140 /** 1141 * Removes the given entity from the entity VIE store.<p> 1142 * 1143 * @param entityId the entity id 1144 */ 1145 public void unregistereEntity(String entityId) { 1146 1147 CmsEntityBackend.getInstance().removeEntity(entityId); 1148 } 1149 1150 /** 1151 * @see org.opencms.acacia.client.CmsEditorBase#clearEditor() 1152 */ 1153 @Override 1154 protected void clearEditor() { 1155 1156 super.clearEditor(); 1157 m_context = null; 1158 if (m_undoRedoHandlerRegistration != null) { 1159 m_undoRedoHandlerRegistration.removeHandler(); 1160 } 1161 if (m_toolbar != null) { 1162 m_toolbar.removeFromParent(); 1163 m_toolbar = null; 1164 } 1165 m_cancelButton = null; 1166 m_localeSelect = null; 1167 m_deleteLocaleButton = null; 1168 m_copyLocaleButton = null; 1169 m_openFormButton = null; 1170 m_saveButton = null; 1171 m_onClose = null; 1172 m_locale = null; 1173 if (m_basePanel != null) { 1174 m_basePanel.removeFromParent(); 1175 m_basePanel = null; 1176 } 1177 if (m_entityObserver != null) { 1178 m_entityObserver.clear(); 1179 m_entityObserver = null; 1180 } 1181 m_changedEntityIds.clear(); 1182 m_registeredEntities.clear(); 1183 m_availableLocales.clear(); 1184 m_contentLocales.clear(); 1185 m_deletedEntities.clear(); 1186 m_definitions.clear(); 1187 m_title = null; 1188 m_sitePath = null; 1189 m_resourceTypeName = null; 1190 if (m_closingHandlerRegistration != null) { 1191 m_closingHandlerRegistration.removeHandler(); 1192 m_closingHandlerRegistration = null; 1193 } 1194 if (m_isStandAlone) { 1195 closeEditorWidow(); 1196 } else { 1197 RootPanel.getBodyElement().getParentElement().getStyle().clearOverflow(); 1198 } 1199 if (m_previewHandlerRegistration != null) { 1200 m_previewHandlerRegistration.removeHandler(); 1201 m_previewHandlerRegistration = null; 1202 } 1203 CmsDomUtil.getHtmlElement().removeClassName(EDITOR_MARKER_CLASS); 1204 } 1205 1206 /** 1207 * Gets the editor context.<p> 1208 * 1209 * @return the editor context 1210 */ 1211 protected CmsEditorContext getContext() { 1212 1213 return m_context; 1214 } 1215 1216 /** 1217 * @see org.opencms.acacia.client.CmsEditorBase#getContextUri() 1218 */ 1219 @Override 1220 protected String getContextUri() { 1221 1222 return CmsCoreProvider.get().getUri(); 1223 } 1224 1225 /** 1226 * Gets the entity id.<p> 1227 * 1228 * @return the entity id 1229 */ 1230 protected String getEntityId() { 1231 1232 return m_entityId; 1233 } 1234 1235 /** 1236 * @see org.opencms.acacia.client.CmsEditorBase#getHtmlContextInfo() 1237 */ 1238 @Override 1239 protected String getHtmlContextInfo() { 1240 1241 return m_context.getHtmlContextInfo(); 1242 } 1243 1244 /** 1245 * Returns the paths to be skipped when synchronizing locale independent fields.<p> 1246 * 1247 * @return the paths to be skipped when synchronizing locale independent fields 1248 */ 1249 protected Collection<String> getSkipPaths() { 1250 1251 return ((CmsDefaultWidgetService)getWidgetService()).getSkipPaths(); 1252 } 1253 1254 /** 1255 * Adds a content definition to the internal store.<p> 1256 * 1257 * @param definition the definition to add 1258 */ 1259 void addContentDefinition(CmsContentDefinition definition) { 1260 1261 m_definitions.put(definition.getLocale(), definition); 1262 m_contentLocales.add(definition.getLocale()); 1263 } 1264 1265 /** 1266 * Calls the editor change handlers.<p> 1267 * 1268 * @param changedScopes the changed content value scopes 1269 */ 1270 void callEditorChangeHandlers(final Set<String> changedScopes) { 1271 1272 m_changedScopes.addAll(changedScopes); 1273 if (!m_callingChangeHandlers && (m_changedScopes.size() > 0)) { 1274 m_callingChangeHandlers = true; 1275 final Set<String> scopesToSend = new HashSet<String>(m_changedScopes); 1276 m_changedScopes.clear(); 1277 final CmsEntity entity = m_entityBackend.getEntity(m_entityId); 1278 final org.opencms.acacia.shared.CmsEntity currentState = entity.createDeepCopy(m_entityId); 1279 CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() { 1280 1281 @Override 1282 public void execute() { 1283 1284 start(200, true); 1285 getService().callEditorChangeHandlers( 1286 getEntityId(), 1287 currentState, 1288 getSkipPaths(), 1289 scopesToSend, 1290 this); 1291 } 1292 1293 @Override 1294 public void onFailure(Throwable t) { 1295 1296 m_callingChangeHandlers = false; 1297 super.onFailure(t); 1298 1299 } 1300 1301 @Override 1302 protected void onResponse(CmsContentDefinition result) { 1303 1304 m_callingChangeHandlers = false; 1305 stop(false); 1306 updateEditorValues(currentState, result.getEntity()); 1307 callEditorChangeHandlers(new HashSet<String>()); 1308 } 1309 }; 1310 action.execute(); 1311 } 1312 } 1313 1314 /** 1315 * Cancels the editing process.<p> 1316 */ 1317 void cancelEdit() { 1318 1319 // store the scroll position 1320 int scrollTop = RootPanel.getBodyElement().getOwnerDocument().getScrollTop(); 1321 setEditorState(false); 1322 unlockResource(); 1323 if (m_onClose != null) { 1324 m_onClose.onClose(m_hasChangedSettings, /*publishDialog=*/false); 1325 } 1326 destroyForm(true); 1327 clearEditor(); 1328 // restore the scroll position 1329 RootPanel.getBodyElement().getOwnerDocument().setScrollTop(scrollTop); 1330 } 1331 1332 /** 1333 * Checks if the content is valid and sends a notification if not.<p> 1334 * This will not trigger a validation run, but will use the latest state.<p> 1335 * 1336 * @return <code>true</code> in case there are no validation issues 1337 */ 1338 boolean checkValidation() { 1339 1340 boolean result; 1341 if (m_changedEntityIds.isEmpty()) { 1342 result = true; 1343 } else if (m_saveButton.isEnabled()) { 1344 result = true; 1345 } else { 1346 result = false; 1347 CmsNotification.get().send(Type.ERROR, m_saveButton.getDisabledReason()); 1348 } 1349 return result; 1350 } 1351 1352 /** 1353 * Asks the user to confirm resetting all changes.<p> 1354 */ 1355 void confirmCancel() { 1356 1357 if (m_saveButton.isEnabled()) { 1358 CmsConfirmDialog dialog = new CmsConfirmDialog( 1359 org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TITLE_0), 1360 org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TEXT_0)); 1361 dialog.setHandler(new I_CmsConfirmDialogHandler() { 1362 1363 public void onClose() { 1364 1365 // nothing to do 1366 } 1367 1368 public void onOk() { 1369 1370 cancelEdit(); 1371 } 1372 }); 1373 dialog.center(); 1374 } else { 1375 cancelEdit(); 1376 } 1377 } 1378 1379 /** 1380 * Opens the confirm delete locale dialog.<p> 1381 */ 1382 void confirmDeleteLocale() { 1383 1384 CmsConfirmDialog dialog = new CmsConfirmDialog( 1385 Messages.get().key(Messages.GUI_CONFIRM_DELETE_LOCALE_TITLE_0), 1386 Messages.get().key(Messages.GUI_CONFIRM_DELETE_LOCALE_TEXT_0)); 1387 dialog.setHandler(new I_CmsConfirmDialogHandler() { 1388 1389 public void onClose() { 1390 1391 // nothing to do 1392 } 1393 1394 public void onOk() { 1395 1396 deleteCurrentLocale(); 1397 } 1398 }); 1399 dialog.center(); 1400 } 1401 1402 /** 1403 * Copies the current entity values to the given locales.<p> 1404 * 1405 * @param targetLocales the target locales 1406 */ 1407 void copyLocales(final Set<String> targetLocales) { 1408 1409 final CmsEntity entity = m_entityBackend.getEntity(m_entityId); 1410 CmsRpcAction<Void> action = new CmsRpcAction<Void>() { 1411 1412 @Override 1413 public void execute() { 1414 1415 start(200, true); 1416 getService().copyLocale(targetLocales, entity, this); 1417 } 1418 1419 @Override 1420 protected void onResponse(Void result) { 1421 1422 stop(false); 1423 } 1424 }; 1425 action.execute(); 1426 for (String targetLocale : targetLocales) { 1427 String targetId = getIdForLocale(targetLocale); 1428 if (!m_entityId.equals(targetId)) { 1429 if (m_registeredEntities.contains(targetId)) { 1430 unregistereEntity(targetId); 1431 } 1432 registerClonedEntity(m_entityId, targetId); 1433 m_registeredEntities.add(targetId); 1434 m_changedEntityIds.add(targetId); 1435 m_contentLocales.add(targetLocale); 1436 m_deletedEntities.remove(targetId); 1437 enableSave(); 1438 } 1439 } 1440 initLocaleSelect(); 1441 } 1442 1443 /** 1444 * Deferrers the save action to the end of the browser event queue.<p> 1445 */ 1446 void deferSave() { 1447 1448 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 1449 1450 public void execute() { 1451 1452 save(); 1453 } 1454 }); 1455 } 1456 1457 /** 1458 * Deferrers the save and exit action to the end of the browser event queue.<p> 1459 */ 1460 void deferSaveAndExit() { 1461 1462 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 1463 1464 public void execute() { 1465 1466 saveAndExit(); 1467 } 1468 }); 1469 } 1470 1471 /** 1472 * Deletes the current locale.<p> 1473 */ 1474 void deleteCurrentLocale() { 1475 1476 // there has to remain at least one content locale 1477 if (m_contentLocales.size() > 1) { 1478 String deletedLocale = m_locale; 1479 m_contentLocales.remove(deletedLocale); 1480 m_registeredEntities.remove(m_entityId); 1481 m_changedEntityIds.remove(m_entityId); 1482 m_deletedEntities.add(m_entityId); 1483 getValidationHandler().getValidationContext().removeEntityId(m_entityId); 1484 unregistereEntity(m_entityId); 1485 enableSave(); 1486 String nextLocale = null; 1487 if (m_registeredEntities.isEmpty()) { 1488 nextLocale = m_contentLocales.iterator().next(); 1489 } else { 1490 nextLocale = CmsContentDefinition.getLocaleFromId(m_registeredEntities.iterator().next()); 1491 } 1492 switchLocale(nextLocale); 1493 } 1494 } 1495 1496 /** 1497 * Disables the save buttons with the given message.<p> 1498 * 1499 * @param message the disabled message 1500 */ 1501 void disableSave(String message) { 1502 1503 m_saveButton.disable(message); 1504 m_saveExitButton.disable(message); 1505 } 1506 1507 /** 1508 * Leaves the editor saving the content if necessary.<p> 1509 */ 1510 void exitWithSaving() { 1511 1512 if (checkValidation()) { 1513 if (m_saveExitButton.isEnabled()) { 1514 saveAndExit(); 1515 } else { 1516 cancelEdit(); 1517 } 1518 } 1519 } 1520 1521 /** 1522 * Handles validation changes.<p> 1523 * 1524 * @param validationContext the changed validation context 1525 */ 1526 void handleValidationChange(CmsValidationContext validationContext) { 1527 1528 if (validationContext.hasValidationErrors()) { 1529 String locales = ""; 1530 for (String id : validationContext.getInvalidEntityIds()) { 1531 1532 locales += "\n"; 1533 1534 String locale = CmsContentDefinition.getLocaleFromId(id); 1535 if (m_availableLocales.containsKey(locale)) { 1536 locales += m_availableLocales.get(locale); 1537 locales += ": " + validationContext.getInvalidFields(id); 1538 } 1539 } 1540 disableSave(Messages.get().key(Messages.GUI_TOOLBAR_VALIDATION_ERRORS_1, locales)); 1541 } else if (!m_changedEntityIds.isEmpty()) { 1542 enableSave(); 1543 } 1544 } 1545 1546 /** 1547 * Hides the editor help bubbles.<p> 1548 * 1549 * @param hide <code>true</code> to hide the help bubbles 1550 */ 1551 void hideHelpBubbles(boolean hide) { 1552 1553 setShowEditorHelp(!hide); 1554 CmsValueFocusHandler.getInstance().hideHelpBubbles(RootPanel.get(), hide); 1555 if (!hide) { 1556 m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_SHOWN_0)); 1557 } else { 1558 m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_HIDDEN_0)); 1559 } 1560 } 1561 1562 /** 1563 * Initializes the editor.<p> 1564 * 1565 * @param context the editor context 1566 * @param contentDefinition the content definition 1567 * @param formParent the inline form parent panel, used for inline editing only 1568 * @param inline <code>true</code> to render the editor for inline editing 1569 * @param mainLocale the main language to copy in case the element language node does not exist yet 1570 */ 1571 void initEditor( 1572 CmsEditorContext context, 1573 CmsContentDefinition contentDefinition, 1574 I_CmsInlineFormParent formParent, 1575 boolean inline, 1576 String mainLocale) { 1577 1578 m_context = context; 1579 m_locale = contentDefinition.getLocale(); 1580 m_entityId = contentDefinition.getEntityId(); 1581 m_deleteOnCancel = contentDefinition.isDeleteOnCancel(); 1582 m_autoUnlock = contentDefinition.isAutoUnlock(); 1583 m_isDirectEdit = contentDefinition.isDirectEdit(); 1584 CmsDomUtil.getHtmlElement().addClassName(EDITOR_MARKER_CLASS); 1585 1586 initClosingHandler(); 1587 setContentDefinition(contentDefinition); 1588 initToolbar(); 1589 if (inline && (formParent != null)) { 1590 if ((mainLocale != null) 1591 && (CmsDomUtil.querySelector( 1592 "[" + CmsGwtConstants.ATTR_DATA_ID + "^='" + m_entityId + "']", 1593 formParent.getElement()) == null)) { 1594 // in case a main locale is given and there are not any HTML elements attributed to the current entity id, 1595 // check if the content was rendered for the main locale 1596 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(m_entityId); 1597 String mainLocaleEntityId = CmsContentDefinition.uuidToEntityId(structureId, mainLocale); 1598 NodeList<Element> elements = CmsDomUtil.querySelectorAll( 1599 "[" + CmsGwtConstants.ATTR_DATA_ID + "^='" + mainLocaleEntityId + "']", 1600 formParent.getElement()); 1601 if (elements.getLength() > 0) { 1602 for (int i = 0; i < elements.getLength(); i++) { 1603 Element element = elements.getItem(i); 1604 element.setAttribute( 1605 CmsGwtConstants.ATTR_DATA_ID, 1606 element.getAttribute(CmsGwtConstants.ATTR_DATA_ID).replace(mainLocaleEntityId, m_entityId)); 1607 } 1608 } 1609 1610 } 1611 initEditOverlay(formParent.getElement()); 1612 addOverlayClickHandler(new ClickHandler() { 1613 1614 public void onClick(ClickEvent event) { 1615 1616 exitWithSaving(); 1617 } 1618 }); 1619 m_hideHelpBubblesButton.setVisible(false); 1620 setNativeResourceInfo(m_sitePath, m_locale); 1621 initEntityObserver(); 1622 if (m_definitions.get(m_locale).hasEditorChangeHandlers()) { 1623 initEditorChangeHandlers(m_definitions.get(m_locale).getEditorChangeScopes()); 1624 } 1625 renderInlineEntity(m_entityId, formParent); 1626 } else { 1627 initFormPanel(); 1628 renderFormContent(); 1629 fixFocus(); 1630 } 1631 if (contentDefinition.isPerformedAutocorrection()) { 1632 CmsNotification.get().send( 1633 CmsNotification.Type.NORMAL, 1634 Messages.get().key(Messages.GUI_WARN_INVALID_XML_STRUCTURE_0)); 1635 setChanged(); 1636 } 1637 } 1638 1639 /** 1640 * Initializes the editor change handler.<p> 1641 * 1642 * @param changeScopes the scopes to watch for changes 1643 */ 1644 void initEditorChangeHandlers(Collection<String> changeScopes) { 1645 1646 if (m_editorChangeHandler != null) { 1647 m_editorChangeHandler.clear(); 1648 } 1649 m_editorChangeHandler = new EditorChangeHandler(getEntity(), changeScopes); 1650 } 1651 1652 /** 1653 * Initializes the entity observer.<p> 1654 */ 1655 void initEntityObserver() { 1656 1657 if (m_entityObserver != null) { 1658 m_entityObserver.clear(); 1659 } 1660 m_entityObserver = new CmsEntityObserver(getCurrentEntity()); 1661 exportObserver(); 1662 } 1663 1664 /** 1665 * Opens the form based editor.<p> 1666 */ 1667 void initFormPanel() { 1668 1669 removeEditOverlays(); 1670 m_openFormButton.setVisible(false); 1671 m_saveButton.setVisible(true); 1672 m_hideHelpBubblesButton.setVisible(true); 1673 m_undoButton.setVisible(true); 1674 m_redoButton.setVisible(true); 1675 m_basePanel = new FlowPanel(); 1676 m_basePanel.addStyleName(I_CmsLayoutBundle.INSTANCE.editorCss().basePanel()); 1677 m_basePanel.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().opencms()); 1678 // insert base panel before the tool bar to keep the tool bar visible 1679 RootPanel.get().add(m_basePanel); 1680 if (m_isStandAlone) { 1681 RootPanel.getBodyElement().addClassName(I_CmsLayoutBundle.INSTANCE.editorCss().standAloneEditor()); 1682 } else { 1683 RootPanel.getBodyElement().getParentElement().getStyle().setOverflow(Overflow.HIDDEN); 1684 } 1685 } 1686 1687 /** 1688 * Opens the copy locale dialog.<p> 1689 */ 1690 void openCopyLocaleDialog() { 1691 1692 CmsCopyLocaleDialog dialog = new CmsCopyLocaleDialog( 1693 m_availableLocales, 1694 m_contentLocales, 1695 m_locale, 1696 m_definitions.get(m_locale).hasSynchronizedElements(), 1697 this); 1698 dialog.center(); 1699 } 1700 1701 /** 1702 * Opens the model file select dialog.<p> 1703 * 1704 * @param context the editor context 1705 * @param definition the content definition 1706 */ 1707 void openModelSelectDialog(final CmsEditorContext context, final CmsContentDefinition definition) { 1708 1709 I_CmsModelSelectHandler handler = new I_CmsModelSelectHandler() { 1710 1711 public void onModelSelect(CmsUUID modelStructureId) { 1712 1713 if (modelStructureId == null) { 1714 modelStructureId = CmsUUID.getNullUUID(); 1715 } 1716 openFormEditor( 1717 context, 1718 definition.getLocale(), 1719 definition.getReferenceResourceId().toString(), 1720 m_clientId, 1721 definition.getNewLink(), 1722 modelStructureId, 1723 null, 1724 null, 1725 null, 1726 1727 null, 1728 m_onClose); 1729 } 1730 }; 1731 String title = org.opencms.gwt.client.Messages.get().key( 1732 org.opencms.gwt.client.Messages.GUI_MODEL_SELECT_TITLE_0); 1733 String message = org.opencms.gwt.client.Messages.get().key( 1734 org.opencms.gwt.client.Messages.GUI_MODEL_SELECT_MESSAGE_0); 1735 CmsModelSelectDialog dialog = new CmsModelSelectDialog(handler, definition.getModelInfos(), title, message); 1736 dialog.center(); 1737 } 1738 1739 /** 1740 * Previews the native event to enable keyboard short cuts.<p> 1741 * 1742 * @param event the event 1743 */ 1744 void previewNativeEvent(NativePreviewEvent event) { 1745 1746 Event nativeEvent = Event.as(event.getNativeEvent()); 1747 if (event.getTypeInt() == Event.ONKEYDOWN) { 1748 int keyCode = nativeEvent.getKeyCode(); 1749 if (nativeEvent.getCtrlKey() || nativeEvent.getMetaKey()) { 1750 // look for short cuts 1751 if (nativeEvent.getShiftKey()) { 1752 if (keyCode == KeyCodes.KEY_S) { 1753 1754 exitWithSaving(); 1755 nativeEvent.preventDefault(); 1756 nativeEvent.stopPropagation(); 1757 } else if (keyCode == KeyCodes.KEY_X) { 1758 confirmCancel(); 1759 nativeEvent.preventDefault(); 1760 nativeEvent.stopPropagation(); 1761 } 1762 } else if (keyCode == KeyCodes.KEY_S) { 1763 if (checkValidation()) { 1764 save(); 1765 } 1766 nativeEvent.preventDefault(); 1767 nativeEvent.stopPropagation(); 1768 } 1769 1770 } 1771 } 1772 } 1773 1774 /** 1775 * Renders the form content.<p> 1776 */ 1777 void renderFormContent() { 1778 1779 initLocaleSelect(); 1780 setNativeResourceInfo(m_sitePath, m_locale); 1781 m_contentInfoHeader = new CmsInfoHeader(m_title, null, m_sitePath, m_locale, m_iconClasses); 1782 m_basePanel.add(m_contentInfoHeader); 1783 if (m_context.isReusedElement()) { 1784 String message = Messages.get().key(Messages.GUI_CONTENT_EDITOR_REUSE_MARKER_0); 1785 Label label = new Label(message); 1786 label.addStyleName("oc-editor-reuse-marker"); 1787 m_contentInfoHeader.addWidget(label); 1788 } 1789 SimplePanel content = new SimplePanel(); 1790 content.setStyleName(org.opencms.acacia.client.css.I_CmsLayoutBundle.INSTANCE.form().formParent()); 1791 m_basePanel.add(content); 1792 initEntityObserver(); 1793 if (m_definitions.get(m_locale).hasEditorChangeHandlers()) { 1794 initEditorChangeHandlers(m_definitions.get(m_locale).getEditorChangeScopes()); 1795 } 1796 renderEntityForm(m_entityId, m_tabInfos, content, m_basePanel.getElement()); 1797 if ((m_clientId != null) && (getFormTabs() != null)) { 1798 1799 CmsListInfoBean pageInfo = CmsContainerpageController.get().getData().getPageInfo(); 1800 m_pageInfoHeader = new CmsInfoHeader( 1801 pageInfo.getTitle(), 1802 null, 1803 pageInfo.getSubTitle(), 1804 CmsContainerpageController.get().getData().getLocale(), 1805 pageInfo.getBigIconClasses()); 1806 updateInfoHeader(CmsContentDefinition.SETTINGS_TAB_ID.equals(getFormTabs().getSelectedId())); 1807 getFormTabs().addSelectionHandler(new SelectionHandler<Integer>() { 1808 1809 public void onSelection(SelectionEvent<Integer> event) { 1810 1811 updateInfoHeader(CmsContentDefinition.SETTINGS_TAB_ID.equals(getFormTabs().getSelectedId())); 1812 } 1813 }); 1814 } 1815 } 1816 1817 /** 1818 * Saves the content and closes the editor.<p> 1819 */ 1820 void save() { 1821 1822 saveAndDeleteEntities(false, new I_CmsSimpleCallback<Boolean>() { 1823 1824 public void execute(Boolean hasChangedSettings) { 1825 1826 setSaved(); 1827 setUnchanged(); 1828 m_hasChangedSettings = m_hasChangedSettings || hasChangedSettings.booleanValue(); 1829 } 1830 }); 1831 } 1832 1833 /** 1834 * Saves the content and closes the editor.<p> 1835 */ 1836 void saveAndExit() { 1837 1838 boolean unlock = shouldUnlockAutomatically(); 1839 // store the scroll position 1840 final int scrollTop = RootPanel.getBodyElement().getOwnerDocument().getScrollTop(); 1841 saveAndDeleteEntities(unlock, new I_CmsSimpleCallback<Boolean>() { 1842 1843 public void execute(final Boolean hasChangedSettings) { 1844 1845 setSaved(); 1846 if (m_onClose != null) { 1847 m_onClose.onClose( 1848 m_hasChangedSettings || hasChangedSettings.booleanValue(), 1849 /*publishDialog=*/false); 1850 } 1851 clearEditor(); 1852 // restore the scroll position 1853 RootPanel.getBodyElement().getOwnerDocument().setScrollTop(scrollTop); 1854 } 1855 }); 1856 } 1857 1858 /** 1859 * Sets the has changed flag and enables the save button.<p> 1860 */ 1861 void setChanged() { 1862 1863 enableSave(); 1864 m_changedEntityIds.add(m_entityId); 1865 m_deletedEntities.remove(m_entityId); 1866 updateOverlayPosition(); 1867 setEditorState(true); 1868 } 1869 1870 /** 1871 * Sets the content definition.<p> 1872 * 1873 * @param definition the content definition 1874 */ 1875 void setContentDefinition(CmsContentDefinition definition) { 1876 1877 if (m_availableLocales.isEmpty()) { 1878 // only set the locales when initially setting the content definition 1879 m_availableLocales.putAll(definition.getAvailableLocales()); 1880 m_contentLocales.addAll(definition.getContentLocales()); 1881 } else { 1882 m_contentLocales.add(definition.getLocale()); 1883 } 1884 m_title = definition.getTitle(); 1885 m_sitePath = definition.getSitePath(); 1886 m_resourceTypeName = definition.getResourceType(); 1887 m_registeredEntities.add(definition.getEntityId()); 1888 m_tabInfos = definition.getTabInfos(); 1889 m_iconClasses = definition.getIconClasses(); 1890 addContentDefinition(definition); 1891 CmsDefaultWidgetService service = (CmsDefaultWidgetService)getWidgetService(); 1892 service.addConfigurations(definition.getConfigurations()); 1893 service.setSyncValues(definition.getSyncValues()); 1894 service.setSkipPaths(definition.getSkipPaths()); 1895 addEntityChangeHandler(definition.getEntityId(), new ValueChangeHandler<CmsEntity>() { 1896 1897 public void onValueChange(ValueChangeEvent<CmsEntity> event) { 1898 1899 setChanged(); 1900 } 1901 }); 1902 } 1903 1904 /** 1905 * Removes the delete on cancel flag for new resources.<p> 1906 */ 1907 void setSaved() { 1908 1909 setEditorState(false); 1910 m_deleteOnCancel = false; 1911 } 1912 1913 /** 1914 * Call after save.<p> 1915 */ 1916 void setUnchanged() { 1917 1918 m_changedEntityIds.clear(); 1919 m_deletedEntities.clear(); 1920 disableSave(Messages.get().key(Messages.GUI_TOOLBAR_NOTHING_CHANGED_0)); 1921 } 1922 1923 /** 1924 * Enables and disabler the undo redo buttons according to the state.<p> 1925 * 1926 * @param state the undo redo state 1927 */ 1928 void setUndoRedoState(UndoRedoState state) { 1929 1930 if (state.hasUndo()) { 1931 m_undoButton.enable(); 1932 } else { 1933 m_undoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_DISABLED_0)); 1934 } 1935 if (state.hasRedo()) { 1936 m_redoButton.enable(); 1937 } else { 1938 m_redoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_REDO_DISABLED_0)); 1939 } 1940 } 1941 1942 /** 1943 * Returns true if the edited resource should be unlocked automatically after pressing Save/Exit.<p> 1944 * 1945 * @return true if the edited resource should be unlocked automatically 1946 */ 1947 boolean shouldUnlockAutomatically() { 1948 1949 if (m_isStandAlone) { 1950 if (m_isDirectEdit) { 1951 // Classic direct edit case - always unlock 1952 return true; 1953 } else { 1954 // Workplace case - determined by configuration 1955 return m_autoUnlock; 1956 } 1957 } 1958 // Container page case - always unlock 1959 return true; 1960 } 1961 1962 /** 1963 * Shows the locked resource error message.<p> 1964 */ 1965 void showLockedResourceMessage() { 1966 1967 CmsErrorDialog dialog = new CmsErrorDialog( 1968 Messages.get().key(Messages.ERR_RESOURCE_ALREADY_LOCKED_BY_OTHER_USER_0), 1969 null); 1970 dialog.addCloseHandler(new CloseHandler<PopupPanel>() { 1971 1972 public void onClose(CloseEvent<PopupPanel> event) { 1973 1974 cancelEdit(); 1975 } 1976 }); 1977 dialog.center(); 1978 } 1979 1980 /** 1981 * Shows the validation error dialog.<p> 1982 * 1983 * @param validationResult the validation result 1984 */ 1985 void showValidationErrorDialog(CmsValidationResult validationResult) { 1986 1987 if (validationResult.getErrors().keySet().contains(m_entityId)) { 1988 getValidationHandler().displayValidation(m_entityId, validationResult); 1989 } 1990 String errorLocales = ""; 1991 for (String entityId : validationResult.getErrors().keySet()) { 1992 String locale = CmsContentDefinition.getLocaleFromId(entityId); 1993 errorLocales += m_availableLocales.get(locale) + ", "; 1994 } 1995 // remove trailing ',' 1996 errorLocales = errorLocales.substring(0, errorLocales.length() - 2); 1997 CmsErrorDialog dialog = new CmsErrorDialog( 1998 Messages.get().key(Messages.GUI_VALIDATION_ERROR_1, errorLocales), 1999 null); 2000 dialog.center(); 2001 } 2002 2003 /** 2004 * Switches to the selected locale. Will save changes first.<p> 2005 * 2006 * @param locale the locale to switch to 2007 */ 2008 void switchLocale(final String locale) { 2009 2010 if (locale.equals(m_locale)) { 2011 return; 2012 } 2013 final Integer oldTabIndex = getTabIndex(); 2014 m_locale = locale; 2015 m_basePanel.clear(); 2016 destroyForm(false); 2017 final CmsEntity entity = m_entityBackend.getEntity(m_entityId); 2018 m_entityId = getIdForLocale(locale); 2019 // if the content does not contain the requested locale yet, a new node will be created 2020 final boolean addedNewLocale = !m_contentLocales.contains(locale); 2021 if (m_registeredEntities.contains(m_entityId)) { 2022 unregistereEntity(m_entityId); 2023 } 2024 if (addedNewLocale) { 2025 loadNewDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() { 2026 2027 public void execute(final CmsContentDefinition contentDefinition) { 2028 2029 setContentDefinition(contentDefinition); 2030 renderFormContent(); 2031 if (oldTabIndex != null) { 2032 if (oldTabIndex.intValue() < getFormTabs().getTabCount()) { 2033 getFormTabs().selectTab(oldTabIndex.intValue()); 2034 } 2035 } 2036 setChanged(); 2037 2038 } 2039 }); 2040 } else { 2041 loadDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() { 2042 2043 public void execute(CmsContentDefinition contentDefinition) { 2044 2045 setContentDefinition(contentDefinition); 2046 renderFormContent(); 2047 if (oldTabIndex != null) { 2048 if (oldTabIndex.intValue() < getFormTabs().getTabCount()) { 2049 getFormTabs().selectTab(oldTabIndex.intValue()); 2050 } 2051 } 2052 } 2053 2054 }); 2055 } 2056 } 2057 2058 /** 2059 * Synchronizes the locale independent fields to the other locales.<p> 2060 */ 2061 void synchronizeCurrentLocale() { 2062 2063 m_basePanel.clear(); 2064 destroyForm(false); 2065 CmsEntity entity = m_entityBackend.getEntity(m_entityId); 2066 m_entityId = getIdForLocale(m_locale); 2067 ((CmsDefaultWidgetService)getWidgetService()).setSkipPaths(Collections.<String> emptyList()); 2068 loadDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() { 2069 2070 public void execute(CmsContentDefinition contentDefinition) { 2071 2072 setContentDefinition(contentDefinition); 2073 renderFormContent(); 2074 setChanged(); 2075 } 2076 }); 2077 } 2078 2079 /** 2080 * Unlocks the edited resource.<p> 2081 */ 2082 void unlockResource() { 2083 2084 if (!shouldUnlockAutomatically()) { 2085 return; 2086 } 2087 if (m_entityId != null) { 2088 final CmsUUID structureId = CmsContentDefinition.entityIdToUuid(m_entityId); 2089 if (m_deleteOnCancel) { 2090 CmsRpcAction<Void> action = new CmsRpcAction<Void>() { 2091 2092 @Override 2093 public void execute() { 2094 2095 CmsCoreProvider.getVfsService().syncDeleteResource(structureId, this); 2096 } 2097 2098 @Override 2099 protected void onResponse(Void result) { 2100 2101 // nothing to do 2102 } 2103 }; 2104 action.executeSync(); 2105 } else { 2106 CmsCoreProvider.get().unlock(structureId); 2107 } 2108 } 2109 } 2110 2111 /** 2112 * Updates the editor values.<p> 2113 * 2114 * @param previous the previous entity state 2115 * @param updated the updated entity state 2116 */ 2117 void updateEditorValues(CmsEntity previous, CmsEntity updated) { 2118 2119 if (!m_isDirectEdit && updated.getId().equals(m_entityId)) { 2120 // only apply the changes to the same locale entity 2121 updateEditorValues(previous, updated, getEntity(), Collections.<String> emptyList()); 2122 } 2123 } 2124 2125 /** 2126 * Updates the info header.<p> 2127 * 2128 * @param isSettingsTabSelected <code>true</code> if the element settings tab is selected 2129 */ 2130 void updateInfoHeader(boolean isSettingsTabSelected) { 2131 2132 m_basePanel.remove(0); 2133 m_basePanel.insert(isSettingsTabSelected ? m_pageInfoHeader : m_contentInfoHeader, 0); 2134 } 2135 2136 /** 2137 * Adds the change listener to the observer.<p> 2138 * 2139 * @param changeListener the change listener 2140 * @param changeScope the change scope 2141 */ 2142 private void addChangeListener(JavaScriptObject changeListener, String changeScope) { 2143 2144 try { 2145 System.out.println("Adding native listener for scope " + changeScope); 2146 m_entityObserver.addEntityChangeListener(new CmsEntityChangeListenerWrapper(changeListener), changeScope); 2147 } catch (Exception e) { 2148 2149 CmsDebugLog.getInstance().printLine("Exception occured during listener registration" + e.getMessage()); 2150 } 2151 } 2152 2153 /** 2154 * Changes a simple type entity value.<p> 2155 * 2156 * @param attributeName the attribute name 2157 * @param index the value index 2158 * @param value the value 2159 * @param parentPathElements the parent path elements 2160 */ 2161 private void changeSimpleValue(String attributeName, int index, String value, List<String> parentPathElements) { 2162 2163 CmsAttributeHandler handler = getAttributeHandler(attributeName, parentPathElements); 2164 handler.changeValue(value, index); 2165 } 2166 2167 /** 2168 * Closes the editor.<p> 2169 */ 2170 private native void closeEditorWidow() /*-{ 2171 if ($wnd.top.cms_ade_closeEditorDialog) { 2172 $wnd.top.cms_ade_closeEditorDialog(); 2173 } else if ($wnd.parent.parent.cms_ade_closeEditorDialog) { 2174 $wnd.parent.parent.cms_ade_closeEditorDialog(); 2175 } else { 2176 var backlink = $wnd[@org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService::PARAM_BACKLINK]; 2177 if (backlink) { 2178 $wnd.top.location.href = backlink; 2179 } 2180 } 2181 }-*/; 2182 2183 /** 2184 * Creates a push button for the edit tool-bar.<p> 2185 * 2186 * @param title the button title 2187 * @param imageClass the image class 2188 * 2189 * @return the button 2190 */ 2191 private CmsPushButton createButton(String title, String imageClass) { 2192 2193 CmsPushButton result = new CmsPushButton(); 2194 result.setTitle(title); 2195 result.setImageClass(imageClass); 2196 result.setButtonStyle(ButtonStyle.FONT_ICON, null); 2197 result.setSize(Size.big); 2198 return result; 2199 } 2200 2201 /** 2202 * Enables the save buttons.<p> 2203 */ 2204 private void enableSave() { 2205 2206 m_saveButton.enable(); 2207 m_saveExitButton.enable(); 2208 } 2209 2210 /** 2211 * Exports the add entity change listener method.<p> 2212 */ 2213 private native void exportObserver()/*-{ 2214 var self = this; 2215 $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::ADD_CHANGE_LISTENER_METHOD] = function( 2216 listener, scope) { 2217 var wrapper = { 2218 onChange : listener.onChange 2219 } 2220 self.@org.opencms.ade.contenteditor.client.CmsContentEditor::addChangeListener(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;)(wrapper, scope); 2221 } 2222 $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::GET_CURRENT_ENTITY_METHOD] = function() { 2223 return new $wnd.acacia.CmsEntityWrapper( 2224 self.@org.opencms.ade.contenteditor.client.CmsContentEditor::getCurrentEntity()()); 2225 } 2226 }-*/; 2227 2228 /** 2229 * Returns the attribute handler for the given attribute.<p> 2230 * 2231 * @param attributeName the attribute name 2232 * @param parentPathElements the parent path elements 2233 * 2234 * @return the attribute handler 2235 */ 2236 private CmsAttributeHandler getAttributeHandler(String attributeName, List<String> parentPathElements) { 2237 2238 List<String> childPathElements = new ArrayList<String>(parentPathElements); 2239 childPathElements.add(attributeName); 2240 CmsAttributeHandler handler = getRootAttributeHandler().getHandlerByPath( 2241 childPathElements.toArray(new String[childPathElements.size()])); 2242 return handler; 2243 } 2244 2245 /** 2246 * Returns the entity id for the given locale.<p> 2247 * 2248 * @param locale the locale 2249 * 2250 * @return the entity id 2251 */ 2252 private String getIdForLocale(String locale) { 2253 2254 return CmsContentDefinition.uuidToEntityId(CmsContentDefinition.entityIdToUuid(m_entityId), locale); 2255 } 2256 2257 /** 2258 * Returns the selected tab index, or null if there are no tabs. 2259 * 2260 * @return the selected tab index or null 2261 */ 2262 private Integer getTabIndex() { 2263 2264 if (getFormTabs() != null) { 2265 return Integer.valueOf(getFormTabs().getSelectedIndex()); 2266 } 2267 return null; 2268 } 2269 2270 /** 2271 * Initializes the window closing handler to ensure the resource will be unlocked when leaving the editor.<p> 2272 */ 2273 private void initClosingHandler() { 2274 2275 m_closingHandlerRegistration = Window.addWindowClosingHandler(new ClosingHandler() { 2276 2277 /** 2278 * @see com.google.gwt.user.client.Window.ClosingHandler#onWindowClosing(com.google.gwt.user.client.Window.ClosingEvent) 2279 */ 2280 public void onWindowClosing(ClosingEvent event) { 2281 2282 unlockResource(); 2283 } 2284 }); 2285 } 2286 2287 /** 2288 * Initializes the event preview handler.<p> 2289 */ 2290 private void initEventPreviewHandler() { 2291 2292 m_previewHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() { 2293 2294 public void onPreviewNativeEvent(NativePreviewEvent event) { 2295 2296 previewNativeEvent(event); 2297 } 2298 }); 2299 } 2300 2301 /** 2302 * Initializes the locale selector.<p> 2303 */ 2304 private void initLocaleSelect() { 2305 2306 if (m_availableLocales.size() < 2) { 2307 return; 2308 } 2309 Map<String, String> selectOptions = new HashMap<String, String>(); 2310 for (Entry<String, String> localeEntry : m_availableLocales.entrySet()) { 2311 if (m_contentLocales.contains(localeEntry.getKey())) { 2312 selectOptions.put(localeEntry.getKey(), localeEntry.getValue()); 2313 } else { 2314 selectOptions.put(localeEntry.getKey(), localeEntry.getValue() + " [-]"); 2315 } 2316 } 2317 if (m_localeSelect == null) { 2318 m_localeSelect = new CmsSelectBox(selectOptions); 2319 m_toolbar.insertRight(m_localeSelect, 1); 2320 m_localeSelect.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().inlineBlock()); 2321 m_localeSelect.getElement().getStyle().setWidth(100, Unit.PX); 2322 m_localeSelect.getElement().getStyle().setVerticalAlign(VerticalAlign.MIDDLE); 2323 m_localeSelect.addValueChangeHandler(new ValueChangeHandler<String>() { 2324 2325 public void onValueChange(ValueChangeEvent<String> event) { 2326 2327 switchLocale(event.getValue()); 2328 } 2329 }); 2330 } else { 2331 m_localeSelect.setItems(selectOptions); 2332 } 2333 m_localeSelect.setFormValueAsString(m_locale); 2334 if (m_deleteLocaleButton == null) { 2335 m_deleteLocaleButton = createButton( 2336 Messages.get().key(Messages.GUI_TOOLBAR_DELETE_LOCALE_0), 2337 "opencms-icon-remove-locale"); 2338 m_deleteLocaleButton.addClickHandler(new ClickHandler() { 2339 2340 public void onClick(ClickEvent event) { 2341 2342 confirmDeleteLocale(); 2343 } 2344 }); 2345 m_toolbar.insertRight(m_deleteLocaleButton, 2); 2346 } 2347 if (m_contentLocales.size() > 1) { 2348 m_deleteLocaleButton.enable(); 2349 } else { 2350 m_deleteLocaleButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_CANT_DELETE_LAST_LOCALE_0)); 2351 } 2352 if (m_copyLocaleButton == null) { 2353 m_copyLocaleButton = createButton( 2354 I_CmsButton.ButtonData.COPY_LOCALE_BUTTON.getTitle(), 2355 I_CmsButton.ButtonData.COPY_LOCALE_BUTTON.getIconClass()); 2356 m_copyLocaleButton.addClickHandler(new ClickHandler() { 2357 2358 public void onClick(ClickEvent event) { 2359 2360 openCopyLocaleDialog(); 2361 } 2362 }); 2363 m_toolbar.insertRight(m_copyLocaleButton, 3); 2364 } 2365 2366 } 2367 2368 /** 2369 * Generates the button bar displayed beneath the editable fields.<p> 2370 */ 2371 private void initToolbar() { 2372 2373 m_toolbar = new CmsToolbar(); 2374 m_toolbar.setAppTitle(Messages.get().key(Messages.GUI_CONTENT_EDITOR_TITLE_0)); 2375 m_publishButton = createButton( 2376 I_CmsButton.ButtonData.PUBLISH_BUTTON.getTitle(), 2377 I_CmsButton.ButtonData.PUBLISH_BUTTON.getIconClass()); 2378 m_toolbar.addLeft(m_publishButton); 2379 m_publishButton.addClickHandler(new ClickHandler() { 2380 2381 public void onClick(ClickEvent event) { 2382 2383 boolean unlock = shouldUnlockAutomatically(); 2384 2385 saveAndDeleteEntities(unlock, new I_CmsSimpleCallback<Boolean>() { 2386 2387 public void execute(final Boolean hasChangedSeetings) { 2388 2389 setSaved(); 2390 HashMap<String, String> params = new HashMap<String, String>( 2391 getContext().getPublishParameters()); 2392 CmsUUID structureId = CmsContentDefinition.entityIdToUuid(getEntityId()); 2393 params.put(CmsPublishOptions.PARAM_CONTENT, "" + structureId); 2394 params.put(CmsPublishOptions.PARAM_START_WITH_CURRENT_PAGE, ""); 2395 CmsPublishDialog.showPublishDialog(params, new CloseHandler<PopupPanel>() { 2396 2397 public void onClose(CloseEvent<PopupPanel> closeEvent) { 2398 2399 if (m_onClose != null) { 2400 m_onClose.onClose( 2401 m_hasChangedSettings || hasChangedSeetings.booleanValue(), 2402 /*publishDialog=*/true); 2403 } 2404 clearEditor(); 2405 } 2406 }, new Runnable() { 2407 2408 public void run() { 2409 2410 // ignore 2411 } 2412 2413 }, null); 2414 2415 } 2416 }); 2417 2418 } 2419 }); 2420 m_saveExitButton = createButton( 2421 Messages.get().key(Messages.GUI_TOOLBAR_SAVE_AND_EXIT_0), 2422 "opencms-icon-save-exit"); 2423 m_saveExitButton.addClickHandler(new ClickHandler() { 2424 2425 public void onClick(ClickEvent event) { 2426 2427 deferSaveAndExit(); 2428 } 2429 }); 2430 m_toolbar.addLeft(m_saveExitButton); 2431 m_saveButton = createButton( 2432 Messages.get().key(Messages.GUI_TOOLBAR_SAVE_0), 2433 I_CmsButton.ButtonData.SAVE_BUTTON.getIconClass()); 2434 m_saveButton.addClickHandler(new ClickHandler() { 2435 2436 public void onClick(ClickEvent event) { 2437 2438 deferSave(); 2439 } 2440 }); 2441 m_saveButton.setVisible(false); 2442 m_toolbar.addLeft(m_saveButton); 2443 disableSave(Messages.get().key(Messages.GUI_TOOLBAR_NOTHING_CHANGED_0)); 2444 m_undoButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_0), "opencms-icon-undo"); 2445 m_undoButton.addClickHandler(new ClickHandler() { 2446 2447 public void onClick(ClickEvent event) { 2448 2449 if (CmsUndoRedoHandler.getInstance().isIntitalized()) { 2450 CmsUndoRedoHandler.getInstance().undo(); 2451 } 2452 } 2453 }); 2454 m_undoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_DISABLED_0)); 2455 m_undoButton.setVisible(false); 2456 m_toolbar.addLeft(m_undoButton); 2457 m_redoButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_REDO_0), "opencms-icon-redo"); 2458 m_redoButton.addClickHandler(new ClickHandler() { 2459 2460 public void onClick(ClickEvent event) { 2461 2462 if (CmsUndoRedoHandler.getInstance().isIntitalized()) { 2463 CmsUndoRedoHandler.getInstance().redo(); 2464 } 2465 } 2466 }); 2467 m_redoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_REDO_DISABLED_0)); 2468 m_redoButton.setVisible(false); 2469 m_toolbar.addLeft(m_redoButton); 2470 2471 m_undoRedoHandlerRegistration = CmsUndoRedoHandler.getInstance().addValueChangeHandler( 2472 new ValueChangeHandler<CmsUndoRedoHandler.UndoRedoState>() { 2473 2474 public void onValueChange(ValueChangeEvent<UndoRedoState> event) { 2475 2476 setUndoRedoState(event.getValue()); 2477 } 2478 }); 2479 m_openFormButton = createButton( 2480 Messages.get().key(Messages.GUI_TOOLBAR_OPEN_FORM_0), 2481 I_CmsButton.ButtonData.EDIT.getIconClass()); 2482 m_openFormButton.addClickHandler(new ClickHandler() { 2483 2484 public void onClick(ClickEvent event) { 2485 2486 initFormPanel(); 2487 renderFormContent(); 2488 } 2489 }); 2490 m_toolbar.addLeft(m_openFormButton); 2491 2492 m_hideHelpBubblesButton = new CmsToggleButton(); 2493 2494 m_hideHelpBubblesButton.setImageClass(I_CmsButton.ButtonData.TOGGLE_HELP.getIconClass()); 2495 m_hideHelpBubblesButton.setButtonStyle(ButtonStyle.FONT_ICON, null); 2496 m_hideHelpBubblesButton.setSize(Size.big); 2497 m_hideHelpBubblesButton.addClickHandler(new ClickHandler() { 2498 2499 public void onClick(ClickEvent event) { 2500 2501 CmsToggleButton button = (CmsToggleButton)event.getSource(); 2502 hideHelpBubbles(!button.isDown()); 2503 } 2504 }); 2505 m_hideHelpBubblesButton.setDown(CmsCoreProvider.get().isShowEditorHelp()); 2506 CmsValueFocusHandler.getInstance().hideHelpBubbles(RootPanel.get(), !CmsCoreProvider.get().isShowEditorHelp()); 2507 if (!CmsCoreProvider.get().isShowEditorHelp()) { 2508 m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_HIDDEN_0)); 2509 } else { 2510 m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_SHOWN_0)); 2511 } 2512 m_toolbar.addRight(m_hideHelpBubblesButton); 2513 2514 m_cancelButton = createButton( 2515 Messages.get().key(Messages.GUI_TOOLBAR_RESET_0), 2516 I_CmsButton.ButtonData.RESET_BUTTON.getIconClass()); 2517 m_cancelButton.addClickHandler(new ClickHandler() { 2518 2519 public void onClick(ClickEvent event) { 2520 2521 confirmCancel(); 2522 } 2523 }); 2524 m_toolbar.addRight(m_cancelButton); 2525 RootPanel.get().add(m_toolbar); 2526 } 2527 2528 /** 2529 * Sets the editor state.<p> 2530 * 2531 * @param changed if the content has been changed 2532 */ 2533 private native void setEditorState(boolean changed)/*-{ 2534 if (typeof $wnd.cmsSetEditorChangedState === 'function') { 2535 $wnd.cmsSetEditorChangedState(changed); 2536 } 2537 }-*/; 2538 2539 /** 2540 * Sets the resource info to native window context variables.<p> 2541 * 2542 * @param sitePath the site path 2543 * @param locale the content locale 2544 */ 2545 private native void setNativeResourceInfo(String sitePath, String locale)/*-{ 2546 $wnd._editResource = sitePath; 2547 $wnd._editLanguage = locale; 2548 }-*/; 2549 2550 /** 2551 * Updates the editor values according to the given entity.<p> 2552 * 2553 * @param previous the previous entity state 2554 * @param updated the updated entity state 2555 * @param target the target entity 2556 * @param parentPathElements the parent path elements 2557 */ 2558 private void updateEditorValues( 2559 CmsEntity previous, 2560 CmsEntity updated, 2561 CmsEntity target, 2562 List<String> parentPathElements) { 2563 2564 for (String attributeName : m_entityBackend.getType(target.getTypeName()).getAttributeNames()) { 2565 CmsAttributeHandler handler = getAttributeHandler(attributeName, parentPathElements); 2566 if (handler == null) { 2567 // non visible attribute, skip it 2568 continue; 2569 } 2570 if (previous.hasAttribute(attributeName) 2571 && updated.hasAttribute(attributeName) 2572 && target.hasAttribute(attributeName)) { 2573 CmsEntityAttribute updatedAttribute = updated.getAttribute(attributeName); 2574 CmsEntityAttribute previousAttribute = previous.getAttribute(attributeName); 2575 CmsEntityAttribute targetAttribute = target.getAttribute(attributeName); 2576 if (updatedAttribute.isSimpleValue()) { 2577 if ((updatedAttribute.getValueCount() == previousAttribute.getValueCount()) 2578 && (updatedAttribute.getValueCount() == targetAttribute.getValueCount())) { 2579 for (int i = 0; i < updatedAttribute.getValueCount(); i++) { 2580 if (!updatedAttribute.getSimpleValues().get(i).equals( 2581 previousAttribute.getSimpleValues().get(i)) 2582 && previousAttribute.getSimpleValues().get(i).equals( 2583 targetAttribute.getSimpleValues().get(i))) { 2584 2585 changeSimpleValue( 2586 attributeName, 2587 i, 2588 updatedAttribute.getSimpleValues().get(i), 2589 parentPathElements); 2590 } 2591 } 2592 } else { 2593 if (targetAttribute.getValueCount() == previousAttribute.getValueCount()) { 2594 // only act, if the value count has not been altered while executing the server request 2595 if (updatedAttribute.getValueCount() > previousAttribute.getValueCount()) { 2596 // new values have been added 2597 for (int i = 0; i < updatedAttribute.getValueCount(); i++) { 2598 if (i >= previousAttribute.getSimpleValues().size()) { 2599 handler.addNewAttributeValue(updatedAttribute.getSimpleValues().get(i)); 2600 } else if (!updatedAttribute.getSimpleValues().get(i).equals( 2601 previousAttribute.getSimpleValues().get(i)) 2602 && previousAttribute.getSimpleValues().get(i).equals( 2603 targetAttribute.getSimpleValues().get(i))) { 2604 changeSimpleValue( 2605 attributeName, 2606 i, 2607 updatedAttribute.getSimpleValues().get(i), 2608 parentPathElements); 2609 } 2610 } 2611 } else { 2612 // values have been removed 2613 for (int i = previousAttribute.getValueCount() - 1; i >= 0; i--) { 2614 if (i >= updatedAttribute.getSimpleValues().size()) { 2615 handler.removeAttributeValue(i); 2616 } else if (!updatedAttribute.getSimpleValues().get(i).equals( 2617 previousAttribute.getSimpleValues().get(i)) 2618 && previousAttribute.getSimpleValues().get(i).equals( 2619 targetAttribute.getSimpleValues().get(i))) { 2620 changeSimpleValue( 2621 attributeName, 2622 i, 2623 updatedAttribute.getSimpleValues().get(i), 2624 parentPathElements); 2625 } 2626 } 2627 } 2628 } 2629 } 2630 } else { 2631 if (targetAttribute.getValueCount() == previousAttribute.getValueCount()) { 2632 // only act, if the value count has not been altered while executing the server request 2633 if (updatedAttribute.getValueCount() > previousAttribute.getValueCount()) { 2634 // new values have been added 2635 for (int i = 0; i < updatedAttribute.getValueCount(); i++) { 2636 if (i >= previousAttribute.getSimpleValues().size()) { 2637 handler.addNewAttributeValue( 2638 m_entityBackend.registerEntity( 2639 updatedAttribute.getComplexValues().get(i), 2640 true)); 2641 } else { 2642 List<String> childPathElements = new ArrayList<String>(parentPathElements); 2643 childPathElements.add(attributeName + "[" + i + "]"); 2644 updateEditorValues( 2645 previousAttribute.getComplexValues().get(i), 2646 updatedAttribute.getComplexValues().get(i), 2647 targetAttribute.getComplexValues().get(i), 2648 childPathElements); 2649 } 2650 } 2651 } else { 2652 // values have been removed 2653 for (int i = previousAttribute.getValueCount() - 1; i >= 0; i--) { 2654 if (i >= updatedAttribute.getValueCount()) { 2655 2656 handler.removeAttributeValue(i); 2657 } else { 2658 List<String> childPathElements = new ArrayList<String>(parentPathElements); 2659 childPathElements.add(attributeName + "[" + i + "]"); 2660 updateEditorValues( 2661 previousAttribute.getComplexValues().get(i), 2662 updatedAttribute.getComplexValues().get(i), 2663 targetAttribute.getComplexValues().get(i), 2664 childPathElements); 2665 } 2666 } 2667 } 2668 } 2669 } 2670 } else if (previous.hasAttribute(attributeName) && target.hasAttribute(attributeName)) { 2671 for (int i = target.getAttribute(attributeName).getValueCount() - 1; i >= 0; i--) { 2672 handler.removeAttributeValue(i); 2673 } 2674 2675 } else if (!previous.hasAttribute(attributeName) 2676 && !target.hasAttribute(attributeName) 2677 && updated.hasAttribute(attributeName)) // 2678 { 2679 CmsEntityAttribute updatedAttribute = updated.getAttribute(attributeName); 2680 for (int i = 0; i < updatedAttribute.getValueCount(); i++) { 2681 if (updatedAttribute.isSimpleValue()) { 2682 handler.addNewAttributeValue(updatedAttribute.getSimpleValues().get(i)); 2683 } else { 2684 handler.addNewAttributeValue( 2685 m_entityBackend.registerEntity(updatedAttribute.getComplexValues().get(i), true)); 2686 } 2687 } 2688 } 2689 } 2690 } 2691}