Using TinyMCE with CakePHP and AJAX

This article is also available in the following languages:
By TommyO
jtreglos wrote an excellent article for integrating TinyMCE into your applications. This takes it one step further, making it possible to have powerful wysiwyg features almost anywhere.

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

  1. 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.
  2. 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.
  3. 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:

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


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

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

<?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

  • Posted 10/01/09 01:05:25 PM
    I have a rather strange problem that I haven't been able to sort out. I'm using the Auth component (without ACL) to handle simple user accounts. On each page on my site I have a search form at the top of the page that is using Scriptaculous for autocompleting the search term.

    There is one page that is like a blog and you can post on. The form is using TinyMCE and Ajax to submit the form. I'm using the 'before'=>"tinyMCE.triggerSave(); solution and it's working fine as well. The strange problem occurs when I submit something using the search form when viewing the blog page. When I submit the search, it renders the result using a search.ctp view file. If there are results returned, there is no problem. However, if the search has no results, for some bizarre reason I get logged out of the site. Whether the search returns results or not it is still getting rendered by the search.ctp view file and I see no errors. I can't figure this out for the life of me.

    So to sum it up:
    1. I view the blog page that has a form using TinyMCE and ajax to submit
    2. Same page has a search form at the top using autoComplete
    3. If I search for something and it actually has results returned, I render view.ctp and I stay logged in to the site
    4. If I search for something and there are no results found, I render view.ctp and I get logged OUT of the site!

    What's killing Auth?

    BTW, I'm loading the js scripts like this:

    echo $javascript->link(array('prototype','tiny_mce/tiny_mce.js','tiny_mce/plugins/media/jscripts/embed.js','scriptaculous/scriptaculous'));

    Also, the TinyMCE init settings are loaded in the specific view that has the form.

    Any help would be greatly appreciated because at this point I'm pulling out what little hair I've got left. Thanks!
  • Posted 05/28/09 11:37:25 AM
    Instead of:
    echo $javascript->codeBlock("tinyMCE.addMCEControl($('ModelField'),'ModelField');");

    Use:
    $javascript->codeBlock("tinyMCE.execCommand('mceAddControl',false,'ModelField');");

    That works for me...
    Idan.
  • Posted 05/25/09 05:20:37 AM
    I also just installed TinyMCE with minimum fuss apart from inconsistent issues when submitting my data, but this seems to be solved by following Daniel Araujo's lead and adding 'before'=>"tinyMCE.triggerSave();" to $ajax->form().
  • Posted 01/21/09 07:05:06 AM
    I didn't have this hard a time to get things going, so I suppose some of the incompatibilities addressed here are long gone.

    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.
  • Posted 08/10/08 04:02:17 PM
    Hello, I want to update textarea with 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.
  • Posted 03/21/08 12:45:12 PM
    If you want to use tinyMCE inside a
    which is toggled with prototypes Effec.toggle() function you can use this wrapper to add and remove tinyMCE after and before toggle the
    content:

    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[i]);    
                tinyMCE.execCommand('mceRemoveControl', false, textareas[i]);
            }
            Effect.toggle(element,effect);
        }else{
            Effect.toggle(element,effect);
            for (i=0;i<textareas.length;i++){
                tinyMCE.execCommand('mceAddControl', false, textareas[i]);    
            }     
        }   
    };

    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:

    <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>
    Note: replace 'firstEditor' with your Model/field
  • Posted 10/11/07 07:29:39 AM
    For those of you have not used tinyMCE before tinyMCE.init is declared in the of your page (in your layout).
    • Posted 10/11/07 11:23:38 AM
      For those of you have not used tinyMCE before tinyMCE.init is declared in the of your page (in your layout). Yes, although that makes for cleaner html, it is not necessary. As jtreglos' original article points out, this can be done in the view.

      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:
      <?php 
      if(isset($javascript)):
          ...
          echo 
      $javascript->link('prototype');
          echo 
      $javascript->link('tiny_mce/tiny_mce');
          echo 
      $javascript->link('scriptaculous');
          ...
      endif;
      ?>
      in your layout with:
      <?php echo $scripts_for_layout ?> Then, in the view that needs Tiny and Ajax loaded, do:
      <?php echo $javascript->link('prototype'false); ?>
      <?php 
      echo $javascript->link('tiny_mce/tiny_mce' false); ?>
      <?php 
      echo $javascript->link('scriptaculous'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_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!
  • Posted 11/25/06 02:36:06 AM
    Quick note about point 3. You should add the mceRemoveControl in the callback if you are doing any type of validation on the server side where you might need a user to maintain the tinyMCE instance after the ajax 'save'.
    Cheers,
    Jeff
    • Posted 11/27/06 02:59:52 PM
      You should add the mceRemoveControl in the callback if you are doing any type of validation on the server side where you might need a user to maintain the tinyMCE instance after the ajax 'save'.
      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.
  • Posted 11/21/06 03:22:49 PM
    How 'before' statement works? I got problem in redirecting the page.

    Thank you
  • Posted 11/09/06 07:41:03 AM
    Good job on finding a way to use TinyMCE in an Ajax context. I haven't tried it yet, but I have an upcoming project where this will be needed, so I'll tell you how it does !

Comments are closed for articles over a year old