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