FCKEditor (with spell check!) on Rails 14

Posted by brian Tuesday, February 14, 2006 22:52:00 GMT


Ever seen Josh Charles' article on Integrating FCKEditor with your Ruby on Rails application? It's a really good step-by-step tutorial on how to get the popular FCKEditor working smoothly with Rails. (If you don't know what FCKEditor is - it's basically a Javascript "rich" text box with a variety of options - kinda like MS Word toolbars.)

Anyway, I really needed FCKEditor + spell check for a project I've been working on. So, with Josh's help, I spent a few weekend hours putting together a solution. FCKEditor comes with integration with Aspell "out-of-the-box" for PHP and Perl already, so a simple translation of the existing PHP code does the trick.

This assumes a model called Document, and a document_controller, and that the FCKEditor code lives in /path/to/your/rails/application/public/javascripts.

0) Follow the steps in Josh's article to write a helper method for the editor, so you can simply do something like <%= fckeditor_text_field 'document', 'text' %> to use it on your form.

1) We want FCKEditor to use our own "spell" action, instead of the default PHP code, so change one line in javascripts/editor/dialog/fck_spellerpages/spellerpages/spellchecker.js from

    this.spellCheckScript = 'server-scripts/spellchecker.php'

to

  
    this.spellCheckScript = '/document/spell'

2) When you hit the spell check button in the editor, it posts the form and gives you the stuff in the text box (escaped html tags and all) in @params[:textinputs][0]. We only want to spell check the raw text, so in the controller, use CGI.unescape to unescape the string that you have (which will look something like "Ice%20Ice%20Baby"). Then, use ActionView's helper method strip_tags to remove all html tags.

    require 'cgi'

    class DocumentController < ApplicationController
    include ActionView::Helpers::TextHelper

    def spell
        @original_text = @params[:textinputs][0]
        unescaped_text = CGI.unescape(@params[:textinputs][0])
        plain_text = strip_tags(unescaped_text)
        @words = Document.spell_check(plain_text, @user)
    end

    end

3) Now, in the Document model (document.rb), we need to write the spell_check method. Basically, we write the text that's passed to this function, write it to a text file, and execute Aspell to do the actual spell checking. You get back some output that needs to be parsed to retrieve the results. Ultimately, there's Javascript (see step 4) that actually displays the results of the spell check, and it expects two arrays - one that represents all misspelled words, and another that holds the corresponding suggestions. Warning - yucky string manipulation/hacking lies ahead:

    def self.spell_check(text, user)

        # shell out to aspell and retrieve the results
        # aspell_program = 'c:\program files\aspell\bin\aspell' # windows
        aspell_program = 'aspell' # every other OS on the planet
        temp_file = "#{somewhere}/spell.txt"
        File.open(temp_file, "wb") { |f| f.write(text) }
        command_line = "\"#{aspell_program}\" -a --lang=en_US --encoding=utf-8 -H < #{temp_file} 2>
&1"
        output = IO.popen(command_line) { |f| f.read }
        lines = output.split("\n")

        # build an array of stuff to put in the javascript
        words = []
        lines.each do |line|
            if line[0,1] == '&'
                details = line.split(' ', 5)
                words << [escape(details[1]), get_suggestions(details[4])]
            end
        end
        words

    end

    def self.get_suggestions(value)
        value.split(', ').collect {|x| "'#{escape(x)}'"}.join(', ')
    end

    # properly escape single-quote for javascript
    def self.escape(x)
        x.gsub(/'/, '\\\\\'')
    end

4) Finally, the view (spell.rhtml), actually displays the results of the spell check.

    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <%= stylesheet_link_tag "spellercss" %>
    <%= javascript_include_tag "wordWindow" %>
    <script language="javascript">
    var suggs = new Array();
    var words = new Array();
    var textinputs = new Array();
    var error;

    textinputs[0] = decodeURIComponent('<%= @original_text %>');
    words[0] = [];
    suggs[0] = [];

    <% @words.length.times do |n| %>
    words[0][<%=n%>] = '<%= @words[n][0] %>';
    suggs[0][<%=n%>] = [<%= @words[n][1] %>];
    <% end %>

    var wordWindowObj = new wordWindow();
    wordWindowObj.originalSpellings = words;
    wordWindowObj.suggestions = suggs;
    wordWindowObj.textInputs = textinputs;

    function init_spell() {
        // check if any error occured during server-side processing
        if( error ) {
            alert( error );
        } else {
            // call the init_spell() function in the parent frameset
            if (parent.frames.length) {
                parent.init_spell( wordWindowObj );
            } else {
                alert('This page was loaded outside of a frameset. It might not display properly');
            }
        }
    }

    </script>

    </head>

    <body onLoad="init_spell();" bgcolor="#ffffff">

    <script type="text/javascript">
        wordWindowObj.writeBody();
    </script>

    </body>
    </html>

Yay!

Update: As David Kessler points out, you'll also need to copy over one JavaScript file for this to work. Copy wordWindow.js from public\javascripts\editor\dialog\fck_spellerpages\spellerpages to public\javascripts.

Comments

Leave a response

  1. kesslerdn@ecity.net   February 24, 2006 @ 03:45 PM

    I am attempting to follow the directions on http://www.softiesonrails.com/articles/2006/02/14/fckeditor-with-spell-check-on-rails.

    I worked through several issues and have finally gotten the correct results passed to spell.rhtml. The final error that I am getting is:

    ActionController::RoutingError (Recognition failed for "/javascripts/wordWindow.js"):

    If I copy the ‘wordWindow.js’ file from ‘\public\javascripts\editor\dialog\fck_spellerpages\spellerpages\’ to ‘\public\javascripts\’ Rails is happy but the ‘originalSpellings’, ‘suggestions’, and the ‘textInputs’ fail to render on the screen. I am able to add some text to the body of speller.rhtml and it renders. I have also checked all of the values received from the controller and they are correct.

    I would appreciate any help that you could offer. Thank you for your time and assistance.

  2. Brian   February 24, 2006 @ 06:51 PM

    Major brain freeze resulted in some missing code. I've updated the post.

  3. Jared Luxenberg   April 09, 2006 @ 07:35 AM
    temp_file = "#{somewhere}/spell.txt"
    File.open(temp_file, "wb") { |f| f.write(text) }

    Maybe it's just me, but isn't there a horrible race condition here? What happens if two users want to spell check a document at the same time?

    Ideally, you'd make a call to a system function like mkstemp (on Linux). Since Ruby doesn't expose and API for this function, a quick and dirty way would be to make a really simple C program that simply calls the mkstemp function and spits the filename back out on standard output. You'd probably have to keep that program running until you're done with the file, so that the kernel doesn't nuke the temporary file.

    It'd be nice if Ruby provided this in its standard library, but as far as I know it does not.

  4. Brian   April 09, 2006 @ 01:24 PM

    Jared: no, you're absolutely right. I should have pointed out that in my app, "somewhere" is a directory that I've created for each user (my app is a line-of-business app that requires users to be logged-in). If it was a public web app, a better solution would have to be implemented.

  5. Conair Answering Automobile   April 28, 2006 @ 04:17 AM

    Thanks for the write-up :)

  6. Meatman Block Table   April 29, 2006 @ 11:45 AM

    That's awesome!

  7. fsdfsdf@fdsdf.es   August 14, 2006 @ 02:28 PM

    salidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditorsalidafckeditor

  8. Paul   September 05, 2006 @ 11:09 PM

    The default when I downloaded fckeditor was to use iespell instead of aspell. To use aspell you have to change your fckconfig.js file and tell it to use aspell instead of iespell by setting

    FCKConfig.SpellChecker = 'SpellerPages'

  9. 78kuz   September 08, 2006 @ 01:23 PM

    jzhmkj

  10. Pillangószív   September 24, 2006 @ 11:57 AM

    Its awesome, thank you!

  11. http://www.thesolutioncafe.com   October 14, 2006 @ 03:41 PM

    Try this:

    http://www.thesolutioncafe.com/fckeditorspellchecker.html

    Three lines of code... and you have a spell checker for FCKEditor!

  12. Jerry   October 14, 2006 @ 04:45 PM

    When i run rake fckeditor:install It show:Don’t know how to build task ‘fckeditor:install’ I don’t know why

  13. kino   May 24, 2008 @ 01:13 AM

    In all theoretical sciences, the phenomena are a representation of, for example, the phenomena.

  14. Ellroy   May 30, 2008 @ 11:23 PM

    Since all of the Categories are hypothetical, it remains a mystery why, in reference to ends, philosophy is the clue to the discovery of our disjunctive judgements, and the noumena are what first give rise to, in accordance with the principles of the Transcendental Deduction, the things in themselves.

Comment


(won't be published)