How to add PDF page number and total pages with CasperJs and PhantomJS - phantomjs

I have adopted a website and I cannot figure something out. The website uses CasperJS called from the command line, which logs into the site and then generates a PDF from the HTML that is downloaded. I think CasperJS will use PhantomJS for the PDF generation, using capture().
I need to add page numbers and the total pages to the PDF. So Page 1 of 5 for example. But I cannot find any details online how to do this with CasperJS and PhantomJS. Is it possible?
The download process creates the following JS file on the server, which is called below:
var casper = require('casper').create({
verbose: false,
logLevel: 'debug',
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22',
pageSettings: {
loadImages: true,
loadPlugins: true
},
});
var url = '{$siteurl}';
casper.start(url, function() {
this.page.paperSize = {
width: '11in',
height: '8.5in',
orientation: 'landscape',
border: '0.1in'
};
this.fill('form#loginform', {
ident: 'username',
password: 'password'
}, true);
});
var url = '{$siteurl}/pdf/{$twigDate}/{$clubId}/{$sessionId}';
casper.then(function() {
casper.start(url, function() {
this.capture('{$genPdf}');
});
});
casper.run();
Then the above file is called with:
$exe = shell_exec('/usr/bin/casperjs --ignore-ssl-errors=true --ssl-protocol=any ' . INC_ROOT . '/pdf/registers/' . $filename . ' 2>&1');
Thanks

PhantomJS
PhantomJS has a paperSize property that defines the size of the web page when rendered as a PDF.
The paperSize property has header and footer subproperties that allow for repeating page headers and footers.
PhantomJS provides the following example on how to accomplish this task:
var webPage = require('webpage');
var page = webPage.create();
page.paperSize = {
width: '8.5in',
height: '11in',
header: {
height: '1cm',
contents: phantom.callback(function (pageNum, numPages) {
return '<h1>Header <span style="float:right">' + pageNum + ' / ' + numPages + '</span></h1>';
}),
},
footer: {
height: '1cm',
contents: phantom.callback(function (pageNum, numPages) {
return '<h1>Footer <span style="float:right">' + pageNum + ' / ' + numPages + '</span></h1>';
}),
},
};
Note: A more detailed example can be found in printheaderfooter.js in the examples folder.
CasperJS
CasperJS allows you to use the page option to access an existing PhantomJS WebPage instance.
In other words, after casperjs.start(), the PhantomJS page module is available in casper.page.
You can access the paperSize attribute in CasperJS using casper.page.paperSize.
casper.page.paperSize = {
width: '8.5in',
height: '11in',
header: {
height: '1cm',
contents: phantom.callback(function (pageNum, numPages) {
return '<h1>Header <span style="float:right">' + pageNum + ' / ' + numPages + '</span></h1>';
}),
},
footer: {
height: '1cm',
contents: phantom.callback(function (pageNum, numPages) {
return '<h1>Footer <span style="float:right">' + pageNum + ' / ' + numPages + '</span></h1>';
}),
},
};

Related

Prevent Popup template to shows multiple feature in ArcGIS 4.16 Angular 10

I have integrated a popup on the map for a specific layer. Some time the poup shows pagination (featureNavigation) multiple data. Sometimes it fails to show the data, or mismatch in the data that actually the service returns.
var popupTrailheads = {
title: "ID: {ID}",
content: this.getcustomcontent.bind(this),
};
// Add layer special layer
this.layer_fifteen = new FeatureLayer({
url: `${this.esriURL}/15`,
visible: true,
outFields: ['*'],
popupTemplate: popupTrailheads,
});
getcustomcontent(feature) {
// The popup content will become here from angular service
return `<div>content</div>`;
}
I have a few options to fix the issue.
1)Popup for this layer enables only at a specific Zoom level. Else the popup won't appear.
2)The click on the map should trigger only for a point. (I believe when click on the layer it triggers multiple points that is the reason it shows multiple feature details.)
Can you please suggest an idea, how to enable a popup in specific zoom level, or select only one feature in one click on the map.
Thanks in advance.
So, there are a number of possible ways to enable the popup under certain conditions, like the zoom level of the view.
In the example that I made for you popup only opens if zoom is greatest than 10.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
<title>PopupTemplate - Auto Open False</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.15/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<script>
var populationChange;
require(["esri/Map", "esri/views/MapView", "esri/layers/Layer"], function (
Map,
MapView,
Layer
) {
var map = new Map({
basemap: "dark-gray"
});
var view = new MapView({
container: "viewDiv",
map: map,
zoom: 7,
center: [-87, 34]
});
var highlightSelect = null;
Layer.fromPortalItem({
portalItem: {
id: "e8f85b4982a24210b9c8aa20ba4e1bf7"
}
}).then(function (layer) {
map.add(layer);
var popupTemplate = {
title: "Population in {NAME}",
outFields: ["*"],
content: [{
type: 'fields',
fieldInfos: [
{
fieldName: "POP2010",
format: {
digitSeparator: true,
places: 0
},
visible: false
},
{
fieldName: "POP10_SQMI",
format: {
digitSeparator: true,
places: 2
}
},
{
fieldName: "POP2013",
format: {
digitSeparator: true,
places: 0
}
},
{
fieldName: "POP13_SQMI",
format: {
digitSeparator: true,
places: 2
}
}
]
}],
};
layer.popupTemplate = popupTemplate;
function populationChange(feature) {
var div = document.createElement("div");
var upArrow =
'<svg width="16" height="16" ><polygon points="14.14 7.07 7.07 0 0 7.07 4.07 7.07 4.07 16 10.07 16 10.07 7.07 14.14 7.07" style="fill:green"/></svg>';
var downArrow =
'<svg width="16" height="16"><polygon points="0 8.93 7.07 16 14.14 8.93 10.07 8.93 10.07 0 4.07 0 4.07 8.93 0 8.93" style="fill:red"/></svg>';
var diff =
feature.graphic.attributes.POP2013 -
feature.graphic.attributes.POP2010;
var pctChange = (diff * 100) / feature.graphic.attributes.POP2010;
var arrow = diff > 0 ? upArrow : downArrow;
div.innerHTML =
"As of 2010, the total population in this area was <b>" +
feature.graphic.attributes.POP2010 +
"</b> and the density was <b>" +
feature.graphic.attributes.POP10_SQMI +
"</b> sq mi. As of 2013, the total population was <b>" +
feature.graphic.attributes.POP2013 +
"</b> and the density was <b>" +
feature.graphic.attributes.POP13_SQMI +
"</b> sq mi. <br/> <br/>" +
"Percent change is " +
arrow +
"<span style='color: " +
(pctChange < 0 ? "red" : "green") +
";'>" +
pctChange.toFixed(3) +
"%</span>";
return div;
}
view.popup.autoOpenEnabled = false; // <- disable view popup auto open
view.on("click", function (event) { // <- listen to view click event
if (event.button === 0) { // <- check that was left button or touch
console.log(view.zoom);
if (view.zoom > 10) { // <- zoom related condition to open popup
view.popup.open({ // <- open popup
location: event.mapPoint, // <- use map point of the click event
fetchFeatures: true // <- fetch the selected features (if any)
});
} else {
window.alert(`Popup display zoom lower than 10 .. Zoom in buddy! .. (Current zoom ${view.zoom})`);
}
}
});
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
Related to only display one result in the popup, you could hide the navigation like this,
view.popup.visibleElements.featureNavigation = false;
Now if what you actually want is to get only one result, then I suggest to use view method hitTest, that only gets the topmost result of the layers. You could do this after inside the click handler and only open if any result of desire layer. For this you need to set featFeatures: false, and set the features with the hit one.
Just as a comment it could result weird or confusing to the user retrieve just one of all possible features. I think probable you may have a problem with the content.

infinte scroll + imagesloaded + masonry on tumblr API loaded via ajax within shopify

I've been trying to make this work for a while but haven't made any progress. Not sure if I'm missing something or if it just won't work within this setup.
In short: client has a shopify site, and wants to load in images from tumblr in an infinite scroll.
I'm using the standards from DeSandro: Infinite Scroll, Masonry, ImagesLoaded, and basing the combination on this pen.
I have the tumblr feed loading in fine via tumblr API, and displayed in a masonry grid, but can't get the infinite scroll to work.
Will InfiniteScroll not work because the content is loaded in via ajax, and isn't actually on the page yet when InfiniteScroll tries to load it in? Any insight would be appreciated!
$(document).ready(function() {
// Main content container
var $container = $('#tblr_container');
$.ajax({
url: 'https://api.tumblr.com/v2/blog/xxxxxxx.tumblr.com/posts?api_key=xxxxxxx&limit={{ pagesize }}&offset={{ offset }}&callback=?',
dataType:'json',
success: function(data) {
$.each(data.response.posts, function(index,item) {
if (this['type'] === 'photo') {
var src = item.photos[0].alt_sizes[0].url;
$("#tblr_container").append('<div class="item masonry__item ' + index + '"><li><img src = "'+src+'"></li></div>');
}
});
// init Masonry
var $grid = $('#tblr_container').masonry({
itemSelector: 'none', // select none at first
columnWidth: '.grid-sizer',
gutter: 5,
percentPosition: true,
stagger: 30,
// nicer reveal transition
visibleStyle: { transform: 'translateY(0)', opacity: 1 },
hiddenStyle: { transform: 'translateY(100px)', opacity: 0 },
});
// get Masonry instance
var msnry = $grid.data('masonry');
// initial items reveal
$grid.imagesLoaded( function() {
$grid.removeClass('are-images-unloaded');
$grid.masonry( 'option', { itemSelector: '.item' });
var $items = $grid.find('.item');
$grid.masonry( 'appended', $items );
});
// init Infinte Scroll
$grid.infiniteScroll({
path: '.pagination__next',
append: '.item',
outlayer: msnry,
hideNav: '#pagination',
status: '.page-load-status',
});
}
});
Here's the link: https://negativeunderwear.com/pages/for-women
And, the link to page 2: https://negativeunderwear.com/pages/for-women?page=2&cache=false - this is .pagination__next
(FYI before clicking, it's a women's underwear site.)
Thanks!

PhantomJS invalid page render

I'm using phantomjs on Windows 7 x64 to make my page screenshot. My code is
var webPage = require('webpage');
var page = webPage.create();
page.viewportSize = { width: 1920, height: 1080 };
page.open("http://localhost/index.html", function start(status) {
page.render('screeshot.png', {format: 'png', quality: '100'});
phantom.exit();
});
I get screenshot like
while Chrome 51 show me page like this
add a delay, maybe phantomjs haven't load the site completely
var webPage = require('webpage');
var page = webPage.create();
page.viewportSize = { width: 1920, height: 1080 };
page.open("http://localhost/index.html", function start(status) {
setTimeout(function() {
page.render('screeshot.png', {format: 'png', quality: '100'});
phantom.exit();
}, 5000);
});
or try this script (https://github.com/ariya/phantomjs/blob/master/examples/rasterize.js) with command line:
phantomjs rasterize.js http://localhost/index.html /yourFolder 1920px

FullCalendar and Flot Resize Conflict

I've successfully integrated both a Flot line graph and an instance of FullCalendar into my site. They are both on separate pages (although the pages are loaded into a div via AJAX).
I've added the Flot Resize plugin and that works perfectly, re-sizing the line graph as expected. However, it seems to cause an error when resizing the calendar.
Even if I load the calendar page first, when I resize the window I get this error in the console (also, the calendar does not resize correctly):
TypeError: 'undefined' is not an object (evaluating 'r.w=o!==c?o:q.width()')
I was struggling to work out where the error was coming from, so I removed the link to the Flot Resize JS and tried again. Of course the line graph does not resize, but when resizing the calendar, it works correctly.
The div containers for the two elements have different names and the resize function is called from within the function to draw the line graph (as required).
I have tried moving the link to the Flot Resize plugin into different places (i.e. above/below the fullCalendar JS, into the template which holds the graph), but all to no avail.
Does anyone have any idea where the conflict might be and how I might solve it??
Thanks very much!
EDIT: It seems that the error is also triggered when loading the line graph (flot) page AFTER the fullcalendar page even without resizing the window.... Now I am very confused!
EDIT 2: The code which draws the line graph. The function is called on pageload and recieves the data from JSON pulled off the server. When the graph is loaded, I still get the error about shutdown() being undefined.
function plotLineGraph(theData){
var myData = theData['data'];
var myEvents = theData['events'];
var myDates = theData['dates'];
var events = new Array();
for (var i=0; i<myEvents.length; i++) {
events.push(
{
min: myEvents[i][0],
max: myEvents[i][1],
eventType: "Calendar Entry",
title: myEvents[i][2],
description: myEvents[i][3]
}
);
}
function showTooltip(x, y, contents) {
$('<div id="tooltip">' + contents + '</div>').css( {
position: 'absolute',
display: 'none',
top: y + 5,
left: x + 5,
border: '1px solid #fdd',
padding: '2px',
'background-color': 'black',
opacity: 0.80
}).appendTo("body").fadeIn(200);
}
var previousPoint = null;
$("#placeholder").bind("plothover", function (event, pos, item) {
$("#x").text(pos.x.toFixed(2));
$("#y").text(pos.y.toFixed(2));
if ($("#enableTooltip:checked").length == 0) {
if (item) {
if (previousPoint != item.dataIndex) {
previousPoint = item.dataIndex;
$("#tooltip").remove();
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
if(item.series.label != null){
showTooltip(item.pageX, item.pageY,
item.series.label + " of " + y);
}
}
}
else {
$("#tooltip").remove();
previousPoint = null;
}
}
});
var d1 = [
myData[0], myData[1], myData[2], myData[3], myData[4],
myData[5], myData[6], myData[7], myData[8], myData[9],
myData[10], myData[11], myData[12], myData[13], myData[14],
myData[15], myData[16], myData[17], myData[18], myData[19],
myData[20], myData[21], myData[22], myData[23], myData[24],
myData[25], myData[26], myData[27], myData[28], myData[29]
];
var markings = [
{ color: '#FFBDC1', yaxis: { from: 0, to: 2 } },
{ color: '#F2E2C7', yaxis: { from: 2, to: 3.5 } },
{ color: '#B6F2B7', yaxis: { from: 3.5, to: 5 } }
];
$.plot($("#placeholder"), [
{label: "Average Daily Rating", data: d1, color: "black"}
], {
events: {
data: events,
},
series: {
lines: { show: true },
points: { show: true }
},
legend: { show: true, container: '#legend-holder' },
xaxis: {
ticks:[
myDates[0], myDates[1], myDates[2], myDates[3], myDates[4],
myDates[5], myDates[6], myDates[7], myDates[8], myDates[9],
myDates[10], myDates[11], myDates[12], myDates[13], myDates[14],
myDates[15], myDates[16], myDates[17], myDates[18], myDates[19],
myDates[20], myDates[21], myDates[22], myDates[23], myDates[24],
myDates[25], myDates[26], myDates[27], myDates[28], myDates[29]
],
},
yaxis: {
ticks: 5,
min: 0,
max: 5
},
grid: {
backgroundColor: { colors: ["#fff", "#eee"] },
hoverable: true,
clickable: true,
markings: markings
},
selection: {
color: 'white',
mode: 'x'
},
});
$('#placeholder').resize();
$('#placeholder').shutdown();
}
EDIT 3:
The calendar is called like this:
function showCalendar() {
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
$('#fullcalendar').fullCalendar({
header: {
left: 'prev',
center: 'title',
right: 'next'
},
clickable: true,
firstDay: 1,
eventSources: [
{
url: '/populate-calendar/{{theProductUuid}}/',
color: 'black',
data: {
text: 'text'
}
}
],
eventClick: function(calEvent, jsEvent, view) {
var startDate = $.fullCalendar.formatDate(calEvent.start, 'yyyy-MM-dd');
var endDate = $.fullCalendar.formatDate(calEvent.end, 'yyyy-MM-dd');
var eventId = calEvent.uuid;
$('#modal-event-title').text(calEvent.title);
$('#edit-event-name').val(calEvent.title);
$('#edit-start-date').val(startDate);
$('#edit-end-date').val(endDate);
$('#edit-event-text').val(calEvent.text);
$('#edit-event-btn').attr('data-uuid', eventId);
$('#modal-edit-event').on('click', '#delete-btn', function(){
deleteCalendarEvent(eventId);
});
$('#modal-edit-event').modal();
},
});
}
The AJAX to load the page containing the flot chart:
function loadDetailedReports(uuid){
$('#product-content').fadeOut('slow', function(){
$('#product-content').empty();
$('#whole-product-sub-nav .active').removeClass('active');
$('#detailed-reports-content').load('/detailed-reports/' + uuid + '/', function(){
$('#detailed-reports-btn').addClass('active');
$('#detailed-reports-content').fadeIn('slow', function(){
if (authorized){
setLocationHash('loadDetailedReports&' + uuid);
getChartData(uuid);
} else {
setLocationHash('');
}
});
});
});
}
And the AJAX to load the page containing the calendar:
function loadCalendar(uuid){
$('#detailed-reports-content').empty().hide();
$('#product-content').fadeOut('slow', function(){
$('#whole-product-sub-nav .active').removeClass('active');
$('#product-content').load('/calendar/' + uuid + '/', function(){
$('#calendar-btn').addClass('active');
$('#product-content').fadeIn('slow', function(){
if (authorized){
setLocationHash('loadCalendar&' + uuid);
} else {
setLocationHash('');
}
showCalendar();
});
});
});
}
The calls to .resize and .shutdown are there because I was under the impression that they are necessary to achieve the resizing function and in response to your earlier comment regarding shutdown...... They're quite possibly n00b errors........?!?!
It looks like this is triggering on line 198 of jquery-resize:
data.w = w !== undefined ? w : elem.width();
This sounds like a race-condition stemming from the way you load different content into the same div. Flot binds the resize event to the chart div, and only un-binds it if the plot is destroyed cleanly.
EDIT: Looking at your code, my first suggestion would be to get rid of the resize and shutdown calls at the end of plotLineGraph. The resize plugin doesn't require any setup; it hooks into Flot to attach automatically to any new plot. So your call to resize is actually to jQuery's resize event trigger, which may be what's causing the error.
EDIT #2: I'm still not clear on your structure, but to generalize: anywhere that you might be getting rid of #placeholder (via emptying its parent or anything like that) you should first call shutdown on the plot object. If you aren't keeping a reference to it, you can do it like this: $("#placeholder").data("plot").shutdown(); but then have to account for the fact that it's undefined prior to the creation of your first plot.
If that still doesn't work, I'd need to see a live (simplified) example to make any further suggestions.

RubyOnRails3 - FullCalendar

I would like to use calendar in my project, I already used table_builder gem and event_calendar gem but fullcalendar gem is more appropriate for me. The problem is I can't find any step by step tutorial to use it and somehow I'm new to RubyOnRails. Can any one help me to be able to understand how to use it please?
*I Figured Out how to use full calendar - just go to fullcalendar web site and download the required version of fullcalendar JavaScript and css files - include fullcalendar.js and fullcalendar.css in the header of layout - create a new js file called calendar and write the following code: *
$(document).ready(function() {
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
$('#my_calendar').fullCalendar({
editable: true,
droppable: true,
theme: true,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultView: 'month',
height: 500,
slotMinutes: 15,
loading: function(bool){
if (bool)
$('#loading').show();
else
$('#loading').hide();
},
// a future calendar might have many sources.
eventSources: [{
url: '/events/index/',
color: 'yellow',
textColor: 'black',
ignoreTimezone: false
}],
dragOpacity: "0.5",
//http://arshaw.com/fullcalendar/docs/event_ui/eventDrop/
eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc){
updateEvent(event);
},
// http://arshaw.com/fullcalendar/docs/event_ui/eventResize/
eventResize: function(event, dayDelta, minuteDelta, revertFunc){
updateEvent(event);
},
// http://arshaw.com/fullcalendar/docs/mouse/eventClick/
eventClick: function(event, jsEvent, view){
window.location = ('/events/show?id=' + event.id)
},
});
});
function updateEvent(the_event) {
$.ajax({
type: 'POST',
url: '/events/update_js?id='+ the_event.id +"&startd=" + the_event.start+"&endd=" + the_event.end, //the script to call to get data
data: {end: the_event.end, start: the_event.start } , //you can insert url argumnets here to pass to api.php
//for example "id=5&parent=6"
dataType: 'json', //data format
success: function() //on receive of reply
{
alert("done!")
}
});
};
There are other code in the model and view in which the calendar will appear. also I changed the code of edit event function because it didn't work with me and function in the controller of event for edit- for any help I'm here any time. hope you can use it as I did