Table of Contents

View Screencast (Quicktime)
Right-click and choose Save As to save file to your computer


Sign Up Now!

If you aren’t already receiving our course lessons via email, sign up now to be sure you don’t miss anything.

Every few days, we’ll send you an email with a link to the next episode, plus a list of additional resources for advancing your knowledge.

There’s no cost and no obligation. And we’ll never share your email address with any third party.


We’ll send you the first lesson right away.


Want to help spread the word? We’d be grateful if you would include a link to the course in your blog, web site, or emails.

Using Textile Markup, plus In-Place Editing with Ajax

15 comments

Goals

In this lesson we’re going to add two features to our content-management system: Textile markup and in-place editing. Both are, in principle, very simple to implement, thanks to a couple of Rails gems and plugins, as well as the Prototype and Scriptaculous JavaScript libraries. The reality turns out to be just as simple as we’d hope in the first example, and rather more complex in the second.

Please note that, while we’ve tried to make these notes complete, they aren’t the full tutorial; that’s in the screencast, which you can access via the link on the left.

Setup

We begin with the code with which we ended Lesson 12. These zip files contain the beginning and ending states of the code:

Using Textile markup

We don’t want our administrative users to have to enter HTML code, so we’ll use a simple markup language called Textile.

There’s two pieces of Ruby code we use to implement this. The first is the gem RedCloth, which you already have if you’re on Leopard. This is the code that translates Textile into HTML. The second is a Rails plugin called “acts_as_textiled”, which makes it trivially easy to add Textile markup to our content blocks.

Run “gem list” to see if you already have RedCloth. If not:

sudo gem install RedCloth

To add the plugin, open a terminal window at the root of your application and enter the following command:

script/plugin install svn://errtheblog.com/svn/plugins/acts_as_textiled

Add to the Page model:

acts_as_textiled :body

That’s it. This plugin is smart enough to automatically display the Textile markup source when you’re displaying the body in a form field, as we are in the admin pages, but to render it into HTML anywhere else you use the body.

You’ll need to stop and restart the server before the plugin will function.

Now you can edit any of the pages in the content management system, such as the admin page, to convert the markup to Textile. Note that the page list view shows the HTML created by RedCloth—that’s because the acts_as_textiled plugin automatically renders the Textile content into HTML anyplace except in a form field.

In-place editing

In-place editing is a common feature of modern web applications, and there’s good support for it built in to Prototype. There’s also a Rails helper that wraps the Prototype method so we don’t even have to touch the JavaScript code.

In Rails 1.2.x, the in-place editor helper was part of the framework. In Rails 2.0, however, it was split out as a plugin, in theory so it could be separately maintained. Unfortunately, it has not been maintained (as of this writing in early May 2008), so not only do we need to install the plug-in, we’re going to have to seek out and install a couple of patches before we have everything working.

Installing the plugin

Install the in_place_editing plugin by entering the following in a console at the root of your application:

script/plugin install http://svn.rubyonrails.org/rails/plugins/in_place_editing

Modifying our code to use the plugin

The readme file provides basic installation instructions. We need to make two small changes to our code. In the file views/viewer/show.html.erb, replace the one line of text that is now in that file with the following:

<%= in_place_editor_field :page, 'body' %>

Then add this line to the top of the class in controllers/viewer_controller.rb:

in_place_edit_for :page, :body

And finally, we need to tell our application to load the JavaScript libraries. Add to the head section in views/layouts/application.html.erb:

<%= javascript_include_tag :defaults %>

Now restart the server, since we’ve installed a plugin, and everything should work as before. But now, when you click on any page text, a little edit box comes up!

Try editing the text and click OK. Unfortunately, the save fails. To get a hint of what’s happening, view the page using Firefox with Firebug installed, and take a look at the console when you click the OK button. Scroll through the response and you’ll see that there’s an issue with the authenticity token.

The CSRF protection bug

The authenticity token is a feature added to Rails 2.0 to improve protection against cross-site request forgery (CSRF) attempts. Unfortunately, the team didn’t update the in-place editor plugin correspondingly, so this security feature trips it up.

Try a google search on “in place editing CSRF”. The first result is a ticket in the Rails Trac titled “in_place_editing plugin does not work with CSRF protection”! Look at the patch suggested; focus on the forgery protection, grab the four lines of code and paste them into vendor/plugins/in_place_editing/lib/in_place_macros_helper.rb (see the screencast for details).

After adding these lines restart the server, reload the web page, click on some text to edit it, and click OK to save. Now it works!

A minor correction to the screencast discussion of the JavaScript code: in the audio, we referred to Ajax.InPlaceEditor as a Scriptaculous method. In fact, the Ajax object is part of the Prototype library, and the InPlaceEditor method is added by Scriptaculous.

Providing a larger text entry area

Next we want to provide a larger text entry field. Add the rows and columns options to the helper invocation:

<%= in_place_editor_field :page, 'body', {}, 
{:rows => 20, :cols => 80} %>

Refresh the browser, click on the text, and you should now see a decent size text area.

Displaying Textile source instead of HTML

Next problem: It is displaying the HTML markup, not the Textile source.

By default, the in_place_editor_field helper pulls the text to be edited from the page itself, not from the server, so naturally it sees the rendered HTML. To fix this, we need to set an option to tell it to fetch the source from the server, and then we need to create the controller code to respond to that request.

The option we need is called load_text_url. With this added to the helper invocation, we now have the following code in the view file:

<%= in_place_editor_field :page, 'body', {}, 
{:rows => 20, :cols => 80, 
 :load_text_url => {:controller => 'viewer', :action => 'get_unformatted_text', :id => @page.id}} %>

This tells the Scriptaculous method to make an XMLHttp request (the workhorse of Ajax applications) to retrieve the unformatted text.

Now we need to add the get_unformatted_text action to viewer_controller.rb:

def get_unformatted_text
  @page = Page.find(params[:id])
  render :text => @page.body(:source)
end

Take a look at the acts_as_textile readme to find the (:source) option that enables us to access the markup source, rather than the rendered HTML.

Reload the page, and it now displays Textile, just as we wanted.

Do we want visitors to be able to edit the site?

One small problem—unless our goal is to provide a wiki, we probably don’t want any visitor to be able to edit our site’s contents. So we need to use the in-place editor only if someone is logged in. Simple enough, just change the view code to:

<% if logged_in? %>

    <%= in_place_editor_field :page, 'body', {}, 
    {:rows => 20, :cols => 80, 
     :load_text_url => {:controller => 'viewer', :action => 'get_unformatted_text', :id => @page.id}} %>

<% else %>

    <%= @page.body %>

<% end %>

Refresh the browser, and now in-place edit is available only to logged-in users.

Adding an external control

One more problem: in admin mode, we can’t access links on the page, because clicking anywhere in the page text takes us into edit mode. So our admin dashboard is now useless. This would also be a problem if any of the regular pages had links and we wanted to be be able to exercise them in admin mode.

Looking back at the in-place editor helper file, we see an option for externalControl, so let’s try that. Add one more option to our in_place_editor_field invocation:

<%= in_place_editor_field :page, 'body', {}, 
{:rows => 20, :cols => 80, :external_control => 'edit', 
 :load_text_url => {:controller => 'viewer', :action => 'get_unformatted_text', :id => @page.id}
} %>

And create an edit link at the top of the page to trigger this:

<a href='#' id='edit'>Edit This Page</a>

Refresh the page, and give the edit link a try. It works! Unfortunately, clicking on the text also triggers the edit control, so we need to somehow prevent that.

Making only the external control activate the edit mode

What we need is a way to have only the external control activate the in-place editor. Searching the web, we find confusing and contradictory results. But in the excellent book Prototype and Scriptaculous, we read up on the in-place editor options, and find a reference to an option externalControlOnly. Alas, the Rails helper doesn’t support this, so it’s back to patching the code.

In the helper, we find the line:

js_options['externalControl'] = "'#{options[:external_control]}'" if options[:external_control]

So let’s try duplicating this line in the helper, changing externalControl to externalControlOnly in the duplicate line, and then we change our view code to set this option to true:

<%= in_place_editor_field :page, 'body', {}, 
{:rows => 20, :cols => 80, :external_control => 'edit', :external_control_only => true,
 :load_text_url => {:controller => 'viewer', :action => 'get_unformatted_text', :id => @page.id}
} %>

And presto, now it behaves as we want!

Life in the open-source world is sometimes not as clean as we’d like it to be. Someone will no doubt update the in-place editor plugin soon, which will cut out more than half the effort of this exercise.

Coming Up

In our next lesson, we’re going to improve the navigation model for our content management system generated pages, so we can have sub-pages as well as main pages, and so the navigation button text can be different from the page title.


Add a Comment

Have a comment or question about this lesson? Add it here.






Comments on This Lesson

From: Michael Slater       Date: 06/29/08 05:17 PM

Subject: Partials in CMS

Walter, this is beyond the scope of the writeup we’ve done here. To render a partial controlled by text in the CMS, that text would have to be executed. You can modify the CMS text rendering to execute ERb (embedded Ruby) in the text block, but that opens up your entire database to anyone who can enter text into the CMS.

There are safe alternatives, such as the Liquid markup language. You can also add custom markup definitions to Textile, so a certain string would trigger generation of a certain partial.

From: Walter       Date: 06/29/08 03:15 PM

Subject: how to insert partial into textile edit?

how do you insert a partial inside the textile markup? I want a partial to load in with the homepage /home menu, otherwise if using the redirect system mentioned later, I end up with two homepages; one that loads from @pages and the other that reacts to the menu click as http://localhost:3000/posts/list?name=home . So how would I add /posts/list?name=home as a partial to /home ? Thus merging both CMS systems.

From: john trenouth       Date: 06/05/08 10:10 AM

Subject: In place editing within loops

Trying this within a loop (like if you have a list of items and you want each to be editable in place) gave me error after error.

I fixed it by adding:

<% @item = item %>

before:

<% in_place_editor_field :item, ‘todo’ %>

From: Christopher Haupt       Date: 06/03/08 08:08 AM

Subject: RE: Answering my own question

Ben, thanks for the comments.

Yes, we need to make the Subversion (and now git too) requirements more clear. In the series of articles I wrote on settiing up your dev environment I walk folks through setting up Subversion, but don’t make it as clear as I could that you need the tool for more than just putting your own code in source code control.

Now that git is becoming the primary revision control tool of choice, and many plugins are moving to that tool, we will need to update our articles in the future to cover this requirement. It will also be important for people to get to Rails 2.1 to make using git easier (via tools like script/plugin).

From: Ben       Date: 06/03/08 07:07 AM

Subject: Answering my own question

OK, so I overlooked the fact that Windows doesn’t have Subversion installed by default. Maybe that should be mentioned, since these tutorials are aimed at newbies… Maybe it was – if so I missed it.

Anyway, I know I’m not the only one that would have been held up here. There are some tutorials stepping through how to install Subversion on Windows, but there is also a GREAT one click installer that walks you through the process: http://blog.briankohrs.com/2005/09/06/less-than-mere-moments-installation-of-subversion/

Very painless.

P.S. Great series incidentally – far superior to anything else I’ve found for beginners to Rails. If you could just add a note for Windows users re: Subversion, it would be even better :D

From: Ben       Date: 06/02/08 10:10 AM

Subject: Cannot install acts_as_textiled plugin

It’s been driving me mad, but I can’t work out why it doesn’t work. Navigated to the application directory, and used: ruby script/plugin install svn://errtheblog.com/svn/plugins/acts_as_textiled

The terminal doesn’t show any installation, but doesn’t throw any errors. But nothing new appears under vendor>plugins.

Can anyone shed some light on what I’m doing wrong?

From: Erik       Date: 05/28/08 08:08 AM

Subject: Keep up the Great Work!

I am thoroughly enjoying these professionally-produced lessons that go into appropriate amounts of detail and don’t overwhelm with information. It is challenging learning to understand the MVC model, since it is quite different than other frameworks out there, but I feel like I’m getting it. I listened to all the audiocasts so far but I definitely have to say the video ones are much more superior in their effectiveness. Being able to see real code and environments is paramount to understanding.

I am an experienced web developer but have to say that learning new languages is always a challenge. I would suggest going into some details about the syntax of Ruby would be helpful as well in context of the lessons, for example, explaining what a kind of object a particular value is, and where it is derived from. I find that the hardest thing to understand are the linkages between the auto-generated code and the new code that is added. I guess it just takes time to understand what certain code is to be able to remember methods and objects for customizing your own code.

Great work and can’t wait for more!

From: Kenn       Date: 05/24/08 11:11 AM

Subject: Waking up

I’m impressed by your decision to handle/deal with the “sub-lesson” about open source issues and the CSRF bug example. Introducing a need to Google for information while stepping through a novice geared tutorial was very real world dimensional.

One of my biggest AHA moments about “learning programming” was accepting “typos” with written instruction. Your screen cast note “while we’ve tried to make these notes complete, they aren’€™t the full tutorial” is why your written lessons w/screen cast are so engaging!

From: Christopher Haupt       Date: 05/19/08 11:11 AM

Subject: RE: Painfully Slow

Thanks for the comment, though we prefer to keep the discourse respectful. This series of ‘casts on LearningRails is a free contribution to the community dedicated to new learners of Ruby and Ruby on Rails. Many of our audience members are new to web development or these technologies, so we are going at a steady, but introductory, pace as we explore facets of Rails. There are other good information sources out there. Check out the book lists and articles at BuildingWebApps.com or Google around for more advanced materials that may be more your speed.

From: alan       Date: 05/18/08 11:23 PM

Subject: Painfully Slow

You should really give people some credit, we’re not all retards. By the time you get anywhere usefull/practical/relevant, we’ll all be using some other framework already.

From: Chris P       Date: 05/18/08 09:09 AM

Subject: Outstanding

The best screencasts I’ve seen. I’ve never learnt so much so fast :)

From: Machipsi       Date: 05/15/08 06:06 AM

Subject: Awsome

I look forward to these screencasts every week. You guys are doing a great job.

From: Owen madden       Date: 05/13/08 03:15 PM

Subject: Screencast

Wondefull – easy to follow as alwasy step by step – Thanks

From: Ray Rogers       Date: 05/12/08 07:07 AM

Subject: Wrong cut and paste

app_root$ script/plugin install svn://errtheblog.com/svn/plugins/acts_as_textiled

From: Ray Rogers       Date: 05/12/08 07:07 AM

Subject: Tried to install acts_as_textiled

i run the following code; app_root$ ruby script/plugin install http://svn.rubyonrails.org/rails/plugins/in_place_editing I get this error: sh: svn: not found