This is my code for creating the pdf. Everything works great except the footer and header doesn't work. They are there (i think) but not visible. I have tried with displayHeaderFooter: true but all that makes is a date stamp in the header and some broken html code in the footer (as last picture).
async function createListenPdf(html, header, footer) {
try {
var jobId = uuidv4();
this.browser = await puppeteer.launch();
const page = await browser.newPage();
var viewport = {
width:1165,
height:1200
}
page.setViewport(viewport);
page.on("console", msg => {
for (let i = 0; i < msg.args.length; ++i) {
console.log(`${jobId} - From page. Arg ${i}: ${msg.args[i]}`);
}
});
await page.goto(`data:text/html,${html}`, { waitUntil: 'networkidle0' });
await page.emulateMedia('screen');
console.log("Header footer");
var buffer = await page.pdf({
printBackground: true,
footerTemplate: "<div style='width: 200px; background-color: #4286f4; position: relative; position: absolute; top:0;'>Hej</div>",
headerTemplate: "<div style='width: 200px; background-color: #4286f4; position: relative; position: absolute; bottom: 0;'>Footer</div>",
//displayHeaderFooter: true,
margin:{
top: "100px",
bottom: "100px"
}
});
console.log(`${jobId} - Done. Will return the stream`);
return buffer;
}
finally {
if (this.browser) {
console.log(`${jobId} - Closing browser`);
this.browser.close();
}
}
}
As you can see i somehow got a footer with some grey area (i don't know why its grey). When i enable displayHeaderFooter: true in the options it looks like this:
Has anyone managed to create a pdf with puppeteer using html with header and footer?
Here in their API description its seems pretty obvious but it really doesn't work.
https://github.com/GoogleChrome/puppeteer/blob/v1.3.0/docs/api.md
Please try the following templates:
headerTemplate: '<span style="font-size: 30px; width: 200px; height: 200px; background-color: black; color: white; margin: 20px;">Header 1</span>',
footerTemplate: '<span style="font-size: 30px; width: 50px; height: 50px; background-color: red; color:black; margin: 20px;">Footer</span>'
Not all styling properties are supported, you should avoid using position, top, bottom.
Also make sure you are on the latest version of puppeteer.
Just bumped into the issue.
My observations about headerTemplate/footerTemplate are the following:
At least the font-size has to be specified (it's set to 1px by default, which is very small):
headerTemplate: '<span style="font-size: 10px"> <span class="pageNumber"></span>/<span class="totalPages"></span></span>',
top/bottom margin must be defined to set space for the header/footer
Some CSS properties indeed don't work. I didn't have any success with background-color, but maybe it's because the background isn't painted for these sections.
No asynchronous code (that would require an HTTP request) is allowed, such as calling a CSS stylesheet, loading an image or a font.
Consequently, CSS have to be inlined. To add an image, it can be base-64 encoded, as shown below. Concerning the fonts, we would need to rely on what is expected to be installed on the Puppeteer server I guess. Maybe there's a way to also embed it in base-64, I didn't try.
const printPDF = async () => {
const footer = `<footer style="margin: auto; width: 40%">
<img style="float: left; marginRight: 8px; marginLeft: 36px; width: 25%" src="data:image/jpeg;base64,/9j/4AAQSkZJRgAB...09Yv//Z" alt="Pivohub" />
<p style="font-family: arial; float: right; width: 55%; color: red; margin-top: 24px; font-size: 10px">
<b>My footer</b>
</p>
</footer>`
const content = `<!DOCTYPE html>
<html>
<head>
<meta charSet="utf-8"/>
<style type="text/css">
body { backgroundColor: "red" }
</style>
</head>
<body>
<h1>Hello</h1>
</body>
</html>`
const browser = await puppeteer.launch({ headless: true })
const page = await browser.newPage()
await page.setContent(content, { waitUntil: "networkidle0" })
const buffer = await page.pdf({
format: "A4",
displayHeaderFooter: true,
headerTemplate: '<span style="font-size: 10px"> <span class="pageNumber"></span> of <span class="totalPages"></span></span>',
footerTemplate: footer,
margin: { top: "100px", bottom: "200px" },
printBackground: true,
})
}
In this sample, the base 64 image was truncated. An image can be converted thanks to an online base-64 converter such as https://www.base64-image.de.
// or a .pdf file
await page.pdf({
printBackground: true,
width: `595px`, // ? 3508 x 2480 (print) || 595 pixels x 842 pixels (screen)
height: `842px`, // ? Here subraction for making alignment looks cool
margin: {
top: '25px',
bottom: '60px',
left: '25px',
right: '25px'
},
path: path.join(ROOT_PATH, 'pdf', 'report.pdf'),
displayHeaderFooter: true,
footerTemplate: `
<p style="margin: auto;font-size: 13px;">
<span class="pageNumber"></span>
/
<span class="totalPages"></span>
</p>
`
});
Hopefully, this one resolved someone's issue with the same problem. The footer was not shown before properly and finally done about a little bit search so comes with the above solution.
NOTE:
Don't forget to mentioned margin-bottom: 60px(atleast).
Same for displayHeaderFooter: true.
Must needs to specify the font-size: 10px(atleast bcze default by font-size: 0).
For more info refer to this issue:
https://github.com/puppeteer/puppeteer/issues/1822
if the above code didn't work for you. please try to use marginTop and marginBottom instead of margin object.
var buffer = await page.pdf({
printBackground: true,
footerTemplate: "<div style='width: 200px; background-color: #4286f4; position: relative; position: absolute; top:0;'>Hej</div>",
headerTemplate: "<div style='width: 200px; background-color: #4286f4; position: relative; position: absolute; bottom: 0;'>Footer</div>",
//displayHeaderFooter: true,
marginTop:"100px",
marginBottom: "100px"
});
notes:
it works for me in nodejs. I use jsreport with recipe "chrome-pdf". The chrome-pdf module using puppeteer.
You need to use:
-webkit-print-color-adjust: exact;
Here is an example of a function that can add a background:
const getHeaderFooter = () => {
return `<html>
<style>html {-webkit-print-color-adjust: exact;} </style>
<span style="margin: -5em 0 0 0; height: 3mm; z-index: 1000;
background: blue; width: 100%;"/>
</html>`
}
And calling the function:
headerTemplate: getHeaderFooter(),
Related
I prepared example of image slider what I need.
I encounter with styling issue when using images with various dimensions. When element leaving the array, its location is set as absolute value, what is necessary for smooth transition, tho, but the image is also moved up.
I would like to have nice vertical align into middle even leave or enter the array, but could not get on any way.
Another issue, what I would like to solve is when left the window and then went back after a while. The animation running all cycles at once to reach current state instead just stop animation and run after. Maybe it is my responsibility, but browsers doesn't offer nice event to catch blur window or am I wrong?
According to this discussion
Thank you for any ideas.
let numbers = [{key:1},{key:2},{key:3},{key:4},{key:5},{key:6},{key:7}]
let images = [
{ key:1,
src:"http://lorempixel.com/50/100/sports/"},
{ key:2,
src:"http://lorempixel.com/50/50/sports/"},
{ key:3,
src:"http://lorempixel.com/100/50/sports/"},
{ key:4,
src:"http://lorempixel.com/20/30/sports/"},
{ key:5,
src:"http://lorempixel.com/80/20/sports/"},
{ key:6,
src:"http://lorempixel.com/20/80/sports/"},
{ key:7,
src:"http://lorempixel.com/100/100/sports/"}
]
new Vue({
el: '#rotator',
data: {
items: images,
lastKey: 7,
direction: false
},
mounted () {
setInterval(() => {
if (this.direction) { this.prevr() } else { this.nextr() }
}, 1000)
},
methods: {
nextr () {
let it = this.items.shift()
it.key = ++this.lastKey
this.items.push(it)
},
prevr () {
let it = this.items.pop()
it.key = ++this.lastKey
this.items.unshift(it)
}
}
})
.litem {
transition: all 1s;
display: inline-block;
margin-right: 10px;
border: 1px solid green;
background-color: lightgreen;
padding: 10px 10px 10px 10px;
height: 100px;
}
.innerDiv {
border: 1px solid red;
}
.container {
overflow: hidden;
white-space: nowrap;
height: 250px;
border: 1px solid blue;
background-color: lightblue;
}
.list-enter {
opacity: 0;
transform: translateX(40px);
}
.list-leave-to {
opacity: 0;
transform: translateX(-40px);
}
.list-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.js"></script>
<div id="rotator">
<button #click="direction = !direction">
change direction
</button>
<transition-group
name="list"
tag="div"
class="container">
<div
v-for="item in items"
:key="item.key" class="litem">
<!--
<div
class='innerDiv'>
{{ item.key }}
</div>
-->
<div class='innerDiv'>
<img :src='item.src'>
</div>
</div>
</transition-group>
</div>
It tooks a while but on the end I think that I have got better result for sliding animation with changing direction feature.
One annoying think is when I swithing the sliding direction so animation is for a 'microsecond' changed to next state and than return to correct one, after it the animation continue as expected. It is happening only in one direction and I don't know how to fix it. Also last box behave differently too only once. No clue at all.
So just 98% solution :-)
let images = [
{key:1, domKey:1, src:"http://lorempixel.com/50/100/sports/" },
{key:2, domKey:2, src:"http://lorempixel.com/50/50/sports/" },
{key:3, domKey:3, src:"http://lorempixel.com/100/50/sports/" },
{key:4, domKey:4, src:"http://lorempixel.com/20/30/sports/" },
{key:5, domKey:5, src:"http://lorempixel.com/80/20/sports/" },
{key:6, domKey:6, src:"http://lorempixel.com/20/80/sports/" },
{key:7, domKey:7, src:"http://lorempixel.com/100/100/sports/" }
]
let setPositionRelative = el => el.style.position = "relative"
new Vue({
el: '#rotator',
data: {
items: images,
lastKey: 7,
direction: true,
changeDirectionRequest: false
},
mounted () {
Array.from(this.$el.querySelectorAll("div[data-key]")).map(setPositionRelative)
setInterval(() => {
if(this.changeDirectionRequest) {
this.changeDirectionRequest = false
this.direction = !this.direction
if (this.direction)
Array.from(this.$el.querySelectorAll("div[data-key]")).map(setPositionRelative)
else
Array.from(this.$el.querySelectorAll("div[data-key]")).map(el => el.style.position = "")
}
if (this.direction) this.prevr()
else this.nextr()
}, 1000)
},
methods: {
nextr () {
let it = this.items.shift()
it.key = ++this.lastKey
this.items.push(it)
},
prevr () {
let it = this.items.pop()
it.key = ++this.lastKey
this.items.unshift(it)
setPositionRelative(this.$el.querySelector("div[data-domkey='"+it.domKey+"']"))
}
}
})
.container {
overflow: hidden;
white-space: nowrap;
height: 200px;
border: 1px solid blue;
background-color: lightblue;
display: flex;
align-items: center;
}
.innerDiv {
border: 1px solid red;
width: auto;
height: auto;
display:-moz-box;
-moz-box-pack:center;
-moz-box-align:center;
display:-webkit-box;
-webkit-box-pack:center;
-webkit-box-align:center;
display:box;
box-pack:center;
box-align:center;
}
.litem {
transition: all 1s;
margin-right: 10px;
border: 1px solid green;
background-color: lightgreen;
padding: 10px 10px 10px 10px;
}
.list2-enter, .list-enter {
opacity: 0;
transform: translateX(40px);
}
.list2-leave-to, .list-leave-to {
opacity: 0;
transform: translateX(-40px);
}
.list-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.js"></script>
<div id="rotator">
<button #click="changeDirectionRequest = true">change direction</button>
<transition-group name="list" tag="div" class="container">
<div v-for="item in items"
:key="item.key"
:data-domkey="item.domKey"
class="litem">
<div class='innerDiv'>
<img :src='item.src'>
</div>
</div>
</transition-group>
</div>
I am working on a photo gallery in AngularJS using Angular Material (run the snippet in fullscreen to see my problem).
var app = angular.module('app', ['ngMaterial']);
app.controller('TitleController', function($scope) {
$scope.title = 'Gallery';
});
app.controller('GalleryCtrl', function($scope, $http, $q, $mdMedia, $mdDialog) {
//https://material.angularjs.org/latest/demo/virtualRepeat
$scope.Images = [],
//add more images
$scope.LoadMore = function() {
for (i = 0; i < 25; i++) {
var randomWidth = Math.round(Math.random() * (800 - 400) + 400);
var randomHeight = Math.round(Math.random() * (800 - 400) + 400);
$scope.Images.push({
src: "http://placehold.it/" + randomWidth + "x" + randomHeight + "/",
id: Math.round(Math.random() * 10000)
});
};
}
$scope.ShowDetails = function(ev, number) {
var useFullScreen = ($mdMedia('sm') || $mdMedia('xs')) && $scope.customFullscreen;
$mdDialog.show({
controller: DialogController,
templateUrl: 'Home/GetInfo?id=' + number,
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose: true,
fullscreen: useFullScreen
})
$scope.$watch(function() {
return $mdMedia('xs') || $mdMedia('sm');
}, function(wantsFullScreen) {
$scope.customFullscreen = (wantsFullScreen === true);
});
};
function DialogController($scope, $mdDialog) {
$scope.hide = function() {
$mdDialog.hide();
};
$scope.cancel = function() {
$mdDialog.cancel();
};
$scope.answer = function(answer) {
$mdDialog.hide(answer);
};
}
//initial loading
$scope.LoadMore();
});
body {
background: #eeeeee;
}
html {
background: #eeeeee;
}
.gridListdemoBasicUsage md-grid-list {
margin: 8px;
}
.gridListdemoBasicUsage .green {
background: #b9f6ca;
}
.gridListdemoBasicUsage md-grid-tile {
transition: all 400ms ease-out 50ms;
}
.responsiveImage {
max-width: 100%;
max-height: 100%;
}
md-content {
background: #eeeeee;
position: relative;
}
.fit {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.toolbardemoScrollShrink .face {
width: 48px;
margin: 16px;
border-radius: 48px;
border: 1px solid #ddd;
}
.md-toolbar-tools {
background-color: #259b24;
}
.dialogdemoBasicUsage #popupContainer {
position: relative;
}
.dialogdemoBasicUsage .footer {
width: 100%;
text-align: center;
margin-left: 20px;
}
.dialogdemoBasicUsage .footer,
.dialogdemoBasicUsage .footer > code {
font-size: 0.8em;
margin-top: 50px;
}
.dialogdemoBasicUsage button {
width: 200px;
}
.dialogdemoBasicUsage div#status {
color: #c60008;
}
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.7/angular-material.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular-animate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular-aria.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.7/angular-material.min.js"></script>
<body ng-app="app" ng-controller="GalleryCtrl as gc" ng-cloak="" id="popupContainer" class="gridListdemoBasicUsage dialogdemoBasicUsage">
<md-toolbar md-scroll-shrink="" ng-if="true" ng-controller="TitleController">
<div class="md-toolbar-tools">
<h3><span>{{title}}</span></h3>
</div>
</md-toolbar>
<md-content style="height:100vh" />
<md-grid-list md-cols-xs="1" md-cols-sm="2" md-cols-md="4" md-cols-gt-md="6" md-row-height-gt-md="1:1" md-row-height="2:2" md-gutter="12px" md-gutter-gt-sm="8px">
<md-grid-tile ng-click="ShowDetails($event, n.id)" ng-repeat="n in Images" class="green">
<img class="responsiveImage" src="{{n.src}}">
<md-grid-tile-footer>
<h3>Photo number {{n.id}}</h3>
</md-grid-tile-footer>
</md-grid-tile>
</md-grid-list>
<section layout="row" layout-sm="column" layout-align="center center" layout-wrap="">
<md-button class="md-raised md-primary" ng-click="LoadMore()">Primary</md-button>
</section>
</body>
Go fullscreen, scroll to the bottom of the page, and press a button to load more images. The problem I'm having: I am trying to get the toolbar at the top of the screen to disappear when scrolling down, and appear again when scrolling up. However, 2 scrollbars appear, and only the right one affects the toolbar.The left scrollbar actually scrolls all the way down on the page.
My desired situation: only 1 visible scrollbar to scroll down the entire gallery, that also makes the toolbar appear and disappear. How do I do this?
try adding overflow-y: hidden; to the body css rule.
Can you please take a look at this demo and let me know how I can separate the .clickme box from the .slidecontent? I need to change the Height of the .slidecontentfor example toheight:300px;` but this also change the grey shadow to 300px
What I need to have is having the .slidecontent with height of 300 and looks like the third (green arrow)
$(function () {
$("#clickme").toggle(function () {
$(this).parent().animate({left:'0px'}, {queue: false, duration: 500});
}, function () {
$(this).parent().animate({left:'-280px'}, {queue: false, duration: 500});
});
});
#slideout {
background: #666;
position: absolute;
width: 300px;
height: 300px;
top: 45%;
left:-280px;
}
#clickme {
float: right;
height: 20px;
width: 20px;
background: #ff0000;
}
#slidecontent {
float:left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="slideout">
<div id="slidecontent">
Yar, there be dragonns herre!
</div>
<div id="clickme">
>
</div>
</div>
I am not 100% sure I understand what you are trying to do but your jQuery code refers to the parent of clickme which is slideout, not slidecontent and it is probable that this is causing the undesired effect. Is there a reason you are not referencing slidecontent directly?
Here is the code, very simple and copy paste from office website
$scope.show = function() {
// Show the action sheet
var hideSheet = $ionicActionSheet.show({
destructiveText: 'Delete Photo',
titleText: 'Modify your album',
cancelText: 'Cancel <i class="icon ion-no-smoking"></i>',
cancel: function() {
// add cancel code..
},
buttonClicked: function(index) {
return true;
}
});
// For example's sake, hide the sheet after two seconds
$timeout(function() {
hideSheet();
}, 2000);
};
I want to change the cancel button have a red color background, how I can achieve it in ionic frameworks?
Easiest way is to look at the markup using your browser (after running ionic serve in your terminal), for example in Chrome ctrl+shift+i, where you can choose the button and see what classes are attached. In your case you'll see something like this:
<div class="action-sheet-group action-sheet-cancel" ng-if="cancelText">
<button class="button ng-binding"
ng-click="cancel()"
ng-bind-html="cancelText">Cancel</button>
</div>
Which has styles that for the parent div, and child button something like this:
.action-sheet-group {
margin-bottom: 8px;
border-radius: 4px;
background-color: #fff;
overflow: hidden;
}
.action-sheet .button {
display: block;
padding: 1px;
width: 100%;
border-radius: 0;
border-color: #d1d3d6;
background-color: transparent;
color: #007aff;
font-size: 21px;
}
Just change these values either in Sass or directly in your styles sheet if you're not using Sass.
I have a div tag that looks as follows:
<div id="loadingDiv" class="loadingDiv"; style="position:absolute; left:400px; top:292px;">
<strong>Retrieving Data - One Moment Please...</strong>
</div>
It seems that Chrome and IE do not render this the same way. In IE, the text is much further to the left than with Chrome. I don't know why this is. So, is there a way I can create a style that is dependent on the browser type? For example, if the browser is IE, I'd like the left value to be maybe 300px, and 400px if Chrome. Or, is there a better way to handle this?
Even if I don't recommend to use browser specific CSS, it is always much better to optimize your CSS to look at least simmilar in all browsers, you can do what you want by using of some javascript combined with CSS.
Here is the code:
<html>
<head>
<title>browser specific css</title>
<style>
.loadingDiv {
position: absolute;
display: none;
font-weight: bold;
}
.loadingDiv.ie {
display: block;
left: 300px;
top: 292px;
background: #00CCFF;
color: #454545;
}
.loadingDiv.chrome {
display: block;
left: 400px;
top: 292px;
background: #FCD209;
color: #E53731;
}
.loadingDiv.firefox {
display: block;
left: 400px;
top: 292px;
background: #D04F16;
color: #FFFFFF;
}
.loadingDiv.default {
display: block;
left: 400px;
top: 292px;
}
</style>
<script>
window.onload = function() {
var check_for_ie = detect_browser("MSIE");
var check_for_chrome = detect_browser("Chrome");
var check_for_firefox = detect_browser("Firefox");
var browser_name = "";
var loading_div = document.getElementById("loadingDiv");
var loading_div_html = loading_div.innerHTML;
if (check_for_ie == true) {
browser_name = "Internet Explorer";
loading_div.className = "loadingDiv ie";
}
else if (check_for_chrome == true) {
browser_name = "Google Chrome";
loading_div.setAttribute("class","loadingDiv chrome");
}
else if (check_for_firefox == true) {
browser_name = "Firefox";
loading_div.setAttribute("class","loadingDiv firefox");
}
else {
browser_name = "Unchecked browser";
loading_div.setAttribute("class","loadingDiv default");
}
loading_div.innerHTML = loading_div_html + "(you are browsing with "+browser_name+")";
}
function detect_browser(look_for) {
var user_agent_string = navigator.userAgent;
var search_for_string = user_agent_string.search(look_for);
if (search_for_string > -1) {
return true;
}
else {
return false;
}
}
</script>
</head>
<body>
<div id="loadingDiv" class="loadingDiv" >
Retrieving Data - One Moment Please...
</div>
</body>
</html>
And here is the working example:
http://simplestudio.rs/yard/browser_specific_css/browser_specific_css.html
EDIT:
If you need to check for some other browsers look at user agent string of that specific browser and find something that is unique in it and makes a difference between that browser and the others and use that like this:
var check_for_opera = detect_browser("Opera");
Detecting browsers by user agent could be tricky so be careful, even upgrade my function if you need...
NOTE, that this is just quick example...