Structuring and Binding (ajax response) - javascript
One of the common patterns I've come across in my many years of coding is the structuring/binding of the data coming from the server response (XMLHttpRequest). This problem of creating elements and appending them in a particular order as well as binding (attributes,events,content) is what I'm am trying to achieve here.
For example purposes and simplicity I am trying to create a tr --- td nested structure as well as bind the attributes from the var instructs object (table-row,table-data).
JSON Response (dummy data)
var response =[{"employeeNumber":"1002","lastName":"Murphy","firstName":"Diane","extension":"x5800","email":"dmurphy#classicmodelcars.com","officeCode":"1","reportsTo":null,"jobTitle":"President"},{"employeeNumber":"1056","lastName":"Patterson","firstName":"Mary","extension":"x4611","email":"mpatterso#classicmodelcars.com","officeCode":"1","reportsTo":"1002","jobTitle":"VP Sales"},{"employeeNumber":"1076","lastName":"Firrelli","firstName":"Jeff","extension":"x9273","email":"jfirrelli#classicmodelcars.com","officeCode":"1","reportsTo":"1002","jobTitle":"VP Marketing"},{"employeeNumber":"1088","lastName":"Patterson","firstName":"William","extension":"x4871","email":"wpatterson#classicmodelcars.com","officeCode":"6","reportsTo":"1056","jobTitle":"Sales Manager (APAC)"},{"employeeNumber":"1102","lastName":"Bondur","firstName":"Gerard","extension":"x5408","email":"gbondur#classicmodelcars.com","officeCode":"4","reportsTo":"1056","jobTitle":"Sale Manager (EMEA)"},{"employeeNumber":"1143","lastName":"Bow","firstName":"Anthony","extension":"x5428","email":"abow#classicmodelcars.com","officeCode":"1","reportsTo":"1056","jobTitle":"Sales Manager (NA)"},{"employeeNumber":"1165","lastName":"Jennings","firstName":"Leslie","extension":"x3291","email":"ljennings#classicmodelcars.com","officeCode":"1","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1166","lastName":"Thompson","firstName":"Leslie","extension":"x4065","email":"lthompson#classicmodelcars.com","officeCode":"1","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1188","lastName":"Firrelli","firstName":"Julie","extension":"x2173","email":"jfirrelli#classicmodelcars.com","officeCode":"2","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1216","lastName":"Patterson","firstName":"Steve","extension":"x4334","email":"spatterson#classicmodelcars.com","officeCode":"2","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1286","lastName":"Tseng","firstName":"Foon Yue","extension":"x2248","email":"ftseng#classicmodelcars.com","officeCode":"3","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1323","lastName":"Vanauf","firstName":"George","extension":"x4102","email":"gvanauf#classicmodelcars.com","officeCode":"3","reportsTo":"1143","jobTitle":"Sales Rep"},{"employeeNumber":"1337","lastName":"Bondur","firstName":"Loui","extension":"x6493","email":"lbondur#classicmodelcars.com","officeCode":"4","reportsTo":"1102","jobTitle":"Sales Rep"},{"employeeNumber":"1370","lastName":"Hernandez","firstName":"Gerard","extension":"x2028","email":"ghernande#classicmodelcars.com","officeCode":"4","reportsTo":"1102","jobTitle":"Sales Rep"},{"employeeNumber":"1401","lastName":"Castillo","firstName":"Pamela","extension":"x2759","email":"pcastillo#classicmodelcars.com","officeCode":"4","reportsTo":"1102","jobTitle":"Sales Rep"},{"employeeNumber":"1501","lastName":"Bott","firstName":"Larry","extension":"x2311","email":"lbott#classicmodelcars.com","officeCode":"7","reportsTo":"1102","jobTitle":"Sales Rep"},{"employeeNumber":"1504","lastName":"Jones","firstName":"Barry","extension":"x102","email":"bjones#classicmodelcars.com","officeCode":"7","reportsTo":"1102","jobTitle":"Sales Rep"},{"employeeNumber":"1611","lastName":"Fixter","firstName":"Andy","extension":"x101","email":"afixter#classicmodelcars.com","officeCode":"6","reportsTo":"1088","jobTitle":"Sales Rep"},{"employeeNumber":"1612","lastName":"Marsh","firstName":"Peter","extension":"x102","email":"pmarsh#classicmodelcars.com","officeCode":"6","reportsTo":"1088","jobTitle":"Sales Rep"},{"employeeNumber":"1619","lastName":"King","firstName":"Tom","extension":"x103","email":"tking#classicmodelcars.com","officeCode":"6","reportsTo":"1088","jobTitle":"Sales Rep"},{"employeeNumber":"1621","lastName":"Nishi","firstName":"Mami","extension":"x101","email":"mnishi#classicmodelcars.com","officeCode":"5","reportsTo":"1056","jobTitle":"Sales Rep"},{"employeeNumber":"1625","lastName":"Kato","firstName":"Yoshimi","extension":"x102","email":"ykato#classicmodelcars.com","officeCode":"5","reportsTo":"1621","jobTitle":"Sales Rep"},{"employeeNumber":"1702","lastName":"Gerard","firstName":"Martin","extension":"x2312","email":"mgerard#classicmodelcars.com","officeCode":"4","reportsTo":"1102","jobTitle":"Sales Rep"}];
Binding (instructions)
var instructs={
tag:"tr",
attributes:{class:"table-row"},
props:{
email:{
tag:"td",
content: null,
attributes:{class:"table-data",id:"table-data-id"}
},
employeeNumber:{
tag:"td",
attributes:{class:"table-data"},
content: null,
props:{
x:{
tag: "input",
attributes:{class:"table-input"},
content: "test"
}
}
},
extension:{
tag:"td",
content: null,
attributes:{class:"table-data"}
},
firstName:{
tag:"td",
content: null,
attributes:{class:"table-data"}
},
jobTitle:{
tag:"td",
content: null,
attributes:{class:"table-data"}
},
lastName:{
tag:"td",
content: null,
attributes:{class:"table-data"}
},
officeCode:{
tag:"td",
content: null,
attributes:{class:"table-data"}
},
reportsTo:{
tag:"td",
content: null,
attributes:{class:"table-data"}
}
}
};
My Function (assemble)
function assemble(r,s,n){
var n = n || new DocumentFragment();
if(typeof r !== 'string'){ //HAS CHILDREN
r.forEach((o)=>{
for(y in s){
switch(y){
case "tag":
var tag = document.createElement(s[y]);
n.appendChild(tag);
break;
case "attributes":
for(a in s[y]) tag.setAttribute(a,s[y][a]);
break;
case "content":
if(s.content === null){
//append current property value
}
else{
tag.innerHTML = s.content;
}
break;
case "props":
for(k in o) assemble(k,s[y][k],tag); //EXECUTE PER CHILDREN
break;
}
}
});
}
else{
for(x in s){
switch(x){
case "tag":
var tag = document.createElement(s[x]);
n.appendChild(tag);
break;
case "content":
if(s.content === null){
//append current property value
}
else{
tag.innerHTML = s.content;
}
break;
case "attributes":
for(a in s[x]) tag.setAttribute(a,s[x][a]);
break;
case "props":
for(c in s[x]) assemble(r,s[x][c],tag);
break;
}
}
return n;
}
return n;
}
document.addEventListener('DOMContentLoaded',()=>{
var data = assemble(response,instructs);
console.log(data);
});
The end result I'm looking for is an array/fragment of nested tr>td both with a class attribute and the values append to the innerHTML.
<tr class ="table-row">
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
<td class="table-data">how do I bind the response values?</td>
</tr>
QUESTION:
How can I bind the property values from the response to the innerHTML of the td's?
Give a try to the following one. Sorry, I started refactoring Yours and in the end I had rewritten it. Take care that the content is always taken as innerHTML, it would not be a bad idea at all to differentiate text(append createTextNode func outcome) and html(innerHTML prop). In your data instructs.props.employeeNumber.props.x.content should be moved into instructs.props.employeeNumber.props.x.attributes.value seen how works the input[text] tag. Btw I see there are a lot of stuff that could be discussed here. Hope it helps!
function assemble (data, instr) {
var n = document.createDocumentFragment(), i;
function create(d) {
var objData = d;
return (function _(_instr, _key, _n) {
var innerHTML = !!_key && _key in objData ? objData[_key] : null,
tag = null, i;
if ('tag' in _instr) {
tag = document.createElement(_instr.tag);
tag.innerHTML = 'content' in _instr && !!_instr.content ? _instr.content : innerHTML;
if ('attributes' in _instr) {
for (i in _instr.attributes) tag.setAttribute(i, _instr.attributes[i]);
}
//recur finally
if ('props' in _instr) {
for (i in _instr.props) _(_instr.props[i], i, tag);
}
!!_n && _n.appendChild(tag);
}
return tag;
})(instr, null);
}
return (function (){
for (i in data) {
n.appendChild(create(data[i]));
}
return n;
})();
}
Though my answer does not address the code context from the question, but for structuring and binding json response i usually use regex templating. I find it simple in declaration and concise. ex,
html:
<table>
<tbody id="employee">
</tbody>
</table>
js:
var template = function(tpl, data) {
return tpl.replace(/\$\{([^\}]+)?\}/g, function($1, $2) {
return data[$2];
});
};
var rowTemplate = '<tr class ="table-row">\
<td class="table-data">${email}</td>\
<td class="table-data"><input type="text" value=' ${employeeNumber}' /></td>\
<td class="table-data">${firstName}</td>\
<td class="table-data">${lastName}</td>\
<td class="table-data">${email}</td>\
<td class="table-data">${jobTitle}</td>\
<td class="table-data">${extension}</td>\
</tr>';
var response = [{"employeeNumber":"1002","lastName":"Murphy","firstName":"Diane","extension":"x5800","email":"dmurphy#classicmodelcars.com","officeCode":"1","reportsTo":null,"jobTitle":"President"}, ....]
var b = document.getElementById('employee');
for (var i = 0; i < response.length; i++) {
var tr = document.createElement('x');
b.appendChild(tr);
tr.outerHTML = template(rowTemplate, response[i]);
}
fiddle
hope this helps.
Related
How to loop through HTML elements and populate a Json-object?
I'm looping through all the html tags in an html-file, checking if those tags match conditions, and trying to compose a JSON-object of a following schema: [ { title: 'abc', date: '10.10.10', body: ' P tags here', href: '' }, { title: 'abc', date: '10.10.10', body: ' P tags here', href: '' }, { title: 'abc', date: '10.10.10', body: ' P tags here', href: '' } ] But I'd like to create the new entry only for elements, classed "header", all the other elements have to be added to earlier created entry. How do I achieve that? Current code: $('*').each((index, element) => { if ( $(element).hasClass( "header" ) ) { jsonObject.push({ title: $(element).text() }); }; if( $(element).hasClass( "date" )) { jsonObject.push({ date: $(element).text() }); } //links.push($(element)) }); console.log(jsonObject) Result is: { title: 'TestA' }, { date: '10.10.10' }, { title: 'TestB' }, { date: '10.10.11' } I'd like it to be at this stage something like: { title: 'TestA' , date: '10.10.10' }, { title: 'TestB' , date: '10.10.11' } UPD: Here's the example of HTML file: <h1 class="header">H1_Header</h1> <h2 class="date">Date</h2> <p>A.</p> <p>B.</p> <p>С.</p> <p>D.</p> <a class="source">http://</a> <h1 class="header">H1_Header2</h1> <h2 class="date">Date2</h2> <p>A2.</p> <p>B2.</p> <p>С2.</p> <p>D2.</p> <a class="source">http://2</a> Thank you for your time!
Based on your example Html, it appears everything you are trying to collect is in a linear order, so you get a title, date, body and link then a new header with the associated items you want to collect, since this appears to not have the complication of having things being ordered in a non-linear fasion, you could do something like the following: let jsonObject = null; let newObject = false; let appendParagraph = false; let jObjects = []; $('*').each((index, element) => { if ($(element).hasClass("header")) { //If newObject is true, push object into array if(newObject) jObjects.push(jsonObject); //Reset the json object variable to an empty object jsonObject = {}; //Reset the paragraph append boolean appendParagraph = false; //Set the header property jsonObject.header = $(element).text(); //Set the boolean so on the next encounter of header tag the jsobObject is pushed into the array newObject = true; }; if( $(element).hasClass( "date" )) { jsonObject.date = $(element).text(); } if( $(element).prop("tagName") === "P") { //If you are storing paragraph as one string value //Otherwise switch the body var to an array and push instead of append if(!appendParagraph){ //Use boolean to know if this is the first p element of object jsonObject.body = $(element).text(); appendParagraph = true; //Set boolean to true to append on next p and subsequent p elements } else { jsonObject.body += (", " + $(element).text()); //append to the body } } //Add the href property if( $(element).hasClass("source")) { //edit to do what you wanted here, based on your comment: jsonObject.link = $(element).next().html(); //jsonObject.href= $(element).attr('href'); } }); //Push final object into array jObjects.push(jsonObject); console.log(jObjects); Here is a jsfiddle for this: https://jsfiddle.net/Lyojx85e/ I can't get the text of the anchor tags on the fiddle (I believe because nested anchor tags are not valid and will be parsed as seperate anchor tags by the browser), but the code provided should work in a real world example. If .text() doesn't work you can switch it to .html() on the link, I was confused on what you are trying to get on this one, so I updated the answer to get the href attribute of the link as it appears that is what you want. The thing is that the anchor with the class doesn't have an href attribute, so I'll leave it to you to fix that part for yourself, but this answer should give you what you need.
$('*').each((index, element) => { var obj = {}; if ( $(element).hasClass( "header" ) ) { obj.title = $(element).text(); }; if( $(element).hasClass( "date" )) { obj.date = $(element).text() } jsonObject.push(obj); });
I don't know about jQuery, but with JavaScript you can do with something like this. const arr = []; document.querySelectorAll("li").forEach((elem) => { const obj = {}; const title = elem.querySelector("h2"); const date = elem.querySelector("date"); if (title) obj["title"] = title.textContent; if (date) obj["date"] = date.textContent; arr.push(obj); }); console.log(arr); <ul> <li> <h2>A</h2> <date>1</date> </li> <li> <h2>B</h2> </li> <li> <date>3</date> </li> </ul>
Always use map for things like this. This should look something like: let objects = $('.header').get().map(el => { return { date: $(el).attr('date'), title: $(el).attr('title'), } })
CasperJS querySelectorAll + map.call
html file <table id="tbl_proxy_list"> ........... <tr> ...... <td align="left"> <time class="icon icon-check">1 min</time> </td> <td align="left"> <div class="progress-bar" data-value="75" title="4625"></div> </td> </tr> </table> ip.js file casper.start('http://www.proxynova.com/proxy-server-list/', function() { var info_text = this.evaluate(function() { var nodes = document.querySelectorAll('table[id="tbl_proxy_list"] tr'); return [].map.call(nodes, function(node) { //return node.innerText; return node; }); }); var tr_data = info_text.map(function(str) { var elements = str; var data = { ip : elements, port : elements[1], lastcheck : elements[2], speed : elements[3], // <== value is 75.. }; return data; }); utils.dump(tr_data); }); casper.run(); return node.innerText is only text. ip is a text value port is a text value lastcheck is a text value speed is not a text value (data-value="75") I want to import data-value="75" (speed value is 75). I do not know what to do. ======================================== It's work.. good. thank you Artjom. but tr_data echo error. first, you code modify.. return { "ip": tr.children[0].innerText.trim(), "port": tr.children[1].innerText.trim(), "lastcheck": tr.children[2].innerText.trim(), "speed": tr.children[3].children[0].getAttribute("data-value") }; and echo.. //this.echo(tr_data.length); for(var ii=0; ii<tr_data.length; ii++) { this.echo(tr_data[ii]['ip']); } at run, blow error.. TypeError: 'null' is not an object (evaluating 'tr_data.length'); what is problem? I need your help.. thanks.
You cannot pass DOM elements from the page context (inside evaluate callback). From the docs: Note: The arguments and the return value to the evaluate function must be a simple primitive object. The rule of thumb: if it can be serialized via JSON, then it is fine. Returning an array of DOM elements will result in an array of as many undefined values. That means you need to map everything inside the page context and then return the resulting array. You also need only one map. var tr_data = this.evaluate(function() { var nodes = document.querySelectorAll('table[id="tbl_proxy_list"] tbody tr'); return Array.prototype.map.call(nodes, function(tr, i) { if (tr.children.length != 6) { return null; // skip ads } return { ip: tr.children[0].innerText.trim(), port: tr.children[1].innerText.trim(), lastcheck: tr.children[2].innerText.trim(), speed: tr.children[3].children[0].getAttribute("data-value") }; }).filter(function(data){ return data !== null; // filter the null out });; }); You also might want to trim the excess white space.
Need an Array Output from javascript function
Chech this fiddle for all the running code i am using i have an array i=[10,20,30,40,50,60,70] and i m getting output as 70:checkbox(checked value 70) . I need all the array to be displayed along with its checkboxes,so that i can check whatever number i want and retrieve the checked ID desired output: 70:checkbox(checked value 70) 60:checkbox(checked value 60) 50:checkbox(checked value 50) 40:checkbox(checked value 40) 30:checkbox(checked value 30) 20:checkbox(checked value 20) 10:checkbox(checked value 10) Code for above Fiddle is here: JS var i=[10,20,30,40,50,60,70]; //$("#add").click(function(){ $(document).ready(function(){ // alert("ff"); var newrow=$('#services .headings').clone().removeClass('headings'); for(var k=0;k<i.length;k++) { var disp = { names: i[k], checks: i[k] } func.call(row,disp); } func(newrow,disp) .insertAfter('#services tr.headings') .show(); }); function func(row,disp) { row.find('.servicenames').text(disp.names); row.find('.servicecheck').data('href',disp.checks); return row; } $("#services").on("click", ".servicecheck",function(){ alert($(this).data('href')); }); html <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> <script src="http://code.jquery.com/jquery-migrate-1.2.1.min.js"></script> <table id="services"> <tr class="headings" style="display:none"> <td><span class="servicenames"> service names here</span></td> <td><span class="servicecheck" data-href=""><input type="checkbox" id="servicecheck" name="servicecheck"> </td> </tr> </table>
in your jsfiddle you have next code .... var newrow=$('#services .headings').clone().removeClass('headings'); for(var k=0;k<i.length;k++) { var disp = { names: i[k], checks: i[k] } } func(newrow,disp).insertAfter('#services tr.headings').show(); .... function func(row,disp) { row.find('.servicenames').text(disp.names); row.find('.servicecheck').data('href',disp.checks); i+=1;//this you try change global array, this string need remove return row; } inside for loop you have only declaration disp, so you call func only once, also you once create new row. for solve this you must change your code like this .... for(var k=0;k<i.length;k++) { var newrow=$('#services .headings').clone().removeClass('headings'); var disp = { names: i[k], checks: i[k] } func(newrow,disp).insertAfter('#services tr.headings').show(); } .... function func(row,disp) { row.find('.servicenames').text(disp.names); row.find('.servicecheck').data('href',disp.checks); return row; }
Try this, var i=[10,20,30,40,50,60,70]; //$("#add").click(function(){ $(document).ready(function(){ // alert("ff"); var newrow=$('#services .headings').clone().removeClass('headings').css("display","block"); for(var k=0;k<i.length;k++) { var disp = { names: i[k], checks: i[k] } $('#services tbody').append(func(newrow,disp).html()); } }); function func(row,disp) { row.find('.servicenames').text(disp.names); row.find('.servicecheck').data('href',disp.checks); return row; } $("#services").on("click", ".servicecheck",function(){ alert($(this).data('href')); }); Fiddle Hope it helps
sorting dynamically created table from html form elemets
sorry for my english. I am new at this so i would appreciate a little help here, I have a html form and its data is shown on my page in html table. After each new saving form data table sorts by form entry id. Now i am stuck with sorting and pagination How can i sort this table data by clicking on column header for some other data, for example by animal_id or animal_name. Thanks for your help here is the code: init: function() { if (!Animals.index) { window.localStorage.setItem("Animals:index", Animals.index = 1); } Animals.$form.reset(); Animals.$button_discard.addEventListener("click", function(event) { Animals.$form.reset(); Animals.$form.id_entry.value = 0; }, true); Animals.$form.addEventListener("submit", function(event) { var entry = { id: parseInt(this.id_entry.value), animal_id:this.animal_id.value, animal_name: this.animal_name.value, animal_type: this.animal_type.value, bday: this.bday.value, animal_sex: this.animal_sex.value, mother_name: this.mother_name.value, farm_name: this.farm_name.value, money: this.money.value, weight: this.weight.value, purchase_partner: this.purchase_partner.value, }; if (entry.id == 0) { // add Animals.storeAdd(entry); Animals.tableAdd(entry); } else { // edit Animals.storeEdit(entry); Animals.tableEdit(entry); } this.reset(); this.id_entry.value = 0; event.preventDefault(); }, true); if (window.localStorage.length - 1) { var animals_list = [], i, key; for (i = 0; i < window.localStorage.length; i++) { key = window.localStorage.key(i); if (/Animals:\d+/.test(key)) { animals_list.push(JSON.parse(window.localStorage.getItem(key))); } } if (animals_list.length) { animals_list.sort(function(a, b) {return a.id < b.id ? -1 : (a.id > b.id ? 1 : 0); }) .forEach(Animals.tableAdd); } } Animals.$table.addEventListener("click", function(event) { var op = event.target.getAttribute("data-op"); if (/edit|remove/.test(op)) { var entry = JSON.parse(window.localStorage.getItem("Animals:"+ event.target.getAttribute("data-id"))); if (op == "edit") { Animals.$form.id_entry.value = entry.id; Animals.$form.animal_id.value = entry.animal_id; Animals.$form.animal_name.value = entry.animal_name; Animals.$form.animal_type.value = entry.animal_type; Animals.$form.bday.value = entry.bday; Animals.$form.animal_sex.value = entry.animal_sex; Animals.$form.mother_name.value = entry.mother_name; Animals.$form.farm_name.value = entry.farm_name; Animals.$form.money.value = entry.money; Animals.$form.weight.value = entry.weight; Animals.$form.purchase_partner.value = entry.purchase_partner; } else if (op == "remove") { if (confirm('Are you sure you want to remove this animal from your list?' )) { Animals.storeRemove(entry); Animals.tableRemove(entry); } } event.preventDefault(); } }, true); }, storeAdd: function(entry) { entry.id = Animals.index; window.localStorage.setItem("Animals:index",++Animals.index); window.localStorage.setItem("Animals:"+entry.id,JSON.stringify(entry)); }, storeEdit: function(entry) { window.localStorage.setItem("Animals:"+entry.id,JSON.stringify(entry)); }, storeRemove: function(entry) { window.localStorage.removeItem("Animals:"+ entry.id); }, tableAdd: function(entry) { var $tr = document.createElement("tr"), $td, key; for (key in entry) { if (entry.hasOwnProperty(key)) { $td = document.createElement("td"); $td.appendChild(document.createTextNode(entry[key])); $tr.appendChild($td); } } $td = document.createElement("td"); $td.innerHTML = '<a data-op="edit" data-id="'+ entry.id+'">Edit</a> | <a data-op="remove" data-id="'+ entry.id +'">Remove</a>'; $tr.appendChild($td); $tr.setAttribute("id", "entry-"+ entry.id); Animals.$table.appendChild($tr); }, tableEdit: function(entry) { var $tr = document.getElementById("entry-"+ entry.id), $td, key; $tr.innerHTML = ""; for (key in entry) { if (entry.hasOwnProperty(key)) { $td = document.createElement("td"); $td.appendChild(document.createTextNode(entry[key])); $tr.appendChild($td); } } $td = document.createElement("td"); $td.innerHTML = '<a data-op="edit" data-id="'+ entry.id +'">Edit</a> | <a data-op="remove" data-id="'+ entry.id +'">Remove</a>'; $tr.appendChild($td); }, tableRemove: function(entry) { Animals.$table.removeChild(document.getElementById("entry-"+ entry.id)); } }; Animals.init();
Since your current approach is to have your code managing the Table HTML and the Animal data directly, it will need to be your code that does the sorting of the data and the rewrite of the table. That is, you will need to listen for click events on the column headers, and then sort your Animal data according to that particular column, and then redraw the table completely. Redrawing the table could simply entail looping through your tableRemove() and then tableAdd() functions for all Animals being displayed, although that might be a little slow. The sorting of the data by each column is a bit more of a mess for which I have a solution below. A better approach would be to allow a RIA (Rich Internet Application) Framework to handle the table information for you. Many of these frameworks (EXT-JS, YUI, etc) provide a Table control that will simplify your coding dramatically. You merely give it a reference to your data (formatted in a relevant format), and it will do the sorting and display for you. If you still want to create the HTML Table yourself, and are more interested in the sorting of the data, then I would suggest going with a Client-side Database solution such as SequelSphere. With SequelSphere, you could create a table to manage your Animal data, as follows: db.catalog.createTable({ tableName: "animals", columns: ["id_entry", "animal_id", "animal_name", "animal_type", "bday", "animal_sex", "mother_name", "farm_name", "money", "weight", "purchase_partner"], primaryKey: ["id_entry"] }); Then you can Insert/Update/Delete rows like this: db.catalog.getTable("animals").insertRow( [ entry.id, entry.animal_id, entry.animal_name, entry.animal_type, entry.bday, entry.animal_sex, entry.mother_name, entry.farm_name, entry.money, entry.weight, entry.purchase_partner ] ); Finally, you can get your data Sorted by whatever column the user clicked by merely changing the ORDER BY clause in the following select statement: var sql = "SELECT id_entry, animal_id, animal_name, animal_type, " + " bday, animal_sex, mother_name, farm_name, money, " + " weight, purchase_partner " + " FROM animals " + " ORDER BY animal_sex "; // why did I choose this column? var sortedEntries = db.query(sql); Oh yes: SequelSphere also stores the data in window.localStorage, so you wouldn't have to write that either. It also has full support for SQL and cursors, so managing the Pagination would be a bit easier as well. Hope this helps! john...
Pulling parts of JSON out to display them in a list
I have the following JSON: var questions = { section: { "1": question: { "1": { "id" : "1a", "title": "This is question1a" }, "2": { "id" : "1b", "title": "This is question2a" } }, "2": question: { "1": { "id" : "2a", "title": "This is question1a" }, "2": { "id" : "2b", "title": "This is question2a" } } } }; NOTE: JSON changed based on the answers below to support the question better as the original JSON was badly formatted and how it works with the for loop below. The full JSON will have 8 sections and each section will contain 15 questions. The idea is that the JS code will read what section to pull out and then one by one pull out the questions from the list. On first load it will pull out the first question and then when the user clicks on of the buttons either option A or B it will then load in the next question until all questions have been pulled and then do a callback. When the button in the appended list item is clicked it will then add it to the list below called responses with the answer the user gave as a span tag. This is what I have so far: function loadQuestion( $section ) { $.getJSON('questions.json', function (data) { for (var i = 0; i < data.length; i++) { var item = data[i]; if (item === $section) { $('#questions').append('<li id="' + item.section.questions.question.id + '">' + item.section.questions.question.title + ' <button class="btn" data-response="a">A</button><button class="btn" data-response="b">B</button></li>'); } } }); } function addResponse( $id, $title, $response ) { $('#responses').append('<li id="'+$id+'">'+$title+' <span>'+$response+'</span></li>'); } $(document).ready(function() { // should load the first question from the passed section loadQuestion( $('.section').data('section') ); // add the response to the list and then load in the next question $('button.btn').live('click', function() { $id = $(this).parents('li').attr('id'); $title = $(this).parents('li').html(); $response = $(this).data('response'); addResponse( $id, $title, $response ); loadQuestion ( $('.section').data('section') ); }); }); and the HTML for the page (each page is separate HTML page): <div class="section" data-section="1"> <ul id="questions"></ul> <ul id="responses"></ul> </div> I've become stuck and confused by how to get only the first question from a section and then load in each question consecutively for that section until all have been called and then do a callback to show the section has been completed. Thanks
Do not have multiple id's in html called "section." Do not have multiple keys in your JSON on the same level called "section". Keys in JSON on the same level should be unique just as if you are thinking about a key-value hash system. Then you'll actually be able to find the keys. Duplicate JSON keys on the same level is not valid. One solution can be section1, section2, etc. instead of just section. Don't rely on data-section attribute in your HTML - it's still not good if you have "section" as the duplicate html id's and as duplicate JSON keys. If you have only one section id in HTML DOM, then in your JSON you must also have just one thing called "section" e.g.: var whatever = { "section" : { "1": { "question" : { "1" : { "id" : "1a", "title" : "question1a" }, "2" : { "id" : "2a", "title" : "question2a" } } }, "2": { "question" : { "1" : { "id" : "1a", "title" : "aquestion1a" }, "2" : { "id" : "2a", "title" : "aquestion2a" } } } } } console.log(whatever.section[1].question[1].title); //"question1a" To get question, do something like this: function loadQuestions(mySectionNum) { $.getJSON('whatever.json', function(data){ var layeriwant = data.section[mySectionNum].question; $.each(layeriwant, function(question, qMeta) { var desired = '<div id="question-' + qMeta.id + '"' + '>' + '</div>'; $("#section").append(desired); var quest = $("#question-" + qMeta.id); quest.append('<div class="title">' + qMeta.title + '</div>'); //and so on for question content, answer choices, etc. }); }); } then something like this to actually get the questions: function newQuestion(){ var myHTMLSecNum = $("#section").attr('data-section'); loadQuestions(myHTMLSecNum); } newQuestion(); //below is an example, to remove and then append new question: $('#whatevernextbutton').on('click',function(){ var tmp = parseInt($("#section").attr('data-section')); tmp++; $("#section").attr('data-section', tmp); $("#section").find('*').remove(); newQuestion(); });
Technically your getJSON function always retrieves the same data. Your code never compares the id given to the id you're extracting. Your getJSON should look something like: function loadQuestion( $section ) { for (var i = 0; i < questions.section.length; i++) { var item = questions.section[i]; if (item.id === $section) { for (var j = 0; j < item.questions.length; j++) { $('#questions').append('<li id="' + item.questions[i].id + '">' + item.questions[i].title + ' <button class="btn" data-response="a">A</button><button class="btn" data-response="b">B</button></li>' ); } } } } Modify your JSON to: var questions = { section: [{ id: 1, questions: [{ id: "1a", title: "This is question1a" },{ id: "2a", title: "This is question2a" }]},{ id: 2, questions: [{ id: "1a", title: "This is question1a" },{ id: "2a" title: "This is question2a" }] }] }; Edit: your first parameter of getJSON is the URL of the JSON returning service. You don't need getJSON at all if your JSON is already defined on the client. I have modified the code above.