Drupal: Limit WYSIWYG editors

See more about:

WYSIWYG,
Oh WYSIWYG,
Your siren call does beckon.
But your sharpish rocks have killed
Many a geek, I reckon.

You promise much,
Oh WYSIWYG.
The users will be happy!
But when they try to make a page,
It ends up looking crappy.

But there's a way,
Oh WYSIWYG,
To bend you to my will.
With careful CSS control,
The users we will thrill.

So fear not,
My Drupal friends!
WYSIWYG shall not defeat you!
CKEditor will serve,
And the users will not hang you.

The problem

I worked on a site recently, a simple thing, with panels and views and the usual stuff. The client wanted to edit content themselves. No problem. I installed the WYSIWYG module, hooked it up to CKEditor, added Insert, Image Resize Filter and their pals, and off we went.

But strange problems kept cropping up. Two panels, right next to each other, with similar content, would have different line spacing, or white space, or something else. Frustrating for everyone.

Looking at the code, it was clear what was happening. CKEditor was inserting all sorts of tags at the users' behest. They looked fine when editing content. But when the content was displayed, it didn't look the same at all.

There's a mismatch between what people expect from a WYSIWYG editor, and what a WYSIWYG on a Web site editor can realistically deliver. People expect Web WYSIWYG editors to be like MS Word or OpenOffice Writer, to have the same level of WYSIWYGiness. "WYSIWYGiness" means the similarity between the way a document looks when editing, and the way it looks when displayed.

Word and Writer were designed from the ground up to be WYSIWYGy. Web browsers, CMSs, and so on, were not. They have trouble delivering the same WYSIWYGiness.

Why? Think about how Web WYSIWYG editors like CKEditor do their work. They use HTML and CSS, of course. They add classes and styles to affect content format. When the user adds a link, CKEditor adds an <a> tag. When the user makes some text red, CKEditor adds <span style="color:red;"> around the highlighted text.

So, what are all the things that determine the look of the content "we are all doomed?" Webers know how complicated things can get. The look of that content depends on the HTML tags <p> ... we are all doomed ... </p>, the <div> the <p> element is in, the <div> that <div> is in, etc., for about 15 levels. Then add the CSS applied to all of those things. The CSS applied by Drupal, modules, the theme currently in use, modifications to the theme (e. g ., a custom style sheet that overrides some of the theme's styles), styles embedded during content creation, and random changes made by the evil Style Altering Demon Intent on Spreading Terror (SADIST).

SADIST

All of the CSS rule sets can interact with each other. Some properties of some styles are overridden by another style sheet, some are not. If your theme uses percentages for font sizes, make an appointment with your therapist. You'll need it.

Take all this together, and it's a wonder that Web WYSIWYG editors get anything right.

Here's the most important thing you need to do: give up. Well, a bit. Give up on the idea that you can match the WYSIWYGiness of Writer or Word on your Web site. Google has done a pretty good job of it, but you don't have Google's budget.

I am in awe of the engineers behind Google Docs. If they wanted to, they could rule the world from a hidden base under a volcano. Hmm, maybe they already do.

You'll have to convince your users to change their expectations. More on that below.

You can reach a reasonable level of WYSIWYGiness, that will get the job done. With CKEditor, I'll show you how to:

  • Only let CKEditor controls (buttons, etc.) add either basic HTML tags (like <code>), and classes you have created.
  • Give users an easy way to use classes you have created.
  • Modify edit view to use those classes, or simulacra you make specially for edit  view.
  • Isolate your code from CKEditor, so that when you upgrade it, your changes won't be lost.

I don't know whether the procedures will work for other editors, like TinyMCE. But I'm guessing they could be adapted.

The goal

Let's see what we end up with. Here's a screen shot of part of a page in CKEditor.

That second button bar is part of a screen shot inserted as an image.

Here's how the content is rendered:

Display view

The two are similar. The fonts use the same typeface, size, and color. The line breaks and white space are more-or-less the same.

You get this equivalence by carefully choosing what code the editor creates. Few of the buttons in the toolbar create inline CSS styling. I left some, since they haven't caused any trouble. Yet. But they're on a short leash.

Let's see what code CKEditor creates for the buttons on the top line of the toolbar:

Bold - <strong>
Italics - <em>
Left, center, right align - text-align
Bullet list - <ul>
Number list - <ol>
Indent, outdent - margin-left
Undo, redo - nothing
Link, unlink - <a>
Picture - <img>
The rest don't create content

There are only two CSS properties, text-align and margin-left. Everything else is done with straight HTML, making the behavior more predictable.

Here are the controls on the bottom line that affect stying:

The one on the left applies HTML tags:

They're mainly block tags, plus a couple of inline tags that don't get used much.

You might be thinking to yourself, "How do users change text color, or background color, or typeface?"

Well, they can't. At least not directly. You can turn those controls on, if you want. But there's a better way.

Most Web sites have style guides, either explicit or implicit. Take CoreDogs. Here are some standards:

  • Default text is Trebuchet, 14px, gray.
  • To show code (HTML, JavaScript, ...), use the <code> tag.
  • When a lesson links to a pattern, style the link like this:

Rather than give users CKEditor buttons that mess up the site - like changing typeface, background color, text color, argh! - give them a way to easily apply the style guide's rules.

This is how you can sell the less-than-pure WYSIWYGiness of your site. You tell the users, or someone who can set policy, that this approach will make the site look more consistent. And, this time, anyway, you'll even be telling the truth!

Your savior is the second control in the bottom line of the toolbar. Here's what it looks like as I am typing this:

I've defined some styles that correspond to the CoreDogs' style guide. If I want to enter a pattern link, I just apply that style. It sets background color, border color, thickness and pattern, and padding. Convenient for the user, and good for stylistic consistency. "Step right up, step right up! Everybody's a winner!"

Making the editor behave

So, how to set all this up? I won't go over the details of installing WYSIWYG module, CKEditor, setting up input formats and filters, and such. You can read about that elsewhere. I'll just talk about two things.

The first is easy - telling Drupal which features CKEditor should offer. Create a profile with the WYSIWYG module, and set the features:

The next task is more difficult. You want to set up this thing:

The official name for it is CKEditor's stylescombo plugin.

There are several parts to making this work:

  • Write JavaScript that will define the formats you want the plugin to show. This will affect what options the combo shows the user, and what elements, tags, and styles it adds to your code.
  • Arrange for Drupal to run this JavaScript.
  • Write some styles that determine how content looks when the user is editing it with CKEditor. You want this to match what the content will look like when it's displayed.

You want to arrange all this so you can upgrade CKEditor without losing your changes.

Note: What I am about to show you is a little different from some of the official docs I've read. But just a little.

Setting the formats in the stylescombo

You need to create a JavaScript array defining the styles. I strongly recommend you use a smart editor to do this, like the editors in Netbeans or Eclipse. There are lots of (, {, and [ to match with their ], }, and ). It's easy to make a mistake. A smart editor will show you any errors you make.

If you don't know how to use Netbeans or Eclipse, stop reading and learn. I mean it. Take a few hours. You'll save time, and your marriage.

Here's the JavaScript file I use to set up the stylescombo:

//Load custom styles into ckeditor stylescombo plugin.
$(document).ready(function() {
  try {
    CKEDITOR.addStylesSet( 'coredogs',
    [
      // Block Styles
      {name : 'Aside', element : 'p',
           attributes : {'class' : 'aside'}},
      {name : 'Pattern link' , element : 'p',
           attributes : {'class' : 'pattern_link'}},
      // Inline Styles
      {name : 'Code', element : 'code'},
    ]);
    CKEDITOR.config.stylesCombo_stylesSet = 'coredogs';
  }
  catch(e){
    //Nothing
  }
});

It uses the jQuery ready function, so it's executed when the document is loaded and ready to go. Most pages won't have CKEditor on them; most page views are people looking at pages, not editing them. The try-catch block takes care of that. If the CKEDITOR object doesn't exist, the error will be caught, and the script will end, without any error messages being generated.

Maybe this could be more efficient, but not much. The very first object reference, to CKEDITOR, will fail when the object does not exist.

The code creates an array defining formats, and passes it to CKEDITOR.addStylesSet. The first element sent to the function, 'coredogs', is a name for this set of formats. The array is the second parameter.

Here's the first entry in the array:

{name : 'Aside', element : 'p', attributes : {'class' : 'aside'}}

This adds a style called Aside to the stylescombo. When the user selects the style, CKEditor creates <p class="aside">.

The CKEditor site has more documentation about the format for creating styles.

You need to put the file with this code somewhere. I like to put stuff like this in /sites/all/libraries, so it isn't affected when modules and themes are updated. I put the code in the file /sites/all/libraries/coredogs/ckeditor-custom/ckeditor-custom-styles.js.

Getting the code to run

We have to get Drupal to run this code when it's making  a page. I put this code in  template.php in the theme directory.

function [theme]_preprocess(&$vars) {
  //Load custom styles for the CKEditor stylescombo plugin.
  $styles_path =
    'sites/all/libraries/coredogs/ckeditor-custom/ckeditor-custom-styles.js';
  drupal_add_js($styles_path);
...
}

Replace [theme] with the name of your theme.

This works on some sites, but not on others. I don't know why; I suspect it has to do with the order in which calls to drupal_add_js are executed.

An alternative approach is to load the JavaScript directly in page.tpl.php, like this:

<head>
  <title><?php print $head_title; ?></title>
  <?php print $head; ?>
...
  <?php print $scripts; ?>
  <script type="text/javascript" src="/sites/all/libraries/k2c-stuff/ckeditor-custom/ckeditor-custom-styles.js"></script>
</head>

The new line is <script type.... The path is to wherever the JavaScript file is.

Setting the look of styles in edit mode

The code above inserts <p class="aside"> when the user selects the Aside style. Where is aside defined? You'll see this in style.css in my theme's directory:

.aside {
  font-size: 12px;
  font-style: italic;
  margin-left: 200px;
  text-align: right;
}

This style is used when people view pages, but not when they edit pages. I want the look of a page in edit mode to be close to the look when the page is viewed. How?

First, I create a new style sheet, to be used by CKEditor when editing content. I called mine ckeditor-custom-styles.css, and put it in the same directory as the JavaScript file above.

Here's what's in the file:

/*
Defines classes for use with CKEditor when editing CoreDogs stuff.
Used in conjunction with JS file for the stylescombo plugin.
*/

/* Set default look for some tags, to match CoreDogs standard. */
body {
  color: #585858;
  font-size: 14px;
  font-family: "trebuchet ms", helvetica, sans-serif;
}
h2,h3,h4,h5,h6 {
  color: #562F00;
}
a {
  text-decoration: underline;
  color: #AB5500;
}
li {

.aside {
  font-size: 12px;
  font-style: italic;
  margin-left: 200px;
  text-align: right;
}
/* Link to pattern page. */
.pattern_link {
  background-color: #FAF1E7;
  border: 1px dotted #713E00;
  padding: 5px;
}

The first few rules define default styles for edit mode. The last two define the special styles needed by the style combo.

How do I tell CKEditor to use these styles? The WYSIWYG module can help. Here's how I set it up:

The path is, of course, to the CSS file we just created.

There may still be some strange stuff in CKEditor. Like links that are underlined in blue, rather than the style you thought you site. Sigh. But it works well enough.

Oh, one more thing

There's always something. To make <br/>s work right, you might need to turn off the line break converter in the input format.

Turn line break filter off

W00f!

Users want WYSIWYG. They really, really want it. They expect that Web WYSIWYG editors will work the same as Word or Writer. But the editors don't have that level of WYSIWYGiness.

What to do? One approach is to make your WYSIWYG editor only produce a small set of HTML tags and classes. This is easier to control than giving users unrestricted access to lots of CSS properties. With CKEditor, you can offer a combo box with a constrained list of classes. You can sell it as making the site comply with company style guidelines.

Questions? Improvements? Comment away.


Lessons

How to...


Dogs