After the Deadline

Add Grammar and Spell Check to Any WYSIWYG Editor

Posted in HOWTO by rsmudge on January 5, 2010

After the Deadline front-end libraries are available for jQuery and TinyMCE. You’ve asked what it would take to make AtD available in another WYSIWYG environment or even in a web-based word processor. In the past I’d answer that you’d need to study the code to either the jQuery or TinyMCE extensions, visit the mountains, meditate and wait for the answer to come.

Today I’m bringing the mountain to you. Much of the code that makes After the Deadline work in TinyMCE and jQuery is similar. Painfully similar. To the point where a bug in one is a bug in the other. I’ve refactored these extensions and created an After the Deadline Core UI module. This module is browser independent with no external dependencies. It provides functionality to parse the AtD XML into an error data structure, retrieve suggestions and other information given an error phrase and a word of context, and it also abstracts away the logic to traverse a DOM and insert the AtD markup for errors.

To test this module, I gave myself one working day to port After the Deadline to CKEditor. CKEditor is a WYSIWYG editor, similar to TinyMCE. I have never used CKEditor before and this was a challenge. Still, I was able to make it quite far and in this post, I’ll show you how to add AtD to an editor using the Core UI Module.

Here is what an After the Deadline editor plugin must do:

  1. The first step is to setup the AtD Core UI module:
    atd_core = new AtDCore();
  2. The next step is to define several functions that the AtD Core UI module expects. The module will use these functions to manipulate the DOM and find elements in the way that your environment expects. The full list of functions is documented in the AtD Core UI README. Here are a few of these functions from CKEditor:
    atd_core.replaceWith = function(old_node, new_node) {
       return new_node.replace(CKEDITOR.dom.element.get(old_node));
    };
    
    atd_core.create = function(node_html) {
       return CKEDITOR.dom.element.createFromHtml( '<span class="mceItemHidden">' + node_html + '</span>' );
    };
    
  3. Once these functions are defined you can set AtD-specific preferences like the list of strings to ignore and which types of errors to show.
    atd_core.showTypes('Complex Expression, Diacritical Marks, Double Negatives, Redundant Expression');
    atd_core.setIgnoreStrings('CKEditor, was thrown');
    
  4. When a user requests proofreading, it is up to you to extract the contents of the editor and post it to the After the Deadline service. Here is how I extract the editor contents in CKEditor:
    var editor_contents = editor.document.getBody().getHtml();
    
  5. You should receive an XML document from the server with an error message or a data structure containing the AtD data. To check the XML document for an AtD error:
    function ajax_callback(xml_response) {
       if (atd_core.hasErrorMessage(xml_response)) {
          alert(atd_core.getErrorMessage(xml_response));
          return;
       }
    
  6. If there are no errors then you’ll want to parse the XML into a data structure the Shared UI code can use. Use the processXML function to do this. It will return a JavaScript object that you will be using again.
    var results = atd_core.processXML(xml_response);
  7. Now, let’s say you want to highlight errors in your editor. Great! Extract the contents of the editor (should be an array of elements from the root element) and pass these to markMyWords. Using the prototypes you provided earlier, this function will walk through these nodes and highlight the errors. Be thankful that you didn’t have to write the code to do this.
    var nodes = editor.document.getDocumentElement().getChildren().getItem(1)['$'].childNodes;
    atd_core.markMyWords(nodes, results.errors);
    
  8. Earlier, I hope you attached a click or context menu listener to your editor. If you did you can use isMarkedElement on the event target when a click occurs in your editor. If this returns true, this is your clue that a user clicked on a marked error and you should display a menu offering them suggestions.To get the suggestions, call findSuggestion using the marked element. This will return a JavaScript object with the following members that may interest you: suggestions, description, moreinfo
    editor.contextMenu.addListener(function(element) {
       if (atd_core.isMarkedNode(element.$)) {
          var meta = atd_core.findSuggestion(element.$);
          var commands = {};
    
          addItem(editor, meta.description, function() { }, 0, commands, 'AtD_description');
    
          for (var x = 0; x < meta.suggestions.length; x++)
             addItem(editor, meta.suggestions[x], makeCallback(element.$, meta.suggestions[x]), x + 1, commands, 'AtD_suggestions');
    
          addItem(editor, 'Ignore', makeIgnoreCallback(element.$, element.$.innerHTML), 1, commands, 'AtD_ignore');
          addItem(editor, 'Ignore All', makeIgnoreAllCallback(element.$, element.$.innerHTML), 0, commands, 'AtD_ignore');
    
          return commands;
       }
    });
    
  9. For each suggestion, I use makeCallback to generate a function to attach to the menu item. This generated function calls applySuggestion in the Core UI Module. You should use applySuggestion as it’s smart enough to do what the suggestion asks. For example, the (omit) suggestion removes the word in question:
    var makeCallback = function(element, suggestion) {
       return function() {
          atd_core.applySuggestion(element, suggestion);
       };
    };
    
  10. You may want to add functionality to let the user ignore the current error or to ignore all occurrences of it. The “Ignore suggestion” menu item removes the marked node keeping its children. No need to call into the AtD Core UI module:
    var makeIgnoreCallback = function(element, word) {
       return function() {
          CKEDITOR.dom.element.get(element).remove(true);
    };
    

    To ignore all occurrences of an error, you can use the removeWords function in the Core UI Module.

    var makeIgnoreAllCallback = function(element, word) {
       return function() {
          atd_core.removeWords(undefined, word);
       };
    };
    
  11. The last thing you should do is attach a listener to remove the After the Deadline markup when the contents of the editor are grabbed. You can remove the AtD markup using the removeWords function.

And that’s it. Add in the necessary scaffolding for your editor and you have After the Deadline integration. Here are some other things you can do:

  • Make an Ignore Always menu option save the user’s preference in a cookie or on your server. Resurrect this setting later with the setIgnoreStrings function.
  • Keep track of the user’s preference for what types of errors to show. Use showTypes to enable these for the user.

These are the kinds of things we do in WordPress to make AtD a first-class feature for our users. If you’re looking to port After the Deadline to another environment, the AtD Core UI module will save you a lot of time.

Coming up I’ll be releasing versions of the AtD/jQuery and AtD/TinyMCE Extensions using this shared module. The AtD/CKEditor extension is available now.

8 Responses

Subscribe to comments with RSS.

  1. Gautam said, on January 5, 2010 at 12:53 pm

    The AtD Core UI download link isn’t working.

    And by the way, the i18n support for script is a very good feature, wp_localize_script function can work great on it.

    • rsmudge said, on January 5, 2010 at 2:45 pm

      Fixed the link. Thanks Gautam. And yeap, the wp_localize_script is a big part of what’s coming next. Have l10n-ready jQuery and TinyMCE extensions almost ready for release (and an l10n-ready AtD/WP plugin too).

  2. [...] in Firefox addon, News by mitcho/芳貴 on January 14, 2010 After the Deadline already can be easily embedded and can travel with you as a bookmarklet, but what if you could have After the Deadline’s [...]

  3. David Foxfire said, on March 3, 2010 at 10:31 pm

    As a struggling writer with his many grammar-based stumbling blocks I really appreciate AtD as a Foxfire Plugin. But here’s something you might want to consider: I know you’re working with AtD plugins to various word processors, like CKEditor and TinyMCE. Not only would I like to see a plugin for the bigger word processors like Open Office, or dare I say it, Word or Wordperfect, but also a universal program that can work with any program by having it run in the taskbar/powerbar like how Whitesmoke works.

    I’ve been searching long and hard for a cheap (and open source) grammar checker for a long time, and I really support AtD. Please keep up the good work and I hope to see more products from you soon.

  4. [...] AtD core library (the foundation of AtD’s front-end) has several bug fixes as [...]

  5. [...] I noticed duplicate functionality. To ease my burden fixing bugs in both and make it easier to port AtD to other applications, the AtD core library was born. This library has functions to parse the AtD XML protocol into a [...]

  6. [...] Deadline is now available in a lot more places. We have stable plugins for jQuery and TinyMCE. The AtD Core library has allowed us to reuse the protocol parsing and error highlighting logic in many [...]


Comments are closed.

Follow

Get every new post delivered to your Inbox.

Join 282 other followers

%d bloggers like this: