I came accross a strange behaviour when using SVG with AngularJS. I'm using the $routeProvider service to configure my routes. When I put this simple SVG in my templates, everything is fine:
<div id="my-template">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect fill="red" height="200" width="300" />
</svg>
// ...
</div>
But when I add a filter, with this code for instance:
<div id="my-template">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<filter id="blurred">
<feGaussianBlur stdDeviation="5"/>
</filter>
</defs>
<rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
</svg>
</div>
Then:
It works on my homepage.
With Firefox, the SVG isn't visible anymore on the other pages, but it still leaves space where it would have been. With Chrome, the SVG is visible, but not blurred at all.
The SVG is visible again when I remove manually (with Firebug) the filter style.
Here is the routes configuration:
$routeProvider
.when('/site/other-page/', {
templateUrl : 'view/Site/OtherPage.html',
controller : 'Site.OtherPage'
})
.when('/', {
templateUrl : 'view/Site/Home.html',
controller : 'Site.Home'
})
.otherwise({
redirectTo : '/'
})
;
Fiddle
Please notice that I've failed to reproduce the problem with Chrome in a Fiddle, although it "works" with Firefox.
I've tried to no avail to create my whole SVG with document.createElementNS().
Does someone has an idea of what is happening?
The problem
The problem is that there is a <base> tag in my HTML page. Therefore, the IRI used to identify the filter is not anymore relative to the current page, but to the URL indicated in the <base> tag.
This URL was also the URL of my home page, http://example.com/my-folder/ for instance.
For the pages other than the home page, http://example.com/my-folder/site/other-page/ for example, #blurred was computed to the absolute URL http://example.com/my-folder/#blurred. But for a simple GET request, without JavaScript, and therefore without AngularJS, this is simply my base page, with no template loaded. Thus, the #blurred filter doesn't exist on this pages.
In such cases, Firefox doesn't render the <rect> (which is the normal behaviour, see the W3C recommandation). Chrome simply doesn't apply the filter.
For the home page, #blurred is also computed to the absolute URL http://example.com/my-folder/#blurred. But this time, this is also the current URL. There is no need to send a GET request, and thus the #blurred filter exists.
I should have seen the additional request to http://example.com/my-folder/, but in my defense, it was lost in a plethora of other requests to JavaScript files.
The solution
If the <base> tag is mandatory, the solution is to use an absolute IRI to identify the filter. With the help of AngularJS, this is pretty simple. In the controller or in the directive that is linked to the SVG, inject the $location service and use the absUrl() getter:
$scope.absUrl = $location.absUrl();
Now, in the SVG, just use this property:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<filter id="blurred">
<feGaussianBlur stdDeviation="5"/>
</filter>
</defs>
<rect style="filter:url({{absUrl}}#blurred)" fill="red" height="200" width="300" />
</svg>
Related: SVG Gradient turns black when there is a BASE tag in HTML page?
It looks like a behaviour I observed before. The root cause is that you end up having multiple elements (filters) with the same id (blurred). Different browsers handle it differently...
Here is what I did to reproduce your case: http://jsfiddle.net/z5cwZ/
It has two svg and one is hidden, firefox shows none.
There are two possibilities to avoid conflicting ids. First you can generate unique ids from your template (I can't help in doing it with angularjs tough). Here is an example: http://jsfiddle.net/KbCLB/1/
Second possibility, and it may be easier with angularjs, is to put the filter outside of the individual svgs (http://jsfiddle.net/zAbgr/1/):
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="0" height="0">
<defs>
<filter id="blurred">
<feGaussianBlur stdDeviation="10" />
</filter>
</defs>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="display:none">
<rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
</svg>
<br/>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
</svg>
Just ran into this problem. Expanding from #Blackhole's answer, my solution was to add a directive that changes the value of every fill attribute.
angular.module('myApp').directive('fill', function(
$location
) { 'use strict';
var absUrl = 'url(' + $location.absUrl() + '#';
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
$attrs.$set('fill', $attrs.fill.replace(/url\(#/g, absUrl));
}
};
});
I recently ran into this issue myself, if the svg is used on different routes you will have to add the $location to each path.
A better way to implement this is to just use a window.location.href instead of the $location service.
Also had issues with svg link:hrefs, and yes the problem is because of the <base> - I had errors being thrown when concatenating the absolute url because of the Strict Contextual Escaping restrictions.
My solution was to write a filter that added the absolute url and also trusted the concatenation.
angular.module('myApp').filter('absUrl', ['$location', '$sce', function ($location, $sce) {
return function (id) {
return $sce.trustAsResourceUrl($location.absUrl() + id);
};
}]);
And then use it like this: {{'#SVGID_67_' | absUrl}
Result : http://www.example.com/deep/url/to/page#SVGID_67_ and no $sce errors
Related
I have a short example of an a-frame page (without React) that uses the raycaster and cursor components. The HTML looks like this:
<a-scene>
<a-entity raycaster="objects: .clickable" cursor="rayOrigin: mouse"></a-entity>
<a-plane class="clickable" color="yellow" position="0 1.5 -3" onclick="handleClick()"></a-plane>
<a-sky color="blue"></a-sky>
</a-scene>`
When I render this HTML and view the DOM, I can see the raycaster and cursor attributes along with their data. According to the latest react docs, starting with React v16, unknown attributes on HTML elements should get passed through JSX. However, the attributes get stripped with the same HTML outputted through React, like this:
ReactDOM.render(
<a-scene>
<a-entity raycaster="objects: .clickable" cursor="rayOrigin: mouse"></a-entity>
<a-plane class="clickable" color="yellow" position="0 1.5 -3" onclick="handleClick()"></a-plane>
<a-sky color="blue"></a-sky>
</a-scene>,
document.getElementById('root')
);
If I inspect the DOM when this second react example is running, it looks like:<a-entity raycaster cursor></a-entity>
If I add "data-" to the front of the attribute name, it passes the data, but then it has the wrong name and a-frame doesn't see it. What am I doing wrong here?
That code will work:
<a-scene>
<a-entity raycaster="objects: .clickable" cursor="rayOrigin: mouse" />
<a-plane class="clickable" color="yellow" position="0 1.5 -3" onClick={handleClick} />
<a-sky color="blue" />
</a-scene>
The problem was that a-plane is not for React, so className doesn't work, but class - works.
But at the same time onclick="handleClick()" doesn't work, and onClick={handleClick} works.
Also there is another way to define what should be intersected:
raycaster="objects: [dataRaycastable]"
and then for example
<a-plane dataRaycastable={true} ... />
<div class="MuiGrid-root jss48321 MuiGrid-item MuiGrid-grid-xs-12 MuiGrid-grid-sm-3 MuiGrid-grid-md-3 MuiGrid-grid-lg-3 MuiGrid-grid-xl-3">
<h4 class="MuiTypography-root jss48324 MuiTypography-h4">Decks</h4>
<button class="MuiButtonBase-root MuiFab-root jss48323 MuiFab-sizeSmall MuiFab-primary" tabindex="0" type="button" aria-label="add" title="Add">
<span class="MuiFab-label">
<svg class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"></path>
</svg>
</span>
<span class="MuiTouchRipple-root"></span>
</button></div>
Since it is react js application, I added
browser.waitForAngularEnabled(false);
Here I wanted to click on the add icon (+)
My test script:
describe('IEC', function () {
it('Login', function () {
browser.waitForAngularEnabled(false);
browser.get('cannot provide the url since it requires VPN');
element(by.css('input[type="text"]')).sendKeys('12345');
element(by.css('input[type="password"]')).sendKeys('Pwd');
element(by.buttonText('Login')).click();
element(by.css('button[title="Add"]')).click();
});
});
Output:
Failed: No element found using locator: By(css selector, button[title="Add"])
How can I resolve this issue?#
Protractor has built in waiting specific to Angular pages. Your page is react, and by running browser.waitForAngularEnabled(false); you disable that waiting
Now your code just does what you instruct it to: set username and password and click submit. Probably 5 milliseconds later you attempt to click add button? but it's not present. I'm pretty sure it's will take at least 10 seconds to load the application after logging in. So you need to add that step. Either browser.sleep(15000) which is VERY bad practice, of add explicit wait for a condition using browser.wait
Also, each line of your code returns Promise, but you don't handle it. The side effect is the order of your commands is not guaranteed. I already told you this in previous posts you need to use async/await. Unless you do so, you'll be having thousands of problems, I guarantee
My general purpose is to make a dynamic and very simple flowchart view. I use svg and angular material. I am trying to show angular material objects like (md-select, md-menu, md-button) inside SVG. After a quick research I saw it is possible with "foreignObject" tags.
Secondly; I want to move all these elements at once inside SVG regarding mouse panning. So I use "viewBox" property.
In my sample;
I use "foreignObject" tag to show angular material "md-select" inside a svg element.
I expect "md-select" to move when I change x and y values of viewBox property of svg element but it keeps its position while clickable area changes.
When I try same scenario with html "select" it moves as I expect. But I can't do the same with angular material objects. (they visually stay in their original position but their click area goes another where regarding viexBox x-y values.)
<div ng-controller="MyCtrl">
x: <input ng-model="vbx">
y: <input ng-model="vby">
<svg id="processDesignPanel" viewBox="{{vbx}} {{vby}} 500 500" name="processDesignPanel" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800px" height="800px">
<foreignObject width="100" height="50" x="100" y="100">
<md-select placeholder="Assign to user" ng-model="userkey" style="width: 200px;">
<md-option ng-repeat="user in formusers">{{user}}</md-option>
</md-select>
</foreignObject>
<foreignObject width="100" height="50" x="100" y="200">
<select placeholder="Assign to user" ng-model="userkey" style="width: 150px;">
<option ng-repeat="user in formusers">{{user}}</option>
</select>
</foreignObject>
</svg>
</div>
sample js
angular.module('MyApp', ['ngMaterial'])
.controller('MyCtrl', function ($scope) {
$scope.formusers=["ally","mike"];
$scope.vbx=null;
$scope.vby=null;
})
here is my fiddle.
This isn't a complete solution, but I think it's a couple steps in that direction. I cut down your fiddle to show the incorrectly-placed MD select along with the correctly-placed regular select, with a button that modifies the style of the md-select-value tag to have position:inherit and z-index:auto, which makes the MD select appear in the correct place. I don't know why it does this, yet, as I haven't delved into the lib's CSS.
document.getElementsByTagName('md-select-value')[0].setAttribute('style','z-index:auto; position:inherit')
https://jsfiddle.net/emamid/n8tr0gfk/6/
I have an edit profile page with html code as follow:
<img ng-src="{$root.user.userProfilePic}" />
where $rootScope.user.userProfilePic = "imageUrlHere" />
However, when i updated the userProfilePic again using controller in the same page, the profile image is still remain the same. This does not occur if i use $scope. How could i fix it?
UpdateUserProfilePicApi.then(function (res) {
$rootScope.user.userProfilePic = res.imgUrl;
});
You are missing {{double braces}}, try this:
<img ng-src="{{user.userProfilePic}}" />
ng-src docs
There is no need of using the $root.Try this :
<img ng-src="{{user.userProfilePic}}" />
This gives you expected result, because user.userProfilePic is evaluated and replaced by its value after angular is loaded.
<img ng-src="{$root.user.userProfilePic}" />
But with this, the browser tries to load an image named $root.user.userProfilePic, which results in a failed request. You can check this in the console of your browser.
You don't need to use $root as $rootScope is accessible from all the views.
Remove $root from your code.
<img ng-src="{{user.userProfilePic}}" />
I am using SVG <use> sprites as described in this CSS-Tricks article. Those icons often live in anchors, as demonstrated below:
<a ui-sref="home">
<svg class="rp-icon">
<use xlink:href="#rp-home"></use>
</svg>
<span>Home</span>
</a>
I've hit an odd bug with AngularJS that only occurs in IE11 (not IE10, and didn't test any earlier IE versions).
Clicking directly on the icon throws the Angular error Unable to get property 'nodeName' of undefined or null reference, thrown in this Angular function:
function nodeName_(element) {
return lowercase(element.nodeName || element[0].nodeName);
}
Clicking on the anchor, but not directly on the icon, works as expected.
Is this an Angular bug, or something wrong with my use of SVG icons?