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>

26 comments:

x0nix said...

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

Ham-the-geek said...

Glad to help !

Anonymous said...

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!

Ham-the-geek said...

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.

archerid said...

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

Ham-the-geek said...

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.

archerid said...

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 :)

abx78 said...

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

Ham-the-geek said...

You might be able to get a clue from

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

I hope this helps.

abellix said...

take a look: this worked for me

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

Diego de Lima said...

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

Bandi said...

Thx!

Andreas said...

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!

Anonymous said...

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!

loftx said...

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

umang said...

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?

Ham-the-geek said...

@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.

umang said...

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

umang said...

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

umang said...

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

Anonymous said...

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

Freelance Web Development said...

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

George said...

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

Stefan said...

Thank you very much! Solved all of my problems.

:)

Arivoli said...

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.

reyt said...

we provide a power leveling and free wow gold wow power leveling|*|wow power leveling|*|http://www.wotlk-powerleveling.com|*|fdgf51

Post a Comment

Recent Entries

Recent Comments