Shaun Mccran

My digital playground

10
F
E
B
2010

Dynamically editing web content inline, using JavaScript and AJAX

Most of us are familiar with the standard method of displaying data in a tabulated fashion, selecting a record, and populating the form that follows. What about editing the content directly into a template that mirrors the actual live version of a page?

This article examines how to edit web content directly inline, and commit it back to a server using an AJAX post request.

The main catalyst for this is that clients that use a content management system do not often have a clear image of how their content will look online. The traditional form layout for entering text does not lend itself well to representing the actual content in the format it is display in.

The aim here is to build a flexible system that allows for inline content editing, and saves it gracefully to a server based database.

I will start by saying thank you to Peter-Paul Koch. His article here (http://www.quirksmode.org/dom/cms.html) on making content editable was invaluable, and a lot of this is based on his theory.

We start by setting a value "editing" to false. This is the default for the page, as the user isn't editing anything when the page loads.

view plain print about
1var editing = false;
2
3if (document.getElementById && document.createElement) {
4    var butt = document.createElement('BUTTON');
5    var buttext = document.createTextNode('Save');
6    butt.appendChild(buttext);
7    butt.onclick = saveEdit;
8}
9
10function catchIt(e) {
11    if (editing) return;
12    if (!document.getElementById || !document.createElement) return;
13    if (!e) var obj = window.event.srcElement;
14    else var obj = e.target;
15    while (obj.nodeType != 1) {
16        obj = obj.parentNode;
17    }
18    if (obj.tagName == 'TEXTAREA' || obj.tagName == 'A') return;
19    while (obj.nodeName != 'P' && obj.nodeName != 'HTML') {
20        obj = obj.parentNode;
21    }
22    if (obj.nodeName == 'HTML') return;
23    var x = obj.innerHTML;
24    var y = document.createElement('TEXTAREA');
25    var z = obj.parentNode;
26    z.insertBefore(y,obj);
27    z.insertBefore(butt,obj);
28    z.removeChild(obj);
29    y.value = x;
30    y.focus();
31    editing = true;
32    getId(e)
33}
34
35function getId(e) {
36    var targ;
37    if (!e) var e = window.event;
38    if (e.target) targ = e.target;
39    else if (e.srcElement) targ = e.srcElement;
40    if (targ.nodeType == 3) // defeat Safari bug
41        targ = targ.parentNode;
42    thisTarget = e.target.id;
43    
44}
45
46function saveEdit() {
47    var area = document.getElementsByTagName('TEXTAREA')[0];
48    var y = document.createElement('P');
49    // set the id back to the original value as the real one is destroyed
50    y.setAttribute('id', thisTarget);
51
52    var z = area.parentNode;
53    y.innerHTML = area.value;
54    z.insertBefore(y,area);
55    z.removeChild(area);
56    z.removeChild(document.getElementsByTagName('button')[0]);
57    editing = false;
58    // action the server request, first var is the value, second var is the id
59    saveToServer(y.innerHTML,thisTarget);
60}
61
62function saveToServer(valToCommit,fieldname) {
63    //alert(valToCommit);
64    $.post("view/appeals/act_commitChange.cfm", { newValue: valToCommit, field: fieldname, appeal: intId },
65
66    function(data){
67        alert(data);
68    });
69
70document.onclick = catchIt;

I wont go into massive depth on a line by line basis but Peter's article does break this down a lot. The premise is that there is a function catchit(), which will intercept any click events. It will then check that the event was triggered from a 'P' tag, which is our defining element for editable content. IE any P elements hold editable content. It will then remove the P html container, replacing it with a textarea, and re insert the P tags previous html content using the innerHTML JavaScript function.

In this way we can create editable inline textareas within the framework of our page.

The next step is to create a save function. The function 'saveToServer{)' take several arguments. It needs the value to commit, IE what the amended text string is, and the fieldname. Each 'P' tag has an id that I am matching to a data field. In this way if there are multiple p tags in a display they can each be attributed to a specific storage field in a database.

Because we are destroying the 'P' tag when we create the textarea we need to re assign the id to it when we save. We can do this by using the JavaScript function 'setAttribute'. The setAttribute function is used to set the value of an attribute on an object. It is typically used along with objects returned by document.getElementById to assign a new value to the object's attribute.

view plain print about
1// set the id back to the original value as the real one is destroyed
2y.setAttribute('id', thisTarget);

If we don't do this then the recreated 'P' tag no longer has an id attribute, so will error on any subsequent updates.

Next we use a JQuery Post function to post the values through an AJAX request.

view plain print about
1$.post("commitChange.cfc", { newValue: valToCommit, field: fieldname, appeal: intId },

This will post the values to the cfml CFC "commitChange.cfc", which handles them in a function.

This will allow you to perform seamless inline edits to the display layer, and commit them back to a server, so they are stored in real time.

There is an example of this here. (Minus the storing). You can track the AJAX post using a tool like charles http proxy, or firefox's firebug.

Now, to write a nice JQuery response handler to fade the returned massage in and out.

17
J
A
N
2010

Using Coldfusion to generate JQuery validation scripts

One of the ideas that we have been throwing around the office is creating a platform that will create the form-validation-database cycle automatically. Something akin to ORM, but not. ORM looks good, but we want more control over what is happening.

In this article I am exploring the idea of automatically creating JQuery validation from a simple Coldfusion input. In this case a list of required fields. I'll say up front Ray Camden's blog entry on Jquery Validation (http://www.coldfusionjedi.com/index.cfm/2009/2/10/An-Introduction-to-jQuery-and-Form-Validation-2) has been an invaluable help.

The principle behind this is that you can create a generic validation object routine, and simple provide it with a set data object (list or struct, haven't decided yet) and have it match against a form and validate it. So with that in mind we will create a simple form.

view plain print about
1<form name="form" id="form" method="post" action="index.cfm?inline=#url.inline#">
2
3<label for="name">Name</label><br/>
4<input type="text" name="name" id="name" class="form-field"><p/>
5
6<label for="telephone">Telephone</label><br/>
7<input type="text" name="telephone" id="telephone" class="form-field"><p/>
8
9<label for="email">Email</label><br/>
10<input type="text" name="email" id="email" class="form-field"><p/>
11
12<label for="favouriteSandwhich">Favourite Sandwhich</label><br/>
13<input type="text" name="favouriteSandwhich" id="favouriteSandwhich" class="form-field"><p/>
14
15<input type="submit" name="action" value="Submit">
16
17</form>
As you can see from above, this is a simple form. Next we will include references to the Google code repository for the AJAX library, and out JQuery validation plugin.
view plain print about
1<scr/ipt type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
2<scr/ipt type="text/javascript" src="jquery.validate.pack.js"></script>
Next we have the JQuery script to validate our fields. Firstly we create a list of the fields that are required. By doing this it is incredibly easy to add another required field to the model.
view plain print about
1<cfset variables.requiredList = "name,email">
The next phase of this will probably accept a structure rather than a list, in that way I can combine field lengths and any other custom criteria, rather than merely the fact that it is required. The JQuery validation starts off in exactly the same way as usual, we create a function to catch the validation request, and use the id of the form as the reference. We declare the error container style of "#error". Next we loop over our list inside the "rules:" element. This creates the required rules for each field. I've also added a basic check for the value 'email'. This will create an email validation rule. Next we create custom messages in the "message:" element. Again this catches the email value and adds an appropriate message.
view plain print about
1<scri/pt>
2        $(document).ready(function(){
3            $("#form").validate({
4            
5            errorContainer: "#error",
6            errorLabelContainer: "#error ul",
7            wrapper: "li",
8
9            rules: {
10                <cfoutput>
11                    <cfloop list="#variables.requiredList#" index="variables.index">
12                    #variables.index#: {required: true <cfif findNoCase('email', variables.index, '1')>, email: true</cfif>, minlength: 5},
13                    </cfloop>
14                </cfoutput>
15                    },
16
17            messages: {
18                
19                <cfoutput>
20                    <cfloop list="#variables.requiredList#" index="variables.index">
21                    #variables.index#: {required: "The #variables.index# field is required",
22                    <cfif findNoCase('email', variables.index, '1')> email: "Email addresses are of the form user@host. Please enter a valid email address.",</cfif>
23                     minlength: jQuery.format("You need to use at least {0} characters for your name.")
24                    },
25                    
26                    </cfloop>
27                </cfoutput>
28            
29                     }
30                                }
31            );
32        });
33        </script>
Lastly we can create a style for the #error container we declared above.
view plain print about
1<style>
2/* Error handling styles */
3#error {-moz-background-clip:border;
4        -moz-background-inline-policy:continuous;
5        -moz-background-origin:padding;
6        background:#FFE7DF;
7        background-position: 5px 8px;
8        border: #FF3F00 solid 1px;
9        color:#440000;
10        margin:10px 0 1em;
11        padding:0px 7px 7px 7px;
12        display:none;
13        width: 90%;}
14
15/* padding for the list */
16#error ul {list-style-type:none; padding: 0px 0px 0px 0px;}
17
18/* padding for the list items */
19#error ul li {padding: 4px 0 2px 16px;}
20
21.error {color: red; list-style-type:none; padding: 0px 0px 0px 0px; width: 198px; color:#440000; background:#FFE7DF;}
22li {list-style-type:none;}    
23.form-field {width: 200px; padding: 0px 0px 0px 0px;}
24</style>

This creates a totally dynamic validation routine - all fed from a list. I think it forms a good basis to build a more dynamic rules driven model, where you can set field lengths as well.

There is an example of the complete code here, along with a variation on the inline or external placing of the validation messages.

Follow this link for a demo of the JQuery validation model.

_UNKNOWNTRANSLATION_