I'm working on creating an analytics page that uses data stored in a text file. The text file is used for my highcharts graph and is separated like so:
November 7th 6AM,19.8
November 7th 6PM,19.8
November 8th 6AM,20.4
November 8th 6PM,14.6
November 9th 6AM,15.9
November 9th 6PM,15.1
November 10th 6AM,16.4
November 10th 6PM,16.4
I'm looking to separate that data into 12 calendar panes that will display overall discrepancies for each month. For example, if a date had the value 16, and then it dropped to 10 the next reading, to take a cumulative sum for how many times that happened for the month.
How do I make sure that it reads from the text file in proper order and that it checks to see if the next value went below 10 and tallies that somewhere?
Yo can use jQuery to separate the file data into array.
$(document).ready(function() {
//fetch text file
$.get('text.txt', function(data) {
//split on new lines
var lines = data.split('\n');
for(var i=0;i<lines.length;i++) {
// use lines[i] the way you want
}
});
});
Then you can use this to get sub-strings:
var a = s.indexOf(',');
var b = s.substring(a,4);
This way you can separate the data and use it as desired.
You need to
Fetch the data file
One usually does this with AJAX (XMLHttpRequest)
Separate your datapoints
Your data seems to be neat enough that you can, for every line in the file, get the name of the month with line.split(" ")[0] and the value with line.split(",")[1]. Splitting a text to lines can usually be as easy as text.split("\n").
Iterate over results
For every month-value pair you now have, compare the name of the months. If it is a match and the value has changed by x, increment a value in your books, e.g. discrepancies[monthName]++. Save the name-value pair and continue.
Example:
// Get the data
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == 200) { // If the request is complete with status code "OK"
handleImportantData(req.responseText);
}
};
// Define the request to be using the method GET to an URL asynchronously.
req.open("GET", "important_data.txt", true);
req.send();
function handleImportantData(data) {
var treshold = 5.0;
var months = {};
var discrepancies = {};
// Separate data points.
var lines = data.split("\n");
lines.forEach(function(line) {
// The || 0.0 part sets the value to zero if our data is malformed.
// We parse the value as a float to prevent it being saved as a string,
// which would make arithmetic operations on it not work as expected.
months[line.split(" ")[0]] = parseFloat(line.split(",")[1]) || 0.0;
});
// Find discrepancies.
var previous = {
name: "",
value: 0.0
};
for (var name in months) {
// If not already there, initialize the data point with ternary operation including NOP.
discrepancies[name] ? {} : discrepancies[name] = 0.0;
// If we're still talking about the same month as before and the value
// has changed for more than our treshold, save it as a discrepancy.
if name === previous.name && Math.abs(previous.value - months[name]) > treshold {
discrepancies[name] ++;
}
previous = {
name: name,
value: months[name]
}; // Set this as the previous value.
}
// Here we have ready discrepancy data stored in the variable discrepancies.
}
Related
Sheet "Main" contains all production data such as production date, machine, product, production quantity, etc. In another sheet "AT Guidecard Tracking", I am entering production date and machine. I want the 3rd column to automatically provide which product was run for the entered combination of machine and production date in form of a dropdown. There might be two or more products on same machine on same date. I have written the following piece of code in apps script:
// Get active sheet
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var listMain = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Main");
// Get active cell in active sheet
var activeCell = ss.getActiveCell()
if(activeCell.getColumn() == 2 && activeCell.getRow() > 1 && ss.getName() == "AT Guidecard Tracking"){
activeCell.offset(0,1).clearContent().clearDataValidations();
if(activeCell.isBlank() == false){
var machine = activeCell.getValue(); // Col 2
var prodDate = activeCell.offset(0,-1).getValue(); // Col 1
//Logger.log(prodDate);
// Filter main proudction data based on machine and date
var MainData = listMain.getRange(2,1,listMain.getLastRow()-1,13).getValues();
//Logger.log(MainData);
// compute prod type using FILTER
var prodTypeList = MainData.filter(function(item){
return item[0] == prodDate && item[1] === machine;
})
var distinct = (value,index,self) => {
return self.indexOf(value) === index;
}
var prodTypeValidationList = prodTypeList.map(x => x[3]).filter(distinct).sort();
var prodTypeValidationRule = SpreadsheetApp.newDataValidation().requireValueInList(prodTypeValidationList).setAllowInvalid(false).build();
// Apply validation rule to adjacent cell using offset
activeCell.offset(0,1).setDataValidation(prodTypeValidationRule);
}
}
However, I am getting an empty list in Col 3 (data validation) when I run this. Both the date columns in sheet "Main" and "AT Guidecard Tracking" are formatted as Date.
For example, I enter date 19/07/2021 (stored in variable prodDate) in sheet "AT Guidecard Tracking" and Machine MS-02. There is a corresponding entry in sheet "Main" for this combination. When I debug and look at the data stored on variable MainData, the dates exactly match! (See attached image).
Why is the filter returning an empty array?
I'd guess that this is never true item[0] == prodDate because one Date() object can never equal another Date() object however if you compare the valueOf() or the getTime() then you can make that kind of a comparison.
try this:
var prodDate = new Date(activeCell.offset(0,-1).getValue()).valueOf(); // Col 1
then try new Date(item[0]).valueOf() == prodDate
be careful that both dates are of the exact same Datetime
To Answer your question:
let dt = new Date();
let today = new Date(dt.getFullYear(),dt.getMonth(),dt.getDate()).valueOf();
let yesterday = new Date(dt.getFullYear(),dt.getMonth(),dt.getDate() - 1).valueOf();
I have some JS that stores the name and value of selected checkboxes on one page and then, on a button click, adds this data to a table on page 2.
This works, but now I am looking to do the same for a textbox containing a number. Specifically, I'm looking to take the value entered by the user and add this to a cell in the table. What would be the best way to approach this? Add to the existing function or create a separate on button click function specifically for the textbox value?
I have added a screenshot of the HTML table on page 2 along with where I would like the textbox value to go (highlighted with a red rectangle).
Here's what I have so far:
HTML for textbox (page 1):
<div class="selecttier">
<h1>5. Number of Clicks</h1>
<input id="numberofclickstextbox" name="numberofclicks" type="text" value="0" data-total="0" oninput="calculatetier()" />
</div>
JS on page 1:
$('#sales_order_form_button').click(function() {
let table_info = [];
$('input[type=checkbox]').each(
function(index, value) {
if($(this).is(':checked')) {
table_info.push(
{
name: $(this).attr('name'),
value: $(this).attr('value'),
}
);
}
});
let base64str=btoa(JSON.stringify(table_info));
window.location = "page2.html?table_data=" + base64str;
});
JS on page 2:
// Helper function
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.href);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};
// actual code
let table_data = getUrlParameter('table_data');
let data_from_page_1 = JSON.parse(atob(table_data));
for(let i = 0; i < data_from_page_1.length; i++){
let row = $("<tr></tr>");
let recordName = $("<td></td>").text(data_from_page_1[i].name);
let recordValue = $("<td></td>").text(data_from_page_1[i].value);
row.append(recordName, recordValue);
$('#output_table').append(row);
}
// code to sum CPC column
var sum1 = 0;
$("#output_table tr > td:nth-child(2)").each(
(_,el) => sum1 += Number($(el).text()) || 0
);
$("#sum1").text(sum1);
//datetime stamp
var dt = new Date();
document.getElementById("datetime").innerHTML = dt.toLocaleString();
Output HTML table (page 2):
<table id="output_table">
<tr>
<th>Name</th>
<th>Value</th>
<th>Number of Clicks</th>
</tr>
<tfoot>
<tr>
<th id="total" colspan="1">Total CPC:</th>
<td id="sum1"></td>
</tr>
</tfoot>
</table>
As stated in the #Manu Varghese comment, the way to go would be using sessionStorage or localStorage.
First, let's differentiate both. According to the Stack Overflow question "HTML5 Local storage vs Session Storage", we have the following answer:
localStorage and sessionStorage both extend Storage. There is no difference between them except for the intended "non-persistence" of sessionStorage.
That is, the data stored in localStorage persists until explicitly deleted. Changes made are saved and available for all current and future visits to the site.
For sessionStorage, changes are only available per tab. Changes made are saved and available for the current page in that tab until it is closed. Once it is closed, the stored data is deleted.
Considering they are used the same way and you must to choose between what better fits your case, I will proceed using sessionStorage.
For that, in the first page you must use:
sessionStorage.setItem("key", "value")
You may set the item right when you perceives a change, like in the input 'blur' event.
and when you land in the second page (right when jQuery calls its start event), you will retrieve your data using:
sessionStorage.getItem("key")
Take in mind that localStorage/sessionStorage can support a limited amount of data. Even if that limit is way bigger than URL, most browsers will store only 2.5MB to 10MB per origin, according to the browser implementation (you may test by yourself in the link recommended in MDN (Mozilla Development Network), http://dev-test.nemikor.com/web-storage/support-test/).
Also, you may want to avoid storing sensitive data in the storages, due to some some discussions about security, which seems not to be a complaint here.
Implementation in the given case
Your code can be modified in three steps:
Change the way you save the data to use the storage
Creates a JSON of an object containing the array, instead the make the JSON using the array itself. Then you can add more fields.
Load the JSON object and its fields (the array and the number).
Step 1 - Changing to sessionStorage
Just now you have your Javascript on page 1 creating an array of data and stringifying that data to a JSON string.
If you want to use the storage rather than the URL for all the data, just change these lines of code from:
let base64str=btoa(JSON.stringify(table_info));
window.location = "page2.html?table_data=" + base64str;
to the code that will save the data into a (local/session)Storage:
let jsonStr=JSON.stringify(table_info); // converts to JSON string
sessionStorage.setItem("oldData", jsonStr); // save to storage
window.location = "page2.html"; // navigate to other page
Notice that the storage can receive any string, but only strings, then we can remove the btoa function, but we must keep the stringify.
Step 2 -- Adding more data to save
Now you have one JSON that is an array of items. But what do you want is to include one more field, parallel to this array. Of course, you can't include it in the array, as it is a different thing. So, what we must to do is to create a JSON object which has a number field AND the array field itself.
Your function to create the array is all ok, then we will use the same "table_data" as the array and include it to a new JSON object:
let table_data = []; // the array you have
$('input[type=checkbox]').each(
... rest of code ...
); // the function that creates the array (I abbreviated it here)
// Creates an object with an array and a number
let jsonObj = {
table_data: table_data,
number_of_clicks: theNumberYouHave/* your variable with the number here */
};
// This is the bit above with CHANGES into variable names
// Instead of "table_data", now we save "jsonObj"
let jsonStr=JSON.stringify(jsonObj); // converts the "jsonObj" to a JSON string
sessionStorage.setItem("oldData", jsonStr);
window.location = "page2.html";
Remember to change "theNumberYouHave" to whatever your variable with the number is called. The you will append the number as a field of the JSON object.
In other words, this simply will create an structure like that:
{
number_of_clicks: 5216,
table_data: [
{ name: "...", value: "..."},
{ name: "...", value: "..."},
{ name: "...", value: "..."},
...
]
}
See? Your table_data is still there, but with a new sibling (number_of_clicks) inside an object.
Step 3 -- Loading data from page 1
For now, you have these two lines of code in page 2 to read data from page 1:
let table_data = getUrlParameter('table_data');
let data_from_page_1 = JSON.parse(atob(table_data));
What do you need there, is to simply replace the getUrlParameter function to read from the storage, and remove the atob function to reflect the changes we made in page 1, this way:
let jsonObj = sessionStorage.getItem("oldData"); // reads the string
let data_from_page_1 = JSON.parse(jsonObj); // parse the JSON string
let table_data = data_from_page_1.table_data; // grab the table data
let number_of_clicks = data_from_page_1.number_of_clicks; // grab the number
Now you are free to use the variable "table_data" like you did, and to use the "number_of_clicks" in the way you want to use it. It is the number passed from page 1, then you may set it to your table cell.
You have it with the unique ID "sum1", the you may just:
$("#sum1").text(number_of_clicks);
And you are done!
I highly recommend localStorage and sessionStorage to be used, as per this and this
Page 1 code full source
$('#next_page_button').click(function(){
let table_info = [];
// Do for checkboxes
$('.campaignstrategy input[type=checkbox]').each(
function(index, value){
if($(this).is(':checked')){
table_info.push(
{
name: $(this).attr('name'),
value: $(this).attr('value'),
type: 'checkbox'
}
);
}
});
$('.campaignstrategy input[type=text]').each(
function(index, value){
table_info.push(
{
name: $(this).attr('name'),
value: $(this).attr('value'),
type: 'text'
}
);
});
let base64str=btoa(JSON.stringify(table_info));
window.location = "page2.html?table_data=" + base64str;
});
Page 2 Code full source
// Helper function
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.href);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};
// actual code
let table_data = getUrlParameter('table_data');
let data_from_page_1 = JSON.parse(atob(table_data));
// clear table
$('#output_table').html("");
// generator checboxes
for(let i=0;i<data_from_page_1.length;i++){
if(data_from_page_1[i].type == "checkbox"){
let row = $("<tr></tr>");
let recordName = $("<td></td>").text(data_from_page_1[i].name);
let recordValue = $("<td></td>").text(data_from_page_1[i].value);
let recordCount = $("<td></td>").text("");
row.append(recordName, recordValue, recordCount); // not used but needed
$('#output_table').append(row);
}
}
// generate textboxes
for(let i=0;i<data_from_page_1.length;i++){
if(data_from_page_1[i].type == "text"){
let row = $("<tr></tr>");
let recordName = $("<td></td>").text("");
let recordValue = $("<td></td>").text("");
let recordCount = $("<td></td>").text(data_from_page_1[i].value);
row.append(recordName, recordValue, recordCount);
$('#output_table').append(row);
}
}
ANSWER:
What would be the best way to approach this?
window.localStorage - stores data with no expiration date
window.sessionStorage - stores data for one session
This script works brilliantly to pull emails into my sheet. My problem is that the function only pulls the first two fields listed. e.g., getPlainBody, getSubject - even though more fields are asked for. So instead of having one function that pulls all the fields I need (getPlainBody, getSubject, getTo, getDate, getFrom) I only get the first two (getPlainBody, getSubject). Is there an obvious modification that will pull all 5 fields into the sheet?
function getEmails_(q) {
sh0.clear();
var emails = [];
var threads = GmailApp.search(q);
for (var i in threads) {
var msgs = threads[i].getMessages();
for (var j in msgs) {
emails.push([msgs[j].getPlainBody(), [msgs[j].getSubject(), [msgs[j].getDate(), [msgs[j].getTo(), [msgs[j].getFrom()
]]]]]);
}
}
return emails;
}
function appendData_(sh0, array2d) {
sh0.getRange(sh0.getLastRow() + 1, 1, array2d.length, array2d[0].length).setValues(array2d);
}
function saveEmails() {
var array2d = getEmails_(SEARCH_QUERY);
if (array2d) {
appendData_(sh0, array2d);
}}
Your code is fine the problem is how you are constructing the 2D array.
in the .push() method, your use of the square brackets - [] - is building an array within an array within another array within another array within another array within another array, it's easier to see it in the screenshot below.
What you need is 1 array of horizontals cells with an array of vertical rows.
So change:
emails.push([msgs[j].getPlainBody(), [msgs[j].getSubject(), [msgs[j].getDate(), [msgs[j].getTo(), [msgs[j].getFrom()]]]]]);
to
emails.push([msgs[j].getPlainBody(), msgs[j].getSubject(), msgs[j].getDate(), msgs[j].getTo(), msgs[j].getFrom()]);
On a personal note, I usually always format the date to something a little more readable. You can use Utilities to do this. Ex: format: Thu Jun 15 2017 11:18:18 GMT+0700 (ICT) to 15/06/2017 # 11:18
var formatDate = Utilities.formatDate(emails[i][2], 'GMT+07:00', 'dd/MM/yyyy # HH:mm')
Hey guys I am trying to make a request to a JSON page, grabbing the data, then displaying it to my console but it is giving me "undefined". Why is that?
Here is the code and then the JSON page will be posted under it:
(function Apod() {
var api_key = 'NNKOjkoul8n1CH1NoUFo',
url = 'https://api.nasa.gov/planetary/apod' + "?api_key=" + api_key,
data;
var apodRequest = new XMLHttpRequest();
apodRequest.onreadystatechange = function() {
if (apodRequest.readyState === 4 && apodRequest.status === 200) {
var response = apodRequest.responseText;
var parsedAPOD = JSON.parse(response);
data += parsedAPOD;
for (i = 0; i < parsedAPOD.length; i++) {
data += parsedAPOD[i];
console.log("Parsing lines: <br>" + parsedAPOD[i]);
}
}
apodRequest.open("GET", url, true);
apodRequest.send(null);
}
}());
JSON page parsing:
{
"date": "2016-11-05",
"explanation": "Shot in Ultra HD, this stunning video can take you on a tour of the International Space Station. A fisheye lens with sharp focus and extreme depth of field provides an immersive visual experience of life in the orbital outpost. In the 18 minute fly-through, your point of view will float serenely while you watch our fair planet go by 400 kilometers below the seven-windowed Cupola, and explore the interior of the station's habitable nodes and modules from an astronaut's perspective. The modular International Space Station is Earth's largest artificial satellite, about the size of a football field in overall length and width. Its total pressurized volume is approximately equal to that of a Boeing 747 aircraft.",
"media_type": "video",
"service_version": "v1",
"title": "ISS Fisheye Fly-Through",
"url": "https://www.youtube.com/embed/DhmdyQdu96M?rel=0"
}
You have a few errors in your code.
First off, the general structure of it should be like this
(function Apod() {
var api_key = 'NNKOjkoul8n1CH1NoUFo',
url = 'https://api.nasa.gov/planetary/apod' + "?api_key=" + api_key,
data;
var apodRequest = new XMLHttpRequest();
apodRequest.onreadystatechange = function() {
//Code here
};
apodRequest.open("GET", url, true);
apodRequest.send(null);
}());
Notice how I moved apodRequest.open("GET", url, true); and apodRequest.send(null); outside of the onreadystatechange handler.
Secondly, instead of
apodRequest.onreadystatechange = function() {
if (apodRequest.readyState === 4 && apodRequest.status === 200) {
//Code here
}
}
you can simply do
apodRequest.onload = function() {
//Code here
};
As it's the event that will fire when a response is returned. So you don't need the if check inside.
Finally, inside the onload handler, you have a few mistakes, such as:
data += parsedAPOD; this is wrong because data is an object undefined up to this point and parsedAPOD is an object. Doing += between two objects will not merge them. If you want to merge two objects there are other ways, e.g. Object.assign
for (i = 0; i < parsedAPOD.length; i++) { ... } is wrong because parsedAPOD is an object. Objects do not have a length property, so this is not the correct way to iterate over it. Use for ... in ... loop instead
data += parsedAPOD[i]; is, again, wrong, because this is not the way to merge objects.
parsedAPOD[i] is wrong because i is an integer, in this case, so parsedAPOD[i] is simply undefined.
Hope this analysis helps you correct your code.
first of all parsedAPOD is an object and parsedAPOD.length is not valid. You can use for in loop to iterate through an object as below.
for (var i in parsedAPOD) {
data += parsedAPOD[i];
console.log("Parsing lines: <br>" + parsedAPOD[i]);
}
I've been playing around with the Last.fm API and JSON and I've been trying to retrieve a user's top artists by month for the past 12 months. I tried to set up a for loop to go through each month and then pull the relevant JSON data corresponding to that month, but from what I can tell it seems like the for loop is being run through much quicker than the JSON call.
I'm using Felix Bruns' last.fm javascript API https://github.com/fxb/javascript-last.fm-api
I checked the console and no values of month were logged except for 12. I'm also getting an Uncaught Reference Error "json##.... is not defined"
I tried looking around for solutions but all of my search results came up as how to loop through the results of an API call whereas I'm looking for how to write a loop that retrieves multiple JSON objects.
<script type="text/javascript">
var apiKey = "b0da1774db3d010f62b11f67c4de0667";
var secret = "0baa4b10c807acc847128599680679a7";
var lastfm = new LastFM({
apiKey : apiKey,
secret : secret,
cache : undefined
});
var lastfm_2 = new LastFM({
apiKey : apiKey,
secret : secret,
cache : undefined
});
$(document).ready(function() {
$("#submit").click(function() {
var username = $("#username").val();
var text = "";
if (username) {
$("#title").html("Your Most Played Artist by Month");
$("#title").css("color", "#222");
// Get top artists for each month
var topArtistsByMonth = new Array();
for (var month = 0; month < 12; month++) {
lastfm.user.getTopArtists({user: username, period: "1month", limit: 15, page: month + 1}, {success: function(data) {
topArtistsByMonth.push(data.topartists);
console.log("Month " + month + ": " + data.topartists);
}});
}
} else {
alert("No username");
}
});
});
</script>
Any help would be appreciated, thanks!
getTopArtists is asynchronous, so calling it only starts the request; it doesn't wait for it to finish. The callback is how you know when it's done. That means that your for loop fires them all off in parallel, and then you collect the results when you're done. However, since they can finish in any order, topArtistsByMonth is not guaranteed to be in any sort of order. To fix that, you'll probably want to make it use explicit indices rather than using push:
for(var month = 0; month < 12; month++) {
// We need to use an anonymous function to capture the current value of month
// so we don't end up capturing the reference to month that the for loop is
// using (which, by the time the callbacks complete, will always be 12.)
(function(month) {
lastfm.user.getTopArtists({user: username, period: "1month", limit: 15, page: month + 1}, {success: function(data) {
topArtistsByMonth[month] = data.topartists;
console.log("Month " + month + ": " + data.topartists);
}});
})(month);
}
If you want to know when all the data has been downloaded, you'll need another variable to keep track of how many have finished so far. Every time the callback is called, you'll need to increment that and see if it's hit 12 yet. When it has, all the data has been downloaded.