Editable Content Using MooTools 1.2, PHP, and MySQL

Written by David Walsh on Thursday, June 26, 2008


This article may feature code that is no longer best practice in MooTools.
Click here to learn what has changed to make your code framework-compatible.

Everybody and their aerobics instructor wants to be able to edit their own website these days. And why wouldn’t they? I mean, they have a $500 budget, no HTML/CSS experience, and extraordinary expectations. Enough ranting though. Having a website that allows for editable content blocks is the dream of many customers. I’ve taken a few hours to develop a system for in-page, editable content blocks.

The XHTML

<h1 class="editable" rel="32" title="Article Title">Editable Title</h1>
<p class="editable textarea" title="Content Paragraph" rel="33">
	This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.
	This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.
	This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.
	This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.
	This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.
	This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.  This is an editable paragraph.
</p>

Note that any editable area is given the editable class. The “rel” attribute reflects the ID of the record that will be updated in the database. Also, if we want the form element to be a textarea instead of an input, we’ll give the editable element the “textarea” class.

The CSS

.editable:hover	{ background:#eee; }
.textarea textarea{ height:200px; padding:3px; }
.editable-empty	{ background:#fffea1; padding:20px; border:1px dashed #fc0; }
.box		{ border:1px solid #ccc; padding:5px; display:block; width:95%; }

The “box” class gets applied to both inputs and textareas. The rest is for gloss factor.

The MooTools Javascript

//once the dom is ready
window.addEvent('domready', function() {
	//find the editable areas
	$$('.editable').each(function(el) {
		//add double-click and blur events
		el.addEvent('dblclick',function() {
			//store "before" message
			var before = el.get('html').trim();
			//erase current
			el.set('html','');
			//replace current text/content with input or textarea element
			if(el.hasClass('textarea'))
			{
				var input = new Element('textarea', { 'class':'box', 'text':before });
			}
			else
			{
				var input = new Element('input', { 'class':'box', 'value':before });
				//blur input when they press "Enter"
				input.addEvent('keydown', function(e) { if(e.key == 'enter') { this.fireEvent('blur'); } });
			}
			input.inject(el).select();
			//add blur event to input
			input.addEvent('blur', function() {
				//get value, place it in original element
				val = input.get('value').trim();
				el.set('text',val).addClass(val != '' ? '' : 'editable-empty');
				
				//save respective record
				var url = 'mootools-editable-content.php?id=' + el.get('rel') + '&content=' + el.get('text');
				var request = new Request({
					url:url,
					method:'post',
					onRequest: function() {
						alert('making ajax call :: ' + url);
					}
				}).send();
			});
		});
	});
});

Once the DOM is ready, we find all of the elements with the editable class. We attach a “doubleclick” event to each editable item that inserts an input element (or textarea, depending on whether the original elements has the “textarea” class). Once we inject the input/textarea element, we attach a “blur” event to the form element which triggers an Ajax request that will save the content.

The PHP & MySQL

if($_SESSION['is_admin'] && is_numeric($_POST['id']) && isset($_POST['content']))
{
	$query = "UPDATE content_table SET content = '".mysql_real_escape_string(stripslashes($_POST['content']))."' WHERE content_id = ".(int)$_POST['id'];
	$result = mysql_query($query,$db_link);
}

When the PHP file receives the $_POST request, it validates credentials and updates the database with the new content. Simple! Click here to see it in action!

A few notes about the code. First, this system isn’t meant to be for a big website. This system requires that editable content placed inside of a database with a unique ID for each area. Ideally, you’d use this one a small, simple site. Second, the textarea does not have a WYSIWYG editor attached to it so the editor (person) would need some knowledge of HTML if they wanted to add links and such. If you do want them to have that capability, download and configure FCKEditor, TinyMCE, or WMD.

Thoughts? Is this rubbish? Useful? Hit me!


Follow via RSS Epic Discussion

Commenter Avatar June 26 / #

Great stuff. I’ve been looking for something like this for just such a small website. BTW, I’ve started using MooTools partially on your recommendations and recent articles.

Thanks!

Commenter Avatar June 26 / #

Great work! That looks like a surprisingly reusable solution. I suppose short writing your own DTD, rel is as good a place as any to store the content ID.
I see you have the security check in place on the server side, but just for clarity, I assume you would also perform the same check before rendering the script?

David Walsh June 26 / #
david says:

@Jeremy: Absolutely. I wouldn’t put the JS and CSS there unless they were confirmed admins. Thanks for pointing that out.

Commenter Avatar June 26 / #

I like the idea of throwing in a markup editor (like WMD editor like David mentioned and is using right here in his comments). This is how you know it’s the future on the web. Like flying cars and woman presidents in the movies.

Commenter Avatar June 26 / #
JP says:

Speaking of WMD… I like the live preview for the comments!

Commenter Avatar June 26 / #
Marie says:

Your opening paragraph sounds shockingly familiar to me – I recently agreed to make a website for my dad for $500, and he has “extraordinary expectations” (or even more frustrating, a change of heart regarding the design every couple of days), while not knowing a lick about even the most basic web development.

Anyway, this method sounds pretty good – probably too far along in my current project to implement it, but I will refer back to it for the next one. Thanks!

Commenter Avatar June 26 / #
Matthias says:

Awesome work! I just had exactly this idea in my mind two days ago when a friend asked for the difficulty of creating editable areas on a simple website. You worked it out very nice!
I’ve recently checked out “CushyCMS” which also just needs a predefined “editable” area and lets the user change content with ease. So your example behaves perfect as a “readymade live-editing package” that can be included in web projects easily.
Thanks again for your fresh ideas.

Commenter Avatar June 26 / #
deef says:

I’d like to add buttons at the bottom of the document, giving you the option to add a new heading, paragraph (css-block) with the same editable property.
Again, nice job!

Commenter Avatar June 26 / #

Neat stuff, the thing I dont understand is why this won’t work for a big site? Let’s say you enable this feature for comments on a article or a blog such as this. Why would giving the user the ability to edit his or her comment not work? (besides making some cache mechanism have a dirty cache)

David Walsh June 26 / #
david says:

@Jesus: I said that because I can’t image a big website having different records for specific paragraphs. That said, your comment idea would work extremely well! Good though!

Commenter Avatar June 26 / #
Valerie says:

This is very similar to cushycms.com, which is just a little more developed and with a complete presentation.

I like the idea in both cases, though for it to be the right answer for a client, it would have to take a specific situation.

Commenter Avatar June 27 / #
Catar4x says:

Nice work David !

Commenter Avatar June 27 / #
Daniele Nabissi says:

Hi David, nice work.

But I think this kind of data editing is quite dangerous for big paragraphs. I’m talking about Undo which is not possible in this situation.

David Walsh June 27 / #
david says:

@Daniele: You’d get the browser’s internal “Undo” functionality, but I see your point.

Commenter Avatar June 27 / #

Hello David… nice work… i changed it a bit to have the possibility to save via keypress of “enter” and just do nothing if you press “esc”.

window.addEvent(‘domready’, function() {
//find the editable areas
$$(‘.editable’).each(function(el) {
//add double-click and blur events
el.addEvent(‘dblclick’,function() {
//store “before” message
var before = el.get(‘html’).trim();
//erase current
el.set(‘html’,”);
//replace current text/content with input or textarea element
if(el.hasClass(‘textarea’))
{
var input = new Element(‘textarea’, { ‘class’:'box’, ‘text’:before });
}
else
{
var input = new Element(‘input’, { ‘class’:'box’, ‘value’:before });
//blur input when they press “Enter”
input.addEvent(‘keydown’, function(e) {
if(e.key == ‘esc’) { this.fireEvent(‘escape’); }
if(e.key == ‘enter’) { this.fireEvent(’save’); }

});
}
input.inject(el).select();
//add blur event to input
input.addEvent('escape', function() {
val = input.get('value').trim();
el.set('text',val).addClass(val != '' ? '' : 'editable-empty');
});

input.addEvent('save', function() {
//get value, place it in original element
val = input.get('value').trim();
el.set('text',val).addClass(val != '' ? '' : 'editable-empty');

//save respective record
var url = 'edit_content.php?id=' + el.get('rel') + '&' + el.get('title') + '=' + el.get('text');
var request = new Request({
url:url,
method:'post',
onRequest: function() {
alert('making ajax call :: ' + url);
}
}).send();
});
});
});

});

Commenter Avatar June 27 / #
Tom says:

Hey I think this is fantastic for simple sites. I don’t know a whole lot about programming on the web but I have a question about how I could have this editing option only available if you are logged in. I know this will only write the changes if you are logged in, but is it possible to hide the ajax altogether if your not logged it?

Thanks

David Walsh June 27 / #
david says:

@Tom: Yes — I’d recommend it. A quick PHP statement would make that happen.

David Walsh June 27 / #
david says:

@captFuture: Awesome!

Commenter Avatar June 28 / #

Very nice. I’ve been doing something similar for a while but without MooTools. It’s a nice addition.

Commenter Avatar June 29 / #
Jérôme says:

Nice script! I went a bit further and created a configurable MooTools class with the same purpose.

Editable Content MooTools class

Here is my related blog entry: Seiten-Inhalte inline bearbeiten (german)

Thank you for the inspiration and many greetings from Germany

Jérôme

Commenter Avatar July 01 / #
Anthony says:

I would love to see this taken one step further with a user authentication built in!

Nice work!

Commenter Avatar July 03 / #
Daniele Nabissi says:

David… yes, I know the “Undo” button, or CTRL+Z… but I can use it only when I’m editing and not after I’ve saved the changes. In fact I think this is dangerous ONLY for long textarea and very useful for simple input text. It would be nice to make a history, maybe using sessions.

David Walsh July 03 / #
david says:

@Daniel: I agree with the dangerousness of not having an undo. That’s where I can see a “backup_content” column coming in handy. Every time there’s a change, you’d “back up” the original content.

Commenter Avatar July 03 / #
Jérôme says:

@Daniel: If you don’t want to have the backups stored in the database, you can also store the backup data in a JavaScript object. The undo operation will then only be available as long as you don’t leave the page, but that’s better than nothing. Did you have a look at my adaption? If you are interested, I can extend my class so that it allows temporary backups.

Cheers
- Jérôme

Commenter Avatar July 04 / #
Daniele Nabissi says:

Jerome, great adaptation.
This is the solution. When I edit a textarea then update the page with a buttom I save the data and I don’t need to make backups.
I thought it was dangerous because I could accidentaly click out of the editable area… Asking for confirmation is the solution.

Commenter Avatar July 08 / #

Great david;

but the updates the database with the new content didn’t worked !!!

David Walsh July 08 / #
david says:

@Jack: What didn’t work? What error are you receiving?

Commenter Avatar July 08 / #

@David:

the new content didn’t update in the database table

Commenter Avatar July 18 / #
lowkzilla says:

Like everybody already said : Nice work so far! ;)
But i have a (mootools noobish) question.

When you make the request to the server, you use POST method, wich is a good thing, but why do you send your datas via GET in the url?

Commenter Avatar July 21 / #
JRameau says:

Cool stuff.

My Problem, for example if my Title is a Link:
-How do I have the double-click take over to display the input form
+ still have ability if I single click the Title link it acts normal and goes to the link.

Currently both events will fire when I Double Click.

David Walsh July 22 / #
david says:

@JRameau: I’m not sure this is the best solution for a link.

Commenter Avatar July 23 / #
JRameau says:

there is a glitch in the code above which is not present in Jérôme’s version.

When you double click repetitively it replaces the text with the new Element’s html code.

Commenter Avatar July 27 / #
makism says:

marvelous post :-)

i noticed that glitch too.

here is a simple workaround, not sure if it’s right:

input.addEvent(‘dblclick’, function(e) {
e.stop();
return;
});

input.addEvent(‘click’, function (e) {
e.stop();
return;
});

put this code, after the instantiation of the Element…

Commenter Avatar August 05 / #
Alex G. says:

Anyone know how to implement the case where you’re left with an empty field into Jérôme’s class? ie:

//get value, place it in original element
val = input.get(‘value’).trim();
el.set(‘text’,val).addClass(val != ” ? ” : ‘editable-empty’);

Thanks for any suggestions!

Commenter Avatar August 05 / #
Jérôme says:

@Alex G.: I’ve already updated the class and the CSS – does it meet your needs?

Commenter Avatar August 05 / #
Alex G. says:

Jérôme, you rock. This works nicely.
I owe you a beer! Thanks!!!

Commenter Avatar August 06 / #
Gianko says:

i also don’t get this to work…

whit the direfox data tamper i don’t see any POST data, only the GET data in the url.

and with Jerome’s i think is passed in a JSON, but only the content not the id…

:(

maybe is my mistake somewhere…

Commenter Avatar August 06 / #
Jérôme says:

@Gianko: No error of yours, but on my side. I just updated my class – the id was indeed not passed to the backend script.

As for David’s script – perhaps, this slight change could make the difference for you:

var postData = {
‘id’: el.get(‘rel’),
‘content: el.get(‘text’)
};

//save respective record
var url = ‘mootools-editable-content.php’;
var request = new Request({
url:url,
method:’post’,
data: postData,
onRequest: function() {
alert(‘making ajax call :: ‘ + url);
}
}).send();

This gathers the post data into one hash (postData) and adds it as a param (data: postData) into the request. The url variable has also changed.

Commenter Avatar August 08 / #
Fabrice Terrasson says:

Thank you David for sharing that, it’s crystal clear. I’m definetly fan of your blog.

Commenter Avatar August 19 / #
Nisse says:

Good work! Exactly what I was looking for :)

Commenter Avatar September 27 / #
Enrico says:

Great! THANK YOU David! I have found it very useful for my needs
Forty-two thousand thankses ;)

. . .

@makism: your solution works well ;)

Otherwise for the same problem I have resolved in this way:

if(el.hasClass(‘editable’)){
el.removeClass(‘editable’);

after the line:

el.addEvent(‘dblclick’,function() {

then in the ajax call:

onComplete: function() {
el.addClass(‘editable’);
},

onFailure: function(instance) {
el.addClass(‘editable’);
}

and finally closing the parenthesis of first if

Commenter Avatar September 29 / #
Sandbird says:

Awesome script..I’ve had something similar with prototype library..A bit fancier with Save and Cancel but on keypress…
Has anyone managed to do this with that function ? Instead of clicking outside the area to have a Save and Cancel button appear ondblclick ?
Thanks

Commenter Avatar October 15 / #

I think is the best site!
Very interesting and useful informations.
Excellent work!
Really good tutorial include so many helpful informations!
Cheers

Commenter Avatar November 05 / #
MoName says:

Hi there, how may i change the code in order to don’t do dclick action when textarea is already opened?

Thank you

Commenter Avatar November 10 / #
dude says:

I was having problems getting it to update as well, I solved it however by changing the element’s attribute to “id”, rather than “rel” in the html. I can only assume that David has a column in his database called “rel” which holds the given element’s id#. As I understand it, the post['id'] and the sql column “id” and the ajax request for “id” must all be of the same name in order to save to the database.

I also added Jerome’s fix posted above (8-06-08 4:33 am) . All is well now.

I have a bit to contribute as well. I’m using this script for user-submitted pic captions (think MySpace). For each user pic there’s an editable caption. I didn’t want to insert a default value of “add caption” in my sql table before the user had a chance to add a value, so I set the default value to null and in my php script that calls up all the user’s pics and captions for each, I did this:

echo empty($pics['caption'])?’<span class=”flicker”>add caption</span>’:$pics['caption'];

This gives a default value of “add caption” to a given row’s empty caption column. At first I used a link here instead of a span, but I noticed in IE6 & IE7 that the text value wasn’t automatically being highlighted when inserted in the input… not sure why that’s the case, but it is. Also, in the JavaScript doubleclick function I replaced ‘html’ with ‘text’. This prevents the html of the default value from being inserted in the input when the event is fired.

Thanks David for this sweet feature, I’ve got your blog bookmarked and I’ll be checking in on you periodically. Also, thanks to Jerome for the fix above.

Commenter Avatar November 11 / #
Cohen says:

I actually developped the same approach some time ago, but with one big difference the .editable elements are passed to an editor factory class which in turn creates the expected editor… for instance
– for a list this would be a textarea per item and an extra new item button…
– for a multiline class a textarea
– for a singleline class an input type text element
– and for a wysiwyg a rte (rich text editor)
every editor is also responsible for serializing its content (so it can be send/saved to a backend through ajax)

The big advantage of a factory class that generates the editors is the extensibility….
(just my two cents though)

Commenter Avatar November 19 / #
Ryan says:

@dude:

To keep your html valid you can’t have numeric id’s on elements like id=”12″, it has to start with a letter. Also, a single page could have data from a few different db tables all with a row id of 12. So you can’t (shouldn’t) have multiple elements on the same page with the same id.

And so, the html attribute ‘rel’ is the perfect fit to store the row’s id. When you want to update, you just have to pass that in to the ajax request and then read it correctly in the PHP.

Maybe I misunderstood what you were saying, however, and you already know all this–if so, I apologize from the depth’s of my geeky heart.

Commenter Avatar November 19 / #
rpflo says:

Why not put the data in the request options instead of the url string?

Ya know, like – data : {‘id’ : el.get(‘rel’), ‘content’ : el.get(‘text’) }

Commenter Avatar November 20 / #
rapt0rb0y says:

Enrico says:

Great! THANK YOU David! I have found
it very useful for my needs Forty-two
thousand thankses ;)

. . .

@makism: your solution works well ;)

Otherwise for the same problem I have
resolved in this way:

if(el.hasClass(’editable’)){
el.removeClass(’editable’);

after the line:

el.addEvent(’dblclick’,function() {

then in the ajax call:

onComplete: function() {
el.addClass(’editable’);
},

onFailure: function(instance) {
el.addClass(’editable’);
}

and finally closing the parenthesis of
first if

What does he mean by saying “in the ajax call:”

Could there be a little more description on this?

Commenter Avatar December 28 / #

Wow a fantastic site, I loved it when I landed on it. I could never do anything like this, maybe I should open a competition for someone to redesin mine. :-)

Commenter Avatar January 21 / #
dude says:

@ryan: No apologies needed. I used the ID attribute and made it valid. I added a letter in the while loop before the item’s ID then stripped it out with PHP before updating the DB.

Commenter Avatar May 31 / #

Hi all.
Great works.
I’m working hard on my new project, and i can do good things with your help ^^.
With your code, i have implemented an inline edit system with nicEdit.

Here is the code :

//once the dom is ready

window.addEvent(‘domready’, function() {

//find the editable areas
$$(‘.editable’).each(function(el) {
//add double-click and blur events
el.addEvent(‘dblclick’,function() {
el.removeClass(‘editable’);
//store “before” message
var before = el.get(‘html’).trim();
//erase current
el.set(‘html’,”);

var input = new Element(‘textarea’, { ‘class’:'box’, ‘text’:before });
input.inject(el).select();
/* create a nicEdit instance in the input element ( must have nicEdit.js loaded befor ) */
var editor = new nicEditor().panelInstance(input);

var input2 = new Element(‘button’, { ‘class’:'button’, ‘text’:'Save’ });
input2.inject(el);
input2.addEvent(‘click’, function() {
//get value, place it in original element
e = nicEditors.findEditor(input);
if(e) {
/* get the content of the editor */
var content = e.getContent();
//e.removePanel();
el.set(‘html’,content).addClass(content != ” ? ” : ‘editable-empty’);

var req = new Request({
url:document.location.href,
onSuccess : function(response) {
var roar = new Roar();
roar.alert(‘my personal response’,response);
el.addClass(’editable’);
},
onFailure: function() {
el.addClass(’editable’);
}
}).post({divContent:content,id:el.get(‘rel’)});
}
});
});
});

});

And it works fine for me.
Great thanks. ( forgive my poor english ^^’ )

Commenter Avatar September 26 / #
vrS says:

Excellent!
I just add this before the Ajax Call
[code]
if(el.get('text')!=before)
[/code]

Commenter Avatar September 26 / #
vrS says:

The complete code, taking the previous enrico’s solution is the following

if(el.get(‘text’)!=before){
…..AJAX CALL
}
else{
el.addClass(‘editable’)
}

my english sucks, i know :(

Commenter Avatar October 28 / #
Hadrien says:

Hello

This script’s very good and it is exactely what I was searching, thanks for this blog David, it has already helped me several time :)
But I have a problem with this script, when you dblclick twice it “edit” the input box and write :

If anyone know this issue, please tell me :)

Commenter Avatar October 28 / #
Hadrien says:

@Hadrien:

Found !
I don’t know how but it works well now :) I have also put a check if leaving with empty value

//once the dom is ready
window.addEvent(‘domready’, function() {
//find the editable

$$(‘.editable’).each(function(el) {

//add double-click and blur events
el.addEvent(‘dblclick’,function() {
el.removeClass(‘editable’);

//store “before” message
var before = el.get(‘text’).trim();
var before2 = el.get(‘id’).trim();
//erase current
el.set(‘html’,”);
//replace current text/content with input or textarea element
if(el.hasClass(‘textarea’))
{
var input = new Element(‘textarea’, { ‘class’:'box’, ‘text’:before });
}
else
{
var input = new Element(‘input’, { ‘class’:'box’, ‘value’:before });
//blur input when they press “Enter”
input.addEvent(‘keydown’, function(e) { if(e.key == ‘enter’) { this.fireEvent(‘blur’); } });
}
input.inject(el).select();
//add blur event to input
input.addEvent(‘blur’, function() {
//get value, place it in original element
val = input.get(‘value’).trim();

var iWhere = val.indexOf(‘ -1) {
el.set(‘text’,before);
} else {
if(val==”) {
el.set(‘text’,before2);
} else {
el.set(‘text’,val);
}
}

//save respective record
var url = ‘ajax/saveCompetence.php?id=’ + el.get(‘rel’) + ‘&content=’ + el.get(‘text’);
var request = new Request({
url:url,
method:’post’
}).send();
});
});
});
});

Be Heard!

I want to hear what you have to say! Share your comments and questions below.

Name*:
Email*:
Website:  


© David Walsh 2007-2010. Contact David Walsh. Powered by the remarkable MooTools javascript framework.