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