saveSvgAsPng - getBBox is not a function - angularjs

I'm using exupero's saveSvgAsPng library to save SVG's to PNG-files, but I've run into a problem when combining it with Angular-Nvd3.
I get an error saying:
Uncaught TypeError: el.getBBox is not a function
Which to me seems like the function cannot "grab" the SVG-element from my nvd3-element.
My code looks like this:
HTML:
<button onclick = "saveAsPng();" type="button" name="button"></button>
<div id = "chart1-canvas">
<nvd3 id = "chart1-svg" options="options1" data="data1"></nvd3>
</div>
Javascript:
function saveAsPng(){
saveSvgAsPng(document.getElementById("chart1-svg"), "diagram.png");
}
Any suggestions on how to make this work properly would be appreciated.

I haven't used that saveSvgAsPng library, but I imagine it expects you to pass it a pointer to an SVG element, not the AngularJS element that surrounds it.
Try the following:
function saveAsPng() {
var svg = document.getElementById("chart1-svg").getElementsByTagName("svg")[0];
saveSvgAsPng(svg, "diagram.png");
}

This worked for me
import saveSvgAsPng from "save-svg-as-png"
let svgDownloadButton = document.getElementsByTagName('button')
svgDownloadButton.addEventListener('click', function () {
console.log("clicked")
var svg = document.getElementById("chart1-svg").getElementsByTagName("svg")[0];
saveSvgAsPng.saveSvgAsPng(svg, "diagram.png");
})

Related

Converting webpage to ReactJS ~ failed to compile "Unexpected use of 'event' no-restricted-globals"

I have some JS code that I need to make compatible with React Web.
I would appreciate some help as I am a little confused with what I need to do.
I get a failed to compile error when I make the following changes.
Currently the onclick function looks like this
<a href="javascript:void(0)" onClick="openMenu(event, 'Starter');">
<div className="w3-col s4 tablink w3-padding-large w3-hover-red">Starter</div></a>
Then I modify the onClick function to this, to attempt to get it to work
``` <a href="javascript:void(0)" onClick={(openMenu) => event, 'Pasta'}>
<div class="w3-col s4 tablink w3-padding-large w3-hover-red">Salads</div> </a>
And the JS function looks like this:
function openMenu(evt, menuName) {
var i, x, tablinks;
x = document.getElementsByClassName("menu");
for (i = 0; i < x.length; i++) {
x[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablink");
for (i = 0; i < x.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" w3-red", "");
}
document.getElementById(menuName).style.display = "block";
evt.currentTarget.firstElementChild.className += " w3-red";
}
document.getElementById("myLink").click();
If I make no changes the function dose not work and my goal is to get this function to work with react web.
Your help would be appreciated.
Just to be clear, you mean the code in the first box doesn't work, and when you try changing it to the second example, you get an error about no-restricted-globals?
This is because you are using a "reserved" keyword (event). So when you change it from the HTML in the first one to a javascript evaluation, the compiler refuses to compile. This is to prevent misalignments of global keywords.
Your almost there with the second example:
<a href="javascript:void(0)" onClick={(openMenu) => event, 'Pasta'}>
currently you have the function name in parens (openMenu) which isn't what you mean. The parens take the arguments you want to call the function with.
Something like
<a href="javascript:void(0)" onClick={(event) => openMenu(event, 'Pasta')}>
should work. The key thing I did was change how you are calling the function. If you have more questions about arrow functions, the React docs can be helpful. It just takes a while to get used to the new ES6 syntax.
Another way to solve this could use function currying:
<a href="javascript:void(0)" onClick={openMenu('Pasta')}>
then the function could be rewritten as such:
const openMenu = (menuName) => event => { ...
event.currentTarget.firstElementChild.className += " w3-red";
either of the ways presented will solve the problem for you (and many other ways as well).

AngularJs Component doesn't update view after img loading

i'm using angularJs 1.7 components.
This is my component who uploads a picture then converts it to base 64, then it is supposed to display it, but the displaying doesnt work .
myApp.component('club', {
templateUrl: 'vues/club.html',
controller: function($log,$scope) {
// HTML form data, 2 way binding ..
this.club = {};
// Bse 64 encoder
encodeImageFileAsURL = function() {
var filesSelected = document.getElementById("inputFileToLoad").files;
if (filesSelected.length > 0) {
var fileToLoad = filesSelected[0];
var fileReader = new FileReader();
fileReader.onload = function(fileLoadedEvent) {
var srcData = fileLoadedEvent.target.result; // <--- data: convert base64 : OK
this.club.img = srcData ; // Displaying in view doesnt work
}
fileReader.readAsDataURL(fileToLoad);
}
}
// Jquery watcher when we upload a picture
$(document).on('change', 'input[type="file"]' , function(){
encodeImageFileAsURL();
});
This is the html button inside the template :
<div id="upload_button">
<label>
<input name="inputFileToLoad" id="inputFileToLoad" ng-model="logo" type="file" onchange="" /> </input>
<span class="btn btn-primary">Upload picture</span>
</label>
</div>
This is the error :
TypeError: this.club is undefined
srcData is ok, and holds a base 64 image, the function works well.
I've tried the solution provided (.bind(this)) there with no luck , i dont know where to place it:
How to access the correct `this` inside a callback?
When using the $scope syntax, it is working, adding $scope.$apply(), but now i'm using components based dev, and the .this syntax, it doesnt work any more .
EDIT 1 :
Ok, i've initialized club with
$scope.club = {} ;
then inside the function, i'm writing
$scope.club.img = srcData ;
Then, it is working ok. I dont understand why .this is not the same than $scope !
See following example for where this object has reference to
a={
firstname:"something",
lastname:"something2",
fullname:function(){
console.log(this.firstname+' '+this.lastname);
}
}
a.fullname();
In above example Object a is created and inside fullname() function this object pointing to 'a' Object.
So that in your case
templateUrl is only variable on this Object if you do not want to use $scope. You can declare it by using var club = {}

Changing the template data not refreshing the elements

I have searched and tried suggestions mentioned in various posts but no luck so far.
Here is my issue.
I have created a custom element <month-view id="month-view-element"></month-view> in my mainpage.html. Inside mainpage.html when this page is initially loaded i created a empty json object for all the 30days of a month and print a placeholder type cards in UI. Using the code below.
var json = [];
for(var x = 0; x < total; x++) {
json.push({'hours': 0, 'day': x+1, 'year': year});
}
monthView.month = json; //Doing this line. Prints out the desired empty cards for me in the UI.
created a month-view.html something like below:
<dom-module id='month-view'>
<template>
<template is="dom-repeat" items= "{{month}}">
<paper-card class="day-paper-card" heading={{item.day}}>
<div class="card-content work">{{item.work}}</div>
<div class="card-actions containerDay layout horizontal">
<div style="display:inline-block" class="icon">
<paper-icon-button icon="icons:done" data-hours = "8" data-day$="{{item.day}}" data-month$={{item.month}} data-year$={{item.year}} on-click="updateWorkHours"></paper-icon-button>
<paper-tooltip>Full day</paper-tooltip>
</div>
</div>
</paper-card>
</template>
</template>
<script>
Polymer({
is: "month-view",
updateWorkHours: function (e, detail) {
console.log(e);
this.fire('updateWorkHour', {day: e.target.dataHost.dataset.day,
month: e.target.dataHost.dataset.month,
year: e.target.dataHost.dataset.year,
hours: e.target.dataHost.dataset.work
});
}
});
</script>
</dom-module>
There is another file script.js which contains the function document.addEventListener('updateWorkHour', function (e) { // doStuff });. I use this function to make a call to a google client API. I created a client request and then do request.execute(handleCallback);
Once this call is passed i landed in handleCallback function. In this function i do some processing of the response data and save parts of data into json variable available in the file already. And once all processing is done i did something like below.
monthView.month = json;
But this above line is not refreshing my UI with the latest data. Is there anything I am missing? Any suggestions or anything i am doing incorrectly.
You need to use 'set' or 'notifyPath' while changing Polymer Object or Arrays in javascript for the databinding/obserers to work. You can read more about it in https://www.polymer-project.org/1.0/docs/devguide/data-binding.html#path-binding
In your case try below code
monthView.set('month',json);
Updated suggestions:
Wrap your script on main page with. This is required for non-chrome browsers.
addEventListener('WebComponentsReady', function() {})
This could be scoping issue. Try executing 'document.querySelector('#month-view-element');' inside your callback addWorkHoursCallBack. Also, Use .notifyPath instead of .set.

cannot manipulate string with angular filter

I am an angular newbie and I study the book "Angular JS by example" and I try to create my own filter. (pp. 93-94).
In my controller this is the string I want to manipulate
procedure: "Assume a position, with feet together .\Slightly bend your knees, .\While in air, bring your legs out .\ As you are moving your legs outward"
and then I sanitise it
$scope.trustedHtml = $sce.trustAsHtml($scope.currentExercise.details.procedure);
Since this is an SPA , the filter is in the description-panel.ejs file, that is inside workout.ejs, that is inside index.ejs
description-panel.ejs has
<div class="panel-body" ng-bind-html ="trustedHtml | myLineBreakFilter"> </div>
workout.ejs has
<div id="video-panel" class="col-sm-3" ng-include="'description-panel.ejs'">
and index.ejs has
<script src="/javascripts/7MinWorkout/filters.js"></script>
filter.js has the filter
angular.module('7minWorkout').filter('myLineBreakFilter', function () {
return function (input) {
var str = input;
var br = "</br></br>";
var position = str.indexOf(".");
var output = [str.slice(0, position), br, str.slice(position)].join('');
return output ;
}
});
The filter should replace all the . with </br></br>.
This does not work and I get no text at all in my front-end. I get this error in the console
TypeError: str.slice is not a function at filters.js:22
Shouldn't basic js stuff like str.slice be supported out of the box? What am I missing?
Thanks
$sce.trustAsHtml() return you an object so slice will not work on it.You can pass that object to $sce.getTrustedHtml(object) to obtain the original value and then can apply slice on it.
angular.module('7minWorkout').filter('myLineBreakFilter', function ($sce) {
return function (input) {
var str = $sce.getTrustedHtml(input);
var br = "</br></br>";
var position = str.indexOf(".");
var output = [str.slice(0, position), br, str.slice(position)].join('');
return $sce.trustAsHtml(output) ;
}
});
Try this add this before the splice
str.toString();
str.splice(//pass parameters);

How do I change AngularJS ng-src when API returns null value?

In working with the API from themoviedb.com, I'm having the user type into an input field, sending the API request on every keyup. In testing this, sometimes the movie poster would be "null" instead of the intended poster_path. I prefer to default to a placeholder image to indicate that a poster was not found with the API request.
So because the entire poster_path url is not offered by the API, and since I'm using an AngularJS ng-repeat, I have to structure the image tag like so (using dummy data to save on space):
<img ng-src="{{'http://example.com/'+movie.poster_path}}" alt="">
But then the console gives me an error due to a bad request since a full image path is not returned. I tried using the OR prompt:
{{'http://example.com/'+movie.poster_path || 'http://example.com/missing.jpg'}}
But that doesn't work in this case. So now with the javascript. I can't seem to get the image source by using getElementsByTagName or getElementByClass, and using getElementById seems to only grab the first repeat and nothing else, which I figured would be the case. But even then I can't seem to replace the image source. Here is the code structure I attempted:
<input type="text" id="search">
<section ng-controller="movieSearch">
<article ng-repeat="movie in movies">
<img id="myImage" src="{{'http://example.com/'+movie.poster_path}}" alt="">
</article>
</section>
<script>
function movieSearch($scope, $http){
var string,
replaced,
imgSrc,
ext,
missing;
$(document).on('keyup', function(){
string = document.getElementById('search').value.toLowerCase();
replaced = string.replace(/\s+/g, '+');
$http.jsonp('http://example.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
});
imgSrc = document.getElementById('myImage').src;
ext = imgSrc.split('.').pop();
missing='http://example.com/missing.jpg';
if(ext !== 'jpg'){
imgSrc = missing;
}
});
}
</script>
Any ideas with what I'm doing wrong, or if what I'm attempting can even be done at all?
The first problem I can see is that while you are setting the movies in a async callback, you are looking for the image source synchronously here:
$http.jsonp('http://domain.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
});
// This code will be executed before `movies` is populated
imgSrc = document.getElementById('myImage').src;
ext = img.split('.').pop();
However, moving the code merely into the callback will not solve the issue:
// THIS WILL NOT FIX THE PROBLEM
$http.jsonp('http://domain.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
// This will not solve the issue
imgSrc = document.getElementById('myImage').src;
ext = img.split('.').pop();
// ...
});
This is because the src fields will only be populated in the next digest loop.
In your case, you should prune the results as soon as you receive them from the JSONP callback:
function movieSearch($scope, $http, $timeout){
var string,
replaced,
imgSrc,
ext,
missing;
$(document).on('keyup', function(){
string = document.getElementById('search').value.toLowerCase();
replaced = string.replace(/\s+/g, '+');
$http.jsonp('http://domain.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
$scope.movies.forEach(function (movie) {
var ext = movie.poster_path && movie.poster_path.split('.').pop();
// Assuming that the extension cannot be
// anything other than a jpg
if (ext !== 'jpg') {
movie.poster_path = 'missing.jpg';
}
});
});
});
}
Here, you modify only the model behind you view and do not do any post-hoc DOM analysis to figure out failures.
Sidenote: You could have used the ternary operator to solve the problem in the view, but this is not recommended:
<!-- NOT RECOMMENDED -->
{{movie.poster_path && ('http://domain.com/'+movie.poster_path) || 'http://domain.com/missing.jpg'}}
First, I defined a filter like this:
In CoffeeScript:
app.filter 'cond', () ->
(default_value, condition, value) ->
if condition then value else default_value
Or in JavaScript:
app.filter('cond', function() {
return function(default_value, condition, value) {
if (condition) {
return value;
} else {
return default_value;
}
};
});
Then, you can use it like this:
{{'http://domain.com/missing.jpg' |cond:movie.poster_path:('http://domain.com/'+movie.poster_path)}}

Resources