Collecting arrays from JSON object and element class names and comparing them - javascript

I have an external JSON feed that displays room bookings for multiple rooms. The problem is that it only shows the timeslots that are booked and does not show empty slots/appointments in the data object. Issue is the feed does not contain the empty timeslots that have not been booked yet.
So I came up with an idea to have a preformatted HTML 'template' page with some data already present - effectively to overlay the JSON data in the booked timeslots and leave the empty timeslots as just static HTML.
Idea was to name the <td> elements with a class name that denotes the room id and the timeslot in 24hr time. Then to construct an array based on all the <td> class names. Then filter my JSON object bookings times and retrieve back an array that contained the room id and booking start time (amongst other necessary data).
Next step is to loop through both arrays and if there is a match against the room id and booking start time across both arrays, then print the data into that cell (which can be accessed via its class name). Right now, I am having problems with getting the arrays to look the same.
The 'simplified' JSON file (just a selection of whole object):
var response={
"bookings": {
"group_id": 12306,
"name": "Public Meeting Rooms",
"url": "http:theurlfeed.from.libcal",
"timeslots": [{
"room_id": "36615",
"room_name": "Meeting Room 2A",
"booking_label": "Mahjong",
"booking_start": "2016-01-20T10:00:00+10:00",
"booking_end": "2016-01-20T11:00:00+10:00"
}, {
"room_id": "36615",
"room_name": "Meeting Room 2A",
"booking_label": "Mahjong",
"booking_start": "2016-01-20T11:00:00+10:00",
"booking_end": "2016-01-20T12:00:00+10:00"
}, {
"room_id": "36616",
"room_name": "Meeting Room 2B",
"booking_label": "IELTS",
"booking_start": "2016-01-20T10:00:00+10:00",
"booking_end": "2016-01-20T11:00:00+10:00"
}, {
"room_id": "36616",
"room_name": "Meeting Room 2B",
"booking_label": "recording",
"booking_start": "2016-01-20T12:00:00+10:00",
"booking_end": "2016-01-20T13:00:00+10:00"
}, {
"room_id": "36616",
"room_name": "Meeting Room 2B",
"booking_label": "Luke",
"booking_start": "2016-01-20T18:00:00+10:00",
"booking_end": "2016-01-20T19:00:00+10:00"
}],
"last_updated": "2016-01-20T12:40:36+10:00"
}
}
Here is the HTML (for the sake of simplification I haven't shown the complete structure):
<table border="1" id="rooms-table">
<thead><tr><th></th> <th>10am</th><th>11am</th><th>12pm</th><th>1pm</th><th>2pm</th><th>3pm</th><th>4pm</th><th>5pm</th><th>6pm</th><th>7pm</th></tr></thead>
<tbody id="main-table-body">
<tr>
<td>Meeting Room 2A</td>
<td class="36615 10:00"></td>
<td class="36615 11:00"></td>
<td class="36615 12:00"></td>
</tr>
<tr>
<td>Meeting Room 2B</td>
<td class="36616 10:00"></td>
<td class="36616 11:00"></td>
<td class="36616 12:00"></td>
</tr>
<tr>
</tbody>
</table>
And this is my JavaScript (contains pseudo code at the bottom for concepts I have not yet coded):
//Convert timestamp to readable format and remove date
var Timeslots = response.bookings.timeslots;
function getTime(timestamp) {
var time = new Date(timestamp);
return [ time.getHours(), time.getMinutes() ].map(function(time){
return ['', "0"][+(String(time).length < 2)] + String(time);
}).join(':');
}
for(var i in Timeslots) (function(Timeslots){
Timeslots.booking_start = getTime(Timeslots.booking_start);
Timeslots.booking_end = getTime(Timeslots.booking_end);
})(Timeslots[i]);
console.log(response);
var roomList = new Array;
var tdClassList;
//function to detect if timeslot is booked or not
//If booked, assigns the booking name and changes bg color of cell
$.each(response.bookings.timeslots, function(index, timeslot) {
var roomBooking = timeslot.room_id + ' ' + timeslot.booking_start;
roomList[roomBooking] = roomBooking;
tdClassList = $('td').map(function () {
return $(this).attr('class');
}).get();
});
console.log(tdClassList);
console.log(roomList);
// This code is incomplete
// It will need to loop through both arrays and compare values and then write some css and html if match is found across the arrays
/*if (roomList == tdClassName) {
$( "td" ).html(timeslot.booking_label)
$( "td" ).css( "background-color", "red" );
} else { $( "td" ).html("");
}*/
Here is a link to a working JS Fiddle showing all elements in play: https://jsfiddle.net/coolwebs/L0ybd0dm/1/
My issue right now is that the array that is being created from the JSON object is coming in as key value pairs.
Instead of returning:
["36615 10:00", "36615 11:00", "36615 12:00", "36615 30:00", ...]
It is returning:
[36615 10:00: "36615 10:00",36615 11:00: "36615 11:00", 36615 12:00: "36615 12:00", 36615 13:00: "36615 30:00", ...]
Um, what am I doing wrong?

This is far from perfect or complete but does simplify your process somewhat.
I don't see any need to create the cell array. Once you have parsed the incoming data, you can loop over the cells and do the matching right away.
I used a simple object to make the room/start comparison.
// example
var timeClasses ={"3456 02:00":true, "56854 11:00": true}
Then when you loop through the table it's a simple check to see if the property matching your cell classes exists
success: function(response) {
var data = response.bookings.timeslots;
var timeClasses = {};
var Timeslots = data.map(function(item) {
// parse the dates
item.booking_start = getTime(item.booking_start);
item.booking_end = getTime(item.booking_end);
// update hashmap
timeClasses[item.room_id + ' ' + item.booking_start] = true;
return item;
});
$('tbody tr').each(function() {
$(this).find('td:gt(0)').each(function() {
var className = $(this).attr('class');
if (timeClasses[className]) {
$(this).css('background', 'red')
}
});
});
}
This is really only intended to give you a starting point.
I don't really like the classes methodology ...especially with the space in them.
You will also need to revisit how you will visualize the full timeslot that a booking covers and will need some more logic to apply for the cells in middle of each booking
DEMO

Related

How to Extract data based on the values in one array after matching the corresponding values from another array in JavaScript?

This is the URL from GeoServer to get feature info
{"type":"FeatureCollection","features":[{"type":"Feature","id":"weather_warning_day_1.fid--418ec0da_178b69d5dfc_-715c","geometry":null,"properties":{"issue_date":"2021-04-09","updated_at":"2021-04-09T09:26:33+05:30","utc_time":0,"state_name":"Odisha","state_id":21,"district_name":"MAYURBHANJ","district_id":232,"api_district_name":"MAYURBHANJ","day_1":"6,9,10","day1_color":3}}],"totalFeatures":"unknown","numberReturned":1,"timeStamp":"2021-04-09T15:38:19.536Z","crs":null}
the data I want to extract is of variable: "day_1":"6,9,10"
which I got from the layer and stored it in the variable as
var warning_day_1 = weather_warning_layer_data.features[0].properties.day_1
so basically the input is "day_1":"6,9,10"
which I have stored in the array as
[{"warning":"6"},{"warning":"9"},{"warning":"10"}]
and corresponding output should be Dust Storm, Heat Wave, Hot Day
Dust Storm, Heat Wave, Hot Day
or if the input was "day_1":"2,5"
then output should have been Heavy Rain, Hailstorm
or if the input was "day_1":"1"
then output should have been No Warning
After reading the data of the string and creating its array, I have to compare it with another array and extract the key values (display) corresponding to the key values (warning) in the 1st array.
var warning_data_split = warning_day_1.split(/[ ,]+/);
var warning_data_from_api_array = new Array;
warning_data_from_api_array.push(warning_data_split);
for (var i = 0; i < warning_data_from_api_array.length; i++) {
var item_in_array_to_compare = warning_data_from_api_array[i];
if(warning_data_from_api_array[item_in_array_to_compare.warning_data_from_api_array])
{warning_data_from_api_array[item_in_array_to_compare.warning_data_from_api_array].push(item_in_array_to_compare);}
else {
warning_data_from_api_array[item_in_array_to_compare.warning_data_from_api_array] = [item_in_array_to_compare];}}
let final_array_to_compare = item_in_array_to_compare
final_array_to_compare = final_array_to_compare.map(x => ({warning: x}));
/// this is the first array ////////////
The values in this array are not static in length, as it keeps on changing like, sometimes the array has value [1] or [1,2], [2,5,8], [4,7,12], etc
so I have to extract the corresponding values of display from the lookup array given below
var warning_code_meaning_list = [
{ warning:"1", display:"No Warning"},
{ warning:"2", display:"Heavy Rain"},
{ warning:"3", display:"Heavy Snow"},
{ warning:"4", display:"Thunderstorm & Lightning, Squall etc"},
{ warning:"5", display:"Hailstorm"},
{ warning:"6", display:"Dust Storm"},
{ warning:"7", display:"Dust Raising Winds"},
{ warning:"8", display:"Strong Surface Winds"},
{ warning:"9", display:"Heat Wave"},
{ warning:"10", display:"Hot Day"},
{ warning:"11", display:"Warm Night"},
{ warning:"12", display:"Cold Wave"},
{ warning:"13", display:"Cold Day"},
{ warning:"14", display:"Ground Frost"},
{ warning:"15", display:"Fog"}
]
The data which I am getting in warning_day_1 (in the very first line of the code) is a string (this couldn’t be saved as float/integer in the database column because sometimes there are more than 1 warning for a specific place, so I have stored this as a text in the database)
Which I’m converting to an array after reading it from the API
Now this string, which I am fetching from API has variable data,
Some time single digit like: 1
Sometime multiple : 1,2,3
And each of the integer present in this array corresponds to the specific text shown in the next array like if the warning is 2 it means the heavy rainfall,
but if the string (later converted to an array, with “warning” as a key) has 2,5 as value, it means: heavy rainfall & Hailstorm
I want that the values which come up in array 1 (the dynamic one) got match with the 2nd array ( a sort of lookup array) and fetch its display value as output.
How to do so?
You could use an object to map your warnings to messages.
Try this:
const data = {"type":"FeatureCollection","features":[{"type":"Feature","id":"weather_warning_day_1.fid--418ec0da_178b69d5dfc_-715c","geometry":null,"properties":{"issue_date":"2021-04-09","updated_at":"2021-04-09T09:26:33+05:30","utc_time":0,"state_name":"Odisha","state_id":21,"district_name":"MAYURBHANJ","district_id":232,"api_district_name":"MAYURBHANJ","day_1":"6,9,10","day1_color":3}}],"totalFeatures":"unknown","numberReturned":1,"timeStamp":"2021-04-09T15:38:19.536Z","crs":null}
var warning_code_meaning_list = {
"1":"No Warning",
"2":"Heavy Rain",
"3":"Heavy Snow",
"4":"Thunderstorm & Lightning, Squall etc",
"5":"Hailstorm",
"6":"Dust Storm",
"7":"Dust Raising Winds",
"8":"Strong Surface Winds",
"9":"Heat Wave",
"10":"Hot Day",
"11":"Warm Night",
"12":"Cold Wave",
"13":"Cold Day",
"14":"Ground Frost",
"15":"Fog",
};
results = data["features"].map(feature => {
return feature.properties.day_1.split(',').map(code => {
return warning_code_meaning_list[code];
});
});
That gives you an array of arrays of the displays:
[ [ 'Dust Storm', 'Heat Wave', 'Hot Day' ] ]

Multiple for loops inside each other

I've bumped into a problem which really bothers me. I've tried to Google the problem but without luck. I got the following code where I want to apply specific nodes to the DOM, getting the information from an array. The while loop works perfectly fine, but it's when it comes to the "for" loop stuff gets funky. I want to filter the different bits using the "collection_id" from the "bitValues" array up against the "collectionValues" id's. The information which should be applied looks like following:
var bitValues = [{
'id': 1,
'collection_id': 1,
'description': "Amazing description",
'radio': "ANR",
'date': "01-01-2018",
'time': "11:45:00",
'seconds': 10,
'delta': '8.5',
'gain_loss': '2',
'total_listeners': '13.343',
'delta_listeners': '22.340',
}, {
'id': 2,
'collection_id': 2,
'description': "DR P3 music is amazing",
'radio': "DR P3",
'date': "05-01-2018",
'time': "13:45:00",
'seconds': 16,
'delta': '12',
'gain_loss': '82',
'total_listeners': '15.343',
'delta_listeners': '102.340',
},
{
'id': 3,
'collection_id': 2,
'description': "Let's go!",
'radio': "Nova FM",
'date': "25-01-2018",
'time': "23:45:00",
'seconds': 126,
'delta': '53',
'gain_loss': '17',
'total_listeners': '28.343',
'delta_listeners': '22.340',
}
];
let collectionValues = [{
'id': 1,
'demographic': "All females",
'delta': "19.5",
'gain_loss': "62.126",
'total_listeners': '43.343',
'delta_listeners': '22.340',
bits: bitValues
}, {
'id': 2,
'demographic': "All 12-24",
'delta': "10.5",
'gain_loss': "52.126",
'total_listeners': '153.343',
'delta_listeners': '132.340',
bits: bitValues
}];
The jQuery to apply the data looks like this:
while (i < collectionAmount) {
(Code that works)...
for (let n = 0; n < bitAmount; n++) {
collection_id = collectionValues[i].id;
bit_reference_id = bitValues[n].collection_id;
if(collection_id == bit_reference_id) {
$('.freestyle-deltas_details_bits').append(`
<tr>
<td><span
class="font-weight-bold">Bit
${bitValues[n].id}: </span>(
${bitValues[n].time}, ${bitValues[n].seconds} sec)</td>
<td><span class="colorChangeByValueDelta">${bitValues[n].delta}%</span></td>
<td><span class="colorChangeByValueGainLoss">${bitValues[n].gain_loss}%</span></td>
<td>${bitValues[n].total_listeners}</td>
<td>${bitValues[n].delta_listeners}</td>
</tr>
`);
}
};
i++;
}
Can anyone help with this problem?
Thanks!
Excellent time to use double filtering! This is why I love Javascript.
const result = first.filter(firstId => second.filter(secondId => firstId.id === secondId.id))
What filter does is that it goes through all the elements in an array and applies logic on it. Since filter takes a function as an argument, it's a perfect way to apply the second filtering on top of it.
If you use bitValues as the first array you'll end up with a list containing the objects in bitValues that are matched in collectionValues.
let table = document.createElement("table");//creating a table element
$(table).append("<tr></tr>");// inserting a row element in the table
let head = table.getElementsByTagName("tr")[0];// selecting the row elment previously selected
for(key in bitValues[0]){//looping through all keys in the object
$(head).append(`<th>${key}</th>`);// putting each key as a head in the first row of the table
}
$(table).append("<tbody></tbody>")// creating a table body
for(row of bitValues){// looping through each object in bitValues array
let tr = document.createElement("tr");// creating a row for each object in the array
for(x in row){// looping through each key of an object in the array
$(tr).append(`<td>${row[x]}<td>`);// putting the value of each key in a td tag and appending it to tr
}
table.appendChild(tr);// appending the tr to the table
}
$('.freestyle-deltas_details_bits').append(table);
Hope it helps
Although I don't know what collectionAmount is, I'd guess it is just the number of items in collectionValues.
What you want is a way, to only render those items from bitValues, which have a collection_id that corresponds to the id of the collection currently being worked on, right? In this case, use a filter and a reduce. Since you can loose the filter by adding a simple if-else inside the reduce, just use a reduce.
First off, let's clean up a bit and move the template into it's own function:
/* template */
const bitValueTemplate = data => `<tr>
<td>
<span class="font-weight-bold">Bit ${data.id}:</span>
(${data.time}, ${data.seconds} sec)
</td>
<td>
<span class="colorChangeByValueDelta">${data.delta}%</span>
</td>
<td>
<span class="colorChangeByValueGainLoss">${data.gain_loss}%</span>
</td>
<td>${data.total_listeners}</td>
<td>${data.delta_listeners}</td>
</tr>`;
Now to the tricky bit, which will build a complete HTML string from the list of bitValues at once (meaning it contains multiple <tr> elements):
/* renders a list into a simple string, only adds items where a certain
property has a certain value. uses a template function/render function to
render each item, then adds it to the end of the overall string. returns
the complete string when finished */
const renderByPropWith = (prop, value, renderFn, xs) => {
return xs.reduce((html, x) => {
if (x[prop] === value) {
return html + renderFn(x);
}
return html;
}, '');
}
OK, time to try it:
var collectionAmount = collectionValues.length;
var i = 0;
while (i < collectionAmount) {
// (Code that works)...
var html = renderByPropWith('collection_id', collectionValues[i].id, bitValueTemplate, bitValues);
$('.freestyle-deltas_details_bits').append(html);
i++;
}

Using a CSV or JS file to populate specific TD in HTML table

I've had a search around but not been able to see if this is possible.
I've developed a HTML page to display the stock status on a number of products. I currently edit each status manually each day (where the status has changed from previous day) and I wish to automate it where ever possible.
For example, I currently have the HTML page displaying by Manufacturer with each product and stock status in a separate table.
.collapse{
cursor: pointer;
display: block;
background: rgb(0, 156, 0);
color: rgb(255, 255, 255);
padding: 6px 12px;
border: solid white;
}
.collapse:hover{
color: rgb(231, 230, 229);
font-weight: bold;
}
.collapse + input{
display: none;
}
.collapse + input + div{
display:none;
}
.collapse + input:checked + div{
display:block;
}
<body>
<div><label class="collapse" for="_bmw">BMW</label>
<input id="_bmw" type="checkbox">
<div><br>
<table border="1" cellpadding="1" cellspacing="1">
<thead>
<tr style="font-weight: bold">
<td style="width: 75px;">Product Code</td>
<td style="width: 200px;">Model</td>
<td style="width: 200px;">Stock Status</td>
</tr>
</thead>
<tbody>
<tr>
<td>1000</td>
<td>M1</td>
<td>Available</td>
</tr>
<tr>
<td>1001</td>
<td>M3</td>
<td>Out of stock</td>
</tr>
<tr>
<td>1002</td>
<td>M5</td>
<td>Available</td>
</tr>
</tbody>
</table>
<br>
</div>
</div>
<div><label class="collapse" for="_ford" style="font-size: 17px;">Ford</label>
<input id="_ford" type="checkbox">
<div><br>
<table border="1" cellpadding="1" cellspacing="1">
<thead>
<tr style="font-weight: bold">
<td style="width: 75px;">Product Code</td>
<td style="width: 200px;">Model</td>
<td style="width: 200px;">Stock Status</td>
</tr>
</thead>
<tbody>
<tr>
<td>1003</td>
<td>Fiesta</td>
<td>Available</td>
</tr>
<tr>
<td>1004</td>
<td>Mondeo</td>
<td>Available</td>
</tr>
<tr>
<td>1004</td>
<td>Escort</td>
<td>End of life</td>
</tr>
</tbody>
</table>
<br>
</div>
</div>
</body>
Is it possible to use javascript or jquery to perform a lookup of the Product Code within the HTML table and return a value from a JS (or CSV) file within the Stock Status TD?
I've created a JS file with the following data, now I just need to know how to populate the Stock Status data based on a lookup of the Product Code:-
[
{
"FIELD1": "1000",
"FIELD2": "Available"
},
{
"FIELD1": "1001",
"FIELD2": "Out of stock"
},
{
"FIELD1": "1002",
"FIELD2": "Available"
},
{
"FIELD1": "1003",
"FIELD2": "Available"
},
{
"FIELD1": "1004",
"FIELD2": "Available"
},
{
"FIELD1": "1005",
"FIELD2": "End of life"
},
]
I'm new to JS & JQuery so any help is appreciated. If I've missed anything or you need further info please ask.
I'll break this down into it's seperate steps:
1) The JSON file
If we carefully choose the format of our JSON file, we can provide all of the data and even more that we need to construct the entire page.
Hence I would put all the information about all cars we have inside this file, so we never have to update the HTML file after adding brands or models.
If we would only keep the availability of a car inside the JSON file, we would need to update both the JSON file AND also the HTML file to add a brand or type.
The availability is also better noted as an integer representing the amount of cars there are available instead of a string. If it was a string, we would need to parse that string to see if there's still cars available.
By seperating the id of the car from it's product code, we can keep the product code as a string so that it can contain more than only numbers and also still keep an easy way to sort our cars. Remember that strings sort differently than integers: "10" < "9" === true and 10 < 9 === false. Else this could lead to problems if we ever have a car with code "999".
An added advantage is that this nicely maps to table columns if we'd ever move this into a database.
[
{
"availability": 25,
"brand": "bmw",
"code": "1000",
"id": 1,
"model": "m1"
},
{
"availability": null,
"brand": "bmw",
"code": "1001",
"id": 2,
"model": "m3"
},
{
"availability": 10,
"brand": "bmw",
"code": "1002",
"id": 3,
"model": "m5"
},
{
"availability": 7,
"brand": "ford",
"code": "1003",
"id": 4,
"model": "fiesta"
},
{
"availability": 14,
"brand": "ford",
"code": "1004",
"id": 5,
"model": "mondeo"
},
{
"availability": null,
"brand": "ford",
"code": "1005",
"id": 6,
"model": "escort"
}
]
2) Fetching the file
We have two mechanisms here to do this. Either the old XMLHttpRequest() if we have to be compatible with old browsers. Or the fetch() API for new browsers.
This choice will determine if we have to use callbacks or promises. ( Unless we transform the XMLHttpRequest version into a promise as well. )
XMLHttpRequest:
// The path where we can find the JSON file.
const PATH_CARS = 'http://path/to/cars.json';
// A getJSON function that will create an ajax request to the provided URL.
const getJSON = ( url, callback ) => {
// Create a new XMLHttpRequest.
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
const request = new XMLHttpRequest();
// Open the request before setting other properties. ( IE11 )
request.open( 'GET', url );
// When the request gets the file, we want to run the callback.
// The responseText will be the JSON string inside our json file.
request.onload = function() {
callback( request.responseText );
};
request.send();
};
// Use the function to get the file.
// Parse and log the contents of the file once it arrives.
getJSON( PATH_CARS, function( response ) {
// cars will be a string here. We want the actual JS object represented by the JSON string
const cars = JSON.parse( response );
console.log( cars );
});
fetch:
// The path where we can find the JSON file.
const PATH_CARS = 'http://path/to/cars.json';
// Same thing, but using the fetch API for browsers that support it.
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
// The fetch API uses promises instead of callbacks to handle the results.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
fetch( PATH_CARS )
.then( response => response.json())
.then( cars => {
console.log( cars );
});
3) Creating the table
We'll switch the logic around a little bit. Instead of having a fixed HTML that we want to update with values coming from a file, we can just create the entire table from the JSON file so that all the updates are already in the table.
If we then need to update the table again, we can just rerender the entire table instead of trying to match the HTML nodes with the correct values inside the JSON. This won't work that fast for huge amount of cars, ( think 1000+ ) but still way faster than updating every car individually.
This falls into what we call the Model-View-Controller architecture. The JSON file gives us a model of the cars. The HTML table is a view of that model. The javascript code binds it all together as the controller. The controller fetches the model, and turns the model into a view representing that model. Every time the model changes, ( you add a car to the JSON file ), we can request the controller to fetch the updated model ( load the JSON file ) and the update the view. ( render the tables again )
// We need to create a table for each brand.
// We need to create a table row for each car model of that type.
// For big projects, one would use a templating language to create the HTML.
// For something as small as thing, we can resort to simple string manipulation.
const createTables = brands => {
// Loop over all the brands, creating a table for each brand.
// I'll use a reduction this time, to show the difference and similarities between reduce() and the forEach() we used in the previous step.
const tables = brands.reduce(( html, brand ) => {
// Copy the header, replacing the brand name.
const header = `<table><thead><tr><th colspan="3">${ brand.name }</th></tr><tr><th>Product Code:</th><th>Model:</th><th>In Stock:</th></tr></thead><tbody>`;
// Loop over the cars and create a row for each car.
// Since we create the same amount of rows as we have cars inside the array, we can use .map()
const rows = brand.cars.map( car => {
// Since we changed the availability to a number, we hve to recreate the string for it.
// This allows us to easily change the label without having to change the logic in multiple places
const availability_label = Number.isInteger( car.availability )
? `${ car.availability } in stock.`
: 'End of life.';
return `<tr><td>${ car.code }</td><td>${ car.model }</td><td>${ availability_label }</td></tr>`;
});
// Append the current header, car rows and the closing tags to the previous HTML, then return.
return html += `${ header }${ rows.join('') }</tbody></table>`;
}, '');
// Return the HTML string. We could also just return the reduction directly, wihtout using th tables variable in between.
return tables;
};
4) Putting it all together
Using all the techniques and functions we created in the examples, we now have everything to create our entire app. I've added another helper function that groups all the cars into their brands, so creating the tables is easier and more clear.
I have mocked up the fetching of the JSON file in the example below so we can actually run the code. IN your own code, you would use the real fetch() or XMLHttpRequest() code.
// FAKE FETCH, DO NOT USE IN THE REAL CODE
const fetch = url => Promise.resolve({json: () => JSON.parse('[{"availability":25,"brand":"bmw","code":"1000","id":1,"model":"m1"},{"availability":null,"brand":"bmw","code":"1001","id":2,"model":"m3"},{"availability":10,"brand":"bmw","code":"1002","id":3,"model":"m5"},{"availability":7,"brand":"ford","code":"1003","id":4,"model":"fiesta"},{"availability":14,"brand":"ford","code":"1004","id":5,"model":"mondeo"},{"availability":null,"brand":"ford","code":"1005","id":6,"model":"escort"}]')});
// The path where we can find the JSON file.
const PATH_CARS = 'http://path/to/cars.json';
// Same thing, but using the fetch API for browsers that support it.
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
// The fetch API uses promises instead of callbacks to handle the results.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
const getCars = url => fetch( url )
.then( response => response.json())
.catch( error => console.error( error ));
// We need to group all the different cars into their respective brands.
const groupBrands = cars => {
// Create a temporary object we'll use to store the different brands.
const brands = {};
// Loop over all the car models, grouping them into the correct brand.
cars.forEach( car => {
// Extract the brand name from the car item.
const brand = car.brand;
// If we haven't seen this brand yet, add it to the different brands as an array.
if ( !brands.hasOwnProperty( brand )) brands[ brand ] = [];
// Push the car model to the brand.
brands[ brand ].push( car );
});
// We now have an object containign all the cars grouped by brand.
// It would be easier however, if we had ana rray we can loop over easily.
// So transform the object back into an array.
// We loop over the entries array of the object to extarct the name and cars at the same time, then wrap them back into an object.
return Object.entries( brands ).map(([ name, cars ]) => ({ name, cars }));
// This entire step can be done in one expression by using array.reduce() instead of array.forEach()
// We could also just return the object and loop over the entries in the render function.
// My personal preference is to always use an array to represent multiples of something:
// A 'collection' of 'brand' objects with each brand containing a 'collection' of 'car' objects.
// We could also already do this grouping inside the JSON file itsself, but I preferred to keep the JSON file itsself simple for this example.
};
// We need to create a table for each brand.
// We need to create a table row for each car model of that type.
// For big projects, one would use a templating language to create the HTML.
// For something as small as thing, we can resort to simple string manipulation.
const createTables = brands => {
// Loop over all the brands, creating a table for each brand.
// I'll use a reduction this time, to show the difference and similarities between reduce() and the forEach() we used in the previous step.
const tables = brands.reduce(( html, brand ) => {
// Copy the header, replacing the brand name.
const header = `<table><thead><tr><th colspan="3">${ brand.name }</th></tr><tr><th>Product Code:</th><th>Model:</th><th>In Stock:</th></tr></thead><tbody>`;
// Loop over the cars and create a row for each car.
// Since we create the same amount of rows as we have cars inside the array, we can use .map()
const rows = brand.cars.map( car => {
// Since we changed the availability to a number, we hve to recreate the string for it.
// This allows us to easily change the label without having to change the logic in multiple places
const availability_label = Number.isInteger( car.availability )
? `${ car.availability } in stock.`
: 'End of life.';
return `<tr><td>${ car.code }</td><td>${ car.model }</td><td>${ availability_label }</td></tr>`;
});
// Append the current header, car rows and the closing tags to the previous HTML, then return.
return html += `${ header }${ rows.join('') }</tbody></table>`;
}, '');
// Return the HTML string. We could also just return the reduction directly, wihtout using th tables variable in between.
return tables;
};
// We have a JSON file, we can fetch that file, we can create tables from the contents, time to put it all together.
// Fetch the JSON file.
getCars( PATH_CARS )
// Group the cars into brands.
.then( groupBrands )
// Create a table for each group.
.then( createTables )
// Render the tables into the page.
.then( html => {
const tableHook = document.querySelector( '#cars' );
if ( tableHook ) tableHook.innerHTML = html;
// else throw new Error(); something went wrong.
})
// Catch any errors encountered.
.catch( error => console.error( error ));
<html>
<head>
<title>Car Stocks</title>
</head>
<body>
<div id="cars"></div>
</body>
</html>
5) Upgrades
Alot of the code above can be written way shorter, but I intentionally used the longer versions to keep the amount of new things to learn to a minimum. Same code can be written with callbacks, in the case that promises aren't supported. Internally, the functions will mostly stay the same.
I'll leave re-adding the CSS again up to you, since that was working well already.
I've implemented the following code which works with the JSON data I export from my daily stock report.
$(document).ready( function() {
$.ajax({
url: "data.json",
method: "GET",
dataType: "json",
success: function(data) {
var $tbody = $("table#data tbody");
$.each(data, function(i, data) {
var $tr = $("<tr></tr>");
$tr.appendTo($tbody);
var $td = $("<td></td>");
$td.html(data.ProdCode)
.appendTo($tr);
$td = $("<td></td>");
$td.html(data.Model)
.appendTo($tr);
$td = $("<td></td>");
$td.html(data.StockStatus)
.appendTo($tr);
});
},
error: function(jqXHR, textStatus, errorThrown) {
console.log("AJAX ERROR",textStatus,errorThrown,jqXHR);
}
});
});

Get value from nested JavaScript object in CasperJS

I'm trying to dig into a nested javascript array to grab the first instance of an object. Here's the code:
var utils = require('utils');
var casper = require('casper').create();
casper.start('http://en.wikipedia.org/wiki/List_of_male_tennis_players', function() {
this.echo(this.getTitle());
// Get info on all elements matching this CSS selector
var tennis_info_text = this.evaluate(function() {
var nodes = document.querySelectorAll('table.sortable.wikitable tbody tr');
return [].map.call(nodes, function(node) { // Alternatively: return Array.prototype.map.call(...
return node.textContent;
});
});
// Split the array into an array of object literals
var tennis_data = tennis_info_text.map(function(str) {
var elements = str.split("\n");
var data = {
name : elements[1],
birth : elements[2],
death : elements[3],
country : elements[4]
};
return data;
});
// Dump the tennis_names array to screen
utils.dump(tennis_data.slice(1,5));
});
casper.run();
The result of stdout is this:
{
"name": "Acasuso, JoséJosé Acasuso",
"birth": "1982",
"death": "–",
"country": " Argentina"
},
{
"name": "Adams, DavidDavid Adams",
"birth": "1970",
"death": "–",
"country": " South Africa"
},...
For the name element, I'm getting everything from the tr row, which matches 2 elements when you look at the target url source. What I want is just the second part of the name element with class "fn"; for instance: "David Adams", "José Acasuso". I'm thinking something like name:elements[1].smtg should work, but I've had no luck.
Additionally, how would I print the available object keys from the elements object?
The problem is that the first cell contains two elements which contain the name and first name of the player with different ordering. When taking the textContent of the whole cell, both name representations are put into the same string, but in the browser only one of them is visible. If you want only to access the visible one, you need to explicitly crawl it.
You could write a custom function that removes the duplicate name from the string, but it is easier to just take the correct element's textContent.
This can be easily done in the page context:
var tennis_data = this.evaluate(function() {
var nodes = document.querySelectorAll('table.sortable.wikitable tbody tr');
return [].map.call(nodes, function(node) {
var cells = [].map.call(node.querySelectorAll("td"), function(cell, i){
if (i === 0) {
return cell.querySelector(".fn").textContent;
} else {
return cell.textContent;
}
});
return {
name: cells[0],
birth: cells[1],
...
}
});
});
Additionally, how would I print the available object keys from the elements object?
elements is an array of strings so there are no keys that you can access besides the array indexes and array functions.

Create array of selected items in a list and display their JSON data

I'm trying to perform what seems like a simple task and display <li>'s for each item in a JSON object. Then, when I click each displayed item, get additional JSON info from all the selected items, and create an array that is stored in a variable for use later.
Here is my JSON. Nothing crazy, simple array of data.
[
{
"subCategory": "Foo",
"total": 100,
"converted": 25,
"revenue": 500
},
{
"subCategory": "Bar",
"total": 100,
"converted": 25,
"revenue": 1000
}
]
With my JS (using jQuery), I'm getting and displaying each item correctly, but I'm not getting the data that I want when i select an item that is getting displayed. In the following JS, I realize I'm not doing much beyond adding the class to the li displayed, but I'm stuck on how to go about getting the data for each selected item and over write the variables below.
(function(){
$('.bubbles').on('click', 'li', function(){
$(this).toggleClass('selected');
});
$.getJSON('data.json', function(data) {
$.each(data,function(i, value){
// Display bubbles
$('.marketingBubbles').append("<li class='"+ value.leads + " "+ value.status + " "+ value.lift + "'>");
// Get data and create arrays
var totalArray = [value.total], // Get values from all totals
convertedArray = [value.converted], // Get all values from converted
revenueArray = [value.revenue]
// Add up numbers in arrays
var totalConverted = eval(convertedArray.join('+')), // Sum of all converted leads
totalLeads = eval(totalArray.join('+')), // Sum of all total leads
totalRevenue = eval(indicatorRevenue.join('+')) // Sum of all revenue
});
});
})();
HTML
<ul class="marketingBubbles"></ul>
Eventually, i would like to take the values within an array, no matter how many times it is changed, perform some simple math operations with the data and display in various divs on the screen.
Any help would be appreciated.
Thank you!

Categories

Resources