I have added a print button and a PrintDialog to my custom app and now need to write a print function.
Do I need to open up a new window and build a table containing the grid data formatted with css styles fitted to A4 paper size or is there something built into Rally that I can use?
I am new to Rally and Ext JS, so any advice would be appreciated!
Ext.create('Rally.ui.dialog.PrintDialog', {
height: 250,
autoShow: true,
autoCenter: false,
shouldShowFormatOptions: false,
defaultTitle: 'Book Of Work Report',
listeners: {
print: function(event) {
//How do I print a grid?
},
To print the grid, I created a Rally.data.WsapiDataStore and loaded the store into an array. I passed this array(StoreData) to the below function which opens a print window displaying the grid in a table.
_printDetails: function(Storedata) {
var myData = Storedata;
var htmlTable ='<table>';
htmlTable +='<width="100%">';
var r,c;
for(r= 0 ; r<myData.length; r++){
htmlTable+= '<tr>';
for(c = 0 ; c<myData[0].length; c++){
htmlTable+='<td>'+myData[r][c]+'</td>';
}
htmlTable+='</tr>';
}
htmlTable+='</table>';
var cssTable = '<style type="text/css">';
cssTable +='table {border-collapse:collapse;...}';
cssTable +='th {color:#080808;border-bottom-style: solid; ...}';
cssTable +='tr {color:#000000; border-bottom-style: solid; ..}';
cssTable +='td {padding:3px 4px; text-align:left; vertical-align:top;}';
cssTable +='#filter {text-align:left; ...}';
cssTable += '</style>';
var printwindow=window.open('', '', 'width=1000,height=500');
var myDate = new Date;
printwindow.document.write('<div id="todayDate">' + Ext.Date.format(myDate,'F j, Y, g:i a') + '</div>');
printwindow.document.write('<div id="header">Book Of Work Report</div>');
printwindow.document.write('<div id="filter"><p>' + this._GetFilterString() + '</p></div>');
printwindow.document.write(htmlTable);
printwindow.document.write(cssTable);
printwindow.document.close();
printwindow.focus();
printwindow.print();
printwindow.close();
},
Your inclination is correct - the best option here is to open a window that you populate with table output/grid content and apply whatever CSS formatting you prefer. Rally doesn't have any server-side printing output functionality (at least that is exposed to AppSDK2).
Related
I finally got my html2pdf to work showing my web page just how I want it in the pdf(Any other size was not showing right so I kept adjusting the format size until it all fit properly), and the end result is exactly what I want it to look like... EXCEPT even though my aspect ratio is correct for a landscape, it is still using a very large image and the pdf is not standard letter size (Or a4 for that matter), it is the size I set. This makes for a larger pdf than necessary and does not print well unless we adjust it for the printer. I basically want this exact image just converted to a a4 or letter size to make a smaller pdf. If I don't use the size I set though things are cut off.
Anyway to take this pdf that is generated and resize to be an a4 size(Still fitting the image on it). Everything I try is not working, and I feel like I am missing something simple.
const el = document.getElementById("test);
var opt = {
margin: [10, 10, 10, 10],
filename: label,
image: { type: "jpeg", quality: 0.98 },
//pagebreak: { mode: ["avoid-all", "css"], after: ".newPage" },
pagebreak: {
mode: ["css"],
avoid: ["tr"],
// mode: ["legacy"],
after: ".newPage",
before: ".newPrior"
},
/*pagebreak: {
before: ".newPage",
avoid: ["h2", "tr", "h3", "h4", ".field"]
},*/
html2canvas: {
scale: 2,
logging: true,
dpi: 192,
letterRendering: true
},
jsPDF: {
unit: "mm",
format: [463, 600],
orientation: "landscape"
}
};
var doc = html2pdf()
.from(el)
.set(opt)
.toContainer()
.toCanvas()
.toImg()
.toPdf()
.save()
I have been struggling with this a lot as well. In the end I was able to resolve the issue for me. What did the trick for me was setting the width-property in html2canvas. My application has a fixed width, and setting the width of html2canvas to the width of my application, scaled the PDF to fit on an A4 paper.
html2canvas: { width: element_width},
Try adding the above option to see if it works. Try to find out the width of your print area in pixels and replace element_width with that width.
For completeness: I am using Plotly Dash to create web user interfaces. On my interface I include a button that when clicked generates a PDF report of my dashboard. Below I added the code that I used for this, in case anybody is looking for a Dash solution. To get this working in Dash, download html2pdf.bundlemin.js and copy it to the assets/ folder. The PDF file will be downloaded to the browsers default downloads folder (it might give a download prompt, however that wasn't how it worked for me).
from dash import html, clientside_callback
import dash_bootstrap_components as dbc
# Define your Dash app in the regular way
# In the layout define a component that will trigger the download of the
# PDF report. In this example a button will be responsible.
app.layout = html.Div(
id='main_container',
children = [
dbc.Button(
id='button_download_report',
children='Download PDF report',
className='me-1')
])
# Clientside callbacks allow you to directly insert Javascript code in your
# dashboards. There are also other ways, like including your own js files
# in the assets/ directory.
clientside_callback(
'''
function (button_clicked) {
if (button_clicked > 0) {
// Get the element that you want to print. In this example the
// whole dashboard is printed
var element = document.getElementById("main_container")
// create a date-time string to use for the filename
const d = new Date();
var month = (d.getMonth() + 1).toString()
if (month.length == 1) {
month = "0" + month
}
let text = d.getFullYear().toString() + month + d.getDay() + '-' + d.getHours() + d.getMinutes();
// Set the options to be used when printing the PDF
var main_container_width = element.style.width;
var opt = {
margin: 10,
filename: text + '_my-dashboard.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 3, width: main_container_width, dpi: 300 },
jsPDF: { unit: 'mm', format: 'A4', orientation: 'p' },
// Set pagebreaks if you like. It didn't work out well for me.
// pagebreak: { mode: ['avoid-all'] }
};
// Execute the save command.
html2pdf().from(element).set(opt).save();
}
}
''',
Output(component_id='button_download_report', component_property='n_clicks'),
Input(component_id='button_download_report', component_property='n_clicks')
)
I have a menu that always has the same structure, but the IDs can change from one installation to another. the only thing that stays the same is the heading (in my case "Plugins"). I call the document.getElementsByClassName function with a Selector inside my test:
var slides = Selector(() =>{
return document.getElementsByClassName("c-p-header-text");
});
Every heading of an menu element has the c-p-header-text class. Here is what a menu heading element looks like:
<div id="ext-comp-1002" class="c-p c-tree c-p-collapsed" style="width: auto;">
<div class="c-p-header c-unselectable c-accordion-hd" id="ext-gen135" style="cursor: pointer;">
<div class="c-tool c-tool-toggle" id="ext-gen140"> </div>
<img src="/backEnd/images/s.gif" class="c-p-inline-icon order"><span class="c-p-header-text" id="ext-gen150">Plugins</span>
</div>
It would be easy to use await t.click("#ext-gen150") but it is not safe that it is always this id.
here is what i tried:
await t
.click('#sites__db');
var slides = Selector(() =>{
return document.getElementsByClassName("c-p-header-text");
});
console.log("[DEBUG]" + slides);
console.log("[DEBUG] found " + slides.length + " elements");
for(var i = 0; i < slides.length; i++)
{
var txtOfCurrElem = slides.item(i).innerText;
console.log("[DEBUG "+ i +"] Text: " + txtOfCurrElem);
}
Running this test give me the following output:
[DEBUG]function __$$clientFunction$$() {
var testRun = builder.getBoundTestRun() || _testRunTracker2.default.resolveContextTestRun();
var callsite = (0, _getCallsite.getCallsiteForMethod)(builder.callsiteNames.execution);
var args = [];
// OPTIMIZATION: don't leak `arguments` object.
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}return builder._executeCommand(args, testRun, callsite);
}
[DEBUG] found 0 elements
The plan is to find the element (with the heading "Plugins") and then click on it when the test continuous.
You don't have to use document.getElementsByClassName in this case. You can just use CSS class selector instead:
var slides = Selector('.c-p-header-text');
You should use the count property when dealing with an array of Selectors. docs. Also, element properties, like exists, count, and DOM node state properties are Promisified, so when you use them not in t.expect, you should use the await keyword:
var count = await slides.length;
console.log("[DEBUG] found " + count + " elements");
for(var i = 0; i < count; i++)
{
var txtOfCurrElem = await slides.nth(i).innerText;
console.log("[DEBUG "+ i +"] Text: " + txtOfCurrElem);
}
I found a simple answer to my question. I use the .withText option to click on the Plugins element:
.click(Selector('span').withText("Plugins"))
Since this name is also unique, it is always the correct element that gets clicked. I do not know if it would have worked with the solution from #AndreyBelym if my site is not an extJS web application.
I create a PDF report with header generated automatically by phantomjs. In the report I have a long div which contains text, blank lines, simple data tables etc.
Since I believe phantomjs might have problem with the page break, I calculate the page break manually, break up the long div into multiple divs with page break at the end of each div.
Here is my calculation in the jQuery(document).ready()
//pagination the Notes pages
if (encData.pnotes && encData.pnotes.length > 0) {
pageCnt++;
var $notes = jQuery('<div id="pageNotes" class="notes" style="width:100%;margin-top:100px;border:1px solid red">' +
pNotesString + '</div>');
$body.append($notes);
var height = 0;
var children = $notes.children();
var p = children.slice(0);
var pages = [];
var counter = 0;
for (var i = 0; i < children.length; i++) {
if (height + children[i].offsetHeight < 1350)
height += children[i].offsetHeight;
else {
pages.push([counter, i]);
height = 0;
counter = i;
}
}
pages.push([counter, children.length]);
for (var i = 0; i < pages.length; i++) {
//first notes page
if (i == 0) {
$notes.children().slice(pages[i][1]).remove();
$notes.after('<div style="border:1px solid black;height:1px"></div><div id="pageNotesBreak" style="page-break-after:always"> </div>');
}
else {
pageCnt++;
var $new = jQuery('<div id="page' + pageCnt + '" class="notes" style="width:100%;margin-top:100px;border:1px solid blue"></div>');
$new.append(p.slice(pages[i][0], pages[i][1]));
$body.append($new);
$body.append('<div style="border:1px solid black;height:1px"></div><div id="pageBreak' + pageCnt + '" style="page-break-after:always"> </div>');
}
}
The "<div style='border:1px solid black;height:1px'>" is just a silly marker to find where my page breaks are on the PDF.
1350px in the estimated height of objects where the page break should be created when I traverse all the children of the long div.
In Chrome, I see that I have a total of 5 pages.
But when it is rendered in PDF, the number of pages goes up to 6 or 7 depending the size of the font, the line height, etc.
Here is the image of the pages
Does anyone know the cause of the problem? And how to solve it in general.
I am trying to use Datatables with fixedheader (v3) as well as enable horizontal scrolling. Attached is the fiddle http://jsfiddle.net/xF8hZ/344/
$(document).ready(function() {
var table = $('#example').DataTable({
searching: false,
paging: false,
ordering: false,
info: false,
fixedHeader: true,
scrollX: true
});
} );
.
When scrolling the fixedheader width doesn't align with the rest of the table. Can you help me solve this please?
Thanks
Pure css solution using css sticky (not work in ie 11):
remove the fixHeader plugin
add this css
.dataTables_scrollHead {
position: sticky !important;
top: 119px;
z-index: 99;
background-color: white;
box-shadow: 0px 5px 5px 0px rgba(82, 63, 105, 0.08);
}
I have read for 2 days about this, so joining all of them together, here's my contribution.
I got it figured out, hopefully this is useful for someone or help in the development as well.
My datatables is in a DIV and horizontal Scrolling enable due to huge table. When fixed header was set it was set as FIXED, and a new table is inserted at the BODY rather than inside the div.
I made it appended to the DIV instead of BODY so that the overflow rule might be inherited.
File:
dataTables.fixedHeader.min.js (search for "appendTo")
From:
e.table().node().cloneNode(!1)).removeAttr("id").append(f).appendTo("body")
To:
e.table().node().cloneNode(!1)).removeAttr("id").append(f).appendTo(".dataTables_scroll")
Now that it's appended to the the datatables-created-div, same level as dataTables_scrollHead, dataTables_scrollBody rather than stranded alone at body, whatever overflow still showing/sticking out.
File:
fixedHeader.bootstrap.min.css
From:
table.dataTable.fixedHeader-floating{position:fixed !important}
To
table.dataTable.fixedHeader-floating{position:absolute !important}
or File:
fixedHeader.dataTables.min.css
From:
table.fixedHeader-floating{position:fixed !important;background-color:white;}
To
table.fixedHeader-floating{position:absolute !important;background-color:white;}
Careful of CACHE of the CSS and JS files.
Now that the floating sticky row has appeared but out of place and overflow in effect.
Have this JS running, detecting when fixedHeader-floating appears, keep adjusting them to follow the horizontal scroll and stick to the top.
setInterval(function(){
if($('.fixedHeader-floating').is(':visible')){
var myoffset = Math.round($(window).scrollTop() - $('#Detail2Container').position().top + $('.topbar').height() - 145);
var positionleft = $('.dataTables_scrollHeadInner').position();
$('.fixedHeader-floating').css({ 'top': myoffset, 'left': positionleft.left + 10 });
}
}, 50); //every 50ms
Detail2Container is the DIV that wrap the Datatables.
I couldn't use dataTables_wrapper as reference as there are a few of them in the same page. In my page, I only one table that needs fixedHeader, if I need 2, it will be tough. But I will deal with it when the needs arise.
You could adjust the calculation according to your own design.
2 days for me to figure this out. So I feel like sharing it too.
I found a solution on my project by doing this:
$('#example').scroll(function() {
if ( $(".fixedHeader-floating").is(":visible") ) {
$(".fixedHeader-floating").scrollLeft( $(this).scrollLeft() );
}
});
DataTables creates a new table as the fixedHeader when you scroll down, what I'm doing here is to detect when the user scrolls horizontally on the $('#example') table and then I use scrollLeft() on the fixedHeader to match the scroll position.
I also added this to my .css so the user won't be able to scroll on the fixedHeader table:
.fixedHeader-floating {
overflow: hidden;
}
Following is working to me
$('.dataTables_scrollBody').on('scroll', function () {
$('.dataTables_scrollHead', $(this).parent()).scrollLeft($(this).scrollLeft());
});
This fixed the problem.
let tableParams = {
autoWidth: false,
// etc...
scrollX: true,
fixedHeader: true,
initComplete: function(settings, json) {
// To fix the issue of when scrolling on the X axis, the header needs also to scroll as well.
this.find('.dataTables_scrollBody').on('scroll', function() {
this.find('.dataTables_scrollHeadInner').scrollLeft($(this).scrollLeft());
});
},
};
Also to hide the vertical scroll bar.
me.containerElement.find('.dataTables_scrollBody').css({'overflow-y': 'hidden'});
where containerElement is the parent element of the datatable element.
Based on this, I was able to make it working (issue: when FixedHeader is floating, sorting would not work ==> see update 1 below to fix it)
Explanations:
FixedHeader (.dataTables_scrollHeadInner) is a different table outside of datatable (.dataTables_scrollBody)
when scrolling vertically, it will check scrolltop and set FixedHeader top accordingly.
when scrolling horizontally, it will scroll FixedHeader with body ($('.dataTables_scrollHeadInner').scrollLeft($(this).scrollLeft()))
JS
// sorry - had to use global variable
// global variable for scroll-body y position
var yPositionOfScrollBody;
function adjustDatatableInnerBodyPadding(){
let $dtScrollHeadInner = $('.dataTables_scrollHeadInner');
let outerHeightOfInnerHeader = $dtScrollHeadInner.outerHeight(true);
//console.log('outerHeightOfInnerHeader => ' + outerHeightOfInnerHeader);
$('.dataTables_scrollBody').css('padding-top', outerHeightOfInnerHeader);
}
function setFixedHeaderTop(header_pos){
//console.log("header_pos : " + header_pos);
$('.dataTables_scrollHeadInner').css({"top": header_pos});
}
function fixDatatableHeaderTopPosition(){
//console.log("fixHeaderTop...");
yPositionOfScrollBody = window.scrollY + document.querySelector('.dataTables_scrollBody').getBoundingClientRect().top;
//console.log("yPositionOfScrollBody: " + yPositionOfScrollBody);
setFixedHeaderTop(yPositionOfScrollBody);
}
function onDataTableInitComplete(settings, json) {
// for vertical scolling
yPositionOfScrollBody = window.scrollY + document.querySelector('.dataTables_scrollBody').getBoundingClientRect().top;
// datatable padding adjustment
adjustDatatableInnerBodyPadding();
// data table fixed header F5 (refresh/reload) fix
let scrollTop = window.pageYOffset || document.documentElement.scrollTop;
//console.log("scrollTop => " + scrollTop);
if(scrollTop > 1){
let header_pos;
if (scrollTop < yPositionOfScrollBody){
header_pos = yPositionOfScrollBody - scrollTop;
} else {
header_pos = 0;
}
setFixedHeaderTop(header_pos);
}
let $dtScrollHeadInner = $('.dataTables_scrollHeadInner');
// horizontal scrolling
$('.dataTables_scrollBody').on('scroll', function () {
let $dtScrollBody = $(this);
// synchronize
let amountOfLeftScroll = $dtScrollBody.scrollLeft();
$dtScrollHeadInner.scrollLeft(amountOfLeftScroll);
let scrollDiff = $dtScrollHeadInner.scrollLeft() - amountOfLeftScroll;
//console.log("scrollDiff: " + scrollDiff);
if(scrollDiff < 0){
$dtScrollHeadInner.css('left', scrollDiff);
}else{
//console.log("scroll back to left side");
$dtScrollHeadInner.css('left', '');
}
});
//console.log("adjusment mergin: " + yPositionScrollHeadInner);
$(document).on('scroll', function () {
let scroll_pos = $(this).scrollTop();
if(scroll_pos <= 0){
fixDatatableHeaderTopPosition();
}else{
let margin = yPositionOfScrollBody; // Adjust it to your needs
let cur_pos = $('.dataTables_scrollHeadInner').position();
let header_pos = cur_pos.top;
if (scroll_pos < margin){
header_pos = margin - scroll_pos;
} else {
header_pos = 0;
}
setFixedHeaderTop(header_pos);
}
});
}
$(function(){
$("#tableId").DataTable({
scrollX: true,
fixedHeader: true,
initComplete: onDataTableInitComplete,
// ... : ...
});
});
CSS
/* data table - scroll and fixed header */
table.dataTable.fixedHeader-floating {
display: none !important; /*Hide the fixedHeader since we dont need it*/
}
.dataTables_scrollHeadInner{
margin-left: 0px;
width: 100% !important;
position: fixed;
display: block;
overflow: hidden;
/*margin-right: 30px;*/
background: white;
z-index: 1;
}
.dataTables_scrollBody{
padding-top: 2.5em;
}
div.dataTables_scrollHead table.dataTable {
padding-right: 0;
}
Update 1 - sort issue fix
use fixedHeader: false
$(function(){
$("#tableId").DataTable({
scrollX: true,
fixedHeader: false,
initComplete: onDataTableInitComplete,
// ... : ...
});
});
You can change 'left' but saving initial value first:
var initLeft = 0;
$(".dataTables_scrollBody").scroll(function () {
if ($(".fixedHeader-floating").is(":visible")) {
if (initLeft == 0)
initLeft = $(".fixedHeader-floating").position().left;
$(".fixedHeader-floating").css("left", $(this).scrollLeft() * (-1) + initLeft);
}
});
$(document).ready(function() {
var table = $('#example').DataTable({
searching: true,
paging: true,
ordering: true,
info: false,
scrollY: 400,
dom: 'Blfrtip',
});
} );
Trying to display a dashed line with KineticJS (v4.7.3). It works fine in Chrome, but in IE (v10), a normal solid line is displayed.
Here's the code:
var element = document.getElementById('target'),
stage = new Kinetic.Stage({
container: element,
width: element.offsetWidth,
height: element.offsetHeight
}),
layer = new Kinetic.Layer();
layer.add(new Kinetic.Line({
points: [10, 10, 190, 190],
stroke: 'black',
strokeWidth: 1,
dashArray: [5, 4]
}));
stage.add(layer);
And you can see the behavior for yourself in here.
Fixed in IE-11 !
Until all "bad" IE's die, you can "do-it-yourself" fairly easily for lines (less easily for curves).
You can use a custom Kinetic.Shape which gives you access to a canvas context (a wrapped context).
Code is taken from this SO post: dotted stroke in <canvas>
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP && CP.lineTo){
CP.dashedLine = function(x,y,x2,y2,dashArray){
if (!dashArray) dashArray=[10,5];
if (dashLength==0) dashLength = 0.001; // Hack for Safari
var dashCount = dashArray.length;
this.moveTo(x, y);
var dx = (x2-x), dy = (y2-y);
var slope = dx ? dy/dx : 1e15;
var distRemaining = Math.sqrt( dx*dx + dy*dy );
var dashIndex=0, draw=true;
while (distRemaining>=0.1){
var dashLength = dashArray[dashIndex++%dashCount];
if (dashLength > distRemaining) dashLength = distRemaining;
var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
if (dx<0) xStep = -xStep;
x += xStep
y += slope*xStep;
this[draw ? 'lineTo' : 'moveTo'](x,y);
distRemaining -= dashLength;
draw = !draw;
}
}
}
Totally off-topic: Go Wisconsin! I spent many a great summer at my grandmothers house in
Lacrosse.
Short answer: Not supported.
Via a thread here, the creator of KineticJS addresses this issue:
"[T]he dashArray property now uses the browser-implemented dashArray
property of the canvas context, according to the w3c spec. Firefox is
a little behind at the moment."
If you follow the thread, you'll discover that this was an issue for Firefox at one point, too, but that has been resolved. However, support for IE should apparently not be expected any time soon.