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