PDF.JS - Detect when page has been rendered - javascript

Once PDF.JS has completed rendered each page, I want to then do a find/replace on the contents of that page.
I invoke PDF.JS by putting the following in a document in an iFrame:
<script>
fileId=0;
function getURLParameter(name) {
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null
}
var fileId = getURLParameter("fileId");
var DEFAULT_URL = '/viewer/fetchpdf.php?fileId='+fileId;
</script>
and then setting the URL from the parent frame:
url = '/_third_party/pdfjs/web/viewer.html?fileId='+$(this).attr('href');
$("#iframeViewPdf").attr('src', url);
I've noticed when using PDF.JS to render a PDF, it initialises each page with a loading placeholder:
<div id="pageContainer3" class="page" style="width: 991px; height: 1319px;">
<div class="loadingIcon"></div>
</div>
<div id="pageContainer4...
It then renders the PDF as html, e.g.
<div id="pageContainer3" class="page" style="width: 991px; height: 1319px;">
<div class="canvasWrapper" style="width: 991px; height: 1319px;">
<canvas id="page46" width="991" height="1319" style="width: 991px; height: 1319px;">
</canvas>
</div>
<div class="textLayer" style="width: 991px; height: 1319px;">
...
</div>
</div>
<div id="pageContainer4...

With the clarification, it is a very different story. You are not using PDF.JS directly, but their web wrapper. One thing that I think you can use (I've never done it, just reading the code now) is the fact that they are emitting pageRendered event on the document, so if you can add a listener to it, you should be fine:
var frameDoc = document.getElementById('iframeViewPdf').contentWindow.document;
frameDoc.addEventListener('pagerendered', function (evt) {
console.log(evt); // see what goodies hide here! like page number etc
}
(Didn't test, might need tweaking.)

So this is how we can detect the rendering of a page. It's important to wait for the iframe contents to load before setting up the listener.
$( "#iframeViewPdf" ).load(function() { // wait for iframe to load
var frameDoc = $("#iframeViewPdf").contents()[0];
frameDoc.addEventListener("pagerendered", function (evt) {
console.log(evt.detail);
});
});

//Step 1: store a refer to the renderer
var pageRendering = page.render(renderContext);
//Step : hook into the pdf render complete event
var completeCallback = pageRendering.internalRenderTask.callback;
pageRendering.internalRenderTask.callback = function (error) {
//Step 2: what you want to do before calling the complete method
completeCallback.call(this, error);
//Step 3: do some more stuff
};

Related

java script document.getElementById() is not working

hope you having great days,
i'm trying to get div for change its attribute value
but document.getElementById() is not working
i put the statement for after the div is completly load,
and even tried windows.load but nothing workout,
any suggestions?
var size_list = [[76.01, 77.81,23.99,11.09,0,11.09]
,[69.9, 71.56,20.51,14.22,9.59,14.22]
,[64.1,65.63,17.56,17.19,18.34,17.19]
,[59.22,60.63,15.15,19.69,25.64,19.69]
,[54.79,56.09,12.87,21.95,32.34,21.95]]
function size_update(src,index) {
console.log("i'm working");
let element = window.getComputedStyle(document.getElementById("thisid"),null)
element.setProperty('.height',size_list[index][0]+'%');
element.setProperty('width',size_list[index][1]+'%');
element.setProperty('top',size_list[index][2]+'%');
element.setProperty('right',size_list[index][3]+'%');
element.setProperty('bottom',size_list[index][4]+'%');
element.setProperty('left',size_list[index][5]+'%');
}
const Videoframe = ({src,index,title,before_span,span,after_span,subtitle}) =>{
try{
// do something
return(
<div>
<script>
function (){console.log("start!")};
</script>
<div className="videoframe" name = "thisid" id = "thisid" >
<div className="textgroup1">
<div className="title">{title}</div>
<div className="main">
<span>{before_span}</span>
<i className="better">{span}</i>
<span> {after_span}</span>
</div>
<div className="padding" />
<div className="subtitle">
{subtitle}
</div>
</div>
</div>
<script>
function (){console.log("ended!")};
</script>
</div>
);}
finally{
size_update(src,index);
}
}
export default Videoframe;
export { size_list }
tried googling and window.load
window.getComputedStyle() returns the style of the object, but you need the DOM-Element.
To get an element once the document is loaded you need to use the window load event by doing either
window.addEventListener("load", () => {
let element = document.getElementById("thisID");
});
or
window.onload = () => {
let element = document.getElementById("thisID");
};
In both cases, you supply a function to be executed once the document is loaded. Note that the code inside the eventhandler is executed only when the site is loaded, so after the code which is written after this code snippet. If you want to access the element, you'll need to write the logic for the element inside the function.

How to show loader until the iframe content fully get loaded in angular 7

I am getting iframe URL from api and I need to load the iframe URL from api in my HTML in the angular 7 app.
Until I get the url I show a loader to users. which is working all great until now. But after that when the iframe content loads into dom it takes a few seconds. At that moment my page is blank. I want to show a loader in this gap as well.
How to achieve it?
urlToOpen: any;
ionViewDidLoad() {
this.dataProcess();
}
dataProcess() {
this.canShowLoader = true;
// #ts-ignore
this.pusher.getEngageMdData().take(1).timeout(60 * 1000).subscribe(
data => {
this.canShowLoader = false;
this.urlToOpen = this.sanitizer.bypassSecurityTrustResourceUrl(data.data);
},
(error) => {
// console.log('error occurred', error);
this.canShowLoader = false;
},
() => {
// console.log('done');
this.canShowLoader = false;
}
);
}
<iframe *ngIf="canShowLoader === false" [src]="urlToOpen" data-tap-disabled="true"></iframe>
<div class="loadingDiv" *ngIf="canShowLoader === true">
<div class="loading_bg d-flex h-100">
<div style="width: 70rem !important;" class="loader justify-content-center align-self-center"><div>
<span style="color: white; font-size: 1.9rem">{{loadingMsg}}</span>
<ion-spinner color="white"></ion-spinner>
</div>
</div>
</div>
You should use a load event. It will emit once iframe is loaded.
So you should display spinner until it will be emitted. It'd look something like it.
d-none is class that sets display:none
<app-spinner *ngIf="isLoading"></app-spinner>
<iframe (load)="isLoading=false" [class.d-none]="isLoading"></iframe>

Executing a <script> tag on append after the DOM has loaded

I'm trying to watch videos onClick in a modal without having to visit the video pages themselves.
I'm using Vue.js and through a series of Ajax calls I am able to get most of the way there.
Codpen: https://codepen.io/nolaandy/pen/BrbBzO
If you click "vs Suns" at top, you get a listing of all video posts. Then clicking any of the images, the modal component pops up and takes in the title of the post dynamically.
I want to run a video in there as well so I try to run this script tag:
< script class="_nbaVideoPlayerScout" data-team="warriors" data-videoId="/video/{{unique videoId from post ajax call}}" data-width="768" data-height="732" src="https://www.nba.com/scout/team/cvp/videoPlayerScout.js"></script>
When the modal pops up, I see the correct title of the post/image I clicked on, and I see the script tag exactly as it should be in the inspector, but the script tag never runs.
Is there some different way I should be injecting this script than this? (This is inside the axios response call)
let theVideoId = response.data.content[0].videoID
let s = document.createElement('script')
s.setAttribute('class', '_nbaVideoPlayerScout')
s.setAttribute('data-team', 'warriors')
s.setAttribute('data-videoId', '/video/' + theVideoId)
s.setAttribute('data-width', '768')
s.setAttribute('data-height', '732')
s.setAttribute('src', 'https://www.nba.com/scout/team/cvp/videoPlayerScout.js')
document.getElementById('popupVideo').appendChild(s);
MODAL COMPONENT -- Fired on the click of one of the post thumbnails
const videoModal = Vue.component('VideoModal', {
props: {
id: {
type: String,
required: true
}
},
data: function () {
return {
post: [],
}
},
mounted() {
const singleApi = 'https://cors-anywhere.herokuapp.com/www.nba.com/warriors/api/1.1/json?textformat=html&nid='
axios.get(singleApi + this.id).then((response) => {
this.post = response.data.content[0]
console.log('THE RESPONSE', response)
let theVideoId = response.data.content[0].videoID
let s = document.createElement('script')
s.setAttribute('class', '_nbaVideoPlayerScout')
s.setAttribute('data-team', 'warriors')
s.setAttribute('data-videoId', '/video/' + theVideoId)
s.setAttribute('data-width', '768')
s.setAttribute('data-height', '732')
s.setAttribute('src', 'https://www.nba.com/scout/team/cvp/videoPlayerScout.js')
document.getElementById('popupVideo').appendChild(s);
}).catch((error) => {
console.log(error)
})
},
methods: {
goBack: function () {
router.go(-1)
}
},
template:`<div>
<div id="video-popup">
<button class="close-video-popup" #click="goBack">close me</button>
<div class="video-popup-wrapper">
<div class="video-popup--title">{{post.title}}</div>
<div class="video-popup--video" id="popupVideo"></div>
<div class="video-popup--share"></div>
</div>
</div>
</div>`
})
On a whim I made this change:
// document.getElementById('scriptMe').appendChild(s);
document.body.appendChild(s);
and boom, script runs and video loads.
Which is super interesting, because "why", right?
Edit:
In addition, trying other script injection methods discussed here.
document.write method
document.write(s.outerHTML) // s is a script node
also works. In fact, you can embed that script node in a div and it works as well.
createContextualFragment method
// var $container = document.getElementById('scriptMe'); // does not work
var $container = document.body
var range = document.createRange()
$container.appendChild(range.createContextualFragment(script_str))
works, where script_str is an html string literal. This will work both as "<script>....</script>" or "<div id="myDiv"><script>...</script></div>"
but all the methods I tested ultimately needed injection to be done in body.
codepen

html2canvas wait for images to load

I'm having this problem for some time ow and I can't seem to find a solution.
I'm using the latest html2canvas js plugin to take a screenshot of chart made with flotcharts and then submit the base64 screenshot via a hidden input. The problem is the div with the chart also has some images and html2canvas return a base64 string before the images are loaded. I can put a setTimeout on the submit untill they are loaded but apparently chrome opens the page where I submitted as a popup (which is really not ok cause our clients don't know how allow popups).
So this is what I tried but the images are not preloaded (because of the async nature of html2canvas)
function getPNGBase64forHtml($container) {
var imageString;
html2canvas($container, {
useCORS: true,
onrendered: function(canvas) {
imageString = canvas.toDataURL('image/png');
}
});
return imageString;
}
And also this (this works ok but it doesn't load the images in time):
function getPNGBase64forHtml($container) {
var h2canvas = html2canvas($container);
var queue = h2canvas.parse();
var canvas = h2canvas.render(queue);
return canvas.toDataURL('image/png');
}
So the problem is with waiting till the images are loaded in html2canvas then exeuting the rest of my stuff.
If anyone can please help, that would be very much appreciated, I bow to you kind sirs and madams! :)
Edit:
Here is the html of the part that I capture, this all is in a print-container div thats all. The arrow (only one is showed) in the timeline-prognose doesn't get captured cause it's a image, everything else does:
<div id="timeline-outer-container">
<div id="timeline-container" class="flot-container">
<div id="timeline-chart" class="flot-chart">
<canvas class="flot-base" width="888" height="335" ></canvas>
<div class="flot-text" >
<div class="flot-x-axis flot-x1-axis xAxis x1Axis">
<div class="flot-tick-label tickLabel" >Q1</div>
<div class="flot-tick-label tickLabel">Q2</div>
...
</div>
<div class="flot-y-axis flot-y1-axis yAxis y1Axis">
<div class="flot-tick-label tickLabel">75</div>
<div class="flot-tick-label tickLabel">100</div>
...
</div>
</div>
<canvas class="flot-overlay" width="888" height="335"></canvas>
<div class="axis-label xaxis">Zeitraum</div>
<div class="axis-label yaxis rotate-90">Anzahl</div>
<div id="zoom-out-button" class="timeline-zoom-button"><i class="fa fa-zoom-out"></i></div>
<div id="zoom-in-button" class="timeline-zoom-button"><i class="fa fa-zoom-in"></i></div>
<div id="zoom-default-button" class="timeline-zoom-button"><i class="fa fa-repeat"></i></div>
</div>
</div>
</div>
<div id="timeline-prognose">
<img id="timeline-arrow-up" class="timeline-arrows" src="/portal//images/arrows/up.png" alt="">
<img id="timeline-arrow-down" class="timeline-arrows" src="/portal//images/arrows/down.png" alt="">
</div>
So since you're using remotes images you can use the following fix doing some modifications in your script:
function getPNGBase64forHtml() {
var imageString;
html2canvas(document.body, {
useCORS: true,
logging : true, //Enable log (use Web Console for get Errors and Warnings)
proxy :"html2canvasproxy.php",
onrendered: function(canvas) {
var img = new Image();
img.onload = function() {
img.onload = null;
document.body.appendChild(img);
};
img.onerror = function() {
img.onerror = null;
if(window.console.log) {
window.console.log("Not loaded image from canvas.toDataURL");
} else {
alert("Not loaded image from canvas.toDataURL");
}
};
imageString = canvas.toDataURL('image/png');
}
});
return imageString;
}
If you are using php then the proxy setting must use the following script: html2canvas-php-proxy
Otherwise with .NET projects you can use these script resources:
html2canvas proxy with asp-classic - vb
html2canvas proxy with asp.net - csharp
If you decided to use local images this bug will not appear.
Hope it works for you, here is the original thread for this html2canvas bug https://github.com/niklasvh/html2canvas/issues/145

Detect change in hash value of URL using JavaScript

I'm working on a simple application which is single page based (due to project restrictions) and has dynamic content. I understand the dynamic content alright but what I don't understand is how to set-up a script that changes the html of a div when the hash value in the URL changes.
I need a JavaScript script to work as such:
Url: http://foo.com/foo.html div contents: <h1>Hello World</h1>
Url: http://foo.com/foo.html#foo div contents: <h1>Foo</h1>
How would this work?
Please help! Thanks.
You can listen to the hashchange event:
$(window).on('hashchange',function(){
$('h1').text(location.hash.slice(1));
});
personally, I'd use sammy which gives you the flexibility to template the hashtag (add placeholders and be able to read them back). e.g.
<script src="/path/to/jquery.js"></script>
<script src="/path/to/sammy.js"></script>
<script>
$(function(){
// Use sammy to detect hash changes
$.sammy(function(){
// bind to #:page where :page can be some value
// we're expecting and can be retrieved with this.params
this.get('#:page',function(){
// load some page using ajax in to an simple container
$('#container').load('/partial/'+this.params['page']+'.html');
});
}).run();
});
</script>
Load foo.html
Load bar.html
An example can be found here: http://jsfiddle.net/KZknm/1/
Suppose we have list of items, each items has a hash tag as #id
const markup = `
<li>
<a class="results__link" href="#${recipe.recipe_id}">
<figure class="results__fig">
<img src="${recipe.image_url}" alt="${limitRecipeTitle(recipe.title)}">
</figure>
<div class="results__data">
<h4 class="results__name">${recipe.title}</h4>
<p class="results__author">${recipe.publisher}</p>
</div>
</a>
</li>
`;
Now when a user click on any of those list item or reload (http://localhost:8080/#47746) an item with hash tag, hash event will be fired. To recive the fired hash event we must register hash event listener in our app.js
//jquery:
['hashchange', 'load'].forEach(event => $(window).on(event, controlRecipe));
//js:
['hashchange', 'load'].forEach(event => window.addEventListener(event, controlRecipe));
catch the id in your controlRecipe function
const controlRecipe = async ()=>{
//jq
const id = $(window)..location.hash.replace('#','');
//js
const id = window.location.hash.replace('#','');
if(id){
//console.log(id);
state.recipe = new Recipe(id);
try {
await state.recipe.getRecipe();
state.recipe.calcTime();
state.recipe.calcServings();
console.log(state.recipe);
} catch (error) {
alert(error);
}
}
}

Categories