How to Create an Image Selection Plugin for tinyMCE with Ruby on Rails
TinyMCE is a great content editor written in javascript from the folks at Moxiecode systems. We created a plugin for tinyMCE that allows users to select images from a selection of thumbnails instead of having to type a URL into a popup. This is part one of a two part tutorial on creating a custom tinyMCE plugin for image selection. Part one covers the basics of getting the plugin created. Part two will add image upload functionality. At the end of this tutorial you should have a tinyMCE popup window like this:
Some administrative details before we get started. You will need a couple things before you can follow along:
- A working rails project and some images.
- TinyMCE installed and working – A tutorial for that is here
We start a plugin that comes with tinyMCE called advimage and modify it by creating a a tab called uploaded images that will contain thumbnail images that can be selected by clicking one of them. After being clicked the window should close and the image should appear in the tinyMCE enabled text box. We want to keep the ability to provide a URL and alt text manually via the general tab. With that out of the way, lets jump into the code!
- Navigate to the tiny_mce/plugins directory.
- make a copy of the advimage directory, I called mine ts_advimage,
- open ts_advimage/image.htm (or whatever you called your directory, from here on it will be ts_advimage)
- We need to add prototype.js to the head section because we are going to use AJAX to fetch the images from our controller:
<script language="javascript" type="text/javascript" src="/javascripts/prototype.js"></script>
- look for the div with class=tabs and add code for a new tab:
<div class="tabs"> <ul> <li id="dynamic_select_tab" class="current"><span><a href="javascript:mcTabs.displayTab('dynamic_select_tab','dynamic_select_panel');" onmousedown="return false;">Uploaded Images</a></span></li> <!--- existing plugin tab code ---> <li id="general_tab"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{$lang_advimage_tab_general}</a></span></li> <li id="appearance_tab"><span><a href="javascript:mcTabs.displayTab('appearance_tab','appearance_panel');" onmousedown="return false;">{$lang_advimage_tab_appearance}</a></span></li> <li id="advanced_tab"><span><a href="javascript:mcTabs.displayTab('advanced_tab','advanced_panel');" onmousedown="return false;">{$lang_advimage_tab_advanced}</a></span></li> </ul> </div>
The new tab is called dynamic_select_tab with a label of ‘Uploaded Images’ it will open dynamic_select_panel, and it is the current tab, i.e. the default when the form is rendered. The only thing you should change below the existing plugin tab code line is to remove class=”current” from general_tab.
- Create a new panel to house the images we are pulling from the controller.
<div class="panel_wrapper"> <div id="dynamic_select_panel" class="panel current" style='overflow:auto'> <fieldset> <legend>Available Images</legend> <script type="text/javascript">//<![CDATA[ new Ajax.Request("/manage_images", {asynchronous:true, evalScripts:true, method:'get'}); //]]> </script> <div id='dynamic_images_list'> Loading Images...<br /> <img src='/images/loading.gif'> </div> </fieldset> </div>
The ‘/manage_images’ parameter to Ajax.Request will need to be replaced with the path to your controller. It is important to note the style overflow:auto in the above code snippet. This will allow the area with images to grow (i.e. a scroll bar will appear as needed).
- Modify your image controller’s index action to respond to javascript. In the index action for the controller you need to make sure the resond_to block has a format.js section has format.js:
respond_to do |format| format.html format.js end
- Create a new file in the controller’s views sub-folder called index.js with the following rjs code:
page.replace_html :dynamic_images_list, :partial => 'show_image_list', :locals => { :images => @images }
I chose to use a partial show_image_list, you can do the same and call it _show_image_list.rhtml.
The page.replace_html call will convert the .rhtml from the partial to javascript and replace the dynamic_images_list div we created earlier with our list of images. The code for the partial:<% if images.size > 0 %> <ul> <% images.each do |image| %> <li> <a href="javascript:void(0)" onclick="<%="ts_insert_image('#{image.val.public_filename()}', '#{image.name}');" %>"> <%= image_tag( image.val.public_filename(:thumb) ) %></a> </li> <% end %> </ul> <% else %> No Images Uploaded Yet. <% end %>
The class variable @images contains all of the image models we would like displayed.
QUICK TIP: If there is a chance that your image name or filename could have single quotes in it, you should escape them.
- An onclick event in the partial is used to take action when a user clicks an image: onclick=”<%=”ts_insert_image(‘#{image.val.public_filename()}’, ‘#{image.name}’);” %>”. This javascript function sets fields in the tinyMCE popup. Create this function in functions.js which lives in ts_advimage/jscripts/functions.js:
function ts_insert_image(url, alt_text){ var formObj = document.forms[0]; formObj.src.value = url; formObj.alt.value = alt_text; insertAction(); }
This function sets the fields in the general tab and closes the popup inserting the image into the text box.
- We are only a couple of steps away from being done, so hang in there. We need to tell tinyMCE about our new plugin so open up ts_advimage/editor_plugin.js. It is one long line (if you want to study the structure of the file look at editor_plugin_src.js). The code below is copied from editor_plugin_src.js so it is easy to understand. You must also make the changes in editor_plugin.js. What I changed is highlighted below:
tinyMCE.importPluginLanguagePack('ts_advimage'); var TinyMCE_ThriveSmartAdvancedImagePlugin = { getInfo : function() { return { longname : 'ThriveSmart Advanced image', author : 'ThriveSmart LLC', authorurl : 'http://www.thrivesmart.com', infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage', version : tinyMCE.majorVersion + "." + tinyMCE.minorVersion }; }, getControlHTML : function(cn) { switch (cn) { case "ts_image": return tinyMCE.getButtonHTML(cn, 'lang_image_desc', '{$themeurl}/images/image.gif', 'mceTSAdvImage'); } return ""; }, execCommand : function(editor_id, element, command, user_interface, value) { switch (command) { case "mceTSAdvImage": var template = new Array(); template['file'] = '../../plugins/ts_advimage/image.htm'; template['width'] = 480; template['height'] = 480; // Language specific width and height addons template['width'] += tinyMCE.getLang('lang_advimage_delta_width', 0); template['height'] += tinyMCE.getLang('lang_advimage_delta_height', 0); var inst = tinyMCE.getInstanceById(editor_id); var elm = inst.getFocusElement(); if (elm != null && tinyMCE.getAttrib(elm, 'class').indexOf('') != -1) return true; tinyMCE.openWindow(template, {editor_id : editor_id, inline : "yes"}); return true; } return false; },
- In the last line of editor_plugin.js and editor_plugin_src.js you need to change
tinyMCE.addPlugin to tinyMCE.addPlugin(“ts_advimage”, TinyMCE_ThriveSmartAdvancedImagePlugin); - Now we need to add our ts_advimage plugin to the tinyMCE menu bar
tinyMCE.init({ mode : "textareas", editor_selector : "tiny_mce", theme : "advanced", plugins : "inlinepopups,ts_advimage,advlink,emotions,iespell,zoom,searchreplace,fullscreen,visualchars,youtube", theme_advanced_buttons1 : "undo,redo,removeformat,|,bold,italic,underline,strikethrough,sub,sup,|,outdent,indent,bullist,numlist,hr,backcolor,|,link,unlink,charmap,emotions,ts_image,youtube,iespell,|,search,replace,|,cleanup,code,fullscreen,help", theme_advanced_buttons2 : "", theme_advanced_buttons3 : "", theme_advanced_toolbar_location : "top", theme_advanced_toolbar_align : "left", theme_advanced_path_location : "bottom", gecko_spellcheck : true, extended_valid_elements : "a[name|href|target|title|onclick],img[class|src|style|border=0|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name],hr[class|width|size|noshade],span[class|align|style]", theme_advanced_resizing : true, theme_advanced_resize_horizontal : false });
I’ve included the entire block for clarity, the important portions of the above code are in plugins : ts_advimage and
theme_advanced_buttons1 : ts_image
Fire up your browser, clear the cache and try it out because now you have an image chooser plugin that works with tinyMCE.
I hope you enjoyed this post. Part two is out as well! will be out soon so stay tuned!
August 24, 2007 at 9:06 am
Great — building a new website and this is just what I need…
Thank yuuuuuuuuuuuuu
August 29, 2007 at 12:11 am
[…] Two: How to Create an Image Selection Plugin for tinyMCE with Ruby on Rails 28Aug07 In the first part of our how-to we created a tinyMCE plugin with a dynamic image selector panel based on the advimage plugin. Now […]
September 12, 2007 at 4:54 pm
Guys
A great ‘How To’! Very useful and easy to follow…
Thank you very much!!!!!!!!!!!
September 24, 2007 at 11:47 am
Awesome howto however you might mention that you need to change the last line of advimage.js
tinyMCE.addPlugin(“advimage”, TinyMCE_AdvancedImagePlugin);
to
tinyMCE.addPlugin(‘ts_advimage’, TinyMCE_AdvancedImagePlugin);
September 27, 2007 at 3:58 am
I’ve tried all this but the button isn’t appearing in the toolbar for some reason. When I change to ts_image from image it simply disappears.
Any ideas?
Thanks.
September 27, 2007 at 4:15 pm
@Forgeten – Good catch, You are correct, I think you meant editor_plugin_src.js and editor_plugin.js though. I update the tutorial with the change. Thanks.
@Aaron Pollock – Hmm, it sounds like tinyMCE can not find the new plugin. I just updated the tutorial (new step 11). If that does not work, there is sample code in part two, I would recommend looking at that, it might be easier than the html formatted stuff here.
November 9, 2007 at 4:00 am
Dan, i think in editor_plugin_src.js case expression should contain “ts_advimage”:
getControlHTML : function(cn) {
switch (cn) {
case “ts_image”:
return tinyMCE.getButtonHTML(cn, ‘lang_image_desc’, ‘{$themeurl}/images/image.gif’, ‘mceTSAdvImage’);
}
return “”;
}
This is why button isn’t appered in Aaron’s case.
November 19, 2007 at 10:43 am
Works like a charm. Thanks!
On to Part Two…
November 26, 2007 at 1:52 am
Thanks a lot, Very good job! Save me a lot of time. Cheers.
December 3, 2007 at 3:55 am
Okay, this is probably the stupidest question ever, but what if you don’t already have an images controller? I’ve created one using the scaffold_resources generator but what is your images model/database like? Are you assuming use of a plugin to control files (I’m currently using acts_as_attachment for file uploads elsewhere in my site?). It would be really helpful if you briefly mentioned how you’ve set up your images controller and model!
December 16, 2007 at 6:49 am
Excellent work, thank you!
I’ve started adapting this for version 3 of TinyMCE, however I’m running into some problems. I was wondering if you’ve already done this?
December 26, 2007 at 9:41 pm
I am trying to wrap my head around how to get this to work with sub-directories. I would love to have a separate folder for each :controller with subdirectories for each :id. I have almost 400 images in my public/images folder, so loading them all from one directory is an aweful way to handle it.
The problem I’m seeing is that the call to the manage_images controller is hard-coded into an Ajax request in the TinMCE plugin’s image.htm. So when it is called it cannot access the params[] array to build a url.
Any thoughts?
December 28, 2007 at 1:51 pm
@Ian, I haven’t tried version 3 of TinyMCE, what kind of problems are you having?
@vim_commando, attachment_fu can create sub directories for images based on the id of the model (I can’t remember if that is the default or not). You could change the javascript in the image.html file and setup routes such that if you have two controllers controller1 and controller2 you would have URI’s like controller1/manage_images or controller2/manage_images. In the manage images controller you could take action based on the request path.
January 3, 2008 at 1:50 pm
I came to realize after-the-fact that this entire howto assumes you are using attachment_fu to handle your image uploads. It should be noted as a prerequisite, as the first reference to it was in the response to my question >.<
January 21, 2008 at 1:43 am
Great tutorial !!
I saw missing in paragraph 6..
March 6, 2008 at 9:41 am
hey… thanks so much for a great tutorial, this worked great. nice to see something like this actually cover enough gotchas to get people up and running too.
March 31, 2008 at 7:14 am
[…] How to Create an Image Selection Plugin for tinyMCE with Ruby on Rails « Hack’d – Great Stuff for… (tags: plugin javascript editor tinymce rubyonrails) […]
April 13, 2008 at 8:42 am
[…] https://hackd.wordpress.com/2007/08/23/how-to-create-an-image-selction-plugin-for-tinymce-with-ruby-o… […]
April 15, 2008 at 2:49 pm
shaaaaaaaweeeeet! works fine except with version 3 and up.
April 24, 2008 at 4:18 am
Hi…
Just the kind of thing I was looking for. Followed right from the beginning but failed to get the “Uploaded Images” tab even. Also tried some hacks in others comments but nothing helped me.. where could i go wrong?
May 16, 2008 at 2:15 am
nice blog for image upload in tiny mce for rails,your demo project is good .Thanks for it
June 7, 2008 at 11:03 pm
[…] How to Create an Image Selection Plugin for tinyMCE with Ruby on Rails « Hack’d – Great Stuff for… […]
June 8, 2008 at 2:15 pm
[…] you’ll want to be able to add images inline style. Here’s a useful tutorial for […]
June 23, 2008 at 8:07 am
I’m having an issue getting the icon to show up in the TinyMCE menu. I looked through the comments and made the changes that people suggested and it is not showing up. Maybe I’m missing in the “editor_plugin.js”. Can someone post their “editor_plugin.js”?
thanks
aaron
August 21, 2008 at 11:44 am
I’m trying to embed a form into TinyMCE and it strips out the form tags… any suggestions??? Thanks.
September 12, 2008 at 10:14 am
In step 6 you are missing a closing div tag for
September 12, 2008 at 10:30 am
Oh whoops, I didn’t realize you are supposed to put the new “dynamic_select_panel” div inside of the existing “panel_wrapper” div.
October 25, 2008 at 11:33 am
Does anyone know a tutorial for uploading and adding files?
November 8, 2008 at 11:09 pm
If I would like to use Tinymce version 3.
How would it be done?