Editable Content Using MooTools 1.2, PHP, and MySQL

By  on  

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!

Recent Features

  • By
    7 Essential JavaScript Functions

    I remember the early days of JavaScript where you needed a simple function for just about everything because the browser vendors implemented features differently, and not just edge features, basic features, like addEventListener and attachEvent.  Times have changed but there are still a few functions each developer should...

  • By
    Responsive and Infinitely Scalable JS Animations

    Back in late 2012 it was not easy to find open source projects using requestAnimationFrame() - this is the hook that allows Javascript code to synchronize with a web browser's native paint loop. Animations using this method can run at 60 fps and deliver fantastic...

Incredible Demos

Discussion

  1. 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!

  2. 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?

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

  4. 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.

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

  6. Marie

    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!

  7. 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.

  8. 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!

  9. 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)

  10. @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!

  11. 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.

  12. Nice work David !

  13. Daniele Nabissi

    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.

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

  15. 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();
            });
        });
    });
    });
    
  16. @Tom: Yes — I’d recommend it. A quick PHP statement would make that happen.

  17. @captFuture: Awesome!

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

  19. 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

  20. Anthony

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

    Nice work!

  21. Daniele Nabissi

    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.

  22. @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.

  23. @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

  24. Daniele Nabissi

    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.

  25. Great david;

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

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

  27. @David:

    the new content didn’t update in the database table

  28. lowkzilla

    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?

  29. 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.

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

  31. 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.

  32. makism

    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…

  33. Alex G.

    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!

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

  35. Alex G.

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

  36. 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…

  37. @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.

  38. Fabrice Terrasson

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

  39. Nisse

    Good work! Exactly what I was looking for :)

  40. Enrico

    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

  41. Sandbird

    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

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

  43. MoName

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

    Thank you

  44. dude

    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.

  45. 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)

  46. Ryan

    @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.

  47. rpflo

    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’) }

  48. 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. :-)

  49. dude

    @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.

  50. 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 ^^’ )

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

  52. 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 :(

  53. Hadrien

    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 :)

  54. Hadrien

    @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();
    			});
    		});
    	});
    });
    
  55. jon

    Is there anyway to add passowrd protection? I want to use this on a webpage for a gig list but boviously only want the club owner to be able to change it.

    Regards

  56. Doug McDonald

    Hi there, am I missing something regarding ajax calls to a php page? using the above example I’ve removed all the conditional bits regarding $_post requirements but my pages don’t seem to reflect any alteration when the ajax event is fired.

    it’s clearly getting as far as the js alert, but using a slightly dumbed down php/mysql example as per this example i don’t seem to even get into the code block when either including the php in the page itself, or firing the Reqest() to a different page.

    Any chance you could offer a tiny touch more explanation on the interaction between mootools and php/mysql? or am I just being a lemon! :P

  57. This example works out great, I edited it a bit to implement into my CMS.

    I wrote a custom PHP script that passes on more variables seperated by a – to declare to the php what it is supposed to update.

    That way this code can very easily be implemented into any application.

    I also disabled the Alert notify since I prefer not to get that window popping up everytime I edit my site.

  58. Jade

    Hi this is my code

    $home_query = "SELECT page_ID, page_name, page_content FROM tbl_pages WHERE page_name = 'home'";
    
    if(isset($_SESSION['login_name']) && is_numeric($_POST['id']) && isset($_POST['content']))
    {
    	$query = "UPDATE tbl_pages SET page_content = '".mysql_real_escape_string(stripslashes($_POST['content']))."' WHERE page_ID = ".(int)$_POST['id'];
    	$result = mysql_query($query,$db_link);
    }
     	
    $home_result = mysql_query ($home_query);
    $row = mysql_fetch_array ($home_result, MYSQL_ASSOC);
    	if(isset($_SESSION['login_name']))
    {
    	echo '	
            		
    				'."{$row['page_content']}".'
    			';
    }else{
    	echo '	
            		'."{$row['page_content']}".'
    			';
    }?>
    

    I cant get my database to update any suggestions?

    Cheers,

  59. pSouper

    Hi David,
    Thanks for the hard work on this, would/could it be able to tackle drop-down lists too?

    cheers,

  60. Hello !

    I’ve found a bug in your script. If you double-click on a text and you double-click again, the input value is < input type="text"….

    An idea for block this action ?

  61. tarik

    simply perfect!

  62. afro360

    Would be nice to see nicedit inline edit used.

  63. Raja

    This was easy to implement. Thanks for the post.

  64. Santana

    What if I want to be able to click a button that I title edit to make my editable fields active? Do I need to specify something different than the dblclick event? Or do I need to set the dblclick event attached to my button?

  65. macmadness86

    I am a self-taught programmer, so this may seem like an obvious question for you pros out there: Where do I put the PHP & MySQL code? Do I put it in a file called mootools-editable-content.php? Is that what this part means: var url = ‘mootools-editable-content.php?id=’ …

    I’ve got the AJAX call working nicely, I’ve got the rel=1, rel=2, rel=3 for each text area corresponding to MySQL ID 1, ID 2, ID 3 respectively.

    I hope somebody can help me out.

  66. macmadness86

    Duh I figured it out. I had to change all the $_POST variables to $_GET in the MySQL & PHP part of the code and it worked fine. I also change the url to the name of my url in the JavaScript.

    Maybe this information help some newbies.

    Great post Mr. Walsh. Really helping my research project move faster.

  67. Julien

    Hi,

    thanks a lot for the code.

    I have one issue. I get 2 consecutive popups on chrome and safari (firefox works fine), with my code (just adaptation of yours, no logic change) as well as the page title in your demo. On my code, debugging with chrome and eclipse, the ajax call is made once, the event is added once, i still get 2 popups.

    Any ideas?
    Thanks again,
    Julien

  68. Julien

    One detail: I get one popup when editing the text then clicking outside the input box, I get 2 when editing then pressing enter. Laptop mac book pro Mac os x lion, Chrome 31.0.
    Thanks
    Julien

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!