I'm trying to write some simple Javascript that uses the Trello API to get all boards / lists / cards from my account and add them into an sortable table (using the Datatables jquery plugin).
I've so far managed to write a jsfiddle that gets all this information and writes it to a page, but I can't work out how to store all this information into some sort of data structure that can then be passed to the datatable plugin.
This is the fiddle I have so far that gets the data from Trello:
JS Fiddle Link
var carddata = [];
Trello.members.get("me", function(member) {
$("#fullName").text(member.fullName);
var boardUrl = "";
boardUrl = "members/me/boards";
Trello.get(boardUrl, function(boards) {
$.each(boards, function(ix, board) {
Trello.get("/boards/" + board.id + "/lists", function(lists) {
$.each(lists, function(ix, list) {
Trello.get("lists/" + list.id + "/cards", function(cards) {
$.each(cards, function(ix, card) {
console.log("boardname: " + board.name + "; list name: " + list.name + "; card name: " + card.name);
carddata.push(
"boardname: " + board.name +
"; list name: " + list.name +
"; card name: " + card.name
);
var $tablerow = "";
$tablerow = $(
"<tr><td>" + board.name +
"</td><td>" + list.name +
"</td><td>" + card.name +
"</td></tr>"
).appendTo("#table_body");
});
/*
for (i = 0; i < carddata.length; i++) {
console.log("carddata: " + carddata[i]);
}
*/
});
});
});
});
});
});
// **** carddata array is empty at this point ****
for (i = 0; i < carddata.length; i++) {
console.log("carddata: " + carddata[i]);
}
It loops through all boards, lists and cards and currently adds what it finds to a html table (and also an array). I then use the Datatables plugin to change that HTML table into a sortable table.
However the plugin is seeing the HTML table as empty (from what I can see), I presume this is because of something like the plugin code being called before the Javascript builds up the table in HTML.
So instead I planned to add all the data into an array, and then pass that array into the datatable as a datasource, but I can 't see how to make the array accessible outside the very inner loop. From doing some searches I think this is to do with closures and scope but I'm struggling to understand how they work (I'm very new to Javascript).
Is anyone able to help me get this basic code working and show me what I'm doing wrong?
Thanks,
David.
The following code snippet demonstrate how to add data to data table after table created. For how to wait for all asyn requests completed, setTimeout is used to simulate Trello.get method for the asyn behavior.
var boardHash = {};
var listHash = {};
var updateLoggedIn = function() {
$("#loggedout").toggle(!isLoggedIn);
$("#loggedin").toggle(isLoggedIn);
};
var loadCardData = function(){
var carddata = [];
var loadMember = function() {
setTimeout(function(){
console.log("Member loaded");
loadBoard();
},2000);
}
var loadBoard = function() {
setTimeout(function(){
console.log("Boards loaded");
var listPromises = [];
loadList(["boardA","boardB","boardC"],listPromises);
$.when.apply($, listPromises).then(function(){
table.rows.add(carddata).draw("");
});
},1000);
};
var loadList = function(boards,listPromises){
$.each(boards,function(boardIndex, boardValue){
var listDefered = $.Deferred();
listPromises.push(listDefered.promise());
setTimeout(function(){
console.log(boardValue+" lists loaded");
var cardPromises = [];
loadCard(["listA","listA","listC"],boardValue,cardPromises);
$.when.apply($, cardPromises).then(function(){
listDefered.resolve();
});
},(boardIndex+1)*900);
});
};
var loadCard = function(lists,boardValue,cardPromises){
$.each(["listA","listA","listC"],function(listIndex, listValue){
var cardDefered = $.Deferred();
cardPromises.push(cardDefered.promise());
setTimeout(function(){
console.log(boardValue+" "+listValue+" cards loaded");
$.each(["cardA","cardB","cardC"],function(cardIndex, cardValue){
carddata.push({
"boardName":boardValue,
"listName":listValue,
"cardName":cardValue
});
});
cardDefered.resolve();
},(listIndex+1)*800);
});
};
loadMember();
};
var logout = function() {
updateLoggedIn();
};
$("#connectLink")
.click(function() {
loadCardData();
});
$("#disconnect").click(logout);
var consoleLine = "<p class=\"console-line\"></p>";
console = {
log: function(text) {
$("#console-log").append($(consoleLine).html(text));
}
};
var table = null;
$(document).ready( function () {
table = $('#table_id').DataTable({
columns: [
{ data: 'boardName' },
{ data: 'listName' },
{ data: 'cardName' }
]
});
} );
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<link href="//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet" />
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<div id="loggedout">
<a id="connectLink" href="#">Connect To Trello</a>
</div>
</head>
<div id="loggedin">
<div id="header">
Logged in to as <span id="fullName"></span>
<a id="disconnect" href="#">Log Out</a>
</div>
<div id="output"></div>
</div>
<table id="table_id" class="display" border=1>
<thead>
<tr>
<th>Board</th>
<th>List</th>
<th>Card</th>
</tr>
</thead>
<tbody id="table_body">
</tbody>
</table>
<div id="console-log"></div>
</html>
For adding data to data table
So in your code, add the columns options to the data table, and use rows.add method to add data to data table when all ajax request are done.
Wait for all ajax request completed
The most tricky part is how to ensure all response are done, this can be achieved by $.Deferred() and $.when.apply, see JQuery document and What does $.when.apply($, someArray) do? for more details.
Related
I am quite new to this all, so i am pretty sure this is a simple oversight on my part, but i cant get it to run.
When i deploy the code below and click on the button, it does not do anything. When i inspect the html in my browser, it says "userCodeAppPanel:1 Uncaught ReferenceError: csvHTML is not defined
at HTMLInputElement.onclick"
When i run the function csvHTML from Code.gs, I can see the expected results in my Logger.log, so it seems the problem does not lie in my code.gs
What i am trying to achieve is showing the csv results in html. When all works fine, i will want to work with the data in some other way.
Attached below is my code.
Index.html:
<!DOCTYPE html>
<!-- styles -->
<?!= HtmlService.createHtmlOutputFromFile("styles.css").getContent(); ?>
<div class="content">
<h1>csv representation</h1>
<input class="button" type="submit" onclick="html();" value="Refresh" id="refresh"><br>
<div id="tabel"></div>
<svg class="chart"></svg>
</div>
<!-- javascript -->
<script src="//d3js.org/d3.v3.min.js"></script>
<?!= HtmlService.createHtmlOutputFromFile("chart.js").getContent() ?>
<?!= HtmlService.createHtmlOutputFromFile("main.js").getContent() ?>
<script>
function html()
{
var aContainer = document.createElement('div');
aContainer.classList.add('loader_div');
aContainer.setAttribute('id', 'second');
aContainer.innerHTML = "<div class='loader_mesage'><center>Fetching csv list. Please be patient!<br /> <br /><img src='https://i.ibb.co/yy23DT3/Dual-Ring-1s-200px.gif' height='50px' align='center'></img></center></div>";
document.body.appendChild(aContainer);
google.script.run
.withSuccessHandler(showTable)
.csvHTML();
}
function showTable(tabel)
{
document.getElementById("tabel").innerHTML = tabel;
var element = document.getElementById("second");
element.parentNode.removeChild(element);
}
</script>
and Code.gs:
function doGet(e) {
return HtmlService.createTemplateFromFile("index.html")
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
// Fecth Data and make a csv output.
function csvHTML()
{
var query = "{ 'query': 'SELECT * FROM `<some table>` limit 1000;', 'useLegacySql': false }";
var job = BigQuery.Jobs.query(query, <projectName>);
var json = JSON.parse(job);
var tabel = json2csv(json);
Logger.log(tabel)
return tabel;
}
function json2csv(json, classes) {
var headerRow = '';
var bodyRows = '';
classes = classes || '';
json.schema.fields.forEach(function(col){
headerRow +=col.name+",";
})
json.rows.forEach(function(row){
row.f.forEach(function(cell){
bodyRows +=cell.v+",";
})
})
return headerRow + bodyRows }
So thanks to the suggestions by TheMaster, i rewritten it into the following:
index.html:
<!-- javascript -->
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
function html()
{
var aContainer = document.createElement('div');
aContainer.classList.add('loader_div');
aContainer.setAttribute('id', 'second');
aContainer.innerHTML = "<div class='loader_mesage'><center>Fetching csv list. Please be patient!<br /> <br /><img src='https://i.ibb.co/yy23DT3/Dual-Ring-1s-200px.gif' height='50px' align='center'></img></center></div>";
document.body.appendChild(aContainer);
google.script.run
.withSuccessHandler(showTable)
.csvHTML();
}
function showTable(tabel)
{
document.getElementById("tabel").innerHTML = tabel;
var element = document.getElementById("second");
element.parentNode.removeChild(element);
}
</script>
<!DOCTYPE html>
<div class="content">
<h1>csv representation</h1>
<input class="button" type="submit" onclick="html();" value="Refresh" id="refresh"><br>
<div id="tabel"></div>
<svg class="chart"></svg>
</div>
Code.gs has not been modified.
It appears that the <?!= htmlService.createHtmlOutputFromFile("styles.css").getContent(); ?> and other createHtmlOutputFromFile were getting in the way. Eventually i need these, but I will figure out how to incorporate that at a later stage.
Thanks for all the advice and help!
Disclaimer: I have zero experience with Google Apps Script, so take this with a grain of salt.
Looking at their documentation for BigQuery, it seems you are not querying the database correctly. I am surprised by your claim that Logger.log() shows the correct output. It does not appear that it should work.
In case I am right, here is what I propose you change your Code.gs file to:
function doGet(e) {
return HtmlService.createTemplateFromFile("index.html")
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
// Fetch Data and make a csv output.
function csvHTML() {
var results = runQuery('SELECT * FROM `<some table>` limit 1000;');
var tabel = toCSV(results);
Logger.log(tabel);
return tabel;
}
/**
* Runs a BigQuery query and logs the results in a spreadsheet.
*/
function runQuery(sql) {
// Replace this value with the project ID listed in the Google
// Cloud Platform project.
var projectId = 'XXXXXXXX';
var request = {
query: sql,
useLegacySQL: false
};
var queryResults = BigQuery.Jobs.query(request, projectId);
var jobId = queryResults.jobReference.jobId;
// Check on status of the Query Job.
var sleepTimeMs = 500;
while (!queryResults.jobComplete) {
Utilities.sleep(sleepTimeMs);
sleepTimeMs *= 2;
queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId);
}
// Get all the rows of results.
var rows = queryResults.rows;
while (queryResults.pageToken) {
queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId, {
pageToken: queryResults.pageToken
});
rows = rows.concat(queryResults.rows);
}
var fields = queryResults.schema.fields.map(function(field) {
return field.name;
});
var data = [];
if (rows) {
data = new Array(rows.length);
for (var i = 0; i < rows.length; i++) {
var cols = rows[i].f;
data[i] = new Array(cols.length);
for (var j = 0; j < cols.length; j++) {
data[i][j] = cols[j].v;
}
}
}
return {
fields: fields,
rows: rows
};
}
function toCSV(results) {
var headerRow = results.fields.join(',');
var bodyRows = results.rows.map(function(rowData) {
return rowData.map(function(value) {
// for proper CSV format, if the value contains a ",
// we need to escape it and surround it with double quotes.
if (typeof value === 'string' && value.indexOf('"') > -1) {
return '"' + value.replace(/"/g, '\\"') + '"';
}
return value;
});
})
.join('\n'); // join the lines together with newline characters
return headerRow + '\n' + bodyRows;
}
Reminder: I have not tested this, I'm purely writing this based on my knowledge of Javascript and their documentation and sample code.
the below code works great for displaying in my first file
$.ajax({
url : "http://localhost/website/files/userstuff/files/",
asynch : false,
cache : false,
success: function (data) {
$(data).find("a").each(function(i, el) {
var val = $(el).attr('href');
if (val.match(/\.(pdf|doc|docx|txt|html|js|css|rar|7zip)$/)) {
var fileslocation = ("http://localhost/website/files/userstuff/files/" + val)
var displayfilestable = ("<table><thead><tr><th>Files</th></tr></table>");
var adddata = ("<tr><td><a href='"+ fileslocation +"'target='_blank'>"+ val +"</td></tr>");
$("#filestable").html(displayfilestable)
$("filestable, table").append(adddata);
console.log(adddata)
}
});
}
});
this code will as you would think pull and display the files in the table row, however it is only performing this for the first file it finds I was wondering if anyone here could help get this to display all of the files in the files folder in the table. thanks in advance
enter image description here
Your code just works fine. The problems is, in that loop (each) you keep re-create table. That why it show only 1 data. Check my example based on your code.
HTML
<div>
sad1.pdf<br>
sad2.pdf<br>
sad3.pdf
<div id="filestable"></div>
</div>
JAVASCRIPT
var displayfilestable = ("<table><thead><tr><th>Files</th></tr></table>");
$("#filestable").html(displayfilestable);
$("DIV").find("a").each(function(i, el) { // this is your data
var val = $(el).attr('href');
if (val.match(/\.(pdf|doc|docx|txt|html|js|css|rar|7zip)$/)) {
var fileslocation = ("http://localhost/website/files/userstuff/files/" + val)
var adddata = ("<tr><td><a href='"+ fileslocation +"'target='_blank'>"+ val +"</td></tr>");
$("filestable, table").append(adddata);
console.log(adddata)
}
});
AND Jsfiddle here :https://jsfiddle.net/synz/yrag1zpr/
So I have some js which is converting a div class's number through a daily exchange rate engine. It outputs correctly as it should and I am now trying to separate this number it outputs using jQuery and a function I found whilst doing some research. I am trying to feed the number to the function using a .innerHTML method. I have got the function to alert a converted number but I have multiple elements which this function should run for, so have used an .each function - this is where something isn't working. I get no alert so I think there is something wrong with the .each code.
Can anyone see anything that might be causing it?
The complete code is here:
<script src="https://raw.githubusercontent.com/openexchangerates/money.js/master/money.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<div class="hello">
2300
</div>
<div class="hello">
52400
</div>
<script>
function ReplaceNumberWithCommas(yourNumber) {
//Seperates the components of the number
var n= yourNumber.toString().split(".");
//Comma-fies the first part
n[0] = n[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
//Combines the two sections
return n.join(".");
}
$(".hello").each(function() {
var currentDiv = $(this);
var currentPrice = currentDiv.text();
var demo = function(data) {
fx.rates = data.rates
var rate = fx(currentPrice).from("GBP").to("USD");
currentDiv.html("<div>"+currentPrice +"</div><div id='converted'> " +rate.toFixed(0)+"</div>");
//alert("Product Costs" + rate.toFixed(4))
}
$.getJSON("http://api.fixer.io/latest", demo);
});
$("#converted").each(function() {
var convertedPrice = $(this.innerHTML);
function runThis() { alert( ReplaceNumberWithCommas(convertedPrice)) }
setTimeout (runThis, 100);
});
</script>
I think the reason is
$("#converted").each(function() {
var convertedPrice = $(this.innerHTML);
function runThis() { alert( ReplaceNumberWithCommas(convertedPrice)) }
setTimeout (runThis, 100);
});
happends before you created the converted elements. Because you put the creation inside a get call.
I suggest you put this inside the callback of your get call.
Something like this
function ReplaceNumberWithCommas(yourNumber) {
//Seperates the components of the number
var n = yourNumber.toString().split(".");
//Comma-fies the first part
n[0] = n[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
//Combines the two sections
return n.join(".");
}
var currentDiv = $(this);
var currentPrice = currentDiv.text();
var demo = function(data) {
fx.rates = data.rates
$(".hello").each(function() {
var currentDiv = $(this);
var currentPrice = currentDiv.text();
var rate = fx(currentPrice).from("GBP").to("USD");
currentDiv.html("<div>" + currentPrice + "</div><div class='converted'> " + rate.toFixed(0) + "</div>");
//alert("Product Costs" + rate.toFixed(4))
});
$(".converted").each(function() {
var convertedPrice = $(this).html();
console.log(ReplaceNumberWithCommas(convertedPrice));
});
}
$.getJSON("https://api.fixer.io/latest", demo);
I created a function that creates a HTML chunk of code. Its ids are created dynamically with a tag variable collected from a form. Code:
$(function() {
$("#addTag").click(function() {
var tag = $("#tag").val();
$('section').append('<div id="galleryContainer' + tag + '"><div class=".gallery-header"><h1 >Tag:' + tag + '</h1><div class=".gallery-sort"><p>Sort by:</p><button onclick="sortImagesByPublishedDate()" >Data published</button><button onclick="sortImagesByTakenDate()">Data taken</button><div data-tag="' + tag + '" class="gallery component" id="' + tag + '"></div></div></div></div>');
mainFunction(tag);
});
});
Then I want to use sortImagesByPublishedDate() and sortImagesByTakenDate() by clicking a button, but I want them to sort images only in this particular gallery and not in all galleries. If I have one gallery, it works fine. Problems begin when I add more galleries. How should I select the variable $gallery in the following functions?
function sortImagesByPublishedDate() {
var $gallery = $('div.gallery'),
$galleryA = $gallery.children('a');
$galleryA.sort(function(a, b) {
var an = a.getAttribute('data-published'),
bn = b.getAttribute('data-published');
if (an > bn) {
return 1;
}
if (an < bn) {
return -1;
}
return 0;
});
$galleryA.detach().appendTo($gallery);
}
Use the .siblings method to select elements that are siblings of another element. So in your case you can just call
var $gallery = $(buttonElement).siblings(".gallery");
Since you are using inline JS to call the sort functions you need to modify it to pass this to your functions that way you can get a reference to the button that was clicked, ie:
Modified html
<button onclick="sortImagesByPublishedDate(this)">Date published</button>
JS
function sortImagesByPublishedDate(ele){
var $gallery = $(ele).siblings(".gallery"),
Demo
$(function(){
$("#addTag").click(function(){
var tag=$("#tag").val();
$('section').append('<div id="galleryContainer'+tag+'"><div class=".gallery-header"><h1 >Tag:'+tag+'</h1><div class=".gallery-sort"><p>Sort by:</p><button onclick="sortImagesByPublishedDate(this)" >Data published</button><button onclick="sortImagesByTakenDate(this)">Data taken</button><div data-tag="'+tag+'" class="gallery component" id="'+tag+'"></div></div></div></div>');
//mainFunction(tag);
});
});
function sortImagesByPublishedDate(ele){
var $gallery = $(ele).siblings(".gallery"),
$galleryA = $gallery.children('a');
alert($gallery[0].id);
$galleryA.sort(function(a,b){
var an = a.getAttribute('data-published'),
bn = b.getAttribute('data-published');
if(an > bn) {
return 1;
}
if(an < bn) {
return -1;
}
return 0;
});
$galleryA.detach().appendTo($gallery);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<input id="tag">
<button id="addTag">Add</button>
<section>
</section>
Instead of inline js you could use delegated event handling to have listeners setup for your buttons:
Modified html
<button class="sortButton" data-sort="date">Data published</button>
<button class="sortButton" data-sort="taken">Data taken</button>
JS
$("section").on("click",".sortButton",function(){
//'this' will be the button clicked
var $gallery = $(this).siblings(".gallery");
var sortBy = $(this).data("sort");
if(sortBy == "date"){
//do date sort
} else if(sortBy == "taken"){
//do taken sort
}
//... rest of code
});
$(function() {
$("#addTag").click(function() {
var tag = $("#tag").val();
$('section').append('<div id="galleryContainer' + tag + '"><div class=".gallery-header"><h1 >Tag:' + tag + '</h1><div class=".gallery-sort"><p>Sort by:</p><button onclick="sortImagesByPublishedDate()" >Data published</button><button onclick="sortImagesByTakenDate()">Data taken</button><div data-tag="' + tag + '" class="gallery component" id="' + tag + '"></div></div></div></div>');
sortImagesByPublishedDate(tag); **// call the function and pass param tag**
mainFunction(tag);
});
});
function sortImagesByPublishedDate(tag) {
**// instead of class select id**
var $gallery = $('div#galleryContainer'+tag),
$galleryA = $gallery.children('a');
$galleryA.sort(function(a, b) {
var an = a.getAttribute('data-published'),
bn = b.getAttribute('data-published');
if (an > bn) {
return 1;
}
if (an < bn) {
return -1;
}
return 0;
});
$galleryA.detach().appendTo($gallery);
}
// hope this helps
Not tested but I think this will worl:
HTML:
sortImagesByPublishedDate(this)//pass this
JS:
function sortImagesByPublishedDate() {
var $gallery = $(this).siblings('.gallery'),
$galleryA = $gallery.children('a');
.
.
Pass this to sortImagesByPublishedDate(this) in your html of append
so your code is
function sortImagesByPublishedDate(thisObj) {
and then do
var $gallery = $(thisObj).siblings(".gallery");
Im starting to do some small functions and tweaks on websites with javascript, but whats really bothers me is that I dont know how to run the javascript again after a function has run?
For instance if I call a function onclick which adds a user to an array that is shown in my website, the new user wont be displayed until the page is refreshed?
How do I work around this?
EXAMPLE:
if (!localStorage.myStorage) {
// CREATE LOCALSTORAGE
}else{
myArray = JSON.parse(localStorage.myStorage);
for (var i = 0; i < myArray.length; i++) {
if(myArray[i].id === 1){
$(".firstIdContainer").append("<p>" + myArray[i].userName + "</p>");
}
if(aUserLogin[i].id === 2) {
$(".secondIdContainer").append("<p>" + myArray[i].userName + "</p>");
}
}
}
$(document).on("click", ".btnRegisterUser", function() {
// ADD NEW USER TO LOCALSTORAGE
}
How do i make sure my new user i register will be shown immediately through my for loop displaying users.
Like:
if(!localStorage.myStorage){
// CREATE LOCALSTORAGE
}
function doIt(){
var myArray = JSON.parse(localStorage.myStorage);
for(var i in myArray){
var apd = '<p>' + myArray[i].userName + '</p>';
if(myArray[i].id === 1){
$(".firstIdContainer").append(apd);
}
else if(aUserLogin[i].id === 2) {
$(".secondIdContainer").append(apd);
}
}
}
}
doIt();
$('.btnRegisterUser').click(doIt);
Try creating a contentUpdate function that resets whatever is getting displayed and creates it again based on new variables (this would go at the bottom of a function to add the user, for example). The reason that variable changes aren't reflected in the DOM is that the DOM has no abstraction for how it was made; it's output, and it won't change itself based on what its input has done after it was put in.
If you just want to insert a new row into a table you don't need to refresh the page.
jsfiddle
html:
<table id="usertable">
<tr><td>user 1</td></tr>
</table>
<input id="newuser"></input>
<input id="adduser" type="submit"></input>
js:
var button = document.getElementById('adduser');
button.onclick = function(event) {
var user = document.getElementById('newuser').value
//add the user to your array here
//add a table row
var table = document.getElementById('usertable');
var row = table.insertRow(0);
var cell1 = row.insertCell(0);
cell1.innerHTML = user;
event.preventDefault();
}