[EXTENSION] TinyMCE using jQuery

Hi!!

How about posts #39 and #40??

Dynamically loading tinymce into jQuery UI/Dialog.

There is a problem with dynamic loading both "native" tinymce and lazy loading

with gzip compressor.

Saying "dynamic loading" I mean complete dynamic loading when tiny_mce.js or

tiny_mce_gzip.js also loaded into the page dynamically.

Here is my use case:

I have GUI builder which creates dynamically tabled views (for example, news

list) and edit forms where none or one or more textareas with tinyMCE can be used.

Forms are added to the page dynamically into jQuery / UI Dialog.

Forms is generated as a set of widget and I have a widget for tinyMCE.

Since this GUI builder is generic I do not want to include tinymce core script

to all pages, but load it dynamically when appropriate widget is rendered on the

form.

Now I get to work both loading tiny_mce.js or tiny_mce_gzip.js dynamically and

had to solve some problems.

  1. Dynamic loading of tiny_mce.js.

We have here two major issues: first - tinyMCE._init fails to set correct baseURL so other scripts not

loaded and second - scriptLoader.loadScripts by default

loads scripts with document.write and this breaks the page (all page content is gone).

Both problems where solved with following approach:




    if (!window.tinymce) {

       //set base URL for tinymce

       window.tinyMCEPreInit = {base : '$assets/tinymce', suffix : '', query : ''};


       //load tinymce

       jQuery.ajax({

                        type : 'GET',

                        url : '$assets/tinymce/tiny_mce.js',

                        async : false,

                        success : function(co) {

                            var w = window;


                            // Evaluate script

                            if (!w.execScript) {

                               try {

                                   eval.call(w, co);

                               } catch (ex) {

                                   eval(co, w); // Firefox 3.0a8

                               }

                            } else {

                              w.execScript(co); // IE

                            }

                            //load scripts with dom manipulation instead of document.write

                            w.tinymce.dom.Event.domLoaded = true;

                         }


       });

    }

    // Init textarea with jquery plugin

    jQuery("#{$id}").tinyMCE({$tinyOptions}, '{$jsMode}', {$jsUseCookies});



I add js code to the page to load core tinymce script dynamically and create

window.tinyMCEPreInit object before script is loaded to set correct baseURL to solve first problem

and set tinymce.dom.Event.domLoaded = true after script is loaded to solve second.

Full text of modified ETinyMCE::init() method:

[spoiler]




    public function init()

    {

        list($name, $id) = $this->resolveNameID();


        if ($this->useCookies) {

            if (isset($_COOKIE[$id.self::COOKIE_SUFFIX]) && in_array($_COOKIE[$id.self::COOKIE_SUFFIX], array('text', 'html'))) {

                $this->setMode($_COOKIE[$id.self::COOKIE_SUFFIX]);

            }

        }


        $baseDir = dirname(__FILE__);

        $assets = Yii::app()->getAssetManager()->publish($baseDir.DIRECTORY_SEPARATOR.'assets');


        $tinyOptions = $this->makeOptions();

        $jsUseCookies = ($this->useCookies) ? 'true' : 'false';

        $jsMode = strval($this->mode);

        $jsToggleLabels = CJavaScript::encode($this->switchLabels);


        $cs = Yii::app()->getClientScript();

        $cs->registerCoreScript('jquery');


        $this->htmlOptions['id'] = $id;

        if (!array_key_exists('style', $this->htmlOptions)) {

           $this->htmlOptions['style'] = "width:{$this->width};height:{$this->height};";

        }

        if (!array_key_exists('cols', $this->htmlOptions)) {

           $this->htmlOptions['cols'] = self::COLS;

        }

        if (!array_key_exists('rows', $this->htmlOptions)) {

           $this->htmlOptions['rows'] = self::ROWS;

        }


        $baseDir = dirname(__FILE__);

        $assets = Yii::app()->getAssetManager()->publish($baseDir.DIRECTORY_SEPARATOR.'assets');

        $bu = Yii::app()->getRequest()->getBaseUrl();


        $js =<<<EOP


function fileBrowserCallback(field_name, url, type, win) {

    var connector = '$assets/tinymce/filemanager/browser.html?Connector=$bu/tinymce/connector';


    var enableAutoTypeSelection = true;


    var cType;

    tinyfck_field = field_name;

    tinyfck = win;


    switch (type) {

        case 'image':

            cType = 'Image';

            break;

        case 'flash':

            cType = 'Flash';

            break;

        case 'file':

            cType = 'File';

            break;

    }


    if (enableAutoTypeSelection && cType) {

        connector += '&Type=' + cType;

    }


    window.open(connector, 'tinyfck', 'modal,width=600,height=400');

};


EOP;


        $cs->registerScript('Yii.'.get_class($this).'initTinyCallbacks', $js, CClientScript::POS_HEAD);

        $cs->registerScriptFile($assets.'/jquery/jquery.tinymce.js');

        $cs->registerScriptFile($assets.'/embedmedia/embed.js');


        if ($this->useCompression) {

            $cs->registerScriptFile($assets.'/tinymce/tiny_mce_gzip.js');

            $gzOptions = $this->makeCompressor();

            $js =<<<EOP


function initTinyGZ_$id() {

    jQuery("#{$id}").tinyMCE({$tinyOptions}, '{$jsMode}', {$jsUseCookies});

}

window.tinyMCEPreInit = {base : '$assets/tinymce', suffix : '', query : ''};

tinyMCE_GZ.init({$gzOptions}, function() { initTinyGZ_$id(); });

EOP;


            $cs->registerScript('Yii.'.get_class($this).'#'.$id, $js, CClientScript::POS_LOAD);

        } else {

            // Load tinymce script manually to be shure tinyMCEPreInit object is created

            // before tinyMCE is initialized


            $js =<<<EOP


    if (!window.tinymce) {

       //set base URL for tinymce

       window.tinyMCEPreInit = {base : '$assets/tinymce', suffix : '', query : ''};


       //load tinymce

       jQuery.ajax({

            type : 'GET',

            url : '$assets/tinymce/tiny_mce.js',

            async : false,

            success : function(co) {

                var w = window;


                // Evaluate script

                if (!w.execScript) {

                   try {

                       eval.call(w, co);

                   } catch (ex) {

                       eval(co, w); // Firefox 3.0a8

                   }

                } else {

                  w.execScript(co); // IE

                }

                //load scripts with dom manipulation instead of document.write

                w.tinymce.dom.Event.domLoaded = true;

             }

       });

    }


    jQuery("#{$id}").tinyMCE({$tinyOptions}, '{$jsMode}', {$jsUseCookies});

EOP;


            $cs->registerScript('Yii.'.get_class($this).'#'.$id, $js, CClientScript::POS_LOAD);

        }




        if($this->hasModel()) {

            $textarea = CHtml::activeTextArea($this->model, $this->attribute, $this->htmlOptions);

        } else {

            $textarea = CHtml::textArea($name, $this->value, $this->htmlOptions);

        }


        $html = '';

        if ($this->useSwitch && !$this->readOnly) {

            $label = $this->switchLabels[($this->mode=='html'?0:1)];

            $css = ($this->labelClass !== '') ? array('class'=>$this->labelClass) : array('style'=>$this->labelStyle);

            $switchOptions = array_merge(array('id'=>$id.self::SWITCH_SUFFIX), $css);

            $uri = rawurlencode('$("#'.$id.'").toggleModeTinyMCE('.$jsToggleLabels.')');

            $link = CHtml::link($label, 'javascript:'.$uri, $switchOptions);

            $switch = CHtml::tag('div', array(), CHtml::tag('span', array(), $link));

            $html = CHtml::tag('div', array(), $textarea.$switch);

        } else {

            $html = $textarea;

        }


        echo $html;

    }



[/spoiler]

  1. Dynamic loading of tiny_mce_gzip.js

Here also two problems exist: first with base URL mentionad above and second -

with sequential calls of tinyMCE_GZ.init for more then one textareas.

I need to call tinyMCE_GZ.init more then once becuse when dialog with form is generated I

could not know how many tinymce widgets exists and each widget initializes

itself with such code:




       function initTinyGZ_$id() {

           // Init textarea with jquery plugin

           jQuery("#{$id}").tinyMCE({$tinyOptions}, '{$jsMode}', {$jsUseCookies});

       }

       // this is for tinymce core when it will be loaded by tiny_mce_gzip.js

       window.tinyMCEPreInit = {base : '$assets/tinymce', suffix : '', query : ''};

       // init compressor and pass textarea initialization as callback

       tinyMCE_GZ.init({$gzOptions}, function() { initTinyGZ_$id(); });



To solve problems I have with tiny_mce_gzip.js I had to modify its code.

To fix problem with base URL I added special setting to provide external base

URL through options and changed init() method to take that parameter.

To solve problem with sequential init() calls I changed init() function to

invoke callback even if core scripts are already loaded:




       if (!t.coreLoaded)

            t.loadScripts(1, s.themes, s.plugins, s.languages, cb, sc);

       else

           // SEB - scripts are loaded only once (loadScripts method sets t.coreLoaded to true)

           // if we have two textareas on the page and provide tiniymce initialization in the

           // callback function then second callback was never called

           cb.call(sc || t, null);



and changed loadScripts to load core synchronously so second init() call will be

performed when scripts are actually loaded into the page:




        //SEB - make syncronous call

        x.open('GET', t.baseURL + '/' + s.page_name + '?' + q, false);

        //x.setRequestHeader('Content-Type', 'text/javascript');

        x.send('');


        if (co)

            t.coreLoaded = 1;


        // SEB - Handle syncronous loading

        if (cb && x.status == 200) {

           t.loaded = 1;

           t.eval(x.responseText);

           tinymce.dom.Event.domLoaded = true;


           cb.call(sc || t, x);

        } else {

          t.eval(x.responseText);

        }



Modified text of tiny_mce_gzip.js:

[spoiler]




var tinyMCE_GZ = {

    settings : {

        themes : '',

        plugins : '',

        languages : '',

        disk_cache : true,

        page_name : 'tiny_mce_gzip.php',

        debug : false,

        suffix : '',

        // SEB - exteral base url

        baseURL : '',

    },


   init : function(s, cb, sc) {

        var t = this, n, i, nl = document.getElementsByTagName('script');


        for (n in s)

            t.settings[n] = s[n];


        s = t.settings;


        if (t.settings.baseURL == '') {


           for (i=0; i<nl.length; i++) {

            n = nl[i];


            if (n.src && n.src.indexOf('tiny_mce') != -1)

                t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));

           }

        } else {

           // SEB - if script is loaded into the page dynamically it can not be found in the scripts array

           // in this case we can specify external base url where tiny mce scripts are located

           t.baseURL = t.settings.baseURL;

        }


        if (!t.coreLoaded)

            t.loadScripts(1, s.themes, s.plugins, s.languages, cb, sc);

        else

           // SEB - scripts are loaded only once (loadScripts method sets t.coreLoaded to true)

           // if we have two textareas on the page and provide tiniymce initialization in the

           // callback function then second callback was never called

           cb.call(sc || t, null);

    },




    loadScripts : function(co, th, pl, la, cb, sc) {

        var t = this, x, w = window, q, c = 0, ti, s = t.settings;


        function get(s) {

            x = 0;


            try {

                x = new ActiveXObject(s);

            } catch (s) {

            }


            return x;

        };


        // Build query string

        q = 'js=true&diskcache=' + (s.disk_cache ? 'true' : 'false') + '&core=' + (co ? 'true' : 'false') + '&suffix=' + escape(s.suffix) + '&themes=' + escape(th) + '&plugins=' + escape(pl) + '&languages=' + escape(la);





        // SEB - Here also was a problem withis.two or more textareas on the page

        // request was asyncronous and "t.coreLoaded = 1" was set before request is even started.

        // When tinyMCE_GZ.init was invoked for the second area we have "t.coreLoaded = 1" and

        // scripts are not loaded yet. So second area initialization was failed.

        // Now request for script is sent syncronously and problem is gone.


        // Send request

        x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Msxml2.XMLHTTP') || get('Microsoft.XMLHTTP');

        x.overrideMimeType && x.overrideMimeType('text/javascript');

        //x.open('GET', t.baseURL + '/' + s.page_name + '?' + q, !!cb);

        //SEB - make syncronous call

        x.open('GET', t.baseURL + '/' + s.page_name + '?' + q, false);

//      x.setRequestHeader('Content-Type', 'text/javascript');

        x.send('');


        if (co)

            t.coreLoaded = 1;


        // SEB - Handle syncronous loading

        if (cb && x.status == 200) {

           t.loaded = 1;

           t.eval(x.responseText);

           tinymce.dom.Event.domLoaded = true;


           cb.call(sc || t, x);

        } else {

          t.eval(x.responseText);

        }


        /*

        // Handle asyncronous loading

        if (cb) {

            // Wait for response

            ti = w.setInterval(function() {

                if (x.readyState == 4 || c++ > 10000) {

                    w.clearInterval(ti);


                    if (c < 10000 && x.status == 200) {

                        t.loaded = 1;

                        t.eval(x.responseText);

                        tinymce.dom.Event.domLoaded = true;


                        cb.call(sc || t, x);

                    }


                    ti = x = null;

                }

            }, 10);


        } else

            t.eval(x.responseText);*/

    },


    ...



[/spoiler]

Thanks for posting your modifications. I’ll try to update the extension soon. It’s been a difficult second part of the year, and I was dedicated mostly to system administration, so I stopped the development a bit :(

Hi I have the same problem with the image manager, did you ever get this to work? If so I would greatly appreciate some advice…

Hi all,

How do you set "document_base_url" with this extension ?

Thanks!

Anyone have any idea why the tiny_mce_###.gz file is not being created in assets?

I have my own server with a site running this extension fine. I use the same exact code on a web host I don’t own and I can’t get this extension to load. The one thing I’m noticing is that on the one that doesn’t work the compressed file doesn’t get created in the assets directory (under the tiny_mce folder). Anyone have any ideas why or what I can do to debug it? I don’t get any errors on the page, it just loads a text box with no TinyMCE.

I don’t understand why for me this solution don’t work. I open dialog so:


$('.update').live('click',(function(){

		var id=$(this).attr('id');

			$.ajax({

			  url: '".Yii::app()->createUrl("admin/offerte/update")."&id='+id,

			  success: function(data) {

			    $('#dialog').html(data);

			    var buttons = $( '#dialog' ).dialog( 'option', 'buttons' );

			    $('#dialog').dialog('open');

			    return false;

		  }});

		  }));

Same here, why it wasn’t solved yet?

What is the purpose of $tinymce variable?

1 - I don’t want to change the core.

2 - I don’t want do disable warning.

What is the best approach?

Thanks

When I try to change the list of plugins:

&#036;this-&gt;widget('application.extensions.tinymce.ETinyMce',


	array(


		'name'=&gt;'html',


		'editorTemplate' =&gt; 'full',


		'options' =&gt; array('plugins' =&gt; 'test1,test2'),


	)


);

Then the list of plug-ins for tinyMCE_GZ remains not changed in page script:

tinyMCE_GZ.init({'plugins':'safari,pagebreak,style,...);

But:

window.onload=function() {


	jQuery(&quot;#html&quot;).tinyMCE({...,'plugins':'test1,test2'}, 'html', true);


};

Is it correct?

It appears the $tinymce variable should be the $assets variable as it is used by makeFullEditor(). I’ve changed this and it appears to be working correctly.

Hello mates,

I have integrated this extension and it works great.

I have a little problem with generated xhtml code. It is written in db as it should be but when I generate view to show the entered elements I see the tags and they are not interpreted by the browser (i see html code on my page, I mean tags).

What could be wrong ?

I’m not sure if anyone has figured this out yet but the extension bailed in Windows Vista running wampp after using it successfully on a linux box and a linux server.

I see some others have found the anomololy but I didn’t read all posts to see if it was solved. This worked for me :)


     // $tinyOptions = $this->makeOptions($tinymce); // broken in Wampp on Vista but works on Linux box ??

      $tinyOptions = $this->makeOptions(); // this works on both    

It seems that $tinymce just evaluates to null on the linux box, wampp croaks.

dunno why!!

doodle

Could you post your changes? Probably better than my approach

I believe this is what he did, but I’ve made several other modifications so I’m not 100% sure that this fixed it completely.

In protected/extensions/tinymce/ETinyMce.php, line 842:

find:


$tinyOptions = $this->makeOptions($tinymce);

change to:


$tinyOptions = $this->makeOptions($assets);

hi everyone, had just founded out a problem maybe it is described already in a topic list just have no time to read them all), i integrated tinymcy into my system and need to remove status/path bar from it, puted

“theme_advanced_path”=> false, removed “theme_advanced_statusbar_location” from list, but it steal showes me path bar, hm thats the problem i decided to dig a source ETinyMce.php and founded out that developers defined it for the user $options[‘theme_advanced_path_location’] = ‘bottom’; twice at line 691 and 698, and other parameter that must be removed $options[‘theme_advanced_path_location’] at line 701 plugin version 1.1 no problems for developer greate thx for him o her just make a remark

I’ve made some fixes and improvements to the extension :

. Fixed $tinymce variable issue (replaced with $assets)

. Upgraded to latest version of tinymce, 3.3.9.2

. Fixed certain plugins from autoloading, due to the $this->plugins being hardcoded. Now looks to see if the plugins array is empty before applying default plugins.

I’d like to share these with the community, how should I go about doing that ?

Thanks

It looks like the author abandoned the project some time ago?

I can see that the extension hasn’t been updated since Aug 18, 2009.

And that means that the author wrote the extension, uploaded the initial version, and then made an update to it two months later.

And then left it behind.

I would create a new extension (page) and put the code up on a public repository.

Google code, Github, Bitbucket?

IMO, that’s the best approach. :)

<edit>

Of course, you could ask ‘someone’ (moderator/team?) to allow you to take over the extension…

</edit>

Can you or did you publish your changes somewhere?

Hello,

I used this extension and it works perfect on localhost, but when I upload it to my web server, it stops working.

After looking I found out that it’s caused because the file tiny_mce_gzip.php could not be accessed because of its containing folder having permission of 777. If I change the permission of the whole folder (/assets/tiny_mce) to 755, then it works.

Why is this happening?

Thanks :)

Today I was bit by Bug #48885 hxxp://bugs.php.net/48885. (This is my first post and I cannot embed links yet so replace hxxp with http.) After spending hours trying to find out what’s wrong I came up with this patch that fixes the problem. I hope that it will be put into the extension soon so others can use it.

1723

mime_type.diff.txt