001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.gwt.client.ui.input.tinymce; 029 030import org.opencms.gwt.client.I_CmsHasInit; 031import org.opencms.gwt.client.ui.I_CmsAutoHider; 032import org.opencms.gwt.client.ui.input.CmsTextBox; 033import org.opencms.gwt.client.ui.input.I_CmsFormWidget; 034import org.opencms.gwt.client.ui.input.form.CmsWidgetFactoryRegistry; 035import org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetFactory; 036import org.opencms.gwt.client.ui.input.form.I_CmsHasPlaceholder; 037import org.opencms.util.CmsStringUtil; 038 039import java.util.Map; 040 041import com.google.common.base.Objects; 042import com.google.common.base.Optional; 043import com.google.gwt.core.client.JavaScriptObject; 044import com.google.gwt.core.client.Scheduler; 045import com.google.gwt.core.client.Scheduler.ScheduledCommand; 046import com.google.gwt.dom.client.Document; 047import com.google.gwt.dom.client.Element; 048import com.google.gwt.dom.client.EventTarget; 049import com.google.gwt.dom.client.NativeEvent; 050import com.google.gwt.event.dom.client.DomEvent; 051import com.google.gwt.event.logical.shared.HasResizeHandlers; 052import com.google.gwt.event.logical.shared.HasValueChangeHandlers; 053import com.google.gwt.event.logical.shared.ResizeEvent; 054import com.google.gwt.event.logical.shared.ResizeHandler; 055import com.google.gwt.event.logical.shared.ValueChangeEvent; 056import com.google.gwt.event.logical.shared.ValueChangeHandler; 057import com.google.gwt.event.shared.HandlerRegistration; 058import com.google.gwt.user.client.DOM; 059import com.google.gwt.user.client.Event; 060import com.google.gwt.user.client.Event.NativePreviewEvent; 061import com.google.gwt.user.client.Event.NativePreviewHandler; 062import com.google.gwt.user.client.Timer; 063import com.google.gwt.user.client.ui.FlowPanel; 064 065import jsinterop.base.Js; 066import jsinterop.base.JsPropertyMap; 067 068/** 069 * This class is used to start TinyMCE for editing the content of an element.<p> 070 * 071 * After constructing the instance, the actual editor is opened using the init() method, and destroyed with the close() 072 * method. While the editor is opened, the edited contents can be accessed using the methods of the HasValue interface. 073 */ 074public final class CmsTinyMCEWidget extends FlowPanel 075implements I_CmsFormWidget, HasResizeHandlers, I_CmsHasInit, HasValueChangeHandlers<String>, I_CmsHasPlaceholder { 076 077 /** Use as option to disallow any HTML or formatting the content. */ 078 public static final String NO_HTML_EDIT = "no_html_edit"; 079 080 /** The widget type id.*/ 081 public static final String WIDGET_TYPE = "wysiwyg"; 082 083 /** Counts currently attached widget instances, for use in registering / deregistering preview event listeners. */ 084 static int attachCount; 085 086 /** The minimum editor height. */ 087 private static final int MIN_EDITOR_HEIGHT = 70; 088 089 /** The preview handler registration. */ 090 private static HandlerRegistration previewRegistration; 091 092 /** The current content. */ 093 protected String m_currentContent; 094 095 /** The TinyMCE editor instance. */ 096 protected JavaScriptObject m_editor; 097 098 /** The DOM ID of the editable element. */ 099 protected String m_id; 100 101 /** The original HTML content of the editable element. */ 102 protected String m_originalContent; 103 104 /** The maximal width of the widget. */ 105 protected int m_width; 106 107 /** The editor height to set. */ 108 int m_editorHeight; 109 110 /** The element to store the widget content in. */ 111 private Element m_contentElement; 112 113 /** Flag controlling whether the widget is enabled. */ 114 private boolean m_enabled = true; 115 116 /** Indicates the value has been set from external, not from within the widget. */ 117 private boolean m_externalValueChange; 118 119 /** Indicating if the widget has been attached yet. */ 120 private boolean m_hasBeenAttached; 121 122 /** Flag indicating the editor has been initialized. */ 123 private boolean m_initialized; 124 125 /** The editor options. */ 126 private Object m_options; 127 128 /** The previous value. */ 129 private String m_previousValue; 130 131 /** 132 * Creates a new instance with the given TinyMCE options. Use this constructor for form based editing.<p> 133 * 134 * @param options the tinyMCE editor options to extend the default settings 135 */ 136 public CmsTinyMCEWidget(Object options) { 137 138 // super(element); 139 m_originalContent = ""; 140 m_options = options; 141 // using a child DIV as content element 142 m_contentElement = getElement().appendChild(DOM.createDiv()); 143 } 144 145 /** 146 * Creates a new instance based on configuration data from the server.<p> 147 * 148 * @param config the configuration data 149 */ 150 public CmsTinyMCEWidget(String config) { 151 152 this(CmsTinyMCEHelper.generateOptionsForTiny(config)); 153 } 154 155 /** 156 * Initializes this class.<p> 157 */ 158 public static void initClass() { 159 160 // registers a factory for creating new instances of this widget 161 CmsWidgetFactoryRegistry.instance().registerFactory(WIDGET_TYPE, new I_CmsFormWidgetFactory() { 162 163 /** 164 * @see org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetFactory#createWidget(java.util.Map, com.google.common.base.Optional) 165 */ 166 public I_CmsFormWidget createWidget(Map<String, String> widgetParams, Optional<String> defaultValue) { 167 168 String cfg = widgetParams.get("v"); 169 if (CmsStringUtil.isEmptyOrWhitespaceOnly(cfg)) { 170 // something went wrong, fall back to text box 171 return new CmsTextBox(); 172 } else { 173 return new CmsTinyMCEWidget(decode(cfg)); 174 } 175 } 176 177 private String decode(String string) { 178 179 StringBuffer result = new StringBuffer(); 180 for (String num : string.split(",")) { 181 result.append((char)(Integer.parseInt(num))); 182 } 183 return result.toString(); 184 } 185 }); 186 } 187 188 /** 189 * Decrements the attach count, removes preview event handler when we go from 1 to 0.<p> 190 */ 191 private static void decrementAttached() { 192 193 attachCount -= 1; 194 if ((attachCount < 1) && (previewRegistration != null)) { 195 previewRegistration.removeHandler(); 196 previewRegistration = null; 197 } 198 199 } 200 201 /** 202 * Increments the attach count, installs preview event handler when we go from 0 to 1.<p> 203 */ 204 private static void incrementAttached() { 205 206 attachCount += 1; 207 if (attachCount == 1) { 208 // Prevent events on TinyMCE popups from being cancelled by the PopupPanel containing the property dialog 209 210 previewRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() { 211 212 public void onPreviewNativeEvent(NativePreviewEvent pEvent) { 213 214 Event event = Event.as(pEvent.getNativeEvent()); 215 EventTarget target = event.getEventTarget(); 216 if (Element.is(target)) { 217 Element elem = Element.as(target); 218 while (elem != null) { 219 if (elem.getClassName().contains("mce-floatpanel")) { 220 pEvent.consume(); 221 return; 222 } 223 elem = elem.getParentElement(); 224 } 225 226 } 227 228 } 229 }); 230 231 } 232 } 233 234 /** 235 * @see com.google.gwt.event.logical.shared.HasResizeHandlers#addResizeHandler(com.google.gwt.event.logical.shared.ResizeHandler) 236 */ 237 public HandlerRegistration addResizeHandler(ResizeHandler handler) { 238 239 return addHandler(handler, ResizeEvent.getType()); 240 } 241 242 /** 243 * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler) 244 */ 245 @Override 246 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) { 247 248 return addHandler(handler, ValueChangeEvent.getType()); 249 } 250 251 /** 252 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getApparentValue() 253 */ 254 public String getApparentValue() { 255 256 return getFormValueAsString(); 257 } 258 259 /** 260 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFieldType() 261 */ 262 public FieldType getFieldType() { 263 264 return FieldType.STRING; 265 } 266 267 /** 268 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValue() 269 */ 270 public Object getFormValue() { 271 272 return getFormValueAsString(); 273 } 274 275 /** 276 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValueAsString() 277 */ 278 public String getFormValueAsString() { 279 280 return getValue(); 281 282 } 283 284 /** 285 * Gets the main editable element.<p> 286 * 287 * @return the editable element 288 */ 289 public Element getMainElement() { 290 291 return m_contentElement; 292 } 293 294 /** 295 * Gets the value.<p> 296 * 297 * @return the value 298 */ 299 public String getValue() { 300 301 if (m_editor != null) { 302 return getContent().trim(); 303 } 304 return m_originalContent.trim(); 305 } 306 307 /** 308 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#isEnabled() 309 */ 310 public boolean isEnabled() { 311 312 return m_enabled; 313 } 314 315 /** 316 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#reset() 317 */ 318 public void reset() { 319 320 setFormValueAsString(""); 321 } 322 323 /** 324 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setAutoHideParent(org.opencms.gwt.client.ui.I_CmsAutoHider) 325 */ 326 public void setAutoHideParent(I_CmsAutoHider autoHideParent) { 327 328 // not supported 329 } 330 331 /** 332 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setEnabled(boolean) 333 * 334 * Partial support: This only works before the actual TinyMCE instance is loaded. 335 */ 336 public void setEnabled(boolean enabled) { 337 338 m_enabled = enabled; 339 } 340 341 /** 342 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setErrorMessage(java.lang.String) 343 */ 344 public void setErrorMessage(String errorMessage) { 345 346 // not supported 347 } 348 349 /** 350 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setFormValueAsString(java.lang.String) 351 */ 352 public void setFormValueAsString(String value) { 353 354 setValue(value, false); 355 } 356 357 /** 358 * @see org.opencms.gwt.client.ui.input.form.I_CmsHasPlaceholder#setPlaceholder(java.lang.String) 359 */ 360 @Override 361 public void setPlaceholder(String placeholder) { 362 363 if (placeholder == null) { 364 placeholder = ""; 365 } 366 JsPropertyMap<Object> optionsMap = Js.cast(m_options); 367 optionsMap.set("placeholder", placeholder); 368 369 } 370 371 /** 372 * Sets the value.<p> 373 * 374 * @param value the value 375 * @param fireEvents true if value change event should be fired 376 */ 377 public void setValue(String value, boolean fireEvents) { 378 379 if (value != null) { 380 value = value.trim(); 381 } 382 if (!Objects.equal(value, getValue())) { 383 setPreviousValue(value); 384 if (m_editor == null) { 385 // editor has not been initialized yet 386 m_originalContent = value; 387 } else { 388 m_externalValueChange = true; 389 setContent(value); 390 } 391 if (fireEvents) { 392 fireValueChange(true); 393 } 394 } 395 396 } 397 398 /** 399 * Checks whether the necessary Javascript libraries are available by accessing them. 400 */ 401 protected native void checkLibraries() /*-{ 402 // fail early if tinymce is not available 403 var w = $wnd; 404 var init = w.tinyMCE.init; 405 }-*/; 406 407 /** 408 * Gives an element an id if it doesn't already have an id, and then returns the element's id.<p> 409 * 410 * @param element the element for which we want to add the id 411 * 412 * @return the id 413 */ 414 protected String ensureId(Element element) { 415 416 String id = element.getId(); 417 if ((id == null) || "".equals(id)) { 418 id = Document.get().createUniqueId(); 419 element.setId(id); 420 } 421 return id; 422 } 423 424 /** 425 * Fires a change event.<p> 426 * 427 * @param force true if the event should be fired even if the value does not differ from the previous one 428 */ 429 protected void fireValueChange(boolean force) { 430 431 String currentValue = getValue(); 432 if (force || !currentValue.equals(m_previousValue)) { 433 m_previousValue = currentValue; 434 ValueChangeEvent.fire(this, currentValue); 435 } 436 437 } 438 439 /** 440 * Returns the editor parent element.<p> 441 * 442 * @return the editor parent element 443 */ 444 protected Element getEditorParentElement() { 445 446 String parentId = m_id + "_parent"; 447 Element result = getElementById(parentId); 448 return result; 449 } 450 451 /** 452 * Gets an element by its id.<p> 453 * 454 * @param id the id 455 * @return the element with the given id 456 */ 457 protected native Element getElementById(String id) /*-{ 458 return $doc.getElementById(id); 459 }-*/; 460 461 /** 462 * Gets the toolbar element.<p> 463 * 464 * @return the toolbar element 465 */ 466 protected Element getToolbarElement() { 467 468 String toolbarId = m_id + "_external"; 469 Element result = getElementById(toolbarId); 470 return result; 471 } 472 473 /** 474 * @see com.google.gwt.user.client.ui.Widget#onDetach() 475 */ 476 @Override 477 protected void onDetach() { 478 479 try { 480 detachEditor(); 481 } catch (Throwable t) { 482 // may happen in rare cases, can be ignored 483 } 484 super.onDetach(); 485 } 486 487 /** 488 * @see com.google.gwt.user.client.ui.Widget#onLoad() 489 */ 490 @Override 491 protected void onLoad() { 492 493 incrementAttached(); 494 495 if (!m_hasBeenAttached) { 496 m_hasBeenAttached = true; 497 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 498 499 @SuppressWarnings("synthetic-access") 500 public void execute() { 501 502 if (isAttached()) { 503 m_editorHeight = calculateEditorHeight(); 504 m_id = ensureId(getMainElement()); 505 m_width = calculateWidth(); 506 checkLibraries(); 507 initNative(!m_enabled); 508 } else { 509 resetAtachedFlag(); 510 } 511 } 512 }); 513 } 514 } 515 516 /** 517 * @see com.google.gwt.user.client.ui.Widget#onUnload() 518 */ 519 @Override 520 protected void onUnload() { 521 522 decrementAttached(); 523 } 524 525 /** 526 * Propagates the a focus event.<p> 527 */ 528 protected void propagateFocusEvent() { 529 530 NativeEvent nativeEvent = Document.get().createFocusEvent(); 531 DomEvent.fireNativeEvent(nativeEvent, this, getElement()); 532 } 533 534 /** 535 * Propagates a native mouse event.<p> 536 * 537 * @param eventType the mouse event type 538 * @param eventSource the event source 539 */ 540 protected native void propagateMouseEvent(String eventType, Element eventSource) /*-{ 541 var doc = $wnd.document; 542 var event; 543 if (doc.createEvent) { 544 event = doc.createEvent("MouseEvents"); 545 event.initEvent(eventType, true, true); 546 eventSource.dispatchEvent(event); 547 } else { 548 eventSource.fireEvent("on" + eventType); 549 } 550 }-*/; 551 552 /** 553 * Sets focus to the editor. Use only when in line editing.<p> 554 */ 555 protected native void refocusInlineEditor() /*-{ 556 var elem = $wnd.document 557 .getElementById(this.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_id); 558 elem.blur(); 559 elem.focus(); 560 }-*/; 561 562 /** 563 * Removes the editor instance.<p> 564 */ 565 protected native void removeEditor() /*-{ 566 var editor = this.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_editor; 567 editor.remove(); 568 }-*/; 569 570 /** 571 * Schedules to reset the focus to the main element.<p> 572 */ 573 protected void scheduleRefocus() { 574 575 // this needs to be delayed a bit, otherwise the toolbar is not rendered properly 576 Timer focusTimer = new Timer() { 577 578 @Override 579 public void run() { 580 581 refocusInlineEditor(); 582 } 583 }; 584 focusTimer.schedule(150); 585 } 586 587 /** 588 * Sets the main content of the element which is inline editable.<p> 589 * 590 * @param html the new content html 591 */ 592 protected native void setMainElementContent(String html) /*-{ 593 var instance = this; 594 var elementId = instance.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_id; 595 var mainElement = $wnd.document.getElementById(elementId); 596 mainElement.innerHTML = html; 597 }-*/; 598 599 /** 600 * Sets the previous value.<p> 601 * 602 * @param previousValue the previous value to set 603 */ 604 protected void setPreviousValue(String previousValue) { 605 606 m_previousValue = previousValue; 607 } 608 609 /** 610 * Calculates the needed editor height.<p> 611 * 612 * @return the calculated editor height 613 */ 614 int calculateEditorHeight() { 615 616 int result = getElement().getOffsetHeight() + 30; 617 return result > MIN_EDITOR_HEIGHT ? result : MIN_EDITOR_HEIGHT; 618 } 619 620 /** 621 * Calculates the widget width.<p> 622 * 623 * @return the widget width 624 */ 625 int calculateWidth() { 626 627 return getElement().getOffsetWidth() - 2; 628 } 629 630 /** 631 * Initializes the TinyMCE instance. 632 * 633 * @param readonly if true, initialize TinyMCE in readonly mode 634 */ 635 native void initNative(boolean readonly) /*-{ 636 637 function merge() { 638 var result = {}, length = arguments.length; 639 for (i = 0; i < length; i++) { 640 for (key in arguments[i]) { 641 if (arguments[i].hasOwnProperty(key)) { 642 result[key] = arguments[i][key]; 643 } 644 } 645 } 646 return result; 647 } 648 649 var self = this; 650 var needsRefocus = false; 651 var elementId = self.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_id; 652 var mainElement = $wnd.document.getElementById(elementId); 653 var editorHeight = self.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_editorHeight 654 + "px"; 655 656 var fireChange = function() { 657 self.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::fireChangeFromNative()(); 658 }; 659 var options = this.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_options; 660 if (options != null && options.editorHeight) { 661 editorHeight = options.editorHeight; 662 delete options.editorHeight; 663 } 664 // default options: 665 var defaults = { 666 elements : elementId, 667 relative_urls : false, 668 remove_script_host : false, 669 entity_encoding : "raw", 670 skin_variant : 'ocms', 671 mode : "exact", 672 theme : "silver", 673 plugins : "autolink lists pagebreak table save hr image link emoticons spellchecker insertdatetime preview media searchreplace print paste directionality noneditable visualchars nonbreaking template wordcount advlist", 674 paste_as_text : true, 675 menubar : false, 676 }; 677 678 self.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_currentContent = self.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_originalContent; 679 defaults.min_height = 100; 680 defaults.max_height = editorHeight; 681 defaults.width = '100%'; 682 defaults.resize = 'both'; 683 684 // extend the defaults with any given options 685 if (options != null) { 686 defaults = merge(defaults, options); 687 } 688 defaults.plugins = "autoresize " + defaults.plugins; 689 // add the setup function 690 defaults.setup = function(ed) { 691 self.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_editor = ed; 692 ed.on('change', fireChange); 693 ed.on('KeyDown', fireChange); 694 ed 695 .on( 696 'LoadContent', 697 function() { 698 699 // firing resize event on resize of the editor iframe 700 ed.dom 701 .bind( 702 ed.getWin(), 703 'resize', 704 function() { 705 self.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::fireResizeEvent()(); 706 }); 707 var content = self.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_originalContent; 708 if (content != null) { 709 ed.setContent(content); 710 } 711 self.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_initialized = true; 712 }); 713 714 }; 715 // initialize tinyMCE 716 if (readonly) { 717 defaults.readonly = 1; 718 } 719 $wnd.tinymce.init(defaults); 720 }-*/; 721 722 /** 723 * Resets the attached flag.<p> 724 */ 725 void resetAtachedFlag() { 726 727 m_hasBeenAttached = false; 728 } 729 730 /** 731 * Removes the editor.<p> 732 */ 733 private native void detachEditor() /*-{ 734 735 var ed = this.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_editor; 736 if (ed != null) { 737 ed.remove(); 738 } 739 // in IE somehow the whole document will be selected, empty the selection to resolve that 740 if ($wnd.document.selection != null) { 741 $wnd.document.selection.empty(); 742 } 743 }-*/; 744 745 /** 746 * Used to fire the value changed event from native code.<p> 747 */ 748 private void fireChangeFromNative() { 749 750 // skip firing the change event, if the external flag is set 751 // String message = "fireChangeFromNative\n"; 752 // message += "init: " + m_initialized + "\n"; 753 // message += "external: " + m_externalValueChange + "\n"; 754 // CmsDebugLog.consoleLog(message); 755 756 if (m_initialized && !m_externalValueChange) { 757 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 758 759 public void execute() { 760 761 try { 762 CmsTinyMCEWidget.this.fireValueChange(false); 763 } catch (Throwable t) { 764 // this may happen when returning from full screen mode, nothing to be done 765 } 766 } 767 }); 768 } 769 // reset the external flag 770 m_externalValueChange = false; 771 } 772 773 /** 774 * Fires the resize event.<p> 775 */ 776 private void fireResizeEvent() { 777 778 ResizeEvent.fire(this, getOffsetWidth(), getOffsetHeight()); 779 } 780 781 /** 782 * Returns the editor content.<p> 783 * 784 * @return the editor content 785 */ 786 private native String getContent() /*-{ 787 var editor = this.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_editor; 788 return editor.getContent(); 789 }-*/; 790 791 /** 792 * Sets the content of the TinyMCE editor.<p> 793 * 794 * @param newContent the new content 795 */ 796 private native void setContent(String newContent) /*-{ 797 var editor = this.@org.opencms.gwt.client.ui.input.tinymce.CmsTinyMCEWidget::m_editor; 798 editor.setContent(newContent); 799 }-*/; 800 801 /** 802 * Sets the editor status to enabled/disabled.<p> 803 * 804 * Warning: This only works before the TinyMCE editor has actually been initialized 805 * @param editor the editor instance 806 * @param enabled true if editor should be enabled 807 */ 808 private native void setEnabled(JavaScriptObject editor, boolean enabled) /*-{ 809 editor.getBody().setAttribute('contenteditable', enabled); 810 }-*/; 811 812}