Using sheetjs to style form headers - where do I add cssStyles to the tabulator xlsx download function? - tabulator

I am trying to make the headers of the exported xlsx tabulator table bold using sheetjs.
They provide documentation to style these fields and say to use cssStyles: true, but where do I add this in the download for function using tabulator?
There's not much documentation on how to get these to work with eachother even though it is recommended!
Any help would be amazing!!
table.download("xlsx", "data.xlsx",{
documentProcessing:function(workbook){
//workbook - sheetJS workbook object
cssStyles: true, <--- Would I add it here??
let ws = workbook.Sheets[workbook.SheetNames[0]]
// Reset range to be correct!
function update_sheet_range(ws) {
let range = {s:{r:Infinity, c:Infinity},e:{r:0,c:0}};
Object.keys(ws).filter(function(x) { return x.charAt(0) !== '!'; }).map(XLSX.utils.decode_cell).forEach(function(x) {
range.s.c = Math.min(range.s.c, x.c); range.s.r = Math.min(range.s.r, x.r)
range.e.c = Math.max(range.e.c, x.c); range.e.r = Math.max(range.e.r, x.r)
});
ws['!ref'] = XLSX.utils.encode_range(range)
}
update_sheet_range(ws)
// Now set range to be correct range!
let range = XLSX.utils.decode_range(ws['!ref'])
// Freeze top row
ws['!freeze'] = 'A2'
// Add filters to headers
ws['!autofilter'] = { ref: range }
// Make all columns auto width
if (!ws['!cols']) ws['!cols'] = []
for (let C = 0; C <= 16383; ++C) { // 0 = "A", 16383 = "XFD"
if(!ws['!cols'][C]) ws['!cols'][C] = { auto: 1 } // default width
}
// Bold the headers - THIS ISN'T WORKING :(
range.s.r = 0; range.e.r = 0; // restrict to the first row
XLSX.utils.sheet_set_range_style(ws, "A1:B1", {
bold: true
cellStyles: true, <--- Or do I add it here?
})
console.log(ws)
return workbook
}
});

Related

PDFKit split text into two equal columns while using for loop

Im trying to use PDFKit to generate a simple pdf, for the most part the pdf works but albeit in a very non useful way, what i have is a deck building API that takes in a number of cards, each of these objects i want to export to a pdf, its as simple as displaying their name, but as it is, the pdf only renders one card at a time, and only on one line, what id like to happen is to get it to split the text into columns so itd look similar to this.
column 1 | column 2
c1 c8
c2 c9
c3 c10
c4 c(n)
here is my code so far,
module.exports = asyncHandler(async (req, res, next) => {
try {
// find the deck
const deck = await Deck.findById(req.params.deckId);
// need to sort cards by name
await deck.cards.sort((a, b) => {
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
// Create a new PDF document
const doc = new PDFDocument();
// Pipe its output somewhere, like to a file or HTTP response
doc.pipe(
fs.createWriteStream(
`${__dirname}/../../public/pdf/${deck.deck_name}.pdf`
)
);
// Embed a font, set the font size, and render some text
doc.fontSize(25).text(`${deck.deck_name} Deck List`, {
align: "center",
underline: true,
underlineColor: "#000000",
underlineThickness: 2,
});
// We need to create two columns for the cards
// The first column will be the card name
// The second column will continue the cards listed
const section = doc.struct("P");
doc.addStructure(section);
for (const card of deck.cards) {
doc.text(`${card.name}`, {
color: "#000000",
fontSize: 10,
columns: 2,
columnGap: 10,
continued: true,
});
}
section.end();
// finalize the PDF and end the response
doc.end();
res.status(200).json({ message: "PDF generated successfully" });
} catch (error) {
console.error(error);
res.status(500).json({
success: false,
message: `Server Error - ${error.message}`,
});
}
});
At Present this does generate a column order like i want, however theres and extreme caveat to this solution and that is, if the card text isnt very long, the next card will start on that same line, it'd be useful if i could find a way to make the text take up the full width of that row, but i havent seen anything to do that with.
I think the problem is that you're relying on PDFKit's text "flow" API/logic, and you're having problems when two cards are not big enough to flow across your columns and you get two cards in one column.
I'd say that what you really want is to create a table—based on your initial text sample.
PDFKit doesn't have a table API (yet), so you'll have to make one up for yourself.
Here's an approach where you figure out the dimensions of things:
the page size
the size of your cells of text (either manually choose for yourself, or use PDFKit to tell you how big some piece of text is)
margins
Then you use those sizes to calculate how many rows and columns of your text can fit on your page.
Finally you iterate of over columns then rows for each page, writing text into those column-by-row "coordinates" (which I track through "offsets" and use to calculate the final "position").
const PDFDocument = require('pdfkit');
const fs = require('fs');
// Create mock-up Cards for OP
const cards = [];
for (let i = 0; i < 100; i++) {
cards.push(`Card ${i + 1}`);
}
// Set a sensible starting point for each page
const originX = 50;
const originY = 50;
const doc = new PDFDocument({ size: 'LETTER' });
// Define row height and column widths, based on font size; either manually,
// or use commented-out heightOf and widthOf methods to dynamically pick sizes
doc.fontSize(24);
const rowH = 50; // doc.heightOfString(cards[cards.length - 1]);
const colW = 150; // doc.widthOfString(cards[cards.length - 1]); // because the last card is the "longest" piece of text
// Margins aren't really discussed in the documentation; I can ignore the top and left margin by
// placing the text at (0,0), but I cannot write below the bottom margin
const pageH = doc.page.height;
const rowsPerPage = parseInt((pageH - originY - doc.page.margins.bottom) / rowH);
const colsPerPage = 2;
var cardIdx = 0;
while (cardIdx < cards.length) {
var colOffset = 0;
while (colOffset < colsPerPage) {
const posX = originX + (colOffset * colW);
var rowOffset = 0;
while (rowOffset < rowsPerPage) {
const posY = originY + (rowOffset * rowH);
doc.text(cards[cardIdx], posX, posY);
cardIdx += 1;
rowOffset += 1;
}
colOffset += 1;
}
// This is hacky, but PDFKit adds a page by default so the loop doesn't 100% control when a page is added;
// this prevents an empty trailing page from being added
if (cardIdx < cards.length) {
doc.addPage();
}
}
// Finalize PDF file
doc.pipe(fs.createWriteStream('output.pdf'));
doc.end();
When I run that I get a PDF with 4 pages that looks like this:
Changing colW = 250 and colsPerPage = 3:

Kendo grid how do i auto-size an excel export row height?

I have a custom excel output class that I'm using to parse the grid, and in some cases replace the data in the grid with template data. In this particular instance, the data i want to output is multi-line. I have it working to that point but the exported sheet is one line high so you can't see lines two thru-seven in the field.
desired result:
actual result:
Here's a relevant section of my code. It's the parsing loop that applies the templates and strips html, but adds line breaks first.
if (me.ColumnTemplates && $.isArray(me.ColumnTemplates)) {
for (let c = 0; c < me.ColumnTemplates.length; c++) {
let ct = me.ColumnTemplates[c];
if (ct.template(dr).includes("</br>")) {
sheet.rows[r + 1].cells[ct.cellIndex - 1].wrap = true;
}
me.elem.innerHTML = ct.template(dr).replace(/<\/br>/g, "\n");
sheet.rows[r + 1].cells[ct.cellIndex - 1].value = me.elem.textContent || me.elem.innerText || "";
}
}
any help would be appreciated. I would like to either have a setting that makes this "just work" or have a way to compute the needed height and set it manually. Either is fine.
I'm not aware of a way to auto-size it, but you can set row height it via sheets.rows.height:
<script>
var workbook = new kendo.ooxml.Workbook({
sheets: [{
rows: [{
cells: [{ value: "this row is 100px high" }],
height: 100
}, {
cells: [{ value: "this row is 200px high" }],
height: 200
}]
}]
});
</script>
example found here
Updating your code to utilize each in the template html you can do something like the following:
if (me.ColumnTemplates && $.isArray(me.ColumnTemplates)) {
for (let c = 0; c < me.ColumnTemplates.length; c++) {
let ct = me.ColumnTemplates[c];
if (ct.template(dr).includes("</br>")) {
sheet.rows[r + 1].cells[ct.cellIndex - 1].wrap = true;
sheet.rows[r + 1].height = (ct.template(dr).match(/<\/br>/g) || []).length * 20 + 20; //20 was default row height.
}
me.elem.innerHTML = ct.template(dr).replace(/<\/br>/g, "\n");
sheet.rows[r + 1].cells[ct.cellIndex - 1].value = me.elem.textContent || me.elem.innerText || "";
}
}
Set column width to auto
Solution 1
When kendo grid bound to data source in JavaScript/jQuery
excelExport: function(e) {
var columns = e.workbook.sheets[0].columns;
columns.forEach(function(column){
// also delete the width if it is set
delete column.width;
column.autoWidth = true;
});
}
more details
Solution 2
When kendo grid is taking data source from action controller not bound to data source in jQuery then add event to call a JavaScript function on excel export
function exportToExcelSheetColumnWidthChange(e) {
var columns = e.workbook.sheets[0].columns;
columns.forEach(function (column) {
delete column.width;
column.autoWidth = true;
});
};
Add event to the kendo grid control
.Events(e => e.ExcelExport("exportToExcelSheetColumnWidthChange"))

Each month, populate spreadsheet data to next column [script needed]

I am developing a "Dashboard" [Sample Sheet Here] in which a number of data points are automatically calculated using formulas in Column C (NOTE: formulas not included in sample sheet).
I would like to create a monthly log of Column C data, in which cell values are copied to the next blank column of the corresponding Row
I have previously used the following script to log changes vertically, and could use a hand with repurposing for my desired outcome.
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Action Items" && r.getColumn() == 2 && r.getValue()) {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Portion Tracking");
if(targetSheet.getLastRow() == targetSheet.getMaxRows()) {
targetSheet.insertRowAfter(targetSheet.getLastRow());
}
//Changes Start Here
var myRow = targetSheet.getLastRow()+1;
s.getRange(row, 1).copyTo(targetSheet.getRange(myRow,1));
s.getRange(row, 2).copyTo(targetSheet.getRange(myRow,2));
s.getRange(row, 3).copyTo(targetSheet.getRange(myRow,3));
}
}
AppendColumn()
I played around with this idea once and using a function I took from here and some of my own carving. I've comeup with an appendColumn() function.
Anyway it sounded like something you might want to have. You can take a look at it and in the meantime I'll take a look at your code.
function appendColumn(columnA)
{
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=sh.getDataRange();
var vA=rg.getValues();
var vB=transpose(vA);
sh.clear();
sh.getRange(1,1,vB.length,vB[0].length).setValues(vB);
sh.appendRow(columnA);
vD=sh.getDataRange().getValues();
sh.clear();
vE=transpose(vD);
sh.getRange(1,1,vE.length,vE[0].length).setValues(vE);;
}
function transpose(a)
{
return Object.keys(a[0]).map(function (c) { return a.map(function (r) { return r[c]; }); });
}
To test it I just called it like this.
function testAppendColumn()
{
appendColumn(['',1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]);
}
I'd like to see the source data. But assuming it has not changed then I'm guessing that this technique will work for you.
function onEdit(event)
{
var ss = event.source;
var s = event.source.getActiveSheet();
var r = event.range;
if(s.getName() == "Action Items" && r.getColumn() == 2 && r.getValue())
{
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Portion Tracking");
if(targetSheet.getLastRow() == targetSheet.getMaxRows())
{
targetSheet.insertRowAfter(targetSheet.getLastRow());
}
//Changes Start Here
var myCol = targetSheet.getLastColumn()+1;
s.getRange(row, 1).copyTo(targetSheet.getRange(1,myCol));
s.getRange(row, 2).copyTo(targetSheet.getRange(2,myCol));
s.getRange(row, 3).copyTo(targetSheet.getRange(3,myCol));
}
}
I don't understand the logic of connecting this to onEdit trigger but I'll assume that you do.

Handsontable numeric cell globalization

I'm relatively new to js and now have to implement a handsontable into our project.
This worked well so far, but I am hitting a roadblock with globalization.
Basically, we use comma as a decimal seperator, but when I try and copy something like "100,2" into a cell designated as 'numeric,' it will show as 1002.
If the same value is entered in a cell designated as 'text' and the type is changed to numeric afterwards, the value will be shown correctly.
For this I already had to add 'de' culture to the table sourcecode.(basically copying 'en' and changing the values currently relevant to me.)
numeral.language('de', {
delimiters: {
thousands: '.',
decimal: ','
},//other non-relevant stuff here
When I copy the values directly from the table and insert them to np++ they show as 100.2 etc. However, when inserting them into handsontable the arguments-array looks as follows:
[Array[1], "paste", undefined, undefined, undefined, undefined]
0: Array[4]
0: 1 //row
1: 1 //column
2: "100.2" //previous value
3: 1002 //new value
Here's what I have tried currently:
hot.addHook("beforeChange", function () {
if (arguments[1] === "paste") {
hot.updateSettings({
cells: function (row, col, prop) {
var cellProperties = {
type: 'numeric',
language: 'en'
};
return cellProperties;
}
});
//hot.updateSettings({
// cells: function (row, col, prop) {
// var cellProperties = {
// type: 'text',
// };
// return cellProperties;
// }
//});
}
}, hot);
hot.addHook("afterChange", function () {
if (arguments[1] === "paste") {
ChangeMatrixSettings(); //reset cell properties of whole table
}
}, hot);
I hope I've made my problem clear enough, not sure if I missed something.
Are there any other ways to get the correct values back into the table? Is this currently not possible?
Thanks in advance.
You asked more than one thing, but let me see if I can help you.
As explained in handsontable numeric documentation, you can define a format of the cell. If you want '100,2' to be shown you would format as follows
format: '0.,'
You can change that to what you really need, like if you are looking for money value you could do something like
format: '0,0.00 $'
The other thing you asked about is not on the latest release, but you can check it out how it would work here
I have since implemented my own validation of input, due to other requirements we have for the table mainly in regards to showing invalid input to user.
function validateInputForNumeric(parameter) {
var value = parameter[3];
var row = parameter[0];
var col = parameter[1];
if (decimalSeperator === '') {
var tmpculture = getCurrCulture();
}
if (value !== null && value !== "") {
if (!value.match('([a-zA-Z])')) {
if (value.indexOf(thousandSeperator) !== -1) {
value = removeAndReplaceLast(value, thousandSeperator, ''); //Thousandseperators will be ignored
}
if (value.indexOf('.') !== -1 && decimalSeperator !== '.') {
//Since numeric variables are handled as '12.3' this will customize the variables to fit with the current culture
value = removeAndReplaceLast(value, '.', decimalSeperator);
}
//Add decimalseperator if string does not contain one
if (numDecimalPlaces > 0 && value.indexOf(decimalSeperator) === -1) {
value += decimalSeperator;
}
var index = value.indexOf(decimalSeperator)
var zerosToAdd = numDecimalPlaces - (value.length - index - 1);
for (var j = 0; j < zerosToAdd; j++) {
//Add zeros until numberOfDecimalPlaces is matched for uniformity in display values
value += '0';
}
if (index !== -1) {
if (numDecimalPlaces === 0) {
//Remove decimalseperator when there are no decimal places
value = value.substring(0, index)
} else {
//Cut values that have to many decimalplaces
value = value.substring(0, index + 1 + numDecimalPlaces);
}
}
if (ErrorsInTable.indexOf([row, col]) !== -1) {
RemoveCellFromErrorList(row, col);
}
} else {
AddCellToErrorList(row, col);
}
}
//console.log("r:" + row + " c:" + col + " v:" + value);
return value;
}
The inputParameter is an array, due to handsontable hooks using arrays for edit-events. parameter[2] is the old value, should this be needed at any point.
This code works reasonably fast even when copying 2k records from Excel (2s-4s).
One of my main hindrances regarding execution speed was me using the handsontable .getDataAtCell and .setDataAtCell methods to check. These don't seem to handle large tables very well ( not a critique, just an observation ). This was fixed by iterating through the data via .getData method.

Jscript to check defined array

My code gathers all categories from a CSV file, sorts and grabs top 10 categories and the top 10 result are displayed on a chart. The code works fine if the gategory found is 10, but if it is less than 10 no chart is displayed..basically code dies.
I am a newbie when it comes to coding and the code was passed on to me by someone else who is not available. What I would like to add is an if cases that checks:
Gather all category, sort
Set category to 1 to 10
if category is null, stop. Chart has no value
if category=1 ===> Display found value
if category=2 ===> Display found value
if category=3 ===> Display found value
if category=3 ===> Display found value
So on, so on..untill it reaches to 10
//collect top 10 cat from array
catArray.sort(sort_by("count", false, function (a) {
return parseInt(a)
}));
var categorytop10 = new Array(catArray[0]["tier3"], catArray[1]["tier3"], catArray[2]["tier3"], catArray[3]["tier3"], catArray[4]["tier3"], catArray[5]["tier3"], catArray[6]["tier3"], catArray[7]["tier3"], catArray[8]["tier3"], catArray[9]["tier3"]);
var categorytop10Count = new Array(catArray[0]["count"], catArray[1]["count"], catArray[2]["count"], catArray[3]["count"], catArray[4]["count"], catArray[5]["count"], catArray[6]["count"], catArray[7]["count"], catArray[8]["count"], catArray[9]["count"]);
Any help is appreciated. Thanks
Based on the limited info provided, here is what I can suggest.
//collect top 10 cat from array
catArray.sort(sort_by("count", false, function (a) {
return parseInt(a)
}));
var categorytop10 = new Array();
var categorytop10Count = new Array();
for (var i = 0; i < 10 && i < catArray.length; ++i)
{
categorytop10.push( catArray[i]["tier3"] );
categorytop10Count.push( catArray[i]["count"] );
}
EDIT: You can also try this
//collect top 10 cat from array
catArray.sort(sort_by("count", false, function (a) {
return parseInt(a)
}));
var categorytop10 = new Array();
var categorytop10Count = new Array();
for (var i = 0; i < 10; ++i)
{
if (i < catArray.length)
{
categorytop10.push( catArray[i]["tier3"] );
categorytop10Count.push( catArray[i]["count"] );
}
else {
categorytop10.push( "?" );
categorytop10Count.push( "0" );
}
}

Resources