Wednesday, November 14, 2007

multiple TinyMCE 3 instances on one page

Two of my favorite javascript libraries, YUI and ExtJS, both have WYSIWYG editors. While the editor for ExtJS , in my opinion, has some more ways to go, I've heard raves about YUI's implementation at least from one other developer.

However, there is one WYSIWYG editor that trumps them both and all the others I've seen, it's called TinyMCE.

So it's no wonder that many a developer would like to use it and/or integrate it into their javascript library of choice, including myself.

If you visit the TinyMCE wiki, you'll probably learn to initialize TinyMCE like this


tinyMCE.init({
theme : "advanced",
mode : "textarea",
language : "en",
theme_advanced_layout_manager : "SimpleLayout",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left",
theme_advanced_buttons1 : "bold,italic,underline,strikethrough"
});


This works great if :
  1. you want the editor to appear immediately after the page has loaded
  2. all the editors on your page share the same configuration
In one of my recent projects with ExtJS 2.0 I needed to instantiate two different TinyMCE instances in different layout panels so the method above won't work, so here's how I did it.

Store the configuration objects in an array.

var configArray = [{
theme : "advanced",
mode : "none",
language : "en",
height:"200",
width:"100%",
theme_advanced_layout_manager : "SimpleLayout",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left",
theme_advanced_buttons1 : "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull",
theme_advanced_buttons2 : "",
theme_advanced_buttons3 : ""
},{
theme : "advanced",
mode : "none",
language : "en",
width:"100%",
theme_advanced_layout_manager : "SimpleLayout",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left"
}]


In a DOM event, like click or expand that reveals one of my TinyMCE instances (textarea1), I do ..


tinyMCE.settings = configArray[0];
tinyMCE.execCommand('mceAddControl', true, "textarea1");



When I want to reveal my second TinyMCE instance (textarea2), I do ..



tinyMCE.settings = configArray[1];
tinyMCE.execCommand('mceAddControl', true, "textarea2");




To clear the contents of the TinyMCE editor on textarea1, I do ...



tinyMCE.editors.textarea1.setContent(" ");




Here is an html page with the above in action. Remember to change the src of the tinymce javascript.


<html>

<head>
<title>TinyMCE </title>

<script type="text/javascript" src="tiny_mce/tiny_mce.js"></script>
<script>

var tinymceConfigs = [ {theme : "advanced",
mode : "none",
language : "en",
height:"200",
width:"100%",
theme_advanced_layout_manager : "SimpleLayout",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left",
theme_advanced_buttons1 : "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull",
theme_advanced_buttons2 : "",
theme_advanced_buttons3 : "" },{ theme : "advanced",
mode : "none",
language : "en",
height:"200",
width:"100%",
theme_advanced_layout_manager : "SimpleLayout",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left"}];

function tinyfy(settingid,el_id) {
tinyMCE.settings = tinymceConfigs[settingid];
tinyMCE.execCommand('mceAddControl', true, el_id);
}

</script>

</head>

<body>

<h2>Editor 1</h2>
<a href="javascript:void(0)" onclick="tinyfy(0,'ed1')">show editor 1</a>

<br><textarea id="ed1"></textarea>

<h2>Editor 2</h2>
<a href="javascript:void(0)" onclick="tinyfy(1,'ed2')">show editor 2</a>

<br><textarea id="ed2"></textarea>

</body>

</html>

40 comments:

  1. Thanks, after couple frustrating hours i found your solution. Thanks.

    ReplyDelete
  2. Thanks for your post! I had a quick question. Did you have to have anything be loaded when you start the page up? We're trying to do something similar, and we get an error such as 's.invalid_elements has no properties'. Where do you do the tinyMCE.init()? Thanks!

    ReplyDelete
  3. Nope I didn't have anything loaded when the page starts and I didn't use tinyMCE.init()

    If you want to create the two instances on page load, I suggest executing execCommand with body onload.

    You can create a function ....

    function init() {

    tinyMCE.settings = configArray[0];
    tinyMCE.execCommand('mceAddControl', true, "textarea1");


    tinyMCE.settings = configArray[1];
    tinyMCE.execCommand('mceAddControl', true, "textarea2");
    }


    In the body tag call the function onload

    body onload="init()"

    Please see if that works for you.

    Thanks for dropping by my blog.

    ReplyDelete
  4. This looks promising, but I'm having problems implementing it. Initially I had the 's.invalid_elements has no properties' error, but I added 'invalid_elements : ""' to the parameters list and that error was replaced with 'fn has no properties'. Do you have an example page that you can provide a link to?

    Thanks,
    archerid

    ReplyDelete
  5. I created a one page demo on my box
    http://8tons.dyndns.biz/demos/tinymce/index.html

    Please see if you can access it. If not , please take a look again at the blog post. I have added the html for that sample page there.

    Hope this helps.

    ReplyDelete
  6. Thanks for posting the example. I was hoping that this method was compatible with prior versions of tinyMCE, but it looks like it has to be v3+. I see an upgrade in my future :)

    ReplyDelete
  7. archerid, I encountered your same troubles... But I don't want upgrade to a beta version!!

    ReplyDelete
  8. You might be able to get a clue from

    http://extjs.com/forum/showthread.php?t=19101&highlight=tinymce

    I hope this helps.

    ReplyDelete
  9. take a look: this worked for me

    http://rorlach.de/mediawiki/index.php/Load_it_ondemand

    ReplyDelete
  10. Worked fine! Just one detail: you must disable flash tiny_mce plugin.

    ReplyDelete
  11. Thanks a LOT for this solution. It just works like a charm. I have had some frustrating hours this evening trying to make this work, and then I found your article! Thanks, realy!

    ReplyDelete
  12. Thank you very very much. You have saved my bacon and a project I'm working on.

    Had a very complicated AJAX application and just couldn't get tinyMCE working on AJAX recalls.

    You are the man!

    ReplyDelete
  13. It is mentioned in the original post (and even the title), but I thought it was worth pointing out this only works on version 3 and not version 2. I tested with 2.1.2 originally and got error 's.invalid_elements has no properties' which is mentioned in an earlier comment

    ReplyDelete
  14. Hi,
    I am trying to use you method, those I have a function similar to tinyfy in a separate js file. I get an error on tinyMCE.get(id).. for the first instance, though the instances after that work perfectly fine... any ideas?

    ReplyDelete
  15. @umang

    Without looking at the code and based on your description, I have a feeling that it may be an issue with timing.

    This means that your function call is executing before TinyMCE is initialized.

    On what browser does it occur ? (e.g. Firefox or IE) Does it occur erratically ? Can you post the error message here ?

    One suggestion I can make is to add a defer attribute to the js file when you declare it. A defer attribute signals the browser to load the whole page first before loading the js file.

    Try googling for defer and javascript.

    Thanks for visiting and do let me know if this fixed your problem.

    ReplyDelete
  16. Hi Ham,
    I actually have a sort of a wrapper over tinymce. The html page only initializes the wrapper. The wrapper has a overlay which when clicked on opens the editor. So I populate the settings array onclick and call mceAddControl. Fot the first wrapper div I click on, the editor opens up but displays an error in a line that comes after the mceAddControl part. The error is
    tinyMCE.get(id).getDoc() has no properties. I am trying to attach a keyup event to the doc.

    This has worked for me when all instances use the same configs(using a init call).


    Thanks in advance

    ReplyDelete
  17. Oh! and I encounter problems on both IE6 and FF2.. though FF2 does not display this error always...sometimes it seems to work fine

    ReplyDelete
  18. Hi Ham,
    I was able to solve the problem by having a default setting and init... and then overriding the setting if requires using your method.


    Thanks a lot

    ReplyDelete
  19. This is wonderful works without a problem.



    If your are looking to reuse this code in multiple pages below script will help.

    function HookMceEditorsByClass()
    {
    for(var i=0;i&lt;mceSettingsArray.length;i++)
    {
    tinyMCE.settings = mceSettingsArray[i];
    var elCssClass = mceSettingsArray[i].editor_selector;
    //get all elements with that class
    var textAreaArray = getElementsByClass(elCssClass,null,"textarea")
    for(var j=0;j<textAreaArray.length;j++)
    tinyMCE.execCommand('mceAddControl', true, textAreaArray[j].id );
    }
    }


    function getElementsByClass(searchClass,node,tag) {
    var classElements = new Array();
    if ( node == null )
    node = document;
    if ( tag == null )
    tag = '*';
    var els = node.getElementsByTagName(tag);
    var elsLen = els.length;
    var pattern = new RegExp("(^|\\\\s)"+searchClass+"(\\\\s|$)");
    for (i = 0, j = 0; i < elsLen; i++) {
    if ( pattern.test(els[i].className) ) {
    classElements[j] = els[i];
    j++;
    }
    }
    return classElements;
    }


    where mceSettingsArray is array of different configurations.

    Now call the function HookMceEditorsByClass() in 'onload' event of the page with textarea decorated with target class.

    -sm

    ReplyDelete
  20. Ham, you saved me tonight!

    To those having problems with waiting for the page to load... I am using scriptaculous througout my app, so I figured I'd use it here as well.

    So, here is my config, which allows for multiple unique configs per page. It's in a ColdFusion Function, so mind the pound signs as variables...

    //-------------------------
    var configArray#arguments.element# = [{
    mode : 'exact',
    element: "#arguments.element#",
    theme : '#theme#',
    plugins : '#plugins#',
    theme_advanced_disable : 'hr',
    theme_advanced_buttons1_add : '#theme_advanced_buttons1_add#',
    theme_advanced_buttons2_add_before: '#theme_advanced_buttons2_add_before#',
    theme_advanced_buttons3_add_before : '#theme_advanced_buttons3_add_before#',
    theme_advanced_buttons3_add : '#theme_advanced_buttons3_add#',
    content_css : '#cssfile#',
    paste_auto_cleanup_on_paste : true,
    paste_convert_pageheaders_to_strong : false,
    paste_strip_class_attributes : 'all',
    paste_remove_spans : false,
    paste_remove_styles : false,
    force_br_newlines : true,
    force_p_newlines : false,
    relative_urls : false,
    gecko_spellcheck : true,
    convert_urls : true,
    theme_advanced_resizing_use_cookie : true,
    theme_advanced_resizing : #resizing#,
    theme_advanced_resize_horizontal : false,
    theme_advanced_toolbar_location : 'top',
    theme_advanced_toolbar_align : 'left',
    theme_advanced_statusbar_location : 'bottom',
    extended_valid_elements : '#extended_valid_elements#',
    file_browser_callback : 'ajaxfilemanager'
    }];

    Event.observe(window,'load', function(){
    tinyMCE.settings = configArray#arguments.element#[0];
    tinyMCE.execCommand('mceAddControl', false, '#arguments.element#');
    });
    //-------------------------

    Thanks,
    Jules

    ReplyDelete
  21. Hey Ham,

    Great code. My only problem is that if I add a plugin, like Plugin: "xxx", it doesn't work. How can I include plugins for TinyMCE with your code?

    Thanks

    ReplyDelete
  22. Thank you very much! Solved all of my problems.

    :)

    ReplyDelete
  23. Hi,
    I tried #Freelance Web Development# example... it gives me a javascript error...
    It says "invalid_elements" is null or not an object.
    Even if i give some invalid_elements in the configuration, it gives me another error... i am not able to fix it... could you please anyone help me on this.

    ReplyDelete
  24. Thanks Ham! Did you ever find out why it's not working if you have certain plugins, like George said (in my case: plugins : "preview,ezfilemanager,tinybrowser",)

    ??

    ReplyDelete
  25. He Ham, nice one!
    Did you ever find out why it's not working if you use plugins, like george said?(in my case: plugins: "preview,ezfilemanager,tinybrowser")???
    Thanks.

    ReplyDelete
  26. Thanks and sorry, I haven't been able to figure it out either.

    ReplyDelete
  27. Hi there

    This is strange as the example code does not work in IE7, it works in Firefox, but IE7 it brings up a javascript error saying:

    --------------8<----------
    Line: 2
    Char: 9950
    Error: Access is Denied
    Code: 0
    --------------8<----------

    Please could you check this

    ReplyDelete
  28. @NRasool

    I'm not getting any error when I load the example on IE7. Please make sure that you have a copy of tiny_mce.js and that you have changed the src attribute in the script tag to point to it.

    I tested with IE v. 7.0.5730.11 not that it makes any difference.

    Please take a look again and let me know.

    ReplyDelete
  29. Apologises Ham, I was using an old version of TinyMCE, downloaded the latest build and it runs for me :-)

    Sorry for wasting your time :-)

    ReplyDelete
  30. No worries. I'm glad it's fixed :-)

    ReplyDelete
  31. I used this solution in a work-in-progress project, but it stopped to work suddenly...
    If I try to pass any setting using tinyMCE.settings = mySettingsArray, TinyMCE does not render and also I get no error, also with firebug.
    Any advice on what could cause this?

    Thanks

    ReplyDelete
  32. THANK YOU! I've been trying to get a 2nd TinyMCE to work in a WordPress admin and I've been google for hours and *nobody* (I mean *nobody*) covers how to set configuration prior to using tinyMCE.execCommand("mceAddControl"). I was about to give up before I found this post. THANK YOU again!

    ReplyDelete
  33. @ Mike Schinkel. Trying to add a custom secondary TinyMCE editor to custom fields myself as well. Have not been able to so far. I can load the standard TinyMCE editor, but not a custom one from an array as suggested here. Could you maybe share your implementation?

    ReplyDelete
  34. @ Ham, maybe you could tell me how I can connect/load your array into my JQuery code? Here is the current code:


    var tinymceConfigs = [ {theme : "advanced",
    mode : "none",
    language : "en",
    height:"200",
    width:"100%",
    theme_advanced_layout_manager : "SimpleLayout",
    theme_advanced_toolbar_location : "top",
    theme_advanced_toolbar_align : "left",
    theme_advanced_buttons1 : "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull",
    theme_advanced_buttons2 : "",
    theme_advanced_buttons3 : "" },{ theme : "advanced",
    mode : "none",
    language : "en",
    height:"200",
    width:"100%",
    theme_advanced_layout_manager : "SimpleLayout",
    theme_advanced_toolbar_location : "top",
    theme_advanced_toolbar_align : "left"}];
    function tinyfy(settingid,el_id) {
    tinyMCE.settings = configArray[0];
    }
    jQuery(document).ready(function() {
    jQuery("#'.$meta_box['name'].'_value").addClass("mceEditor");
    if ( typeof( tinyMCE ) == "object" && typeof( tinyMCE.execCommand ) == "function" ) {
    tinyMCE.execCommand("mceAddControl", false, "'.$meta_box['name'].'_value");
    }


    });
    Thanks in advance for all your help!

    ReplyDelete
  35. Ham, I must say, this is some excellent work you have done! I really would like to thank & appreciate you for coming up with the solution as I am sure, it has already made life very easy for many of us & would even help others in future.

    Thank you for putting up the solution for us.

    ReplyDelete
  36. Thank you! Great work! I just wanted to point out to others what took me some time to figure out.... if you're using tinyMCE 4.0.xxx this will not work. They've replaced "mceAddControl" with "mceAddEditor". Swapping that out will get this working on the new version.

    ReplyDelete