tcpdf module on Silverstripe Framework - tcpdf

I am trying to use the tcpdf module on silverstripe but it doesn't seem to generate the pdf file.
Below is the class that has the method that should be generating the pdf. The $url_handler part is commented because am not sure how to re-route the request from link $PDFLink to this class.
Any kind of help will be greatly appreciated.
class RaaOrders Extends ReportsModule {
public static $allowed_actions = array (
'pdf'
);
public static $url_handlers = array (
// 'get_pdf' => 'pdf'
);
public function pdf(){
// create new PDF document
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
// set document information
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('Nicola Asuni');
$pdf->SetTitle('TCPDF Example 001');
$pdf->SetSubject('TCPDF Tutorial');
$pdf->SetKeywords('TCPDF, PDF, example, test, guide');
// set default header data
$pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, PDF_HEADER_TITLE.' 001', PDF_HEADER_STRING, array(0,64,255), array(0,64,128));
$pdf->setFooterData(array(0,64,0), array(0,64,128));
// set header and footer fonts
$pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
$pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
// set default monospaced font
$pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
// set margins
$pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
$pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
$pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
// set auto page breaks
$pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
// set image scale factor
$pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
// set some language-dependent strings (optional)
if (#file_exists(dirname(__FILE__).'/lang/eng.php')) {
require_once(dirname(__FILE__).'/lang/eng.php');
$pdf->setLanguageArray($l);
}
// ---------------------------------------------------------
// set default font subsetting mode
$pdf->setFontSubsetting(true);
// Set font
// dejavusans is a UTF-8 Unicode font, if you only need to
// print standard ASCII chars, you can use core fonts like
// helvetica or times to reduce file size.
$pdf->SetFont('dejavusans', '', 14, '', true);
// Add a page
// This method has several options, check the source code documentation for more information.
$pdf->AddPage();
// set text shadow effect
$pdf->setTextShadow(array('enabled'=>true, 'depth_w'=>0.2, 'depth_h'=>0.2, 'color'=>array(196,196,196), 'opacity'=>1, 'blend_mode'=>'Normal'));
// Print a text
$html = '<span style="background-color:yellow;color:blue;"> PAGE 1 </span>
<p stroke="0.2" fill="true" strokecolor="yellow" color="blue" style="font-family:helvetica;font-weight:bold;font-size:26pt;">You can set a full page background.</p>';
$pdf->writeHTML($html, true, false, true, false, '');
// Close and output PDF document
// This method has several options, check the source code documentation for more information.
$pdf->Output('example_001.pdf', 'I');
}
}

I found out that the issue was exactly what I thought it was. Dependencies were not well taken care of because I was doing a manual install.
So I used composer and everything works fine. And you can actually use SS templating to populate the pdf with data, by returning your Objects to the template as shown below.
$pdf = new TCPDF();
$pdf->AddPage();
$vd = new ViewableData();
$str = $vd->customise(ArrayData::create(array('MyVariable'=>'MyValue')))->renderWith('MyTemplateName');
$pdf->writeHTMLCell(0, 0, '', '', $str, 0, 1, 0, true, '', true);
$strContent = $pdf->Output(BASE_PATH.'/tmp/tmp.pdf', 'S');

Call exit when you're done. That's probably the easiest way.
As with most frameworks the output is done elsewhere, after your logic is implemented. Since you are not returning anything, it may give an empty response, or set an error code, etc. Likely interfering with what the pdf library is trying to do. You can avoid that by prematurely exiting (after the pdf library has output a valid response directly to the browser).

Related

pdfmake problem displaying accurate page numbers

I'm using pdfmake to generate a document that contains a number of sub-documents, e.g. a PDF containing invoices; each invoice is a logical document inside the PDF document.
I have a requirement that in the footer or header of each page I show "Page of " where both those numbers are relative to a single invoice, and not the overall document.
The header and footer callbacks look good, but only specify the page number and count relative to the entire document, and the pageBreakBefore callback doesn't generate anything like the documented information, and in any case I can't figure out how I could use it here.
This doesn't seem like a unique requirement, so hopefully I'm just missing something obvious.
Thanks!
I believe that pdkmake's header and footer function's arguments can only contain the global pageCount.
There is however a way to reproduce the behavior that you want, if you handle manually the pageBreaks :
const realPageIndexes = [];
let currSubPdfIdx = 0;
let currPageCountForSubPdf = 0;
const getPageBreak = (newSubPdfIdx: number) => {
if (currSubPdfIdx !== newSubPdfIdx) {
currSubPdfIdx = newSubPdfIdx;
currPageCountForSubPdf = 0;
} else {
currPageCountForSubPdf += 1;
}
realPageIndexes.push(currPageCountForSubPdf);
return {text: '', pageBreak: 'after'}; // return elem causing the pageBreak
}
The footer function, filling the page numbers, is called once the document definition generation is done.
If you handle every overflow by yourself, by calling getPageBreak(currentSubPdfIndex) at the end of each page, you will end up knowing which sub-Pdf is displayed in each page :
I display part of the 1rst subPdf in page 1:
I display the end of 1rst subPdf in page 2
I display 2nd subPdf in page3
I display 3rd subPdf in page4 ....
.
let subPdfIdx = 0;
pdfContent.push(subPdf1FirstPart)
pdfContent.push(getPageBreak(subPdfIdx))
pdfContent.push(subPdf1SecondPart)
pdfContent.push(getPageBreak(subPdfIdx))
subPdfIdx++;
pdfContent.push(subPdf2)
pdfContent.push(getPageBreak(subPdfIdx))
subPdfIdx++;
pdfContent.push(subPdf3)
pdfContent.push(getPageBreak(subPdfIdx))
realPageIndexes[] then looks like :
[ 1, 1, 2, 3 ];
The only step left is to tell the footer function to use the page counts we just created instead of the global page count :
const documentDefinition = {
content: [YOUR CONTENT],
footer: (currPage, allPages) => 'subPdf index is ' + realPageIndexes[currPage];
}
Please note that this method will fail if you don't handle overflows correctly:
if for example a paragraph is too big to fit a page, and you call getPageBreak() after that, pdfmake will automatically create a new page for the end of the paragraph (page which is untracked in our realPageIndexes) and then add the page caused by your getPageBreak() right after the end of the text. So just make sure not to overflow the page :)

How to set pdf font size for table content using jspdf?

How to set pdf font size for table content using jspdf?
setFontSize doesn't work in my case.
Setting font size for text in a table using jspdf-autotable looks like this:
var doc = new jsPDF();
doc.autoTable({html: '#table', styles: {fontSize: 20}})
doc.save("table.pdf");
Edit this: jspdf.plugin.autotable.min.js
Find this part and edit fontSize: textColor:20,halign:"left",valign:"top",fontSize:10,cellPadding:5
the default size is 10 and I change it to 7:
textColor:20,halign:"left",valign:"top",fontSize:7,cellPadding:5
Before printing, make a copy of the table object.
Let's say:
let temporalTable = document.getElementsByClassName("table")[0];
Then apply a style to this object. You can use plain JS functions.
And finally:
html2canvas(temporalTableWithNewStyles)
.then((canvas: any) => {
doc.addImage(canvas.toDataURL("image/jpeg"), "JPEG", 0, 50, doc.internal.pageSize.width, element.offsetHeight / 5 );
doc.save(`Report-${Date.now()}.pdf`);
})

Getting duplicate pdf with jsPDF

I have just started using jsPDF for creating PDF. When I am saving it, it generates two PDF files at that time. The first PDF name is the same as I gave in the code but the second PDF name is any text (like: DXTRE5.pdf). I need only one PDF with the given file name. Please help me.
$('#print').click(function () {
var doc = new jsPDF();
var chartHeight = 80;
doc.setFontSize(15);
doc.text(35, 25, "Prospect Report Graph");
$('.myChart').each(function (index) {
var imageData = $(this).highcharts().createCanvas();
doc.addImage(imageData, 'JPEG', 45, (index * chartHeight) + 40, 120, chartHeight);
});
doc.save('reports_graph.pdf');
});
The code looks fine. This won't trigger any PDF export twice. It should be somewhere else where click binding of the #print should exist in your code. Check and find the code you could fix that issue.
That duplicate click binding may have line like doc.save('DXTRE5.pdf'); find and remove that binding.

Bootstrap Carousel Next/Prev controls stop responding when page is reloaded

I'm using the Bootstrap Carousel feature. On initial load of the page the Carousel next/prev controls work perfectly however when I simpy reload the page these controls no longer respond to clicks even though the interval for "auto" rotating the slides that I've configured continues to work.
I've done alot of research trying to troubleshoot this problems..including reviewing probably 30+ stackoverflow postings on Bootstrap Carousel but none of them offered any solutions for me. After two days of researching this problem I'm still stumped.
Environment/setup:
My browser is Chrome
Libraries included in the following order: a)jQuery 2.1.3, b)Bootstrap CSS 3.3.5, c)Bootsrap JS 3.3.5
Running in Ruby on Rails 4.2 environment
My solution is a pure bootstrap solution (e.g., no special JS other than including variants of $('#carousel-generic-example).carousel() type statement per examples I've seen online..apparently no change in behavior with or without this statement).
I'm a bit puzzled as to why it works properly on initial load but not on subsequent reloads. Perhaps something isn't being loaded properly when I do a re-load of the web page??? Maybe there is some behavior within Rails that is causing this behavior?
If anyone has any theories as to what might be causing this behavior I'd love to hear it (even if you don't have time to dig into the code). Or if you have any specific ideas on what I might be able to do troubleshoot this problem.
There is a lot of relevant code associated with my solution...rather than trying to copy and paste it all in this post I think the most effective and efficient way of "sharing" it with you is to point you to my public Github account where the code is located and provide a map to the relevant files within the project.
First the full set of code can be found on my Github account at: gitHub site
Within this project here is where you can find the relevant sections of code:
app/views/application.html.erb : List of external libraries (e.g., jQuery, bootstrap files, etc.)
app/views/portfolio_items/show.html.erb : HTML for page including Carousel
app/assets/javascripts/jet.js : see what I tried lines 17-20, 199-202. I'm not sure if I really need to include javascript as it didn't really seem to affect the behavior of the solution. So I ultimately commented it out.
app/assets/stylesheets/jet.css.erb : Lines 2332-2354
Note 1: you'll see that in my javascript file I'm using both $(window).load(function ()... and `$(document).ready(function() {' functions. I have to admit that I'm a bit fuzzy on what these two functions do other than generally make sure that the items on the page load before the javascript starts to run. I leveraged parts of my template from a theme and I noticed that they used both of these functions in their theme so I don't think this is the cause of my issues...but thought I would reference it just in case.
Note 2: I'm not sure it is relevant but I'm also using another Carousel solution (caroufredsel) on the same page with the Bootstrap Carousel. I don't think there is any conflict as the theme I borrowed from did the same thing...but I thought I would mention it.
UPDATE: I've continued to try and resolve my issue (i.e., Bootstrap Carousel next/prev buttons don't advance images) and stumbled upon something interesting that could be the key to fixing my issue. Specifically
When I "close" the $(document).ready" statement immediately as follows:
'$(document).ready(function() {});'
in my javascript "app/assets/javascripts/jet.js" the Bootstrap Carousel behaves as I would expect (prev/next controls advance images when clicked on).
Unfortunately making the change breaks the other carousel functionality (caroufredsel) at the bottom of the web page (i.e., instead of only showing 3 elements at a time in caroufredsel it shows all of the items and the controls for caroufredsel then don't work). I really need to wrap other jQuery code in the $(document).ready functionality...but when I do the Bootstrap prev/next controls don't work properly.
Anybody have any suggestions on what is going on here and how I could fix it?
Here is the full version of the jet.js file:
function scroll_to(clicked_link, nav_height) {
var element_class = clicked_link.attr('href').replace('#', '.');
var scroll_to = 0;
if(element_class != '.top-content') {
element_class += '-container';
scroll_to = $(element_class).offset().top - nav_height;
}
if($(window).scrollTop() != scroll_to) {
$('html, body').stop().animate({scrollTop: scroll_to}, 1000);
}
}
$(document).ready(function() {
// Bootstrap Carousel -- Tried each of the following lines but neither of them helped
// $('#carousel-generic-example').carousel()
// $('.carousel').carousel()
// $('#carousel-generic-example').carousel()});
// {
// 'prev'
// 'next'
// pause: true,
// interval: false,
// keyboard: true
// }
// jQuery('#carousel-generic-example').carousel();
// Pretty photo script
$("a[data-rel^='prettyPhoto']").prettyPhoto({
theme: 'light_square',
social_tools: false,
hook: 'data-rel'
});
// ------------------------------------------------------------------------------------------
// Code below attempted to dynamically change glyphicons used on web page separator
// to avoid having to distinct CSS code for every separator (e.g., blog-separator, project-separator, etc)
// Unfortunately I couldn't get this to work...this code displays the character string
// for the blog glyphicon (i.e., e043) rather than the actual glyphicon. StackOverflow (http://stackoverflow.com/questions/5041494/manipulating-css-pseudo-elements-such-as-before-and-after-using-jquery?lq=1) from Blazemonger (#3) suggest it may only work for strings (maybe not hex values)
// So I'm assuming i can't dynamically insert glyphicons.
// ------------------------------------------------------------------------------------------
// var regExp = /\#([a-z]+)/;
//
// $(".menu-items a").on('click', function () {
// var href = $(this).attr('href');
// var matches = regExp.exec(href);
// switch(matches[1]) {
// case "home":
// alert(href);
//
// break;
// case "about":
// alert(href);
// break;
// case "projects":
// alert(href);
// break;
// case "blog":
// $('.separator-line').attr('data-content', '\e043');
// // $(this).attr('data-content', '\e043');
// // $(".separator-line::after.content").attr('glyphicon-code',"\e043");
// var separatorCode = $(this).attr('data-content', '\e043').val();
// alert(separatorCode);
// break;
// case "contact":
// alert(href)
// break;
// }
// });
// -------------------------------------------------------------------------------
// Portfolio Javascript to load images
// var $container = $('.container');
//
// $container.imagesLoaded( function() {
// $container.masonry({
// itemSelector : '.post-box',
// columnWidth : '.post-box',
// transitionDuration : 0
// });
// });
$(".truncateIt").dotdotdot({
// configuration goes here
/* The text to add as ellipsis. */
ellipsis : '... ',
/* How to cut off the text/html: 'word'/'letter'/'children' */
wrap : 'word',
/* Wrap-option fallback to 'letter' for long words */
fallbackToLetter: true,
/* jQuery-selector for the element to keep and put after the ellipsis. */
after : 'a.next',
/* Whether to update the ellipsis: true/'window' */
watch : false,
/* Optionally set a max-height, if null, the height will be measured. */
height : 60,
/* Deviation for the height-option. */
tolerance : 0,
/* Callback function that is fired after the ellipsis is added,
receives two parameters: isTruncated(boolean), orgContent(string). */
callback : function( isTruncated, orgContent ) {},
lastCharacter : {
/* Remove these characters from the end of the truncated text. */
remove : [ ' ', ',', ';', '.', '!', '?' ],
/* Don't add an ellipsis if this array contains
the last character of the truncated text. */
noEllipsis : []
}
});
// Scroll location for buttons on banner page
$('a.scroll-link').on('click', function(e) {
e.preventDefault();
scroll_to($(this), $('nav').outerHeight());
});
// Link and activate WOW.js
new WOW().init();
$(".nav a").on("click", function(){
$(".nav").find(".active").removeClass("active");
$(this).parent().addClass("active");
});
// Smooth scrolling logic
$('a[href*=#]:not([href=#])').click(function() {
if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'')
|| location.hostname == this.hostname) {
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
if (target.length) {
$('html,body').animate({
scrollTop: target.offset().top
}, 1000);
return false;
}
}
});
$(".cnbox").each(function () {
var nheight = $(this).find(".nbox").height();
$(this).find(".cbox").css("height", nheight + 50);
});
}); // /document.ready()
var caroufredsel = function () {
$('#caroufredsel-portfolio-container').carouFredSel({
responsive: true,
scroll: 1,
circular: false,
infinite: false,
items: {
visible: {
min: 1,
max: 3
}
},
prev: '#portfolio-prev',
next: '#portfolio-next',
auto: {
play: false
}
});
$('#caroufredsel-blog-posts-container').carouFredSel({
responsive: true,
scroll: 1,
circular: false,
infinite: false,
items: {
visible: {
min: 1,
max: 3
}
},
prev: '#blog-posts-prev',
next: '#blog-posts-next',
auto: {
play: false
}
});
};
// Isotope Portfolio
var $container = $('.portfolio-container');
var $blogcontainer = $('.posts-wrap');
var $filter = $('.portfolio-filter');
$(window).load(function () {
// Bootstrap Carousel -- Tried each of the following lines but neither of them helped
// jQuery('.carousel').carousel();
// jQuery('#carousel-generic-example').carousel();
caroufredsel();
// Initialize Isotope
$container.isotope({
itemSelector: '.portfolio-item-wrapper'
});
$blogcontainer.isotope({
itemSelector: '.article-wrap'
});
$('.portfolio-filter a').click(function () {
var selector = $(this).attr('data-filter');
$container.isotope({ filter: selector });
return false;
});
$filter.find('a').click(function () {
$filter.find('a').parent().removeClass('active');
$(this).parent().addClass('active');
});
});
$(window).smartresize(function () {
$container.isotope('reLayout');
$blogcontainer.isotope('reLayout');
});
$(window).resize(function () {
caroufredsel();
});
Cheers.
After much trial and error I found the solution through trial and error. As it turns out the $(document).ready(function() { had to be closed (i.e., }); immediately before the Smooth Scrolling logic (i.e., code that starts with $('a[href*=#]:not([href=#])').click(function() {).
I'm not completely sure why the "close" has to be placed there and not after the Smooth scrolling logic.
If anyone has an explanation I'd love to hear why...As I mentioned I discovered the solution largely by trial and error.
Bootstrap needs normally a JQuery file to overcome this error. So, better embed it in your file and it works when online.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

Display ASP.MVC view in CKEditor dialog window

I created a plugin with a custom dialog window.
CKEDITOR.plugins.add('imggallery',
{
init: function (editor) {
var pluginName = 'imggallery';
editor.ui.addButton('Image',
{
label: 'Add image',
command: 'OpenWindow',
icon: CKEDITOR.plugins.getPath('imggallery') + 'lightbulb.gif'
});
editor.addCommand('OpenWindow', new CKEDITOR.dialogCommand('simpleLinkDialog'));
var html2 = "<h1>This is a heading</h1>";
CKEDITOR.dialog.add('simpleLinkDialog', function (editor) {
return {
title: 'LinkProperties',
minWidth: 400,
minHeight: 200,
contents:
[
{
id: 'general',
label: 'Settings',
elements:
[
{
type: 'html',
html: html2
}
]
}
]
};
});
}
});
My question is: Is it possible to somehow display ASP.MVC view in window content?
When I assign html2 to elements property the text is shown without formatting (plain text).
Are you sure it's plain text and not a H1 tag that is formatted to look like plain text? There's a big difference :). The CKE dialogs reset most of the standard browser styles so that elements appear like plain text, even though they are not.
As for the MVC view, I would recommend that you add an iframe within the CKE dialog and display the page normally there. Then you can control or get/set values from the iframe using JavaScript. It will be a bit tricky, but should work.
var html2 = '<iframe id="DialogIframe" src="/MyController/MyView?foo=bar"></iframe>';
The other option is to use something like jQuery to $.get() the HTML and then use it, should be relatively simple if you have worked with ajax before. If not, here's a good chance to start! :)

Resources