|
Was He Whig? No, WYSIWYG! |
Right behind pictures of frogs, speech balloons are the next most important part of my web comic. I've
discussed previously how the balloons in my comic are really just cleverly styled
<P> tags. For my WYSIWYG comic editor, I needed to display them just like they appear in the comics, but easily allow me to type new text into them.
Inline editing of text on a page can be a helpful addition to many types of web applications that rely on human-entered data. It's not just about speech balloons. If you need to use a feature like this as well, consider
X-editable, the JavaScript/CSS library I selected for my comic editor.
X-editable is a great package to use when you need inline editing and your site uses
jQuery. It gets even better if you also use
jQuery UI and/or
Bootstrap, but it can be a little confusing to set up properly. First of all, it offers multiple download packages depending on if you want to use it with just jQuery, jQueryUI, or two different version of Bootstrap. My comic uses jQuery and Bootstrap 3, so my examples are based off of that package.
Here is the HTML for a simple demo.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Editable Text</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/bootstrap-editable.css">
<style>
p {
border: 1px solid #CCCCCC;
margin: 10px;
padding: 5px;
width: 250px;
}
.editable-clear-x {
background: url('clear.png');
}
.editableform-loading {
background: url('loading.gif');
}
</style>
</head>
<body>
<p id="e12" class="canedit">This is some text you can edit.</p>
<p id="e13">This is some text you can't edit.</p>
</body>
<script src="jquery-1.11.2.min.js"></script>
<script src="bootstrap.min.js"></script>
<script src="bootstrap-editable.min.js"></script>
</html>
For stylesheets you'll need Bootstrap (
bootstrap.min.css in my example) and X-editable (
bootstrap-editable.css). You'll also need the jQuery JavaScript, Bootstrap's JavaScript, and then finally X-editable's JavaScript.
By default, X-editable tries to find some images in the "
../img/" path. That will only work if you keep your static images in a directory called "img" which is sibling to the "css" directory where you keep the X-editable CSS file. That wasn't an option for me, so I get around it by just copying the "clear.png" and "loading.gif" images to my preferred location and then overriding the CSS for those elements with my own background URL path.
In my example I have two sections of text, one that should be editable and other other not. To make the text editable inline, just execute the following JavaScript:
$(".canedit").editable({
type: "text",
mode: "inline",
escape: false
});
It finds all the elements with the
canedit class and makes them editable. The options object passed indicates that the type of editor will be a simple text box, using inline mode (a popup box is the alternative), and the contents will not be escaped. "Escaping" in this context means getting the value via jQuery's
.text() vs.
.html(). I choose not to escape so that I can put links and other HTML markup in the speech balloons. A full list of options
can be found in the documentation. After executing that code, the editable text will be indicated by a blue dotted underline. Clicking on the text will replace it with a text box and some buttons. Type something new and hit enter or click the check and it turns back into plain text with the new value.
X-editable has a whole set of features that enable it to automatically POST updates to the text to a server as soon as the changes are made, but my comic editor uses a different model. I arrange all my images and text and then click on a "Save" button that reads through the DOM and builds a complete JSON representation of the comic. For speech balloons, I just do something like this:
var balloons = [];
$('p.editable').each(function(idx, elem) {
var b = {
// ...
// other balloon data
// ...
text: $(elem).html()
};
balloons.push(b);
});
That finds all the editable text (in my comic editor, all <p> tags inside cells are editable), grabs the contents, creates a "balloon" object out of it, and adds it to the list of balloons.
If you're wondering what "other balloon data" I need to collect, consider that editable <p> tags can also be made draggable and resizable with jQuery UI (
see my previous post where I did it with images) which enables me to easily position, size, and edit the frog speech balloons. So in addition to the actual contents of the balloons, I also read their
top,
left, and
width CSS attributes just like I do for images.
There are lots more ways you can use X-editable in your applications. It supports built-in validation functions so you can check the input before accepting it. For example, execute this JavaScript on the demo page:
$(".canedit").editable({
type: "text",
mode: "inline",
escape: false,
validate: function(value) {
if (value.match(/cat/gi)) {
return "no talking about cats!";
}
}
});
Now you won't be able to enter "cat" in the text area. If you try it, you'll get a warning message and the edit won't complete. Your only options are to remove the cat or cancel the edit.
I mentioned above how X-editable has the capability to POST updates back to a server as soon as they are made. I don't use that capability at all, but it also lets you replace that behavior with your own custom handler. Instead of sending the data to a server via Ajax, you could
send it via Websockets or update a client-side data model. To take advantage of this ability, specify a function instead of a string as the
url parameter. The only catch is that the function has to return a Deferred object so the rest of the X-editable code can process it the same as a jQuery Ajax call.
But you should like using Deferreds anyway, right? Check out this code:
$(".canedit").editable({
type: "text",
mode: "inline",
escape: false,
validate: function(value) {
if (value.match(/cat/i)) {
return "no talking about cats!";
}
},
url: function(params) {
var d = new $.Deferred;
console.log("id of element changed: " + params.name);
console.log("new value: " + params.value);
setTimeout(function() {
// to simulate some asynchronous processing
d.resolve();
}, 500);
return d.promise();
}
});
In your custom function, you can access the new value of the element in
params.value. If the element that was edited had an
id attribute, as my example does, it will be accessible in
params.name. If you want to be able to tell your fields apart, you should probably make sure they all have ids! Finally, my example just sets a 500 millisecond timeout before resolving the Deferred, but if the action you need to take has no asynchronous component you could call
d.resolve() immediately.
With all of these great tools, creating WYSIWYG editors for web content, even complex content like a comic, is easier than ever before. Now if only writing jokes for the comics was somehow made easier...