I'm using attached script to allocate content of div into .pdf file with jsPDF and html2canvas.
All works nicely with content of one single div (inputdiv).
However, I would like to add conent from few more additional div's into same .pdf file.
Question is how to do that?
With approach from below, only content of single div can be mapped through relevant canvas function. I tried to find some extra details in documentation and also here but unfortunately without success.
Any reference would be highly appreciated!
function generatePDF() {
var doc = new jsPDF ("l", "mm", "a4", "true");
html2canvas(inputdiv, {
width: 2000,
height: 1450,
scale: 2,
onclone: function (clonedDoc) {
clonedDoc.getElementById('inputdiv').style.display = 'block';
}
})
.then(function(canvas) {
var imgData = canvas.toDataURL('image/jpeg',1.0);
const imgProps= doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.addImage(imgData, 'jpeg', 14, 19, pdfWidth, pdfHeight, undefined, 'FAST');
doc.save('File.pdf');
});
}
This is an example of how you can solve this, for 2 divs that will print on 2 pages.
Of course if you have many divs, it would need to be refactored to avoid code repetition; but this one worked for me to include 2 different divs.
html2canvas(div1)
.then((canvasDiv1) =>{
//...
pdf.addImage(/* ... */);
})
.then(() => {
pdf.addPage();
html2canvas(div2)
.then((canvasDiv2) => {
//...
pdf.addImage(/* ... */);
// save on the last element
pdf.save(/* ... */);
})
})
If you need both divs to be on the same page, then just remove the pdf.addPage() and adjust the coordinates to place the second div where you need.
Related
I'm trying to inject some html to a Mattertag as shown in this example here:
http://jsfiddle.net/guillermo_matterport/njhm5aos/
const postMessage = await sdk.Mattertag.injectHTML(tagByTV.sid, htmlString, {
size: {
w: 400,
h: 50,
},
})
However I don't know the sid of the Mattertag, how can I find out?
To get a Mattertag sid, you can set a listener to print out the sid on each click:
sdk.on(sdk.Mattertag.Event.CLICK,
function (tagSid) {
console.log(tagSid + ' was selected');
}
);
Alternatively, you can base the injection on the data from the tags. You'd first need to get the Mattertag data from the space.
Using the Mattertag.data observable map:
const tagData = [];
sdk.Mattertag.data.subscribe({
onAdded: (idx, item) => {
tagData.push(item);
}
});
You can read more about observable maps and when methods are fired here.
You can then identify the Mattertag to inject the HTML, whether it's the title, color, or description. You can also incorporate the injection with the observable map. For example, based on the title:
const tagData = [];
const postMessages = {}; // save postMessage for multiple Mattertag injection references
sdk.Mattertag.data.subscribe({
onAdded: async (idx, item) => {
tagData.push(item);
if(item.label.includes("TV")){
const htmlString = makeHtmlString(item.sid);
const postMessage = await sdk.Mattertag.injectHTML(item.sid, htmlString, {
size: {
w: 400,
h: 50,
},
});
postMessages[item.sid] = postMessage;
}
}
});
See the modified fiddle here.
I found below code and trying to modify it to print an specific div element. Below code opens print dialog but shows empty screen. I dont know where to put my id of div element in the code.
print(){
var doc = new jsPDF("portrait", "mm", "a4");
doc.autoPrint();
const hiddFrame = document.createElement('iframe');
hiddFrame.style.position = 'fixed';
// "visibility: hidden" would trigger safety rules in some browsers like safari,
// in which the iframe display in a pretty small size instead of hidden.
// here is some little hack ~
hiddFrame.style.width = '1px';
hiddFrame.style.height = '1px';
hiddFrame.style.opacity = '0.01';
const isSafari = /^((?!chrome|android).)*safari/i.test(window.navigator.userAgent);
if (isSafari) {
// fallback in safari
hiddFrame.onload = () => {
try {
hiddFrame.contentWindow.document.execCommand('print', false, null);
} catch (e) {
hiddFrame.contentWindow.print();
}
};
}
var blob = doc.output("blob");
window.open(URL.createObjectURL(blob), '_blank');
}
There are 2 examples in the Matterport SDK for Embeds documentation to show how to place Mattertags in a scene:
The Intersection Inspector which only allows you to see coordinates for placing a Mattertag where the cursor is if you wait a little bit ... Not very user friendly, you need to copy the coordinates manually in your program.
The Transient Tags Editor which enable you to interactively place multiple Mattertags visually, edit them and then to extract them easily in a JSON file ...
I was wondering how to reproduce the Transient Tags Editor visual UX as I would like to use it in an application.
Insert Mattertags into the model visually
The source code of the app of the Transient Tags Editor is privately hosted on github (Maybe because it doesn't run perfectly on Firefox?), unlike the source code of the Intersection Inspector which is publicly hosted on JSFiddle.
But the user friendliness of the Transient Tags Editor intrigued me and I wanted to understand the difference between the two tools Matterport SDK provides to find out Mattertags coordinates.
How the Intersection Inspector works
The Intersection Inspector uses a timer to display a button at the position of the Pointer when the user does not move the pointer for more than one second. The user can then click the button to see the Mattertag coordinates and copy them manually ...
To achieve that, it needs the current Camera position, which it obtains by observing the camera's pose property:
var poseCache;
mpSdk.Camera.pose.subscribe(function(pose) {
poseCache = pose;
});
Also, it needs the current Pointer position, which it obtains by observing the pointer's intersection property:
var intersectionCache;
mpSdk.Pointer.intersection.subscribe(function(intersection) {
intersectionCache = intersection;
intersectionCache.time = new Date().getTime();
button.style.display = 'none';
buttonDisplayed = false;
});
※ An intersection event is triggered the user moves the pointer, so we hide the button to make sure it is not displayed before the one second delay is over.
Then, a timer is set up using setInterval() to display the button at the right time:
setInterval(() => {
// ...
}, 16);
In the timer callback, we check wether all the conditions to display the button are met ...
First, check we have the information we need:
setInterval(() => {
if (!intersectionCache || !poseCache) {
return;
}
// ...
}, 16);
Then, check one second has elapsed since the last intersection event was received, or we wait the next tick to check again:
var delayBeforeShow = 1000;
setInterval(() => {
if (!intersectionCache || !poseCache) {
return;
}
const nextShow = intersectionCache.time + delayBeforeShow;
if (new Date().getTime() > nextShow) {
// ...
}
}, 16);
Finally, we check the button is not already being displayed:
var delayBeforeShow = 1000;
var buttonDisplayed = false;
setInterval(() => {
if (!intersectionCache || !poseCache) {
return;
}
const nextShow = intersectionCache.time + delayBeforeShow;
if (new Date().getTime() > nextShow) {
if (buttonDisplayed) {
return;
}
// ...
}
}, 16);
Once the conditions are met, we can display the button using Conversion.worldToScreen() to get the screen coordinate of the pointer :
// ...
setInterval(() => {
// ...
if (new Date().getTime() > nextShow) {
// ...
var size = {
w: iframe.clientWidth,
h: iframe.clientHeight,
};
var coord = mpSdk.Conversion.worldToScreen(intersectionCache.position, poseCache, size);
button.style.left = `${coord.x - 25}px`;
button.style.top = `${coord.y - 22}px`;
button.style.display = 'block';
buttonDisplayed = true;
}
}, 16);
The button is a simple HTML button hidden by default using display: none; and positioned relative to the body with position: absolute;.
When the user clicks the button, the current coordinates of the pointer are displayed in a <div> tag above the <iframe> and the button is hidden:
button.addEventListener('click', function() {
text.innerHTML = `position: ${pointToString(intersectionCache.position)}\nnormal: ${pointToString(intersectionCache.normal)}\nfloorId: ${intersectionCache.floorId}`;
button.style.display = 'none';
iframe.focus();
});
The coordinates are formatted using the following function:
function pointToString(point) {
var x = point.x.toFixed(3);
var y = point.y.toFixed(3);
var z = point.z.toFixed(3);
return `{ x: ${x}, y: ${y}, z: ${z} }`;
}
Now, let's see how the easier-to-use and user-friendlier Transient Tags Editor interface works ...
How the Transient Tag Editor works
The Intersection Inspector is enough if you just have a few __Mattertag__s to set permanently in a few models in your application. But if you need your users to set tags interactively in models, something like the Transient Tags Editor's GUI is a better starting point.
The main advantage of using the Transient Tags Editor is that you can see how the Mattertag will be displayed before creating it and! That allows you to place the tag precisely without trial and error ...
To add a tag, you must click on the "Place New Tag" button to enter the "add tag" mode, then you can place one new tag anywhere you want.
We will only focus on that aspect of the editor and produce a simplified code sample that only add tags:
As the user move a tag along the pointer when in "add tag" mode, the first step is to create a new tag and place it. Let's create a function for that using Mattertag.add():
function addTag() {
if(!tag){
mpSdk.Mattertag.add([{
label: "Matterport Tag",
description: "",
anchorPosition: {x: 0, y: 0, z: 0},
stemVector: {x:0, y: 0, z: 0},
color: {r: 1, g: 0, b: 0},
}])
.then((sid) => {
tag = sid[0];
})
.catch( (e) => {
console.error(e);
})
}
}
Then we will have to place the tag at a position near the pointer, and update its position as the user moves the pointer, so let's create a function for that using Mattertag.editPosition():
function updateTagPos(newPos, newNorm=undefined, scale=undefined){
if(!newPos) return;
if(!scale) scale = .33;
if(!newNorm) newNorm = {x: 0, y: 1, z: 0};
mpSdk.Mattertag.editPosition(tag, {
anchorPosition: newPos,
stemVector: {
x: scale * newNorm.x,
y: scale * newNorm.y,
z: scale * newNorm.z,
}
})
.catch(e =>{
console.error(e);
tag = null;
});
}
As you can see the updateTagPos() function takes 3 parameters:
newPos: the new anchor position for the Mattertag.
newNorm: an optional new stem vector for the Mattertag.
scale: an optional new scale for the stem of the Mattertag.
To update the tag position as the user moves the pointer, let's observe the pointer's intersection property to call updateTagPos():
mpSdk.Pointer.intersection.subscribe(intersectionData => {
if(tag){
if(intersectionData.object === 'intersectedobject.model' || intersectionData.object === 'intersectedobject.sweep'){
updateTagPos(intersectionData.position, intersectionData.normal);
}
}
});
To place the new tag, the user simply clicks their mouse button, the Transient Tags Editor provides its own version of the document.activeElement method for intercepting clicks on the <iframe> (but does not work with Firefox so the editor use a quite complex workaround):
function focusDetect(){
const eventListener = window.addEventListener('blur', function() {
if (document.activeElement === iframe) {
placeTag(); //function you want to call on click
setTimeout(function(){ window.focus(); }, 0);
}
window.removeEventListener('blur', eventListener );
});
}
But, we will use our version which works better with Firefox (But still stop working after the first click in Firefox for whatever reason):
window.addEventListener('blur',function(){
window.setTimeout(function () {
if (document.activeElement === iframe) {
placeTag(); //function you want to call on click
window.focus()
addTag();
}
}, 0);
});
Finally, let's the function that navigates to the new tag and opens its billboard, usingMattertag.navigateToTag()
function placeTag(){
if(tag) mpSdk.Mattertag.navigateToTag(tag, mpSdk.Mattertag.Transition.INSTANT);
tag = null;
}
Simple Editor Code Sample
First, the complete JavaScript source code:
"use strict";
const sdkKey = "aaaaXaaaXaaaXaaaXaaaXaaa"
const modelSid = "iL4RdJqi2yK";
let iframe;
let tag;
document.addEventListener("DOMContentLoaded", () => {
iframe = document.querySelector('.showcase');
iframe.setAttribute('src', `https://my.matterport.com/show/?m=${modelSid}&help=0&play=1&qs=1>=0&hr=0`);
iframe.addEventListener('load', () => showcaseLoader(iframe));
});
function showcaseLoader(iframe){
try{
window.MP_SDK.connect(iframe, sdkKey, '3.10')
.then(loadedShowcaseHandler)
.catch(console.error);
} catch(e){
console.error(e);
}
}
function loadedShowcaseHandler(mpSdk){
addTag()
function placeTag(){
if(tag) mpSdk.Mattertag.navigateToTag(tag, mpSdk.Mattertag.Transition.INSTANT);
tag = null;
}
window.addEventListener('blur',function(){
window.setTimeout(function () {
if (document.activeElement === iframe) {
placeTag(); //function you want to call on click
window.focus()
addTag();
}
}, 0);
});
function updateTagPos(newPos, newNorm=undefined, scale=undefined){
if(!newPos) return;
if(!scale) scale = .33;
if(!newNorm) newNorm = {x: 0, y: 1, z: 0};
mpSdk.Mattertag.editPosition(tag, {
anchorPosition: newPos,
stemVector: {
x: scale * newNorm.x,
y: scale * newNorm.y,
z: scale * newNorm.z,
}
})
.catch(e =>{
console.error(e);
tag = null;
});
}
mpSdk.Pointer.intersection.subscribe(intersectionData => {
if(tag){
if(intersectionData.object === 'intersectedobject.model' || intersectionData.object === 'intersectedobject.sweep'){
updateTagPos(intersectionData.position, intersectionData.normal);
}
}
});
function addTag() {
if(!tag){
mpSdk.Mattertag.add([{
label: "Matterport Tag",
description: "",
anchorPosition: {x: 0, y: 0, z: 0},
stemVector: {x:0, y: 0, z: 0},
color: {r: 1, g: 0, b: 0},
}])
.then((sid) => {
tag = sid[0];
})
.catch( (e) => {
console.error(e);
})
}
}
} // loadedShowcaseHandler
The HTML source code:
<!DOCTYPE html>
<html>
<head>
<title>Transient Tag Editor</title>
<style>
#showcase {
width: 100%;
height: 100vh;
}
</style>
<script src="https://static.matterport.com/showcase-sdk/2.0.1-0-g64e7e88/sdk.js"></script>
<script src="/js/app-editor.js" type="text/javascript" defer></script>
</head>
<body>
<iframe id="showcase" frameborder="0" allowFullScreen allow="xr-spatial-tracking"></iframe>
</body>
</html>
It works!
Complete Code
The complete code for this sample and others is available on github:
github.com/loic-meister-guild/pj-matterport-sdk-2021-tutorial
See Also
Matterport SDK 2021 Tutorial
Node.js + Express Tutorial for 2021
How to detect a click event on a cross domain iframe
$("#ddl").change(function () {
var strSelected = "";
$("#ddl option:selected").each(function () {
strSelected += $(this)[0].value;
});
if (strSelected.length != 0) {
var url = "/Reseller/MailPartial/?resellerId=" + strSelected;
$("#mail").empty();
$("#mail").load(url);
}
this is code I use to load partial in my View (partial is only 1 label and 1 editorfor,the one that should load tinymce). I have [UIHint("tinymce_jquery_full"), AllowHtml] in my model and tinymce editor loads perfectly normal in other views. But when I use partial views it comes back as plain text area. How to fix this?
thanks
EDIT:
I figured it out,ijaz was almost correct ;)
I needed to reinit tinymce like ijaz said but even when I called INitTinyMCE like ijaz said it wouldn't have mattered because the element hasn't loaded yet to html and I have no idea why. Solution was to call initTinyMce after the element has loaded to the page.
I tried to use
$("#mail").load(url, InitTinyMCE());
but it didn't work.
Any ideas how to call InitTinyMCE() after the element has loaded? It's working now but it's relying on pressing another button to trigger InitTinyMCE()
EDIT again
I changed code to pure ajax,no more .load()
sorry for being so messy :)
In the above code,it seems that [UIHint] is not applied properly. so get things work, kindly initialize the TinyMCE manualy, i mean change you code as ,
$("#ddl").change(function () {
var strSelected = "";
$("#ddl option:selected").each(function () {
strSelected += $(this)[0].value;
});
if (strSelected.length != 0) {
var url = "/Reseller/MailPartial/?resellerId=" + strSelected;
$("#mail").empty();
$("#mail").load(url);
**Re-Init TinyMCE**
InitTinyMCE();
}
function InitTinyMCE()
{
$('##ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)').tinymce({
// Location of TinyMCE script
script_url: '#Url.Content("~/Scripts/tinymce/tiny_mce.js")',
theme: "advanced",
height: "170",
width: "240",
verify_html : false,
plugins : "pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,wordcount,advlist,autosave",
// Theme options
theme_advanced_buttons1: "undo, redo,pasteword,|, bold, italic, underline,|,justifyleft,justifycenter,justifyright,justifyfull,|,image, emotions" ,
theme_advanced_buttons2: "charmap, bullist, numlist,|,formatselect,fontselect,fontsizeselectcode, |,tiny_mce_wiris_formulaEditor, fullscreen",
theme_advanced_buttons3: "",
theme_advanced_toolbar_location: "top",
theme_advanced_toolbar_align : "left",
theme_advanced_statusbar_location : "bottom",
theme_advanced_resizing : true,
// Example content CSS (should be your site CSS)
content_css : "#Url.Content("~/Scripts/tinymce/css/content.css")",
convert_urls : false,
// Drop lists for link/image/media/template dialogs
template_external_list_url : "lists/template_list.js",
external_link_list_url : "lists/link_list.js",
external_image_list_url : "lists/image_list.js",
media_external_list_url : "lists/media_list.js"
});
}
I want to create "CRUD" functions by calling a modal form by clicking on a row in Datatables.
I've been at this for hours traversing through each step of my code and it seems I'm getting a conflict between my JQ-UI and Datatables. I found several examples, including the Datatables example for "live" functions, where you can initialize a table and call a simple jquery function.
I'm using:
code.jquery.com/jquery-1.9.1.js
code.jquery.com/ui/1.10.2/jquery-ui.js
../DataTables-1.9.4/media/js/jquery.dataTables.js
This example will give me the cursor, then makes the table "jump" across the page.
Does anyone have a working example or a fiddle I can experiment with?
function openDialog() {
$("#dialog-modal").dialog({
height: 140,
modal: true
});
}
/* Init DataTables */
$('#example').dataTable();
/* Add events */
$('#example tbody tr').on('click', function () {
$('#example tbody tr').css('cursor', 'pointer');
var sTitle;
var nTds = $('td', this);
var sBrowser = $(nTds[1]).text();
var sGrade = $(nTds[4]).text();
/*
if (sGrade == "A")
sTitle = sBrowser + ' will provide a first class (A) level of CSS support.';
else if (sGrade == "C")
sTitle = sBrowser + ' will provide a core (C) level of CSS support.';
else if (sGrade == "X")
sTitle = sBrowser + ' does not provide CSS support or has a broken implementation. Block CSS.';
else
sTitle = sBrowser + ' will provide an undefined level of CSS support.';
*/
openDialog();
//alert( sTitle )
});
A little sleep and another stab at this yielded a solution that at least solves the Datatable Dialog issue, I'll have to assume that any other issues I was having lies the other add-ins that I included. So to me this is solved.
The answer was 99% in this post - thanks to the author for the great working example.
I modified their link solution, combined with Datatables "live" solution example with variables, and was able to successfully pass data to a working dialog that works with pagination as the previous link explains.
This set up would allow me to create JQuery-UI Modal Forms, pass the ID from mySQL table column, and execute the form that's handing the Server Side PHP CRUD functions I needed.
(I can't take credit for any part of this, other than time spent making sure it worked).
The working example is taken straight from Datatables "live events" example, should be easy to drop in if you remove the sAjaxsource and go with a plain Datatable..
$('#example').dataTable( {
"bProcessing": true,
"bServerSide": true,
"bJQueryUI": true,
"bStateSave": true,
"sPaginationType": "full_numbers",
"sAjaxSource": " /*your data source page here*/ "
} );
/* Add events */
$("body").on("click", "#example tbody tr", function (e) {
e.preventDefault();
var nTds = $('td', this);
//example to show any cell data can be gathered, I used to get my ID from the first coumn in my final code
var sBrowser = $(nTds[1]).text();
var sGrade = $(nTds[4]).text();
var dialogText="The info cell I need was in (col2) as:"+sBrowser+" and in (col5) as:"+sGrade+"" ;
var targetUrl = $(this).attr("href");
$('#table-dialog').dialog({
buttons: {
"Delete": function() {
window.location.href = targetUrl;
},
"Cancel": function() {
$(this).dialog("close");
}
}
});
//simple dialog example here
$('#table-dialog').text(dialogText ).dialog("open");
});