In my angular app i display JSON data in a table with pagination using ngx-pagination i want to export my table in pdf format I used a couple of npm packages that take a screenshot off the HTML page and convert it to a pdf to export, the only problem is not all the data is being displayed because of the pagination and number of items per page, is there a work around this?
Here is the code I used :
<table
class="
table
is-hoverable is-striped is-fullwidth
content-table
#userTable
"
id="htmlData"
>
<thead>
<tr>
<th>{{ "DataSource.RecordID" | translate }}</th>
<th colspan="6">{{ "DataSource.Details" | translate }}</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="
let row of FilteredMatchTransactions
| paginate: { itemsPerPage: itemsPerPage, currentPage: p };
let i = index
"
>
<td>
{{ "DataSource.SessionID" | translate }} :
{{ row.SESSION_ID }}
</td>
<td>
{{ "DataSource.ExternalStan" | translate }} :
{{ row.EXTERNAL_STAN }}
</td>
<td>
PAN :
{{ row.CARD_NUMBER }}
</td>
<td>
{{ "DataSource.Amount" | translate }} :
{{ row.TRANSACTION_AMOUNT.split(" ")[0] }}
</td>
<td>
{{ "DataSource.Currency" | translate }} :
{{ row.TRANSACTION_AMOUNT.split(" ")[1] }}
</td>
<td>
Terminal :
{{ row.TERMINAL_ID }}
</td>
<td>
{{ "DataSource.TransactionDate" | translate }} :
{{ row.TRANSACTION_DATE }}
</td>
</tr>
</tbody>
</table>
ts :
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
....
....
public downloadPDF(): void {
let DATA = document.getElementById('htmlData');
console.log(DATA);
html2canvas(DATA).then((canvas) => {
let fileWidth = 208;
let fileHeight = (canvas.height * fileWidth) / canvas.width;
const FILEURI = canvas.toDataURL('image/png');
let PDF = new jsPDF('p', 'mm', 'a4');
let position = 0;
PDF.addImage(FILEURI, 'PNG', 0, position, fileWidth, fileHeight);
PDF.save('angular-demo.pdf');
});
}
my idea is about to use a temporary created table DOM element instead of using angular active table (situated in the view), this is an overview of the idea:
// I suppose that this jsonApiData is an array got from API which you used it in DataSource and containing all data without pagination, so you should be sure that you already loaded all data from the API before performing any data manipulation or consumption:
private jsonApiData;
public downloadPDF(): void {
/*let table = document.getElementById('htmlData');*/
// refer to getTemporaryTableDom method below
let table = this.getTemporaryTableDom(this.jsonApiData);
html2canvas(table).then((canvas) => {
...
...
// Important: after finishing your save, remove the temporary table DOM from the memory
table.remove(); ==> to free up your memory.
});
}
...
...
/** this method will create a temporary table dom,
* and fill it with your api data.
**/
private getTemporaryTableDom(jsonData){
let t = document.createElement('table');
// fill your DOM Html table with your api data
for(let [index,obj] of Object.entries(jsonData)){
let newInsertedRow = t.insertRow(index);
// to insert cells and its values you can also use loop, i will let you free to chose
c = newInsertedRow.insertCell(0); // example SessionID
c.innerHTML = obj.SessionId; // SessionID value
c = r.insertCell(1); // example: ExternalStan
c.innerHTML = obj.ExternalStan; // ExternalStan value
...
...
}
return t; // to return the DOM TAble html element
}
This is just an idea to make sure you are using static data so that you will be able to control it without any problem, because using the angular view Doms can make things more difficult to manage because they are based on many concepts like ChangeDetection, component lifecycle, etc..
for more table dom manipulation details, you can refer to these ressources:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableElement
https://stackoverflow.com/a/2324826/8678900
Related
I am having a problem when I try to update the DOM with new information coming from an API.
Every time that I click to add new users, the array displays the old, and new information. Ideally, it would update the array first and then display only the new information. I will attach a picture of what is happening. I would like to every time the user click on add new user, the DOM update with only the information of that new user.
HTML part
<table class="table is-fullwidth table is-hoverable table-info">
<thead>
<tr">
<th title="Channel Name" class="has-text-left"> Channel Name </th>
<th title="View per week" class="has-text-right"> View per week </th>
</tr>
</thead>
<tbody id="body-table">
<tr id="tr-table">
</tr>
</tbody>
</table>
script.js
const trline = document.getElementById('body-table')
let usersList = [];
async function getnewUsers(){
const res = await fetch('https://randomuser.me/api')
const data = await res.json()
// create an instance of the results
const user = data.results[0]
// create the new user
const newUser = {
name:`${user.name.first} ${user.name.last}`,
social: Math.floor(Math.random() * 10000 )
}
// update the new user to the database...
addData(newUser)
}
function addData(obj) {
usersList.push(obj)
// update the information on the screen
updateDOM()
}
function updateDOM( providedData = usersList){
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}
addUser.addEventListener('click', getnewUsers)
Result picture:
I found the problem and the solution.
I didn't reset the HTML part to clear before adding a new item. I had to fix the function updateDOM with this: trline.innerHTML = ''
After that, the function works fine.
function updateDOM( providedData = usersList){
trline.innerHTML = '' // clear everything before adding new stuff
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}
I have a problem that my dynamic data is not getting binded to the UI and also Data tables. I have tried using various ways but its not working. I am using smart admin latest theme for my development of website
when I hit the api i get response.
var tabledata:any[]=[]
Get(){
this.getservice().subscribe(
res=>{
if(res && res.data && res.data.length>0){
this.tabledata=res.data;
console.log(this.tabledata);
}
}
)
}
In html
<tr *ngFor="let data of tabledata">
<td>{{data.name}}</td>
<td>{{data.age}}</td>
</tr>
Could you please try to excute your Get method in OnInit?
Why do your Get method starts with a capital letter?
Why do you declare your table as a table of any? Using typed objects i always better.
Can you try this code below?
vartabledata: Person[];
class MyComponent implements OnInit {
ngOnInit() { this.get()
}
get(){
this.getservice().subscribe(
res=> {this.tabledata = res.data;
console.log(this.tabledata);}
)}
}
In front i would suggest you to add *ngIf so you display table only when your data are loaded.
<div *ngIf="tabledata">
<tr *ngFor="let data of tabledata">
<td>{{data.name}}</td>
<td>{{data.age}}</td>
</tr>
</div>
If there are no data even with this, try to do
<div *ngIf="tabledata">
<tr *ngFor="let data of tabledata">
{{data | json}}
</tr>
</div>
I think that you receive a data object that doesn't contains name and age
attributes
I have an index.html where I have a div for table, that I am populating with javascript:
<div class="table-responsive table-hover">
<table class="table" id="table">
</table>
</div>
I have created handlebar template file:
<tr>
<td>{{ avatar }}</td>
<td>{{ name }}</td>
<td>{{ homepage }}</td>
<td>{{ score }}</td>
</tr>
In my script I am importing the template and trying to send data which is an array of objects that I get from an api endpoint, I am doing a forEach loop on an array of objects first, and calling a createHtml function:
var tableTemplate = require('../../templates/table.handlebars');
var createHtml = function(data) {
console.log(data);
var table = document.querySelector('#table');
table.innerHTML = tableTemplate({
avatar: data.owner.avatar_url,
name: data.full_name,
homepage: data.homepage,
score: data.score
});
}
array.forEach(createHtml);
But, even though I get all the objects from the array logged in the console inside the function createHtml only the first row for the table is being created, why is that, and how can I fix it?
Update
I have tried something like this, but I get table undefined:
var tableTemplate = require('../../templates/table.handlebars');
var createHtml = function(data) {
console.log(data);
table += tableTemplate({
avatar_url: data.owner.avatar_url,
full_name: data.full_name,
homepage: data.homepage,
score: data.score
});
}
export function createTable(repositories, page) {
var itemsPerPage = 20,
offset = page*20;
table = document.querySelector('#table');
table.innerHTML = '';
repositories.slice(offset, offset+itemsPerPage).forEach(createHtml);
}
You are replacing the HTML using innerHTML for each iteration, losing the HTML from the previous iteration. Concatenate all the strings and set the innerHTML once, outside the forEach. A better option (using only local variables) is to use Array#map and Array#join.
var tableTemplate = require('../../templates/table.handlebars');
document.querySelector('#table').innerHTML = array.map(function(data) {
return tableTemplate({
avatar: data.owner.avatar_url,
name: data.full_name,
homepage: data.homepage,
score: data.score
});
}).join('');
I'm currently learning d3.js for visualization, using Flask as the python backend, and following this example of showing two simple charts with different datasets on the same page.
I'm attempting the following modification: instead of using CSVs, I'd like to pass a json file from a python backend (the example just reads in data from two csv files). How might I pass 2 json datasets over? I can pass one as follows:
Python backend
import flask...
app = flask.Flask(__name__)
#app.route("/")
def index():
return flask.render_template("index.html")
#app.route("/data")
def data():
x = [1, 2, 3, 4, 5]
y = [1, 2, 3, 4, 5]
js = [{"x":x[i], "y":y[i]} for i in range(len(x))]
return json.dumps(js)
index.html
<script>
d3.json("/data", function(data) ...)
</script>
I've tried writing another python function, like def data_bar to return a separate json dataset, but that couldn't be read. In theory, I could pass both data sets in one json dataset, like:
x = [1,2,3,4,5]
y = [1,2,3,4,5]
x1 = [10,20,30]
y1 = [40,50,60]
But this encounters problems if the domains of the first set (x and y) and the second set (x1 and y1) aren't the same. e.g. the first set could be "student_name" and "grade", the second set could be "class_name" and "average_grade".
render with one data set passed in:
return render_template("result.html",resultVarInHtml = myData)
render with multiple data set passed in:
return render_template("result.html",resultVarInHtml = myData, resultVarInHtml2 = myData2)
Now when I load result.html
I can use this code to show my data (assuming a key value dictionary object, but you get the point)
results.html
<!doctype html>
<html>
<body>
<p>First Data Set</p>
<table border = 1>
{% for key, value in resultVarInHtml.iteritems() %}
<tr>
<th> {{ key }} </th>
<td> {{ value }} </td>
</tr>
{% endfor %}
</table>
<p>2nd Data Set</p>
<table border = 1>
{% for key, value in resultVarInHtml2.iteritems() %}
<tr>
<th> {{ key }} </th>
<td> {{ value }} </td>
</tr>
{% endfor %}
</table>
</body>
</html>
And you will get a proper table, mate.
I'd like to filter dynamically a flask generated table thanks to a variable set in JavaScript from another similar table.
Unfortunately, it seems that Javascript variables cannot be reused in Jinja2 contexts (because jinja2 contexts are run prior to Javascript).
In the example below, I'd like to filter the tasks with the project_id. This project_id was set thanks to the value selected in another table.
Note: I'd like to avoid to reload the page thanks to this solution.
{% for Task in mytasks %}
{% if Task.project_id == var_project_id %} <- Not working, the javascript variable is not recognized
<tr class="clickable-row">
<td style="display:none;"> {{ Task.task_id }} </td>
<td style="display:none;"> {{ Task.project_id }} </td>
<td>{{ Task.title }}</td>
<td class="task_description" > {{ Task.description }} </td>
<td class="task_creation_date"> {{ Task.creation_date }} </td>
</tr>
{% endfor %}
I've found a solution thanks to a simple javascript function.
Here is it, just in case some one else has the same issue:
<script>
//The project id is defined when the project is selected in a hover table
$('#myTableProject tbody tr').click(function (event) {
$('tr').not(this).removeClass('highlight');
$(this).addClass('highlight');
project_id = $(this).find('td.project_id').text();
//...
var tableTasks;
tableTasks = document.getElementById("myTableTasks");
tr = tableTasks.getElementsByTagName("tr");
// Loop through all table rows, and hide those who don't match the search query
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[1];// [1] is the column number you want to filter
if (td) {
//each cell of the column [1] is compared to the project id
if (td.innerHTML.toUpperCase().indexOf(project_id) > -1) {
tr[i].style.display = "";//the data is displayed
} else {
tr[i].style.display = "none";//the data is hidden
}
} }
</script>
More info:
https://www.w3schools.com/howto/howto_js_filter_table.asp