How to remove certain elements before taking screenshot? - html2canvas

I am able to take screenshot of the page using the example code below:
html2canvas(document.body, {
onrendered: function(canvas) {
document.body.appendChild(canvas);
}
});
Now there are certain div's i dont want to be part of the page when I take the screenshot?
How can i prevent them from being part of the screenshot.
One way I thought was to clone the element and then remove the elements, but taking a screenshot of the clone gives a white screen. Here is the code I used:
html2canvas($(document.body).clone()[0], {
onrendered: function(canvas) {
document.body.appendChild(canvas);
}
});

Add this attribute: data-html2canvas-ignore to any element you don't want to be taken when the screenshot is processed.
Hopefully this will help the next guy.

When I used this library I faced a problem that the lib download all the images in my application, that cause the application to run slowly. I resolved the problem using the ignoreElements option.
This is my code:
var DropAreaElement= document.getElementById("123");
var config= {
useCORS: true,
ignoreElements: function (element) {
if (element.contains(DropAreaElement) || element.parentElement.nodeName =="HTML" || element == DropAreaElement || element.parentNode == DropAreaElement) {
console.log("elements that should be taken: ", element)
return false;
}else {
return true;
}
}
};
html2canvas(DropAreaElement, config).then(function (canvas){
var imgBase64 = canvas.toDataURL('image/jpeg', 0.1);
console.log("imgBase64:", imgBase64);
var imgURL = "data:image/" + imgBase64;
var triggerDownload = $("<a>").attr("href", imgURL).attr("download", "layout_" + new Date().getTime() + ".jpeg").appendTo("body");
triggerDownload[0].click();
triggerDownload.remove();
}).catch(Delegate.create(this, function (e){
console.error("getLayoutImageBase64 Exception:", e);
});

If you don't want to use an attribute, html2canvas does provide a method to remove elements. For example:
html2canvas( document.body, {
ignoreElements: function( element ) {
/* Remove element with id="MyElementIdHere" */
if( 'MyElementIdHere' == element.id ) {
return true;
}
/* Remove all elements with class="MyClassNameHere" */
if( element.classList.contains( 'MyClassNameHere' ) ) {
return true;
}
}
} ).then( function( canvas ) {
document.body.appendChild( canvas );
} );
For more information, see html2canvas options.

You can create HOC for <Printable/> and <NonPrintable/> , you can wrap your component with <NonPrintable><YourCoolComponent/></NonPrintable>
those children components would be excluded.
import React from "react"
interface INonPrintable {
children: React.ReactChildren
}
/*
HOC - Printable which injects the printId to the React component
which gets us Printable Context to html2canvas => jsPDF
eg:
<Printable printId="about-you-print">
<PersonalInfo badEmail={badEmail} />
<IdentityInfo />
<AdditonalInfo />
<AddressInfo
serviceAddress={serviceAddress}
billingAddress={this.state.billingAddress}
setBillingAddress={this.setBillingAddress}
/>
</Printable>
*/
export default function Printable({ printId = "", children, ...restProps }) {
return <div print-id={printId} {...restProps}>{children}</div>
}
/*
HOC - NONPrintable which injects the data-html2canvas-ignore to the React component
which gets us Printable Context to html2canvas => jsPDF
eg:
<NonPrintable style={{display:"flex",justifyContent:'space-around'}}>
<Button
text="Print PDF using Own utility"
onClick={this.handlePrintPdf}
/>
<Button
text="Print PDF using html2canvas + jsPDF"
onClick={this.handlePrintwithPDFjs}
/>
</NonPrintable>
*/
export const NonPrintable = ({ children, ...restProps }) => {
return <div data-html2canvas-ignore {...restProps}>{children}</div>
}

Related

Vuetify TreeView + Drag and drop

I am trying to implement drag and drop on Vuetify Treeview and data table. It seems like it is not supported fully but a workaround is described in this thread. The workaround is however not complete. Perhaps the community would benefit if someone created a codepen or similar on this?
What confuses me is that the component DragDropSlot.vue is created but "drag-drop-slot" is used in the code. Also there is a "_.cloneDeep(this.tree)" call where _ is not defined. I assume it should be replaced by something. When I comment that out drag and drop does still not work. Probably missed something more like defining data. Not sure of correct data types. It seems to be based on react which I have not worked with. Have just started to learn vue and vuetify.
I'm open for any suggestion for how to solve this.
All the best
I use V-Treeview with Vue.Draggable (https://github.com/SortableJS/Vue.Draggable).
I use direct link.
<script src="//cdn.jsdelivr.net/npm/sortablejs#1.8.4/Sortable.min.js"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0 vuedraggable.umd.min.js"/>
<v-treeview
:active.sync="active"
:items="users"
:search="search"
item-key="Id"
item-text="UserName"
item-children="Children"
:open.sync="open"
activatable
color="warning"
dense
transition
return-object
>
<template v-slot:label="{ item }">
<draggable :list="users" group="node" :id="item.Id" :data-parent="item.ParentId" #start="checkStart" #end="checkEnd" >
<label>
<i class="fas fa-user mr-3" />
<span id="item.id" >{{item.UserName}}</span>
</label>
</draggable>
Also I add ParentId property to item tree model:
{
Id:1,
UserName: "John Doe",
ParentId: null,
Children:[{Id:2, ParentId: 1,...}]
}
Then I use start and end events where I search parent start node from I drag the item and parent end node where I drop the item. When parent is null the item is a root.
new Vue({
el: '#app',
vuetify: new Vuetify(),
components: {
vuedraggable
},
data() {
return {
active: [],
open: [],
users: [],
selectedItems: [],
}
},
mounted: function () {
this.fetchUsers();
},
methods: {
findTreeItem: function (items, id) {
if (!items) {
return;
}
for (var i = 0; i < items.length; i++) {
var item = items[i];
// Test current object
if (item.Id === id) {
return item;
}
// Test children recursively
const child = this.findTreeItem(item.Children, id);
if (child) {
return child;
}
}
},
checkStart: function (evt) {
var self = this;
self.active = [];
self.active.push(self.findTreeItem(self.users, evt.from.id))
},
checkEnd: function (evt) {
var self = this;
var itemSelected = self.active[0];
var fromParent = itemSelected.ParentId ? self.findTreeItem(self.users, itemSelected.ParentId) : null;
var toParent = self.findTreeItem(self.users, evt.to.id);
var objFrom = fromParent ? fromParent.Children : self.users;
objFrom.splice(objFrom.indexOf(itemSelected), 1);
if (toParent.Id === itemSelected.Id) {
itemSelected.ParentId = null;
self.users.push(itemSelected);
}
else {
itemSelected.ParentId = toParent.Id;
toParent.Children.push(itemSelected);
}
self.saveUser(itemSelected);
// self.active = [];
return false;
},
fetchUsers: function () {
//load from api
},
saveUser: function (user) {
//save
},
},
computed: {
selected() {
if (!this.active.length) return undefined
return this.active[0];
},
}
})
Hope I help you.
IngD.
After some additional work I ended up with implementing Drag and Drop on top of vuetify tree view and data table using this library:
https://www.vuetoolbox.com/projects/vue-drag-drop
At first I looked at draggable and similar but realized it was always based on that you move an element from position A to position B. I needed more control. For example I wanted the element to disappear when dropping on some drop zones.
found this component.
https://vuejsexamples.com/vuetify-draggable-v-treeview-component/
I didn't try it myself (because it has too few options), but it looks working well in demo.
Anyways, just to try

How to use the numericRefinementList to set allowed distances?

I would like to be able to use the numericRefinementList to allow users to pick the distance from themselves an item can be? This would be using the IP geo-location feature or inputting the geo-location from browser if available.
Less than 50km
50 - 100km
100 - 150km
More than 150km
https://community.algolia.com/instantsearch.js/documentation/#numericrefinementlist
This is unfortunately not something you can do with the numericRefinementList but you can probably build a custom widget setting the aroundRadius depending on the link you've clicked on:
function radiusList(options) {
if (!options.container) {
throw new Error('radiusList: usage: radiusList({container, ...})');
}
var $container = $(options.container);
if ($container.length === 0) {
throw new Error('radiusList: cannot select \'' + options.container + '\'');
}
return {
init: function(args) {
// event delegation: set the aroundRadius of the underlying link
$(document).on('click', '.radius-link', function(e) {
e.preventDefault();
args.helper.setQueryParameter('aroundRadius', +$(this).data('radius'));
args.helper.search();
});
},
render: function(args) {
// FIXME: display the list of radius links
var html = '<ul>';
html += '<li>< 100km</li>';
html += '</ul>';
$container.html(html);
}
};
}
And then you use it with:
search.addWidget(radiusList({container: '#my-radius-list'}));

Embedding Facebook Posts Responsive

Facebook claims its embedded posts are adjusted automatically based on the screen sizes.
Please see Can I customize the width of Embedded Posts? section in the below link.
https://developers.facebook.com/docs/plugins/embedded-posts
However, the embed doesn't seem to be responsive. Please see my test here,
http://colombowebs.com/test/fb/
Is there anything I have to do in addition to make it responsive?
You have to use javascript/jquery to obtain the desired result. I have taken help from responsive function and created the following which works almost for all widths.
(function ($) {
jQuery.fn.autoResizeFbPost = function () {
var fixWidth = function ($container, $clonedContainer, doParse) {
// default parameter.
doParse = typeof doParse == 'undefined' ? true : doParse;
var updatedWidth = $container.width();
// update all div.fb-post with correct width of container
var isMobile = window.matchMedia("only screen and (max-width: 600px)");
if (isMobile.matches) {
//Conditional script here
if (window.matchMedia("(orientation: portrait)").matches) {
// you're in PORTRAIT mode
updatedWidth = $(window).width();
}
if (window.matchMedia("(orientation: landscape)").matches) {
// you're in LANDSCAPE mode
updatedWidth = $(window).height();
}
}
$clonedContainer
.find('div.fb-post')
.each(function () {
$(this).attr('data-width', updatedWidth);
});
$('div.embedded', $clonedContainer).each(function () {
$(this).attr('max-width', updatedWidth);
});
// update page with adjusted markup
$container.html($clonedContainer.html());
//should we call FB.XFBML.parse? we don't want to do this at page load because it will happen automatically
if (doParse && FB && FB.XFBML && FB.XFBML.parse)
FB.XFBML.parse();
};
return this.each(function () {
var $container = $(this),
$clonedContainer = $container.clone();
// make sure there is a .fb-post element before we do anything.
if (!$container.find('div.fb-post').length) {
return false;
}
// execute once (document.ready) and do not call FB.XFBML.parse()
fixWidth($container, $clonedContainer, false);
$(window).bind('resize', function () {
fixWidth($container, $clonedContainer);
}).trigger('resize');
});
};
})(jQuery);
(function ($) {
$(document).ready(function () {
$('#post').autoResizeFbPost();
});
})(jQuery);
And your HTML should be like
<div style="background-color: white;" id="post">
<div class="fb-post" data-href="your-facebook-post-url" mobile="false"></div>
Hope this helps you. Feel free to post your comments.

AngularJS: Move to next form input element after successful validation

I have written a custom directive for validation of my form fields. When certain criteria are met (i.e. it is dirty and valid), I want to set the focus automatically to the next input element. This is a requirement from my users, such that they can move through the forms most efficiently.
The simplified directive looks like this:
directive('custom', ['$parse', function($parse) {
return {
restrict: 'A',
require: ['ngModel', '^ngController'],
link: function(scope, element, attrs, ctrls) {
var model=ctrls[0], form=ctrls[1];
scope.next = function(){
return model.$valid
}
scope.$watch(scope.next, function(newValue, oldValue){
if (newValue && model.$dirty){
???
}
})
Now my question is: how can I identify
- the next input element (which is the next sibling) or possibly via the tabindex
- and focus on it
without using Jquery?
For me, it is currently not clear, how to get to the next input element from the available "scope" or "element" attributes without Jquery; and JQlite does nothave a "focus" method. Basically, I need a working substitute for ??? in my code.
Any help is highly appreciated. Thanks
Juergen
You can use [0] to get the underlying input element (which has a focus() function) from the angular/jqLite object (which doesn't).
app.directive('custom', ['$parse', function($parse) {
return {
restrict: 'A',
require: ['ngModel'],
link: function(scope, element, attrs, ctrls) {
var model=ctrls[0], form=ctrls[1];
scope.next = function(){
return model.$valid;
}
scope.$watch(scope.next, function(newValue, oldValue){
if (newValue && model.$dirty)
{
var nextinput = element.next('input');
if (nextinput.length === 1)
{
nextinput[0].focus();
}
}
})
}
}
}])
http://jsfiddle.net/Y2XLA/
element.next().focus() might not work if you have a complex form and input are nested into different divs.
I ended writing this directive (here I move the focus on Enter, but can be adapted to whatever event):
.directive('enterToTab', function($timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var procAttr = 'data-ett-processed';
$timeout(function() { // Use $timeout to run the directive when the DOM is fully rendered
var formElements = element[0].querySelectorAll('input:not([' + procAttr + '="true"]), select:not([' + procAttr + '="true"]), textarea:not([' + procAttr + '="true"])');
// Run through all elements in form
var formElementsLength = formElements.length;
for (var i = 0; i < formElementsLength; i++) { // Add tabindex attribute
formElements[i].setAttribute('tabindex', i + 1);
// Go to next element on Enter key press
formElements[i].addEventListener('keypress', function(event) {
if (event.keyCode === 13) { // Enter
// Prevent Angular from validating all the fields and submitting
if (event.target.tagName !== 'TEXTAREA') { // Not on textarea, otherwise not possible to add new line
event.stopPropagation();
event.preventDefault();
}
var nextIndex = parseInt(event.target.getAttribute('tabindex')) + 1;
// Move focus to next element
// TODO: find next visible element
var nextElem = element[0].querySelector('[tabIndex="' + (nextIndex) + '"]');
if (nextElem) {
nextElem.focus();
}
}
});
formElements[i].setAttribute(procAttr, true); // Add attribute to prevent adding 2 listeners on same element
}
});
}
};
});
Event should be in HTML component (keypress) = "keyFocus($event)"
Method shoulb be like .ts file.
keyFocus(input1){
input1.srcElement.nextElementSibling.focus();
}
AngularJS already contains a light version of jQuery so you can as well use it...
http://docs.angularjs.org/api/angular.element
You could try something like this:
element.next().focus()

can I build a css class on the fly in tiny mce?

I'm using tiny mce, but I found it adds multiple spans with inline styles to the content for any applied style. Inline styles are not W3c Compliant, so must avoid inline css. Is it possible to create css class on the fly and apply to the selection, while editing content in tiny mce ?
Yes that is possible, but it took me some effort. What needs to be done is to write the class into the head of the editors iframe. Here is some example code which should work for IE,FF, Safari and point you into the right direction:
fonturl = "http://myfonts.com/arial.ttf"
csstext_to_add = '#font-face {font-family: "ownfont";src: url("'+fonturl+'");}'; // example
iframe_id = ed.id;
with(document.getElementById(iframe_id).contentWindow){
var h=document.getElementsByTagName("head");
if (!h.length) {
return;
}
var newStyleSheet=document.createElement("style");
newStyleSheet.type="text/css";
h[0].appendChild(newStyleSheet);
try{
if (typeof newStyleSheet.styleSheet !== "undefined") {
newStyleSheet.styleSheet.cssText = csstext_to_add;
}
else {
newStyleSheet.appendChild(document.createTextNode(csstext_to_add));
newStyleSheet.innerHTML=csstext_to_add;
}
}
catch(e){}
}
It is also possible to add that class as option into a dropdown (what takes some effort).
Thariama's answer was perfect. I'm using the tinyMCE jQuery connector for some of my pages and I have multiple instances of tinyMCE on the page. I made some modifications, but essentially its the same thing. I've created a text area field on the page that people can provide their own CSS. Also, I needed to change some CSS rules on the fly...
// function to change tinyMCE css on the fly
function checkCustomCSS() {
var $css = $('#page_css'),
newcss;
if ($css.val().length > 0) {
// since front end, we are wrapping their HTML in a wrapper and
// the tinyMCE edit iFrame is just using <body>, we need to change
// some rules so they can see the changes
newcss = $css.val().replace('#content_wrapper', 'body');
// loop through each tinyMCE editor and apply the code changes
// You could check the editor.id to make sure that the correct
// editor gets the appropriate changes.
$.each(tinyMCE.editors, function() {
var $this = $(this),
editorID = $this[0].id,
$ifrm = $('#' + editorID+ '_ifr'),
cwin, head, sheet;
if ($ifrm.length > 0 /* && editorID === 'OUR_EDITOR_ID_NAME' */) {
cwin = $ifrm[0].contentWindow;
head = cwin.document.getElementsByTagName("head");
if (!head.length) {
return;
}
sheet = cwin.document.createElement("style");
sheet.type = "text/css";
head[0].appendChild(sheet);
try {
if (typeof sheet.styleSheet !== "undefined") {
sheet.styleSheet.cssText = newcss;
} else {
sheet.appendChild(cwin.document.createTextNode(newcss));
sheet.innerHTML = newcss;
}
} catch (e) {}
}
});
}
}
Then in the tinyMCE init call I added and onInit call to setup changes to the #page_css , like this:
oninit: function() {
$('#page_css').on('change', function() {
checkCustomCSS();
});
}
Works like a charm.