Eugene's Blog

I can't believe it's blog!

Using Dojo Rich Editor with Django's Admin

Many years ago I decided to replace plain text areas in Django’s Admin with rich text editor, so I can edit HTML on my blog using WYSIWYG. Six (yes, 6) years ago I looked around and selected TinyMCE. Over time it turned out that I was forced to upgrade TinyMCE and the link script I had because new browsers continue breaking my rich editor editing. Finally it stopped working again in all modern browsers, and I decided that enough is enough. It is time to replace it. This time I settled on Dojo’s Rich Editor hosted on Google CDN — simple, functional, less work to set up.

Selecting features

Adding Dojo’s editor to Django’s Admin was very simple. The most complex part turned out to be selecting what cool features I want to use. See, dijit.Editor is fully pluggable, and it is easy to extend with your own plugins. Some day I’ll do just that, but for now let’s use what’s available.

dijit.Editor comes with two sets of plugins. Let’s go over them and select plugins we want to use.

The first set is in Dijit’s editor plugin repository:

  • FullScreen: allows editor to take over the whole page. Toggleable. This is one of the most important plugins for me: I hate to edit text in an itsy-bitsy window, yet if I make an editor window big, it becomes hard to see other parts of the document with all scrolling around. Ideally I want it small, when I look at my document with a rich text screen, but becoming big when I want to edit it. Hence FullScreen.
  • ViewSource: toggles between rich editor and a simple text editor to edit underlying HTML. I am an HTML professional and time to time I need to edit my HTML manually, so this plugin is extremely important for me too.
  • TextColor: allows to select foreground and background color. Need it.
  • FontChoice: formatting paragraphs, selecting font families and sizes. Need it too.
  • LinkDialog: adding links and images. Need it.
  • Print (printing), NewPage (clearing editor), and ToggleDir (switching between LTR and RTL languages) — I have no need for them. Skip.

The second set of plugins comes from DojoX’ editor plugin repository:

  • PrettyPrint: formats HTML in a readable way. Well, I’ll be manually editing HTML, so I obviously want it.
  • ShowBlockNodes: shows what elements are used for formatting in WYSIWYG mode. Obviously I can use ViewSource for that, but this plugin sounds like fun, let’s add it.
  • InsertEntity: allows selecting HTML entities visually. I love it! I frequently use "—" (em-dash), arrows like "⇒", and so on, yet rarely remember abbreviations. It is a keeper.
  • NormalizaIndentOutdent: some enhancements on indenting/outdenting to avoid browser-specific differences. Sounds useful.
  • FindReplace: finds and/or replaces text. Obviously I want this functionality in my editor.
  • CollapsibleToolbar: hide/show editor’s toolbar to make more space for edited text. Sounds cool, I like it.
  • Blockquote: it sounds exactly like what it does. I use block quotes, so we’ll add this plugin as well.
  • InsertAnchor: inserts an anchor for future references. Useful.
  • TextColor: again? Yep. But it is much cooler than its counterpart from Dijit. We’ll replace that plugin with this one.
  • AutoUrlLink: converts URLs into proper <a> tags. Need it.
  • PasteFromWord: clean up dirty HTML using predefined filters. Sometimes I use Open Office or Google Docs to prepare my posts ⇒ I think this functionality will come handy.
  • ToolbarLineBreak: allows to insert line breaks into editor’s toolbar. We are going to have quite a few plugins, so we will need this plugin for sure.
  • TablePlugins (table operations), PageBreak (inserting <hr>), Preview (show edited content in a separate window), Save (POST content to a server), AutoSave (save automatically), Breadcrumb (show your current environment by listing parent nodes), NormalizeStyle (some HTML filtering), StatusBar, InsertImage (cooler insert image), SpellCheck (API for spell checker), ColorTableCell, ResizeTableColumn — I either do not have much use for them, or don’t want to implement server-side APIs.

So we made our selection. BTW, if you follow links above to plugin repositories and can read up on each plugin and see small examples of them in action.

Like I said I don’t want to host Dojo myself and will use Google CDN for that. It simplifies a lot of things: no need for copying stuff around; I am bypassing custom builds hoping that it’ll be fast enough for me; all I need to do is to add two files: a JavaScript link, and a CSS file to tweak visuals. Let’s do it.

JavaScript

This part is about loading plugins we need, and instantiating dijit.Editor on our <textarea> nodes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
dojo.require("dijit.Editor");

// extra plugins
dojo.require("dijit._editor.plugins.FontChoice");
dojo.require("dojox.editor.plugins.TextColor");
dojo.require("dojox.editor.plugins.Blockquote");
dojo.require("dijit._editor.plugins.LinkDialog");
dojo.require("dojox.editor.plugins.InsertAnchor");
dojo.require("dojox.editor.plugins.FindReplace");
dojo.require("dojox.editor.plugins.ShowBlockNodes");
dojo.require("dojox.editor.plugins.PasteFromWord");
dojo.require("dijit._editor.plugins.ViewSource");
dojo.require("dijit._editor.plugins.FullScreen");
dojo.require("dojox.editor.plugins.InsertEntity");

// headless plugins
dojo.require("dojox.editor.plugins.CollapsibleToolbar");
dojo.require("dojox.editor.plugins.NormalizeIndentOutdent");
dojo.require("dojox.editor.plugins.PrettyPrint");  // let's pretty-print our HTML
dojo.require("dojox.editor.plugins.AutoUrlLink");
dojo.require("dojox.editor.plugins.ToolbarLineBreak");

dojo.ready(function(){
  var textareas = dojo.query("textarea");
  if(textareas &amp;&amp; textareas.length){
    dojo.addClass(dojo.body(), "claro");
    textareas.instantiate(dijit.Editor, {
      styleSheets: "/appmedia/style.css;/appmedia/blog/style.css",
      plugins: [
        "collapsibletoolbar",
        "fullscreen", "viewsource", "|",
        "undo", "redo", "|",
        "cut", "copy", "paste", "|",
        "bold", "italic", "underline", "strikethrough", "|",
        "insertOrderedList", "insertUnorderedList", "indent", "outdent", "||",
        "formatBlock", "fontName", "fontSize", "||",
        "findreplace", "insertEntity", "blockquote", "|",
        "createLink", "insertImage", "insertanchor", "|",
        "foreColor", "hiliteColor", "|",
        "showblocknodes", "pastefromword",
        // headless plugins
        "normalizeindentoutdent", "prettyprint",
        "autourllink", "dijit._editor.plugins.EnterKeyHandling"
      ]
    });
  }
});

As you can see it has three major logical blocks:

  1. List of dojo.require() calls — we request dijit.Editor and all plugins we selected above.
  2. Function inside of dojo.ready(), which looks for <textarea> nodes and instantiate dijit.Editor on them. Why dojo.ready()? Because we use CDN and load all resources asynchronously ⇒ we need to wait while they all are loaded.
  3. List of all plugins including built-in ones. Why? I want to make the toolbar to look good and be functional, so I manually specified what I want to see. "|" produces a vertical separator. "||" breaks a line.

This is how it looks now:

Django Admin

Everything is exactly where I want it to be.

But in order to look like that we should create a CSS file.

CSS

Now we want to include necessary CSS files and make sure that our editor play nice with Django Admin’s CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Import standard Dojo CSS files */
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dijit/themes/claro/claro.css";

/* Import custom style sheets for plugins */
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/FindReplace.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/ShowBlockNodes.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/InsertEntity.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/CollapsibleToolbar.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/Blockquote.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/PasteFromWord.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/InsertAnchor.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/TextColor.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/SpellCheck.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/PasteFromWord.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/PasteFromWord.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/PasteFromWord.css";

/* update CSS rules to cater for Django Admin */
.dijitEditor {display: inline-block;}
.dijitEditor .dijitToolbar .dijitTextBox {width: 20ex;}
.dijitEditor .dijitToolbar label {display: inline; float: none; width: auto;}

As you can see it includes external CSS from Google CDN required by certain plugins.

At the end it contains three rules, which make sure that our editor looks good and not distorted by Django Admin’s CSS.

Let’s include both files in our Admin pages.

Django Admin

In order to enable what we wrote above we should add it to our admin.py files. For that let’s define a shell class to hold our additional media files:

1
2
3
4
5
6
7
8
class CommonMedia:
  js = (
    'https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojo/dojo.xd.js',
    '/appmedia/admin/js/editor.js',
  )
  css = {
    'all': ('/appmedia/admin/css/editor.css',),
  }

We include two JavaScript files: Dojo 1.6 from Google CDN, and our JavaScript file detailed above. And one CSS file described above. It is as simple as it can be. No copying more files, no hosting hundreds of static files, no additional server-side code.

As you can see it is uber simple and described in Django’s documentation.

Now all we need to do is to reference this class in our model registrations, like that:

1
2
3
4
5
site.register(models.Category,
  list_display  = ('full_name',),
  search_fields = ['full_name',],
  Media = CommonMedia,
)

That’s it!

Conclusion

To help people to add nice WYSIWYG HTML editor to their Django Admin pages I published snippets described above as a gist: https://gist.github.com/868595.

I made this guide as simple as possible. Obviously the implementation can be improved. For example, you can do a custom build effectively collapsing all JavaScript and CSS files — it will improve the performance greatly provided that you can host this custom build as efficiently as Google. I didn’t do it because I am the only user of my Django Admin, it is not exposed to my readers, so I don’t expect it to be performance critical.

Have fun!