I've been using google's charts API and have reached a dead end. I use the API to query a spreadsheet and return some data. For visualizations I'm using Razorflow - a JS dashboard framework - not Google Charts. Getting the data is pretty straight forward using code like this (this code should work - spreadsheet is public):
function initialize() {
// The URL of the spreadsheet to source data from.
var myKey = "12E2fE8GWuPvXJoiRZgCZUCFhRKlW69uJAm7fch71jhA"
var query = new google.visualization.Query("https://docs.google.com/spreadsheets/d/" + myKey + "/gviz/tq?sheet=Sheet1");
query.setQuery("SELECT A,B,C WHERE A>=1 LIMIT 1");
query.send(function processResponse(response) {
var KPIData = response.getDataTable();
var KPIName = [];
myNumberOfDataColumns = KPIData.getNumberOfColumns(0) - 1;
for (var h = 0; h <= myNumberOfDataColumns ; h++) {
KPIName[h] = KPIData.getColumnLabel(h);
};
});
};
google.charts.setOnLoadCallback(initialize);
The above will create an array holding the column labels for column A,B and C.
Once the data is fetched I want to use the data for my charts. Problem is, I need to have the data ready before I create the charts. One way I have done this, is creating the chart before calling google.charts.setOnLoadCallback(initialize) and then populate the charts with data from inside the callback. Like this:
//create dashboard
StandaloneDashboard(function (db) {
//create chart - or in this case a KPI
var firstKPI = new KPIComponent();
//add the empty component
db.addComponent(firstKPI);
//lock the component and wait for data
firstKPI.lock();
function initializeAndPopulateChart() {
// The URL of the spreadsheet to source data from.
var myKey = "12E2fE8GWuPvXJoiRZgCZUCFhRKlW69uJAm7fch71jhA"
var query = new google.visualization.Query("https://docs.google.com/spreadsheets/d/" + myKey + "/gviz/tq?sheet=Sheet1");
query.setQuery("SELECT A,B,C WHERE A>=1 LIMIT 1");
query.send(function processResponse(response) {
var KPIData = response.getDataTable();
var KPIName = [];
myNumberOfDataColumns = KPIData.getNumberOfColumns(0) - 1;
for (var h = 0; h <= myNumberOfDataColumns ; h++) {
KPIName[h] = KPIData.getColumnLabel(h);
};
//use label for column A as header
firstKPI.setCaption(KPIName[0]);
//Set a value - this would be from the query too
firstKPI.setValue(12);
//unlock the chart
firstKPI.unlock();
});
};
google.charts.setOnLoadCallback(initializeAndPopulateChart);
});
It works but, I would like to separate the chart functions from the data loading. I guess the best solution is to create a promise. That way I could do something like this:
//create dashboard
StandaloneDashboard(function (db) {
function loadData() {
return new Promise (function (resolve,reject){
//get the data, eg. google.charts.setOnLoadCallback(initialize);
})
}
loadData().then(function () {
var firstKPI = new KPIComponent();
firstKPI.setCaption(KPIName[0]);
firstKPI.setValue(12);
db.addComponent(firstKPI);
})
});
As should be quite obvious, I do not fully understand how to use promises. The above does not work but. I have tried lots of different ways but, I do not seem to get any closer to a solution. Am I on the right track in using promises? If so, how should i go about this?
Inside a promise you need to call resolve or reject function when async job is done.
function loadData() {
return new Promise (function (resolve,reject){
query.send(function() {
//...
err ? reject(err) : resolve(someData);
});
})
}
And then you can do
loadData().then(function (someData) {
//here you can get async data
}).catch(function(err){
//here you can get an error
});
});
new Promise(resolve => {
google.charts.setOnLoadCallback(resolve);
}).then(getValues);
Related
I have an issue related to database. I am currently working with Gupshup bot programming. There are two different data persistence modes which can be read here and here. In the advanced data persistence, the following code is documented to put data into data base:
function MessageHandler(context, event) {
if(event.message=='update bug - 1452') {
jiraUpdate(context);
}
}
function jiraUpdate(context){
//connect to Jira and check for latest update and values
if(true){
context.simpledb.doPut("1452" ,"{\"status\":\"QA pending\",\"lastUpdated\":\"06\/05\/2016\",\"userName\":\"John\",\"comment\":\"Dependent on builds team to provide right build\"}");
} else{
context.sendResponse('No new updates');
}
}
function DbPutHandler(context, event) {
context.sendResponse("New update in the bug, type in the bug id to see the update");
}
If I want to change only one of column (say status or last Updated) in the table for the row with key value 1452, I am unable to do that. How can that be done?
I used the following code:
function MessageHandler(context, event) {
// var nlpToken = "xxxxxxxxxxxxxxxxxxxxxxx";//Your API.ai token
// context.sendResponse(JSON.stringify(event));
if(event.message=='deposit') {
context.sendResponse("Enter the amount to be deposited");
}
if(event.message=="1000") {
jiraUpdate(context);
}
if(event.message== "show"){
context.simpledb.doGet("1452");
}
}
function HttpResponseHandler(context, event) {
var dateJson = JSON.parse(event.getresp);
var date = dateJson.date;
context.sendResponse("Today's date is : "+date+":-)");
}
function jiraUpdate(context){
//connect to Jira and check for latest update and values
if(true){
context.simpledb.doPut("aaa" ,"{\"account_number\":\"90400\",\"balance\":\"5800\"}");
} else{
context.sendResponse('No new updates');
}
}
/** Functions declared below are required **/
function EventHandler(context, event) {
if (!context.simpledb.botleveldata.numinstance)
context.simpledb.botleveldata.numinstance = 0;
numinstances = parseInt(context.simpledb.botleveldata.numinstance) + 1;
context.simpledb.botleveldata.numinstance = numinstances;
context.sendResponse("Thanks for adding me. You are:" + numinstances);
}
function DbGetHandler(context, event) {
var bugObj = JSON.parse(event.dbval);
var bal = bugObj.balance;
var acc = bugObj.account_number;
context.sendResponse(bal);
var a = parseInt (bal,10);
var b = a +1000;
var num = b.toString();
context.simpledb.doPut.aaa.balance = num;
}
function DbPutHandler(context, event) {
context.sendResponse("testdbput keyword was last put by:" + event.dbval);
}
Since the hosted DB that is provided by Gupshup is the DynamoDB of AWS. Hence you can enter something as a key, value pair.
Hence you will have to set the right key while using doPut method to store data into the database and use the same key to get the data from the database using the doGet method.
To update the data you should first call doGet method and then update the JSON with right data and then call doPut method to update the database with the latest data.
I have also added something which is not present in the documentation, You can now make DB calls and choose which function the response goes to.
I am refactoring your example as using 3 keywords and hard coding few things just for example -
have - this will update the database with these values
{"account_number":"90400","balance":"5800"}
deposit - on this, the code will add 1000 to the balance
show - on this, the code show the balance to the user.
Code -
function MessageHandler(context, event) {
if(event.message=='have') {
var data = {"account_number":"90400","balance":"5800"};
context.simpledb.doPut(event.sender,JSON.stringify(data),insertData); //using event.sender to keep the key unique
return;
}
if(event.message=="deposit") {
context.simpledb.doGet(event.sender, updateData);
return;
}
if(event.message== "show"){
context.simpledb.doGet(event.sender);
return;
}
}
function insertData(context){
context.sendResponse("I have your data now. To update just say \"deposit\"");
}
function updateData(context,event){
var bugObj = JSON.parse(event.dbval);
var bal = bugObj.balance;
var a = parseInt(bal,10);
var b = a + 1000;
var num = b.toString();
bugObj.balance = num;
context.simpledb.doPut(event.sender,bugObj);
}
function EventHandler(context, event) {
if (!context.simpledb.botleveldata.numinstance)
context.simpledb.botleveldata.numinstance = 0;
numinstances = parseInt(context.simpledb.botleveldata.numinstance) + 1;
context.simpledb.botleveldata.numinstance = numinstances;
context.sendResponse("Thanks for adding me. You are:" + numinstances);
}
function DbGetHandler(context, event) {
var accountObj = JSON.parse(event.dbval);
context.sendResponse(accountObj);
}
function DbPutHandler(context, event) {
context.sendResponse("I have updated your data. Just say \"show\" to view the data.");
}
I am trying to fetch data from Firebase to display a chart. The promises seems to work fine when I individually enter the url instead through the for loop. When I use the for loop, the ref is not updating and week[i] is undefined. Once the xaxis array is filled, I will be calling another function to display a chart. I am very new to asynchronous callbacks and any direction/hint will be greatly appreciated. I am wondering why week[i] is undefined within the callback?
EDIT: Looks like the for loop runs fully first and then firebase is loaded. So, value of i will be 6 by the time firebase is loaded and ready to go.
var xaxis = [];
var week = [582016, 592016, 5102016, 5112016, 5122016, 5132016, 5142016];
for (var i = 0; i < 6; i++) {
var total_day_calories = 0;
var ref = new Firebase("https://xxxxxxxxxx.firebaseio.com/"+ week[i] );
ref.once('value').then(function(snapshot){
snapshot.forEach(function(childSnapshot) {
var childData = childSnapshot.val().total_calories;
total_day_calories = total_day_calories + childData;
});
xaxis.push(total_day_calories);
console.log(xaxis);
}, function(errorObject) {
console.log("The read failed: " + errorObject.code);
});
}
I am writing some JavaScript codes using Parse.com.
To be honest, I have been reading how to use Promise and done lots of research but cannot still figure out how to use it properly..
Here is a scenario:
I have two tables (objects) called Client and InvoiceHeader
Client can have multiple InvoiceHeaders.
InvoiceHeader has a column called "Amount" and I want a total amount of each client's InvoiceHeaders.
For example, if Client A has two InvoiceHeaders with amount 30 and 20 and Client B has got nothing, the result I want to see in tempArray is '50, 0'.
However, with the following codes, it looks like it's random. I mean sometimes the tempArray got '50, 50' or "50, 0". I suspect it is due to the wrong usage of Promise.
Please help me. I have been looking into the codes and stuck for a few days.
$(document).ready(function() {
var client = Parse.Object.extend("Client");
var query = new Parse.Query(client);
var tempArray = [];
query.find().then(function(objects) {
return objects;
}).then(function (objects) {
var promises = [];
var totalForHeader = 0;
objects.forEach(function(object) {
totalForHeader = 0;
var invoiceHeader = Parse.Object.extend('InvoiceHeader');
var queryForInvoiceHeader = new Parse.Query(invoiceHeader);
queryForInvoiceHeader.equalTo('headerClient', object);
var prom = queryForInvoiceHeader.find().then(function(headers) {
headers.forEach(function(header) {
totalForHeader += totalForHeader +
parseFloat(header.get('headerOutstandingAmount'));
});
tempArray.push(totalForHeader);
});
promises.push(prom);
});
return Parse.Promise.when.apply(Parse.Promise, promises);
}).then(function () {
// after all of above jobs are done, do something here...
});
} );
Assuming Parse.com's Promise class follows the A+ spec, and I understood which bits you wanted to end up where, this ought to work:
$(document).ready(function() {
var clientClass = Parse.Object.extend("Client");
var clientQuery = new Parse.Query(clientClass);
clientQuery.find().then(function(clients) {
var totalPromises = [];
clients.forEach(function(client) {
var invoiceHeaderClass = Parse.Object.extend('InvoiceHeader');
var invoiceHeaderQuery = new Parse.Query(invoiceHeaderClass);
invoiceHeaderQuery.equalTo('headerClient', client);
var totalPromise = invoiceHeaderQuery.find().then(function(invoiceHeaders) {
var totalForHeader = 0;
invoiceHeaders.forEach(function(invoiceHeader) {
totalForHeader += parseFloat(invoiceHeader.get('headerOutstandingAmount'));
});
return totalForHeader;
});
totalPromises.push(totalPromise);
});
return Parse.Promise.when(totalPromises);
}).then(function(totals) {
// here you can use the `totals` array.
});
});
Basically I'm making a nice and simple mobile web app for a couple of my friends. It uses some online databases to store position data of shops. I've got the databases working like a charm. No problems there. In fact everything is working except it's all happening in the wrong order I think. The data from the database should be stored in an array and then the objects in that array are displayed on screen. However, using some console logs I've found that the data is being displayed, then being retrieved from the database, then the arrays are filled. But no matter what I do, I can't get it to work! Here is my code:
var latOfSpots;
var lngOfSpots;
var nameOfSpots;
var spotArray;
var spotLatLng;
var spotCollection;
var markers;
var Spot;
var spot;
function init() {
//-------------------------- INITIATE SPOT VARIABLES ---------------------------//
map = new google.maps.Map2(document.getElementById("map"));
latOfSpots= new Array(51.14400,51.02295);
lngOfSpots= new Array(0.25721,0.26450);
nameOfSpots= new Array('Tescos', 'Sainsburys');
spotLatLng= new Array();
markers= new Array();
Spot = Parse.Object.extend("Spot");
spot = new Spot();
//----------------- GET DATA FROM THE PARSE.COM DATABASE ---------------------//
//---------------------- DISPLAY ARRAY DATA ON MAP ---------------------------//
GetData();
DisplayData();
//----------------------- SET MAP SETTINGS -----------------------------------//
map.setCenter(spotLatLng[0],8);
//map.addControl(new google.maps.LargeMapControl());
map.addControl(new google.maps.MapTypeControl());
}; //END OF INIT FUNCTION ------------------------------------------------//
google.setOnLoadCallback(init);
//------------------- PRIMARY FUNCTION TO GET DATA FROM DATABASE ---------------//
function GetData()
{
var query = new Parse.Query(Spot);
spotCollection = query.collection();
spotCollection.fetch({
success: function(spotCollection) {
// spotCollection.toJSON()
// will now be an array of objects based on the query
FillArrays();
console.log('data retreived' + spotCollection);
}
});
}
//----------------- FUNCTION TO LOAD DATABASE INTO ARRAYS -------------------//
function FillArrays()
{
spotArray = spotCollection.toJSON();
for (var j = 0; j<spotArray.length; j++)
{
latOfSpots.push(spotArray[j].Latitude);
lngOfSpots.push(spotArray[j].Longitude);
nameOfSpots.push(spotArray[j].Name);
}
}
//------------------------ FUNCTION TO DISPLAY ALL ARRAY DATA ONSCREEN -----------------//
function DisplayData()
{
for(var i = 0; i<latOfSpots.length; i++)
{
spotLatLng[i] = new google.maps.LatLng(latOfSpots[i], lngOfSpots[i]);
for(var x = 0; x<latOfSpots.length; x++)
{
markers[x] = new google.maps.Marker(
spotLatLng[i], {
"draggable":false,
"title":nameOfSpots[i],
});
map.addOverlay(markers[x]);
}
}
console.log('data displayed');
}
Your database query is asynchronous. You need to use the data in the Get_Data callback function (after it has come back from the server). Currently you are attempting to use it before the server sends it back.
//------------------- PRIMARY FUNCTION TO GET DATA FROM DATABASE ---------------//
function GetData()
{
var query = new Parse.Query(Spot);
spotCollection = query.collection();
spotCollection.fetch({
success: function(spotCollection) {
// spotCollection.toJSON()
// will now be an array of objects based on the query
FillArrays();
console.log('data retreived' + spotCollection);
DisplayData();
}
});
}
I have a function that consumes data with a WCF service (in SharePoint). The service does not return a specific field that I need for items so I use the SharePoint Client Object Model to query for the field by using the ID I have in the returned result from the WCF service.
function LoadAllNews() {
var listUrl = "/_vti_bin/ListData.svc/Pages";
$.getJSON(listUrl,
function (data) {
$.each(data.d,
function (i, result) {
GetImageUrl(result.Id, function (image) {
$(ConstructHtml(image, result.Title, result.Path, result.Name)).appendTo("#News");
});
});
});
}
When I debug result here I always get the items returned in the same order but since the GetImageUrl executes a query async the items are not appended in the same order. Most of the times they do must some times it appears to be random since time to get the image varies:
function GetImageUrl(id, callback) {
var context = new SP.ClientContext();
var items = context.get_web().get_lists().getByTitle('Pages').getItemById(id);
context.load(items);
context.executeQueryAsync(function () {
callback(items.get_item('PublishingRollupImage'));
});
}
function ConstructHtml(imageUrl, title, path, name) {
var html = "" // a long html string..
return html;
}
I could post this on sharepoint.stackexchange but the audience is wider here and it's more of a question how to handle this with JavaScript than with SharePoint itself.
Any ideas on how I should approach this? I was thinking something like skip the image in LoadAllNews() and then when all items are appended use JavaScript/jQuery to load the image for each news item.
Thanks in advance.
Based on the fork function from my answer to this question: Coordinating parallel execution in node.js. I would do it like this:
var getImages = [];
var meta = [];
$.each(data.d,
function (i, result) {
getImages.push(function(callback){
GetImageUrl(result.Id, callback);
});
meta.push({
title : result.Title,
path : result.Path,
name : result.Name
});
});
fork(getImages,function(images) {
$.each(images,function(i,image){
$(ConstructHtml(
image,
meta[i].title,
meta[i].path,
meta[i].name
)).appendTo("#News");
});
});
The implementation of fork is simply this:
function fork (async_calls, shared_callback) {
var counter = async_calls.length;
var all_results = [];
function makeCallback (index) {
return function () {
counter --;
var results = [];
// we use the arguments object here because some callbacks
// in Node pass in multiple arguments as result.
for (var i=0;i<arguments.length;i++) {
results.push(arguments[i]);
}
all_results[index] = results;
if (counter == 0) {
shared_callback(all_results);
}
}
}
for (var i=0;i<async_calls.length;i++) {
async_calls[i](makeCallback(i));
}
}
The fork function above gathers asynchronous results in order so it does exactly what you want.
If the order of events matters, make it a synchronous procedure