Wednesday, June 28, 2006

Trying out Dojo AJAX

As I have alluded to sometime back, I wanted to try out Dojo. Boy, was Dojo hard. I wanted to have my users to be able to edit fragments of a bigger page inline. The lack of documentation was almost debilitating. I almost gave up and went for alternative richtext editor like FCKeditor or TinyMCE. You really need to be patient enough to sift through the javascript for the demo and Dojo widgets. I must admit, Dojo javascript code is highly structured and readable. Also, don't forget to subscribe to the very active dojo-interest mailing list.

I like Dojo richtext editor (dojo.widget.Editor2) mainly because of its "grow-as-you-write" feature. I believe the scrollbar in the browser window is enough for the user. In addition, it doesn't limit what HTML element we can make editable. Furthermore, while the javascript API to the editor has not completely matured yet as compared to, say, TinyMCE API, it was not too bad. Dojo seems to allow for more flexibility on what we can do to its widgets through javascript.

For what I am trying to do, I have a button that is used to turn an innocuous HTML element into becoming editable. Example HTML fragment is given below:

<div style="float:left">
<button type="button" item="h1"
onclick="doEdit(this)">edit</button>
</div>
<div id="h1"><h1>Inlinely editable, yum!</h1></div>

<div style="float:left">
<button type="button" item="p1"
onclick="doEdit(this)">edit</button>
</div>
<div id="p1"><p>This is also editable</p></div>

<div style="float:left">
<button type="button" item="p2"
onclick="doEdit(this)">edit</button>
</div>
<div id="p2"><p>And this too.</p></div>

<div style="float:left">
<button type="button" item="p3"
onclick="doEdit(this)">edit</button>
</div>
<div id="p3"><p>And this too, yeah.</p></div>

Each button has a custom item attribute to point to the element to be edited (the <div> next to it). When the button is clicked the doEdit is called, its item attribute is retrieved and the element it refers to is converted into a richtext editor. Note that if the editor has already been instantiated before, it is kept in variable last. But first, we check whether we need to save the content from the last editor:

dojo.require("dojo.widget.Editor2");

var last;
function doEdit(button) {
var save, lastbutton;
if (last) {
if (changed(last)) save = confirm("Save?");
last.close(save);
if (save) {
// so call server to update. Use last.getEditorContent().
}
lastbutton = last.button;
lastbutton.innerHTML = "edit";
last.destroy();
last = null;
}

Since the Dojo richtext editor does not have a method to check whether its content has changed, a separate changed function has been written for that. If the content has changed, the user is asked whether or not she wants to save it. Ideally, the server will be called using some AJAX RPC to save it. Finally, the editor is transformed back to normal HTML element by closing and destroying it.

Now if the button is the same button used to create the editor, we are done. If not, we need to create the editor:

if (lastbutton != button) {
var id = button.getAttribute("item");
var t = document.getElementById(id);
last = dojo.widget.createWidget(
"Editor2",
{
shareToolbar: false,
toolbarAlwaysVisible: false,
focusOnLoad: true,
closeOnSave: true
}, t);
last.button = button;
button.innerHTML = "close";
dojo.event.connect(last, "onLoad", onEdLoad);
}
}

Note that a custom property (button) has been added to the editor object to remember which button used to create it. And finally the onLoad event for the editor is connected to the onEdLoad. This is important if we have some initialization we need to do on the editor once it has fully been created. The editor seems to be created asynchronously.

That is it. Below is the complete HTML file. Put it in your Dojo installation directory (where dojo.js) is:

<html>
<head>
<title>Dojo is cool</title>
<script type="text/javascript" src="dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.Editor2");

var last;
function doEdit(button) {
var save, lastbutton;
if (last) {
if (changed(last)) save = confirm("Save?");
last.close(save);
if (save) {
// so call server to update. Use
// last.getEditorContent().
}
lastbutton = last.button;
lastbutton.innerHTML = "edit";
last.destroy();
last = null;
}
if (lastbutton != button) {
var id = button.getAttribute("item");
var t = document.getElementById(id);
last = dojo.widget.createWidget(
"Editor2",
{
shareToolbar: false,
toolbarAlwaysVisible: false,
focusOnLoad: true,
closeOnSave: true
}, t);
last.button = button;
button.innerHTML = "close";
dojo.event.connect(last, "onLoad", onEdLoad);
}
}

function onEdLoad() {
// now editor is completely loaded. Do whatever
// we want when editor is loaded here.
}

function changed(ed) {
return ed.editNode.innerHTML
!= last.savedContent.innerHTML;
}

</script>
</head>
<body>

<div style="float:left">
<button type="button" item="h1"
onclick="doEdit(this)">edit</button>
</div>
<div id="h1">
<h1>Inlinely editable, yum!</h1>
</div>

<div style="float:left">
<button type="button" item="p1"
onclick="doEdit(this)">edit</button>
</div>
<div id="p1">
<p>This is also editable</p>
</div>

<div style="float:left">
<button type="button" item="p2"
onclick="doEdit(this)">edit</button>
</div>
<div id="p2"><p>And this too.</p></div>

<div style="float:left">
<button type="button" item="p3"
onclick="doEdit(this)">edit</button>
</div>
<div id="p3"><p>And this too, yeah.</p></div>

</body>
</html>

3 comments:

Anonymous said...

Very nice article put in simple words
It will be more useful for dojo-starters

Anonymous said...

Lovely! Any idea why a span is left in Firefox and not IE?

Another copy of the edited text is duplicated below for some reason.

myusri said...

Glad to hear that someone actually tested out my code even after close to 2 years since I posted it. Are you using the current version of Dojo? Then it is remarkable that the example I have still working (somewhat). I don't have duplicated text problem with the version of Dojo I got two years. When I become motivated enough, I will try the example out with the current version of Dojo.

Anyway, you are right. A span element can be observed after the editor is closed and turned into normal HTML. Is this a problem? I guess it could be if we use the technique across browsers. I can't test with IE at the moment (I am using Linux). I have no idea why this happens. Perhaps someone more well-versed in Dojo intricacies may be able to explain?