Using TinyMCE with CakePHP and AJAX
This tutorial assumes you\'ve already read Using TinyMCE with CakePHP (http://bakery.cakephp.org/articles/view/60). Here I will address some of the questions from the comments of that tutorial.
How does TinyMCE work?
I won\'t go into the gory details, but knowing some of the "How" might help to understand a lot of the "Why".
- TinyMCE doesn\'t play nicely with Script.aculo.us. It\'s not really explained anywhere, except maybe on a back page of some forum about javascript effects. I don\'t understand it, I just followed one simple rule and everything got better: load TinyMCE code before loading Script.aculo.us code.
- TinyMCE instantiates itself at page load. It immediately attaches itself to all textareas that exist or that you specify. If the textarea does not yet exist, as is often the case with AJAX forms, then the form element remains untouched.
- TinyMCE isn\'t really your textarea. Actually, it temporarily replaces your textarea with a table and an iframe. It also places a javascript event trigger on the submit button which copies all of the data from the editor\'s iframe back to your textaea before saving.
Configuration
In response to point 1
You need to load the code before you load Script.aculo.us. Change the line in your layout(s) to read:
PHP Snippet:
Download code
<?php
if(isset($javascript)):
...
echo $javascript->link('prototype');
echo $javascript->link('tiny_mce/tiny_mce');
echo $javascript->link('scriptaculous');
...
endif;
?>
Note: Script.aculo.us is not required and not used in this tutorial, but knowing this will save you hours of time debugging.
In response to point 2
We will turn off TinyMCE\'s automatic attachment to textareas on load, and instead trigger it when necessary. Change your init code to read:
HTML:
Download code
<script type="text/javascript">
tinyMCE.init({
theme : "simple",
mode : "none",
convert_urls : false
});
</script>
The mode setting is what we need changed here.
Note: the block that initiates TinyMCE must be part of the base page. It may very well be best suited right in your template file. DRY design is best, and here it\'s almost mandatory.
Now in the view that generates the form, after the textarea is rendered, we need to initiate TinyMCE. It would be best to script this, especially if you want to bind TinyMCE to all textarea\'s, but I include it here for clarity.
PHP Snippet:
Download code
<?php
echo $html->textarea('Model/field', array('cols' => '60', 'rows' => '2'));
echo $javascript->codeBlock("tinyMCE.addMCEControl($('ModelField'),
'ModelField');");
?>
In response to point 3
Now that TinyMCE is bound and loaded on your form field, we need to make sure that the data in the editor makes it back into the form before the save is triggered. We also want to unbind TinyMCE from the field to keep future instances of the editor from tripping over itself. Again, it would be best to scipt this, but I provide it here for clarity. Add a 'before' entry to your AJAX submit options, like so:
PHP Snippet:
Download code
<?php
echo $ajax->submit( 'Save', array(
...
'before'=>"tinyMCE.triggerSave();
tinyMCE.execCommand(
'mceRemoveControl', true, 'ModelField');",
...));
?>
There\'s a TinyMCEHelper just waiting to be written
With so many more features to be utilized, and integration with Script.aculo.us yet to be done, there would be great use for a helper. The gauntlet has been thrown down - who will stand up to the challenge?
Comments
Comment
1 Nicely done
Question
2 How to..
Thank you
Comment
3 Easy.
Cheers,
Jeff
Comment
4 The reason behind the mceRemoveControl
In this example, the entire form is replaced with each call. If the form values fail the validation, the form is sent again and a new TinyMCE instance is attached to the new form field with the same name as the previous form field. That is why it is important to destroy the previous instance.
It should be possible to leave the form up and pass back only the value, thereby removing the need to destroy and reinstantiate Tiny, but I haven't tried this as it wasn't a requirement of mine. If anyone succeeds in doing this, I'd love to see how it works out.
Comment
5 tinyMCE.init
Comment
6 Not required
1.2 now makes it possible to put the load in the view, but have the code rendered in the page head tags by passing false in as a second parameter to JavascriptHelper::link() To do this, replace:
<?phpin your layout with:if(isset($javascript)):
...
echo $javascript->link('prototype');
echo $javascript->link('tiny_mce/tiny_mce');
echo $javascript->link('scriptaculous');
...
endif;
?>
<?php echo $scripts_for_layout ?>Then, in the view that needs Tiny and Ajax loaded, do:<?php echo $javascript->link('prototype', false); ?>As you can see, this is much cleaner, and your scripts only end up in the pages that need it. If you really care about having the tinyMCE.init in the head, you should be able to make a webroot/js/tiny_mce/tiny_init.js file, and add this after the above:<?php echo $javascript->link('tiny_mce/tiny_mce' false); ?>
<?php echo $javascript->link('scriptaculous', false); ?>
<?php echo $javascript->link('tiny_mce/tiny_init', false); ?>Maybe once 1.2 is released, we can rewrite these two articles using the new, cleaner, 1.2 methods. But unitl then, enjoy!Comment
7 Wrapper for prototype Effects
1. create the file toggleTinyMCEWrapper.js and paste this code into it:
function toggleTinyMCEEffect(element, effect, refTextareas){
var element = $(element);
var effect = (effect || 'appear').toLowerCase();
var textareas = $(refTextareas);
if (element.visible()) {
for (i=0;i<textareas.length;i++){
tinyMCE.execCommand('mceFocus', false, textareas);
tinyMCE.execCommand('mceRemoveControl', false, textareas);
}
Effect.toggle(element,effect);
}else{
Effect.toggle(element,effect);
for (i=0;i<textareas.length;i++){
tinyMCE.execCommand('mceAddControl', false, textareas);
}
}
};
Note: this function takes
element -> the id of the div to toggle
effect -> the effect to be used by prototype [appear|blind|slide] textareas -> the id's of the textareas inside this div
if the div is in a closed state it opens it and adds the tinyMCE editor to the textareas
if it is in an opened state it puts the focus on each tinyMCE editor and removes it befor closing the div
2. load the wrapper toggleTinyMCEWrapper.js (see code below) and other libraries
<?= $javascript->link('scriptaculous/prototype.js')?>
<?= $javascript->link('tiny_mce/tiny_mce.js')?>
<?= $javascript->link('toggleTinyMCEWrapper.js')?>
<?= $javascript->link('scriptaculous/scriptaculous.js')?>
Note: Load the tiny_mce.js before scriptaculous.js
3. in your view:
Note: replace 'firstEditor' with your Model/field<a href onclick="toggleTinyMCEEffect('divToToggle','blind',['firstEditor','secondEditor']); return false">+/- Toggle me</a>
<div id="divToToggle" style="display:none">
<?= $form->textarea('firstEditor',array('rows' => '20', 'cols' => '10')) ?>
<?= $form->textarea('secondEditor',array('rows' => '20', 'cols' => '10')) ?>
</div>
Question
8 Update textarea TinyMCE by ajax
The target is to update textarea $MessageBody content when user is changing the $templates select list. update_message is a function in my controller class. In my view class i use:
$bodyoptions = array('url' => 'update_message/','update' =>'MessageBody');
echo $ajax->observeField('templates',$bodyoptions);
When user is selecting some value from the list the textarea $MessageBody is successfully updated. I want to use the same with TinyMCE.
This is not working for me:
echo $html->textarea('Model/field', array('cols' => '60', 'rows' => '2'));
echo $javascript->codeBlock("tinyMCE.addMCEControl($('ModelField'),
'ModelField');");
?>
So I use:
echo $javascript->codeBlock("tinyMCE.execCommand('mceAddControl', true, 'MessageBody');");
I understand that if I want to change/update content of my text area with a TinyMCE I should to switch of it for a moment, so I use:
echo $ajax->submit( 'Save', array('before'=>"tinyMCE.triggerSave();tinyMCE.execCommand( 'mceRemoveControl', false, 'MessageBody');"));
It is turning off TinyMCE, but content of my text area is not updating by switching select list (when I remove all stuff connected with TinyMCE it is working). Content of my ext area is between so I guess that mceRemovingControl is not working properly.
I have two questions:
How should I remove it correctly and how to connect it with a $ajax->observeField method?
The cake version is cake_1.2.0.7125-rc1.
Comment
9 tinyMCE.triggerSave()
However, I should mention that I still had to manually call tinyMCE.triggerSave() before submitting a form I had created with the Ajax helper. Otherwise, TinyMCE wouldn't update the actual contents in my textarea.
Comment
10 Inconsistent issues [resolved]
Bug
11 To use tip #2 with the new tinyMCE (3.x)
echo $javascript->codeBlock("tinyMCE.addMCEControl($('ModelField'),'ModelField');");
Use:
$javascript->codeBlock("tinyMCE.execCommand('mceAddControl',false,'ModelField');");
That works for me...
Idan.
Login To Submit A Comment