Refresh datatables data from values from array list - javascript

I have 2 HTML in which I'm just loading data with json then applying jquery datables on them. What I need now is to refresh the data but with new parameters.
example.
JSON:
[
{"name":"jon","sales":"100","set":"SET1"},
{"name":"charlie","sales":"500","set":"SET1"},
{"name":"jon","sales":"350","set":"SET2"},
{"name":"charlie","sales":"300","set":"SET2"}]
<table id="SET1" class="display compact">
<tr><th>Name</th><th>Sales</th>
</table>
<table id="SET2" class="display compact">
<tr><th>Name</th><th>Sales</th>
</table>
JS:
var uri = 'api/schedules';
$(document).ready(function () {
//function to refresh data
//loop thru every dept and query new data.
(function () {
var departments = ['Accounting', 'Sales', 'Marketing']
var idx = 0;
var len = departments.length;
function doNext() {
var entry = departments[idx];
console.log(idx + ":" + entry);
GetData(entry)
idx++;
if (idx < len) {
// Don't do anything special
} else {
// Reset the counter
idx = 0;
}
setTimeout(doNext, 10000);
}
doNext();
}());//end of function
}); //End Jquery Ready
function GetData(dept) {
$.getJSON(uri, { department: dept })
.done(function (data) {
// On success, 'data' contains a list of products.
var tr;
$.each(data, function (key, item) {
tr.append("<td >" + item.NAME + "</td>");
tr.append("<td>" + item.SALES+ "</td>");
tr.append("</tr>");
//loading the data to respective table set
$("#" + item.SET).append(tr);
});
})
}
The function to load the data works. I can see the new data every minute. Initializing the datatables works when is just ran manually.
But if I call the GetData(dept) only the first 'Department' gets loaded. at the second department I get errors from datatable that it can't load the data to the table.
I tried clearing the table with table.empty() aldo table.destroy() and redraw
but I have not been able to make it work.
How do I refresh and redraw the data on the datatables?

The issue is that you're not using DataTables to add your data. Rather, you're adding rows manually, and that is inefficient and means DataTables won't know about them, so sorting, searching etc. won't work as expected. Rather than do that use rows.add(). This JSFiddle seems to be doing what you need, though I'm having to fake the ajax, so you'll need to put the proper getJSON() back in:
let columns = [{
"title": "Name",
"data": "name"
}, {
"title": "Sales",
"data": "sales"
}];
let SET1 = $("#SET1").DataTable({
"columns": columns
});
let SET2 = $("#SET2").DataTable({
"columns": columns
});
let response = [{
"name": "jon",
"sales": "100",
"set": "SET1"
}, {
"name": "charlie",
"sales": "500",
"set": "SET1"
}, {
"name": "jon",
"sales": "350",
"set": "SET2"
}, {
"name": "charlie",
"sales": "300",
"set": "SET2"
}];
let GetData = (dept) => {
$.ajax({
"type": "POST",
"dataType": "json",
"url": "/echo/json/",
"data": {
"json": JSON.stringify(response)
},
"success": (data) => {
console.log(data);
SET1.clear().rows.add(data.filter(x => x.set === "SET1")).draw();
SET2.clear().rows.add(data.filter(x => x.set === "SET2")).draw();
}
});
}
let departments = [
'Accounting',
'Sales',
'Marketing'
];
let idx = 0;
let doNext = () => {
let entry = departments[idx];
console.log(idx + ":" + entry);
GetData(entry)
idx++;
if (idx === (departments.length)) {
idx = 0;
}
setTimeout(doNext, 10000);
}
doNext();
Also, your HTML table creation can be simplified:
<table id="SET1" class="display compact"></table>
<table id="SET2" class="display compact"></table>
Hope that helps.

Related

Reduce time complexity in formatting loop

I am writing a component in Vue that lets us take an array of objects for a table and apply HTML formatting to the data, but the way I have it written currently is an O(n^2) complexity, and I am not sure how to make it any faster.
example row data:
[
{
"product": "Socks",
"price": 39,
"sales": 20,
"inventory": 68
},
{
"product": "Shoes",
"price": 23,
"sales": 99,
"inventory": 79
},
{
"product": "Pants",
"price": 45,
"sales": 46,
"inventory": 58
}
]
example formatter array:
[
{
data: 'product',
format: (data) => {
return '<em>' + data + '</em>';
}
},
{
data: 'price',
format: (data) => {
return '$' + data + '.00';
}
},
{
data: 'sales',
format: (data) => {
return '<span class="text-success">' + data + '</span>';
}
},
{
data: 'inventory',
format: (data) => {
return '<span class="text-primary">' + data + '</span>';
}
},
];
And here is the code that takes the formatter and applies it to the data:
for (let row of rows) {
let renderedRow = {};
for (let key in row) {
let format = render.find(renderObject => renderObject.data === key);
renderedRow[key] = format.format(row[key]);
}
renderedRows.push(renderedRow);
}
this.rows = renderedRows;
The loop iterates over the rows and then iterates over the object in the row. Inside the object's loop, it finds the formatter (renderObject) with the matching key where data === key, and then uses the renderObject's format function to apply the formatting and push the data to a new array.
Because the formatting needs to be applied to the data in every row, I tried to just access it using the key, but since each key is inside an object inside an array I couldn't figure out how to access that.
A simple thing you could do is take the find call and change it to a look-up table computed before the nested loop starts:
const formatters = Object.fromEntries(render.map(({ data, format }) => [data, format]));
for (let row of rows) {
let renderedRow = {};
for (let key in row) {
let format = formatters[key];
renderedRow[key] = format.format(row[key]);
}
renderedRows.push(renderedRow);
}
this.rows = renderedRows;

Javascript changing multiple object values

I am trying to replace / change the values in an object, but I can't seem to work out how it's done or if it can even be done.
I'm trying to add https://SiteName.com to the start of each of the values so it will be like https://SiteName.com\/asset\/image\/map\/map-grass.svg
var assets = [{
"name": "\/asset\/image\/map\/map-grass.svg",
"url": "\/asset\/image\/map\/map-grass.svg"
}, {
"name": "\/asset\/image\/map\/map-stone.svg",
"url": "\/asset\/image\/map\/map-stone.svg"
}]
Object.keys(assets).forEach(key => {
const val = assets[key];
console.log(val)
});
Try this:
var assets = [{
"name": "\/asset\/image\/map\/map-grass.svg",
"url": "\/asset\/image\/map\/map-grass.svg"
}, {
"name": "\/asset\/image\/map\/map-stone.svg",
"url": "\/asset\/image\/map\/map-stone.svg"
}]
let url = "https://SiteName.com";
Object.keys(assets).forEach(key => {
const val = assets[key];
val.name = url + val.name;
val.url = url + val.url;
});
console.log(assets)
You need a nested loop (or forEach) here - one to go over the elements of the assets array, and then, for each object in there, go over all its properties:
var assets = [{
"name": "\/asset\/image\/map\/map-grass.svg",
"url": "\/asset\/image\/map\/map-grass.svg"
}, {
"name": "\/asset\/image\/map\/map-stone.svg",
"url": "\/asset\/image\/map\/map-stone.svg"
}]
assets.forEach(o => {
Object.keys(o).forEach(key => {
o[key] = 'https://SiteName.com' + o[key];
})
});
console.log(assets);

How can I concatenate two equally structured JSON datasets in JavaScript?

I have the following code which calls two different API's, parses the JSON data and displays it on a webpage. Both JSON datasets have the same structure, one with 5 columns and the other one with 20 columns.
The JavaScript code I am using is shown below. How can I combine both JSON datasets into one, so there's a resulting dataset with 25 columns, enabling me to search/reference across all those 25 columns?
The Data Structure of both JSON datasets is as follows:
{
"datatable": {
"data": [
[
"TSLA",
"2019-02-22",
"2019-02-22",
58995.9,
-231.2
]
],
"columns": [
{
"name": "ticker",
"type": "String"
},
{
"name": "date",
"type": "Date"
},
{
"name": "lastupdated",
"type": "Date"
},
{
"name": "ev",
"type": "BigDecimal(15,3)"
},
{
"name": "evebit",
"type": "BigDecimal(15,3)"
}
]
},
"meta": {
"next_cursor_id": null
}
}
The JavaScript Code is as follows:
var apiurls = [
'api1.json',
'api2.json'
],
elroot = document.getElementById('root'),
index = 0;
function setup() {
loadJSON(apiurls[index], gotData);
}
function gotData(data) {
var daten = data.datatable.data[0],
spalten = data.datatable.columns,
output = '';
for (var i = 0; i < spalten.length; i++) {
output = '<p>' + spalten[i].name + ': ' + daten[i] + '</p>';
elroot.innerHTML += output;
}
if (++index < apiurls.length) {
setup();
}
}
something like that ?
var
Json_1 = {
"datatable": {
"data" : ['aa','bb','cc'],
"columns": ['x','y','z']
},
"meta": { 'meta1': 15, 'meta2':87 }
},
Json_2 = {
"datatable": {
"data" : ['ZZbb','cZc'],
"columns": ['xf','yf','zf','zgg']
},
"meta": { 'meta1': 879, 'meta2':4 }
},
Json_cumul_typ0 = { Json_1, Json_2 },
Json_cumul_typ1 = {
"data" : [].concat( Json_1.datatable.data, Json_2.datatable.data ),
"columns": [].concat( Json_1.datatable.columns, Json_2.datatable.columns ),
}
;
console.log( Json_cumul_typ0 );
console.log( Json_cumul_typ1 );
It would be easier to make all the API calls first, combining them into a single result object before doing any processing. Currently, you are making an API call, then processing the results before making the next API call.
I think the nature of async callbacks is making your task more difficult. I suggest using async/await to simplify the logic. Something like this:
var apiurls = [
'api1.json',
'api2.json'
],
elroot = document.getElementById('root');
// Combine all API responses into this object
allResults = {
data: [[]],
columns: []
};
// loadJSON() is probably not async, so here is an async version using fetch()
async function loadJSON(url) {
response = await fetch(url);
return response.json()
}
// Wrap logic in async function so await can be used
(async () => {
// First make all the API calls
for (url of apiurls) {
results = await loadJSON(url);
allResults.data[0] = allResults.data[0].concat(results.datatable.data[0]);
allResults.columns = allResults.columns.concat(results.datatable.columns);
}
// Then process combined allResults object here once.
var daten = allResults.data[0],
spalten = allResults.columns,
output = '';
for (var i = 0; i < spalten.length; i++) {
output = '<p>' + spalten[i].name + ': ' + daten[i] + '</p>';
elroot.innerHTML += output;
}
})();
The loadJSON() you are using probably isn't async. Here are some alternatives you can use:
fetch()
axios
var object1 = {
"datatable": {
"data": [],
"columns": [1,2,3,4]
},
"meta": {}
}
var object2 = {
"datatable": {
"data": [],
"columns": [6,7,8,9,0,11,12,123]
},
"meta": {}
}
Now you want to concatenate columns field. So what you can do is create a deep copy of one of the above. There are better ways to do this than the one mentioned below.
var object3 = JSON.parse(JSON.stringify(object1));
Now to concatenate columns do this,
object3.datatable.columns = object3.datatable.columns.concatenate(object2.datatable.columns);
If you want to concatenate multiple fields you can use a for loop on an object, check if the data type is an array and do the concatenation.
I hope this helps.

Creating Tableau WDC from associative array

I am creating a Tableau Web Data Connector as described in their Getting Started guide HERE.
I have implemented a similar solution previous with data from a basic associative array, but I am having trouble with my current API call.
I make an API call to an external web service that returns a JSON similar to the one listed below (simplified version)(COMMENT simply added for clarity and not part of original code).
{
"status": true,
"employee": {
"id": 123
},
"company": {
"id": 123
},
"job": {
"id": 123,
"job_workflows": [{
"id": 1,
"name": "Start",
"action_value_entered": "Start"
}, {
"id": 2,
"name": "Date",
"action_value_entered": "2017-09-11"
}, {
"id": 3,
"name": "Crew",
"action_value_entered": "Crew 3"
},
**COMMENT** - 17 other unique fields - omitted for brevity
]
}
}
For my requirements, I need to create a new column for each of my JSON job_workflows to display the data in Tableau. I.e.
Column 1 Header = Start
Column 1 value = Start
Column 2 Header = Date Complete
Column 2 value = 2017-09-11
Etc.
My Tableau JavaScript file looks as below:
(function () {
var myConnector = tableau.makeConnector();
myConnector.init = function(initCallback) {
initCallback();
tableau.submit();
};
myConnector.getSchema = function (schemaCallback) {
var cols = [
{ id : "start", alias : "Start", dataType: tableau.dataTypeEnum.string },
{ id : "date", alias : "Date", dataType: tableau.dataTypeEnum.datetime },
{ id : "crew", alias : "Crew", dataType: tableau.dataTypeEnum.string }
];
var tableInfo = {
id : "myData",
alias : "My Data",
columns : cols
};
schemaCallback([tableInfo]);
};
myConnector.getData = function (table, doneCallback) {
$.getJSON("http://some/api/call/data.php", function(response) {
var resp = response; // Response is JSON as described above
var tableData = [];
// Iterate over the JSON object
for (var i = 0, len = feat.length; i < len; i++) {
// not working
tableData.push({
"start": resp.job.job_workflows[i].action_value_entered,
"date": resp.job.job_workflows[i].action_value_entered,
"crew": resp.job.job_workflows[i].action_value_entered
});
}
table.appendRows(tableData);
doneCallback();
});
};
tableau.registerConnector(myConnector);
})();
How do I iterate over the JSON job_workflow and assign each action_value_entered to be a id in my getSchema() function? My current version simply hangs and no values are returned. NO error or warnings thrown.

Need assitance with matching JSON files

I am pretty new to JSON and JS and I am hoping someone can help me out. I am working with two separate JSON files. The recipe JSON file has an ID field and an ingredientNum field. In my second JSON file, I need to match the ingredientNum from the first JSON file with the corresponding field in the second JSON file called itemFullUPC. If there is a match in the fields, I need to replace the current ingredientNum that is displayed on the page in the unordered list with the itemName from the second JSON file that corresponds to the correct itemUPC. Below are the databases and my code. Hope someone can help me out!
Recipe JSON Example:
[
{
"recipeName":"Test",
"id":"10",
"ingredients":[
{
"ingredientNum":"070796501104",
"ingredientMeasure":"1 bottle",
"ingredientMisc1":"(33.8 fl oz)"
},
{
"ingredientNum":"070796000164",
"ingredientMeasure":"1/2 cup",
"ingredientMisc1":""
}
]
}
]
Product JSON Example:
[
{
"productName":"Tomatoes",
"itemFullUPC":"070796501104"
},
{
"productName":"Cherries",
"itemFullUPC":"070796000164"
}
]
For example, in the second database. The productName called "Cherries" has the same number in the first database, I need to replace the list that is currently generated on the page with the item names.
Expected Output
6-8 oz 070796501104 will become 6-8 oz Tomatoes
1/4 tsp 070796000164 will become 1-4 tsp Cherries
I need to do this for the whole list or anything the matches. I have included my attempt below thanks.
$(document).ready(function() {
'use strict';
$.ajax({
url: 'path to recipeDB',
cache: true,
success: function(data){
data = data.replace(/\\n/g, "\\n")
.replace(/\\'/g, "\\'")
.replace(/\\"/g, '\\"')
.replace(/\\&/g, "\\&")
.replace(/\\r/g, "\\r")
.replace(/\\t/g, "\\t")
.replace(/\\b/g, "\\b")
.replace(/\\f/g, "\\f");
data = data.replace(/[\u0000-\u0019]+/g,"");
var json = JSON.parse(data);
$.ajax({
dataType: "jsonp",
url: 'path to itemDB',
cache: true,
success: function(itemData){
var product_data = itemData;
var productUPC = '';
var productName = '';
$.each(product_data, function(i, item) {
productUPC += item.itemFullUPC;
productName += item.itemName;
});
var ingredients = '';
$.each(json, function(i, item) {
if (item.id == "10") {
ingredients += '<ul>';
for (var i = 0; i < item.ingredients.length; i++) {
ingredients += '<li>' + item.ingredients[i].ingredientMeasure + ' ' + item.ingredients[i].ingredientNum + ' ' + item.ingredients[i].ingredientMisc1 + '</li>';
}
ingredients += '</ul>';
}
});
$('#recipeIngredients').html(ingredients);
}
});
}
});
});
I successfully have the list working from the first database but I am not sure how to link to the second database and change the items from showing UPC in the list to the item name.
You can use Array.prototype.map() and Array.prototype.find()
var recipe = [{
"recipeName": "Test",
"id": "10",
"ingredients": [{
"ingredientNum": "070796501104",
"ingredientMeasure": "1 bottle",
"ingredientMisc1": "(33.8 fl oz)"
}, {
"ingredientNum": "070796000164",
"ingredientMeasure": "1/2 cup",
"ingredientMisc1": ""
}]
}];
var product = [{
"productName": "Tomatoes",
"itemFullUPC": "070796501104"
}, {
"productName": "Cherries",
"itemFullUPC": "070796000164"
}];
recipe.ingredients = recipe[0].ingredients.map(function(o) {
o.ingredientName = product.find(function(p) {
return p.itemFullUPC === o.ingredientNum;
}).productName;
return 0;
});
console.log(recipe);
The solution using Array.prototype.forEach() and Array.prototype.some() functions:
var recipes = [{"recipeName":"Test","id":"10","ingredients":[{"ingredientNum":"070796501104","ingredientMeasure":"1 bottle","ingredientMisc1":"(33.8 fl oz)"},{"ingredientNum":"070796000164","ingredientMeasure":"1/2 cup","ingredientMisc1":""}]}],
products = [{"productName":"Tomatoes","itemFullUPC":"070796501104"},{"productName":"Cherries","itemFullUPC":"070796000164"}];
recipes[0].ingredients.forEach(function (recipe) {
products.some(function (product) {
var cond = product.itemFullUPC === recipe.ingredientNum;
if (cond) {
recipe.ingredientNum = product.productName;
}
return cond;
});
});
console.log(recipes);
Now, you can iterate through the recipe ingredients and fill the unordered list
Assuming you have an array of recipes, you can remap the array like this:
var recipes = [{
"recipeName": "Test",
"id": "10",
"ingredients": [{
"ingredientNum": "070796501104",
"ingredientMeasure": "1 bottle",
"ingredientMisc1": "(33.8 fl oz)"
}, {
"ingredientNum": "070796000164",
"ingredientMeasure": "1/2 cup",
"ingredientMisc1": ""
}]
}]
var ingredients = [{
"productName": "Tomatoes",
"itemFullUPC": "070796501104"
}, {
"productName": "Cherries",
"itemFullUPC": "070796000164"
}]
recipes = recipes.map(function(recipe) {
return $.extend(recipe, {
ingredients: recipe.ingredients.map(function(ingr) {
return $.extend(ingr, {
productName: ingredients.find(function(el) {
return ingr.ingredientNum == el.itemFullUPC;
}).productName || ""
});
})
});
});
console.log(recipes);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I would change the products array over to an object so you do not have to keep looping over it to find the products. If you can change the server to return an object with keys instead of an array, that would be a bonus.
var recipe = [{
"recipeName": "Test",
"id": "10",
"ingredients": [{
"ingredientNum": "070796501104",
"ingredientMeasure": "1 bottle",
"ingredientMisc1": "(33.8 fl oz)"
}, {
"ingredientNum": "070796000164",
"ingredientMeasure": "1/2 cup",
"ingredientMisc1": ""
}]
}];
var products = [{
"productName": "Tomatoes",
"itemFullUPC": "070796501104"
}, {
"productName": "Cherries",
"itemFullUPC": "070796000164"
}];
//change products to object for easier lookup
var prodHash = products.reduce(function(o, item) {
o[item.itemFullUPC] = item.productName;
return o;
}, {});
var ingredients = recipe[0].ingredients.map(function(item) {
return "<li>" + item.ingredientMeasure + (item.ingredientMisc1.length ? " " + item.ingredientMisc1 + " " : " ") + prodHash[item.ingredientNum] + "</li>";
}).join("");
document.getElementById("out").innerHTML = ingredients;
<ul id="out"></ul>
first a sidenote:
//these don't do anything, you're literally replacing these strings with the very same strings
data = data.replace(/\\n/g, "\\n")
.replace(/\\'/g, "\\'")
.replace(/\\"/g, '\\"')
.replace(/\\&/g, "\\&")
.replace(/\\r/g, "\\r")
.replace(/\\t/g, "\\t")
.replace(/\\b/g, "\\b")
.replace(/\\f/g, "\\f");
//and these should usually not be in the JSON-string
data = data.replace(/[\u0000-\u0019]+/g, "");
so to the code:
$(document).ready(function() {
'use strict';
//first let's make the ajax-calls parallel
$.when(
$.ajax({
dataType: "jsonp",
url: 'path to recipeDB',
cache: true
}),
$.ajax({
dataType: "jsonp",
url: 'path to itemDB',
cache: true
})
).then(function(recipes, products){
//now let's convert the products into a more useful structure
var productsByUPC = products.reduce(function(acc, item){
acc[ item.itemFullUPC ] = item.productName;
return acc;
}, {});
//a sinple utility
//and don't be shy to use long and speaking names
//it's not your task to minify your code, it'the minifiers task
//and due to autocompletition one can not even brag about having to much to type
function formatIngredientAndAddName( ingredient ){
//here it makes no sense to add "ingredient" to each property name
//do you think, that `ingredient.ingredientMeasure`
//has any benefit over `ingredient.measure`
return {
name: productsByUPC[ ingredient.ingredientNum ],
measure: ingredient.ingredientMeasure,
misc: ingredient.ingredientMisc1
}
}
//and clean up the recipes
return recipes.map(function(recipe){
return {
id: recipe.id,
name: recipe.recipeName,
ingredients: recipe.ingredients.map( formatIngredientAndAddName )
}
});
}).then(function(recipes){
//now we have some clean data, containing all we need,
//let's create some markup
function ingredient2Markup(ingredient){
return '<li>'
+ ingredient.measure
+ ' '
+ ingredient.name
+ ' '
+ ingredient.misc1
+ '</li>';
}
function recipe2Markup(recipe){
return '<ul>' +
recipe.ingredients
.map( ingredient2Markup )
.join("")
+'</ul>';
}
$('#recipeIngredients').html(
recipes.map( recipe2Markup ).join("\n")
);
})
});
Edit:
the recipe data set is actually a php file that is formatted as an array so i cant use json p
I used jsonp there because the other request also was jsonp.
<?php
//... your php file
//json
$output = json_encode( $yourDataStructure );
$contentType = 'application/json';
//optional jsonp output
if(!empty( $_GET['callback'] )){
$contentType = 'application/javascript';
$output = $_GET['callback'] . '(' . $output . ')';
}
//setting the correct Content-Type
//and will throw if you already started sending something besides
header('Content-Type: ' . $contentType);
//ensure that this is the last/only thing that is sent to the client
exit( $output );
?>

Categories