|
Using the JQuery dataTables plugin to display dynamic data in tables: part 1 |
Rather than writing out long winded table code to display your data in a tabulated fashion why not use the dataTables JQuery plugin to do it for you?
In this blog entry I'll be generating tables using a JQuery plugin, but I will also be generating the JQuery code from an XML document.
The theory behind the dataTables JQuery plugin is that when the template loads it makes an AJAX request to a remotely specified template. That template returns a JSON object of data which is formatted and used in a tabular display. This means that you can perfom filtering and sort functions inline, and the JQuery simply re submits the AJAX request, receiving new JSON each time. So no refreshing.
I'll be dealing with the auto generation of the JSON back end in the second part of this article. Here is how I setup the tabular display.
Build a standard html template, including the CSS and JQuery plugins.
2<html lang="en-GB">
3<head>
4<InvalidTag http-equiv="Content-Type" content="text/html; charset=iso-8859-1" lang="en">
5<InvalidTag name="language" content="en-GB">
6
7<style type="text/css" title="currentStyle">
8 @import "demo_page.css";
9 @import "demo_table.css";
10</style>
11
12<s/cript type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
13<s/cript type="text/javascript" language="javascript" src="jquery.dataTables.js"></script>
Next we build our JQuery function. I wont go into massive detail about all the parameters and values being passed in here, but it is well documented on http://www.datatables.net/.
2$(document).ready(function() {
3 $('#example').dataTable( {
4 "bProcessing": true,
5 "bServerSide": true,
6 "sAjaxSource": "content.cfm",
7 "aoColumns": [
8{ "sName": "Edit", "sTitle": "Edit", "sWidth":"10%"} ,
9{ "sName": "Band", "sTitle": "Band"} ,
10{ "sName": "Genre","sTitle": "Genre"} ,
11{ "sTitle": "Fake Column"}
12],
13 "sPaginationType": "full_numbers",
14 "aaSorting": [[1,'asc']],
15 "oLanguage": {
16 "sLengthMenu": "Page length: _MENU_",
17 "sSearch": "Filter:"
18 }
19 } );
20 } );
21</script>
The really important column here is the "aoColumns" JSON data block. This specifies what fields are returned from your AJAX call, and parameters they must adhere to.
In this example we are anticipating that we will receive four columns of data back (Edit,Band,Genre and Fake Column).
Lastly we create a table with an ID of "example", as this is what the JQuery is looking for. This table must be formatted in a certain way, as the JQuery plugin will re write the specified elements.
2<thead>
3<tr>
4 <th>Edit</th>
5 <th>Band</th>
6 <th>Genre</th>
7 <th>Fake Column</th>
8</tr>
9</thead>
10 <tbody>
11 <tr>
12 <td colspan="3" class="dataTables_empty">Loading data from server</td>
13 </tr>
14 </tbody>
15</table>
The code also contains the header elements that will match the returned column values from the AJAX request. The last part of the table is displayed when the data is loading.
This all works well, but to extend it further I have altered the code to read from an XML document. The XML document Is loaded when the template starts, and the data fields and attributes are read, and looped over to create the JQuery code and table headers. In this way it is a generic table display template, driven from an XML document.
The XML doc:
2 <form>
3 <field sName="Edit" source="data" sTitle="Edit" sWidth="10%">Edit</field>
4 <field sName="Band" source="data" sTitle="Band">Band</field>
5 <field sName="Genre" source="data" sTitle="Genre">Genre</field>
6 <field sName="fake column" source="" sTitle="Fake Column">Fake column</field>
7 </form>
Read the XML file and parse it out into an Array:
2<cfset variables.xml = XMLParse("test.xml") />
3<!--- <cfdump var="#variables.xml#" label="Raw xml document"> --->
4<cfset variables.fields = XMLSearch(variables.xml,"form/field")>
5<cfset variables.totalRecords = ArrayLen(variables.fields)>
Use something like this to dynamically generate the JQuery and table values:
2<cfloop index="variables.index" from="1" to="#ArrayLen(variables.fields)#">
3 { <cfif variables.fields[variables.index].XmlAttributes.source EQ "data">"sName": "#variables.fields[variables.index].XmlAttributes.sName#",</cfif>
4 <cfif structkeyexists(variables.fields[variables.index].XmlAttributes, 'sTitle')>"sTitle": "#variables.fields[variables.index].XmlAttributes.sTitle#"</cfif>
5 <cfif structkeyexists(variables.fields[variables.index].XmlAttributes, 'sWidth')>,"sWidth":"#variables.fields[variables.index].XmlAttributes.sWidth#"</cfif>
6 } <cfif variables.index NEQ variables.totalRecords>,</cfif>
7</cfloop>
8</cfoutput>
9<!--- table values --->
10
11<cfoutput>
12 <cfloop index="variables.index" from="1" to="#ArrayLen(variables.fields)#">
13 <th align="left">#variables.fields[variables.index].XmlText#</th>
14 </cfloop>
15</cfoutput>
The JSON response is hard coded in this example, so the result will not filter or search. I'll handle that in article two.
There is a full example of this here.
|
Controlling show() hide() Divs with a JQuery powered Select field |
There are many ways of hiding all your page elements then only showing specific areas based on user selection. One old school way of changing the displayed page content was with a select field that had an onchange event that posted to the same page, but with a URL variable.
I have been testing out a variation on this using the JQuery show() / hide() functions.
We attach an event to the select field, and pass the selected option to a JQuery function, as in the example code below.
2
3<select name="field_data_loc[key]" class="form-select required" id="edit-field-data-loc-key" onChange="showHide(this.value)" >
4<option value="didnotchoose" selected="selected">Choose</option>
5<option value="web">Website</option>
6<option value="file">File to upload</option>
7<option value="url">Url</option>
8</select>
We have a series of DIV's that are named the same as the select field options. All these DIV's will be hidden by default, apart from the "start" div.
2
3<div id="url">url</div>
4
5<div id= "file">file</div>
6
7<div id= "web">web</div>
Lastly we have our JQuery script. Here we have our showHide function. This accepts a DIV id, which is then shown. All other DIV's are hidden.
The last piece of script here simply hides all the DIV's on the page load, so that they are all hidden by default.
2
3 function showHide(obj)
4 {
5 var showDiv = "#"+obj;
6 $("div").hide();
7 $(showDiv).show();
8 }
9
10 // hide all the divs on start up
11 $('#web').hide();
12 $('#file').hide();
13 $('#url').hide();
14
15</script>
Putting it all together, and making it dynamic based on a list, it looks like this:
2
3<s/cript type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
4
5<cfoutput>
6<select name="field_data_loc[key]" class="form-select required" id="edit-field-data-loc-key" onChange="showHide(this.value)" >
7<option value="didnotchoose" selected="selected">Choose</option>
8
9 <cfloop list="#variables.mylist#" index="variables.index">
10 <option value="#variables.index#">#variables.index#</option>
11 </cfloop>
12
13</select>
14
15<div id="start">Starting div</div>
16
17<cfloop list="#variables.mylist#" index="variables.index">
18 <div id="#variables.index#">#variables.index#</div>
19</cfloop>
20
21<s/cript language="javascript">
22
23 function showHide(obj)
24 {
25 var showDiv = "##"+obj;
26
27 <cfloop list="#variables.mylist#" index="variables.index">
28 $('###variables.index#').hide();
29 </cfloop>
30 $('##start').hide();
31
32 //$("div").hide();
33 $(showDiv).show();
34 }
35
36<cfloop list="#variables.mylist#" index="variables.index">
37 $('###variables.index#').hide();
38</cfloop>
39
40</script>
41</cfoutput>
An example is here.
|
Displaying and sorting/paging tabular data using the JQuery tablesorter plugin, and query objects |
One of the more repetitive tasks a server side developer encounters is displaying the results from a query. This is traditionally in the format of a table that displays the rows of data, along with any other functionality, such as paging controls and sortable headers.
I was recently commissioned to look into building a generic table display "engine", and thought I'd investigate if there were any JQuery plugins that could do the bulk of the work for me. Ideally I didn't want to have to write a whole load of script to parse sorting variables, and detect if a limit was set on the returned record set for paging.
After some investigation I ended out using the table sorter JQuery plugin http://tablesorter.com/docs/.
This plugin allows for sortable results that you can page through, and it does not keep posting the values back and forth to the server.
Start by including the references to the JQuery libraries. I've also included references to the paging plugin, and the blue theme stylesheet.
2
3<s/cript type="text/javascript" src="/path/to/jquery.tablesorter.js"></script>
4
5<s/cript type="text/javascript" src="/path/to/ /jquery.tablesorter.pager.js"></script>
6
7<link rel="stylesheet" href="css/blue.css" type="text/css" />
Next we will create a fake query, so that we have some records to display.
2<cfset variables.qOptions = QueryNew( "id, name, color" ) />
3
4<cfset QueryAddRow( variables.qOptions ) />
5<cfset variables.qOptions[ "id" ][ variables.qOptions.RecordCount ] = "1" />
6<cfset variables.qOptions[ "name" ][ variables.qOptions.RecordCount ] = "Value 1" />
7<cfset variables.qOptions[ "color" ][ variables.qOptions.RecordCount ] = "Red" />
8
9<cfset QueryAddRow( variables.qOptions ) />
10<cfset variables.qOptions[ "id" ][ variables.qOptions.RecordCount ] = "2" />
11<cfset variables.qOptions[ "name" ][ variables.qOptions.RecordCount ] = "Value 2" />
12<cfset variables.qOptions[ "color" ][ variables.qOptions.RecordCount ] = "Green" />
13
14<cfset QueryAddRow( variables.qOptions ) />
15<cfset variables.qOptions[ "id" ][ variables.qOptions.RecordCount ] = "3" />
16<cfset variables.qOptions[ "name" ][ variables.qOptions.RecordCount ] = "Value 3" />
17<cfset variables.qOptions[ "color" ][ variables.qOptions.RecordCount ] = "Blue" />
18
19<cfset QueryAddRow( variables.qOptions ) />
20<cfset variables.qOptions[ "id" ][ variables.qOptions.RecordCount ] = "4" />
21<cfset variables.qOptions[ "name" ][ variables.qOptions.RecordCount ] = "Value 4" />
22<cfset variables.qOptions[ "color" ][ variables.qOptions.RecordCount ] = "White" />
We have to change the way we build the table code slightly, as the JQuery plugin is expecting certain field naming conventions. Give your table a class name of tablesorter. This is the style that the JQuery is watching for.
2 <thead>
3 <tr>
4 <cfoutput>
5 <cfloop list="#variables.qOptions.columnlist#" delimiters="," index="variables.index">
6 <th>#variables.index#</th>
7 </cfloop>
8 </cfoutput>
9 </tr>
10 </thead>
In the code above I am looping over the columnlist of the query to generate headings. I've had to change the headings to 'th' tags which I never normally use.
Next we can generate the table content, making sure it is inside a 'tbody' html tag. Simply loop over the query displaying all the results within td tags.
2 <cfoutput query="variables.qOptions">
3 <tr>
4 <td>#variables.qOptions.id#</td>
5 <td>#variables.qOptions.name#</td>
6 <td>#variables.qOptions.color#</td>
7 </tr>
8 </cfoutput>
9 </tbody>
10</table>
Lastly the pager plugin is looking for a div with a class of pager. Inside this div you place your paging controls, and the value of the page recordsets that you want to offset by.
2 <form>
3 <img src="addons/pager/icons/first.png" class="first"/>
4 <img src="addons/pager/icons/prev.png" class="prev"/>
5 <input type="text" class="pagedisplay"/>
6 <img src="addons/pager/icons/next.png" class="next"/>
7 <img src="addons/pager/icons/last.png" class="last"/>
8 <select class="pagesize">
9 <option selected="selected" value="10">10</option>
10
11 <option value="20">20</option>
12 <option value="30">30</option>
13 <option value="40">40</option>
14 </select>
15 </form>
16</div>
This builds a one page table display that can paginate and sort with a single refresh.
A full example of this is here.
|
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.
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.
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.
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.