Vue.js anchor to div within the same component - html

I'm developing a Vue.js application and I'm having trouble to link an anchor to a certain div within a component.
I have the following anchor:
Porto, Portugal
and the following div:
<div id="porto" class="fl-porto">
I'm using vue-router in hash mode.
The problem is, whenever I click the "porto-button" it will redirect me to the "home" page ( ' / ' )
I'm using Vue.js 1.X and I tried using history mode (URL without the hashbang) but it gives me a cannot GET '/page' error upon refreshing a page.
Am I doing something wrong? What can I do about this?

Because you are using router in hash mode, you will not be able to scroll that easily because scrolling to /#something will actually redirect you to 'something' page.
You will have to emulate scrolling behaviour on your own, try doing something like that:
//P.S. the code is written for Vue 2.
//You will have to adjust it to Vue 1.
//Your view:
<a class="porto-button" #click="scrollMeTo('porto')">Porto, Portugal</a>
...
<div ref="porto" class="fl-porto">
//Your code:
methods: {
scrollMeTo(refName) {
var element = this.$refs[refName];
var top = element.offsetTop;
window.scrollTo(0, top);
}
}
How it works:
Set the references through ref attribute to the element you would like to scroll to;
Write a function that will programmatically set window.scrollY to the top of the referenced element.
Job is done :)
Update 1:
jsfiddle https://jsfiddle.net/5k4ptmqg/4/
Update 2:
Seems that in Vue 1 ref="name" looked like el:name (docs), here is an updated example:
https://jsfiddle.net/5y3pkoyz/2/

Another method is to use "scrollIntoView()"
So, euvl's code still stands, except you would change the method slightly:
new Vue({
el: '#app',
methods: {
goto(refName) {
var element = this.$els[refName];
element.scrollIntoView();
}
}
})
If you wanted to get fancy and make the scroll smooth, you can even add the following:
element.scrollIntoView({ behavior: 'smooth' });
Note that this will need a polyfill for older browsers.

What worked for me
<router-link to="#leaders">Leaders</router-link>
or dynamic
<router-link :to="`#${subMenuItem.linkTarget}`" class="page-submenu-list__link">
{{subMenuItem.linkTitle}}
</router-link>
in router
routes:[],
scrollBehavior (to, from, savedPosition) {
//https://router.vuejs.org/guide/advanced/scroll-behavior.html
if (to.hash) {
return { selector: to.hash }
} else if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 }
}
}

An alternative solution is to use the v-scroll-to directive (webpage, github).
I find this solution to be clean, simple, flexible and effective. To use:
Install it:
npm install --save vue-scrollto
Have Vue 'use' it:
var VueScrollTo = require('vue-scrollto');
Vue.use(VueScrollTo)
Apply it as a directive in your Vue component's template:
Scroll to #element
<div id="element">
Hi. I'm #element.
</div>
Or apply it programmatically in your Vue component's methods:
this.$scrollTo('#element', 500, { easing: 'ease-in-out' })
Or apply it programmatically in your Vuex actions:
import { scrollTo } from 'vue-scrollto'
scrollTo('#element', 500, { easing: 'ease-in-out' })
Another solution, if you're already using Vuetify, you may prefer to use Vuetify's built-in programmatic scrolling method, $vuetify.goTo():
<v-btn #click="$vuetify.goTo('#element', {duration: 500, easing: 'easeInOutCubic'})">
Scroll to #element
</v-btn>
<div id="element">
Hi. I'm #element.
</div>

If you set a ref="something" on an element, you could also use this oneliner with #click:
<a #click="$refs.something.$el.scrollIntoView()">
Go to something
</a>

Related

Sometimes links are not clickable [duplicate]

I have a link button inside a <td> which I have to disable. This works on IE but not working in Firefox and Chrome.
I tried all the following but not working on Firefox (using 1.4.2 js):
$(".myLink").attr('disabled', 'disabled');
$(".myLink").attr('disabled', true);
$(".myLink").attr('disabled', 'true');
Note - I cannot de-register the click function for the anchor tag as it is registered dynamically. AND I HAVE TO SHOW THE LINK IN DISABLED MODE.
You can't disable a link (in a portable way). You can use one of these techniques (each one with its own benefits and disadvantages).
CSS way
This should be the right way (but see later) to do it when most of browsers will support it:
a.disabled {
pointer-events: none;
}
It's what, for example, Bootstrap 3.x does. Currently (2016) it's well supported only by Chrome, FireFox and Opera (19+). Internet Explorer started to support this from version 11 but not for links however it's available in an outer element like:
span.disable-links {
pointer-events: none;
}
With:
<span class="disable-links">...</span>
Workaround
We, probably, need to define a CSS class for pointer-events: none but what if we reuse the disabled attribute instead of a CSS class? Strictly speaking disabled is not supported for <a> but browsers won't complain for unknown attributes. Using the disabled attribute IE will ignore pointer-events but it will honor IE specific disabled attribute; other CSS compliant browsers will ignore unknown disabled attribute and honor pointer-events. Easier to write than to explain:
a[disabled] {
pointer-events: none;
}
Another option for IE 11 is to set display of link elements to block or inline-block:
<a style="pointer-events: none; display: inline-block;" href="#">...</a>
Note that this may be a portable solution if you need to support IE (and you can change your HTML) but...
All this said please note that pointer-events disables only...pointer events. Links will still be navigable through keyboard then you also need to apply one of the other techniques described here.
Focus
In conjunction with above described CSS technique you may use tabindex in a non-standard way to prevent an element to be focused:
...
I never checked its compatibility with many browsers then you may want to test it by yourself before using this. It has the advantage to work without JavaScript. Unfortunately (but obviously) tabindex cannot be changed from CSS.
Intercept clicks
Use a href to a JavaScript function, check for the condition (or the disabled attribute itself) and do nothing in case.
$("td > a").on("click", function(event){
if ($(this).is("[disabled]")) {
event.preventDefault();
}
});
To disable links do this:
$("td > a").attr("disabled", "disabled");
To re-enable them:
$("td > a").removeAttr("disabled");
If you want instead of .is("[disabled]") you may use .attr("disabled") != undefined (jQuery 1.6+ will always return undefined when the attribute is not set) but is() is much more clear (thanks to Dave Stewart for this tip). Please note here I'm using the disabled attribute in a non-standard way, if you care about this then replace attribute with a class and replace .is("[disabled]") with .hasClass("disabled") (adding and removing with addClass() and removeClass()).
Zoltán Tamási noted in a comment that "in some cases the click event is already bound to some "real" function (for example using knockoutjs) In that case the event handler ordering can cause some troubles. Hence I implemented disabled links by binding a return false handler to the link's touchstart, mousedown and keydown events. It has some drawbacks (it will prevent touch scrolling started on the link)" but handling keyboard events also has the benefit to prevent keyboard navigation.
Note that if href isn't cleared it's possible for the user to manually visit that page.
Clear the link
Clear the href attribute. With this code you do not add an event handler but you change the link itself. Use this code to disable links:
$("td > a").each(function() {
this.data("href", this.attr("href"))
.attr("href", "javascript:void(0)")
.attr("disabled", "disabled");
});
And this one to re-enable them:
$("td > a").each(function() {
this.attr("href", this.data("href")).removeAttr("disabled");
});
Personally I do not like this solution very much (if you do not have to do more with disabled links) but it may be more compatible because of various way to follow a link.
Fake click handler
Add/remove an onclick function where you return false, link won't be followed. To disable links:
$("td > a").attr("disabled", "disabled").on("click", function() {
return false;
});
To re-enable them:
$("td > a").removeAttr("disabled").off("click");
I do not think there is a reason to prefer this solution instead of the first one.
Styling
Styling is even more simple, whatever solution you're using to disable the link we did add a disabled attribute so you can use following CSS rule:
a[disabled] {
color: gray;
}
If you're using a class instead of attribute:
a.disabled {
color: gray;
}
If you're using an UI framework you may see that disabled links aren't styled properly. Bootstrap 3.x, for example, handles this scenario and button is correctly styled both with disabled attribute and with .disabled class. If, instead, you're clearing the link (or using one of the others JavaScript techniques) you must also handle styling because an <a> without href is still painted as enabled.
Accessible Rich Internet Applications (ARIA)
Do not forget to also include an attribute aria-disabled="true" together with disabled attribute/class.
Got the fix in css.
td.disabledAnchor a{
pointer-events: none !important;
cursor: default;
color:Gray;
}
Above css when applied to the anchor tag will disable the click event.
For details checkout this link
Thanks to everyone that posted solutions (especially #AdrianoRepetti), I combined multiple approaches to provide some more advanced disabled functionality (and it works cross browser). The code is below (both ES2015 and coffeescript based on your preference).
This provides for multiple levels of defense so that Anchors marked as disable actually behave as such.
Using this approach, you get an anchor that you cannot:
click
tab to and hit return
tabbing to it will move focus to the next focusable element
it is aware if the anchor is subsequently enabled
How to
Include this css, as it is the first line of defense. This assumes the selector you use is a.disabled
a.disabled {
pointer-events: none;
cursor: default;
}
Next, instantiate this class on ready (with optional selector):
new AnchorDisabler()
ES2015 Class
npm install -S key.js
import {Key, Keycodes} from 'key.js'
export default class AnchorDisabler {
constructor (config = { selector: 'a.disabled' }) {
this.config = config
$(this.config.selector)
.click((ev) => this.onClick(ev))
.keyup((ev) => this.onKeyup(ev))
.focus((ev) => this.onFocus(ev))
}
isStillDisabled (ev) {
// since disabled can be a class or an attribute, and it can be dynamically removed, always recheck on a watched event
let target = $(ev.target)
if (target.hasClass('disabled') || target.prop('disabled') == 'disabled') {
return true
}
else {
return false
}
}
onFocus (ev) {
// if an attempt is made to focus on a disabled element, just move it along to the next focusable one.
if (!this.isStillDisabled(ev)) {
return
}
let focusables = $(':focusable')
if (!focusables) {
return
}
let current = focusables.index(ev.target)
let next = null
if (focusables.eq(current + 1).length) {
next = focusables.eq(current + 1)
} else {
next = focusables.eq(0)
}
if (next) {
next.focus()
}
}
onClick (ev) {
// disabled could be dynamically removed
if (!this.isStillDisabled(ev)) {
return
}
ev.preventDefault()
return false
}
onKeyup (ev) {
// We are only interested in disabling Enter so get out fast
if (Key.isNot(ev, Keycodes.ENTER)) {
return
}
// disabled could be dynamically removed
if (!this.isStillDisabled(ev)) {
return
}
ev.preventDefault()
return false
}
}
Coffescript class:
class AnchorDisabler
constructor: (selector = 'a.disabled') ->
$(selector).click(#onClick).keyup(#onKeyup).focus(#onFocus)
isStillDisabled: (ev) =>
### since disabled can be a class or an attribute, and it can be dynamically removed, always recheck on a watched event ###
target = $(ev.target)
return true if target.hasClass('disabled')
return true if target.attr('disabled') is 'disabled'
return false
onFocus: (ev) =>
### if an attempt is made to focus on a disabled element, just move it along to the next focusable one. ###
return unless #isStillDisabled(ev)
focusables = $(':focusable')
return unless focusables
current = focusables.index(ev.target)
next = (if focusables.eq(current + 1).length then focusables.eq(current + 1) else focusables.eq(0))
next.focus() if next
onClick: (ev) =>
# disabled could be dynamically removed
return unless #isStillDisabled(ev)
ev.preventDefault()
return false
onKeyup: (ev) =>
# 13 is the js key code for Enter, we are only interested in disabling that so get out fast
code = ev.keyCode or ev.which
return unless code is 13
# disabled could be dynamically removed
return unless #isStillDisabled(ev)
ev.preventDefault()
return false
Try the element:
$(td).find('a').attr('disabled', 'disabled');
Disabling a link works for me in Chrome: http://jsfiddle.net/KeesCBakker/LGYpz/.
Firefox doesn't seem to play nice. This example works:
<a id="a1" href="http://www.google.com">Google 1</a>
<a id="a2" href="http://www.google.com">Google 2</a>
$('#a1').attr('disabled', 'disabled');
$(document).on('click', 'a', function(e) {
if ($(this).attr('disabled') == 'disabled') {
e.preventDefault();
}
});
Note: added a 'live' statement for future disabled / enabled links.
Note2: changed 'live' into 'on'.
Bootstrap 4.1 provides a class named disabled and aria-disabled="true" attribute.
example"
<a href="#"
class="btn btn-primary btn-lg disabled"
tabindex="-1"
role="button" aria-disabled="true"
>
Primary link
</a>
More is on getbootstrap.com
So if you want to make it dynamically, and you don't want to care if it is button or ancor than
in JS script you need something like that
let $btn=$('.myClass');
$btn.attr('disabled', true);
if ($btn[0].tagName == 'A'){
$btn.off();
$btn.addClass('disabled');
$btn.attr('aria-disabled', true);
}
But be carefull
The solution only works on links with classes btn btn-link.
Sometimes bootstrap recommends using card-link class, in this case solution will not work.
Just add a css property:
<style>
a {
pointer-events: none;
}
</style>
Doing so you can disable the anchor tag.
I've ended up with the solution below, which can work with either an attribute, <a href="..." disabled="disabled">, or a class <a href="..." class="disabled">:
CSS Styles:
a[disabled=disabled], a.disabled {
color: gray;
cursor: default;
}
a[disabled=disabled]:hover, a.disabled:hover {
text-decoration: none;
}
Javascript (in jQuery ready):
$("a[disabled], a.disabled").on("click", function(e){
var $this = $(this);
if ($this.is("[disabled=disabled]") || $this.hasClass("disabled"))
e.preventDefault();
})
In Razor (.cshtml) you can do:
#{
var isDisabled = true;
}
Home
You can disable the HTML link as given below:
<style>
.disabled-link {
pointer-events: none;
}
</style>
Google.com
You can use inline JavaScript:
Google.com
you cannot disable a link, if you want that click event should not fire then simply Remove the action from that link.
$(td).find('a').attr('href', '');
For More Info :- Elements that can be Disabled
I would do something like
$('td').find('a').each(function(){
$(this).addClass('disabled-link');
});
$('.disabled-link').on('click', false);
something like this should work. You add a class for links you want to have disabled and then you return false when someone click them. To enable them just remove the class.
To disable link to access another page on touch device:
if (control == false)
document.getElementById('id_link').setAttribute('href', '#');
else
document.getElementById('id_link').setAttribute('href', 'page/link.html');
end if;
I would suggest turning the link into a button and using the 'disabled' attribute. You can see this issue to check how to convert a link to a button: How to create an HTML button that acts like a link
You can use this to disabled the Hyperlink of asp.net or link buttons in html.
$("td > a").attr("disabled", "disabled").on("click", function() {
return false;
});
There is one other possible way, and the one that I like best. Basically it's the same way lightbox disables a whole page, by placing a div and fiddling with z-index. Here is relevant snippets from a project of mine. This works in all browsers!!!!!
Javascript (jQuery):
var windowResizer = function(){
var offset = $('#back').offset();
var buttontop = offset.top;
var buttonleft = offset.left;
$('#backdisabler').css({'top':buttontop,'left':buttonleft,'visibility':'visible'});
offset = $('#next').offset();
buttontop = offset.top;
buttonleft = offset.left;
$('#nextdisabler').css({'top':buttontop,'left':buttonleft,'visibility':'visible'});
}
$(document).ready(function() {
$(window).resize(function() {
setTimeout(function() {
windowResizer();
}, 5); //when the maximize/restore buttons are pressed, we have to wait or it will fire to fast
});
});
and in html
<img src="images/icons/back.png" style="height: 50px; width: 50px" />
<img src="images/icons/next.png" style="height: 50px; width: 50px" />
<img id="backdisabler" src="images/icons/disabled.png" style="visibility: hidden; position: absolute; padding: 5px; height: 62px; width: 62px; z-index: 9000"/>
<img id="nextdisabler" src="images/icons/disabled.png" style="visibility: hidden; position: absolute; padding: 5px; height: 62px; width: 62px; z-index: 9000"/>
So the resizer finds the anchor's (the images are just arrows) locations and places the disabler on top. The disabler's image is a translucent grey square (change the width/height of the disablers in the html to match your link) to show that it is disabled. The floating allows the page to resize dynamically, and the disablers will follow suit in windowResizer(). You can find suitable images through google. I have placed the relevant css inline for simplicity.
then based on some condition,
$('#backdisabler').css({'visibility':'hidden'});
$('#nextdisabler').css({'visibility':'visible'});
I think a lot of these are over thinking. Add a class of whatever you want, like disabled_link. Then make the css have .disabled_link { display: none }
Boom now the user can't see the link so you won't have to worry about them clicking it. If they do something to satisfy the link being clickable, simply remove the class with jQuery: $("a.disabled_link").removeClass("super_disabled"). Boom done!

How to gently change img src in Angular2+

When I dynamically change img's src attribute, old image is displayed while loading a new one.
I have a component which displays some data: text and image. On click the underlying data is changed (i.e. new data from server). Once click, text is changed immediately, but component displays old image while new one is loaded. When new image is loaded, then it is visually displayed which can take noticeable amount of time.
In real application one can have product details and changing products on button click. All data is replaced immediately but not image.
Problem exists when the component is not destroyed (reused).
I've already tried clear image src after click, but it not worked.
I have simple binding in template
img [src]="img.url" style="width: 300px; height: 300px">
<p>{{ img.text }}</p>
and image change on click
this.img = this.images[1];
You can see sample app here https://stackblitz.com/edit/angular-cojqnf
Is this possible to take more control of this image change process? It would be great to clear image on click and wait for new one with empty background for example.
I hacked around a little with your stackblitz demo, I basically wrapped your code in an ImageGhostDirective to make it reusable. The directive listens to any changes on the src attribute using a MutationObserver to change the style. Using a HostListener on the 'load' event, it reverts the styles back to normal. I start with an opacity of 0 for the first load, followed by an opacity of 0.2 between successive image changes, but this is completely arbitrary and could be replaced by a spinner or any kind of placeholder...
Here is the link to the stackblitz: https://stackblitz.com/edit/angular-image-ghost-directive
<img [src]="'https://loremflickr.com/300/300?random=' + index"
style="width: 300px; height: 300px" imgGhost>
#Directive({
selector: 'img[imgGhost]'
})
export class ImageGhostDirective implements OnDestroy {
private changes: MutationObserver;
constructor(private elementRef: ElementRef) {
this.changes = new MutationObserver((mutations: MutationRecord[]) =>
mutations.filter(m => m.attributeName === 'src').forEach(() => this.opacity = 0.2)
);
this.changes.observe(this.elementRef.nativeElement, {
attributes: true,
childList: false,
characterData: false
});
}
ngOnDestroy(): void {
this.changes.disconnect();
}
#HostBinding('style.display') display = 'block';
#HostBinding('style.opacity') opacity = 0;
#HostListener('load')
onLoad(): void {
this.opacity = 1;
}
}
It is also possible to tell Angular to automatically attach this directive to every img element by using the img:not([imgGhost]) selector in the directive decorator. That way, you don't have to manually place the directive on every image in your app.
Hope this is useful.
Finally I achieved what I want by leveraging (load) event on img and [ngStyle].
In template I added load handler and style:
<img [src]="img.url" style="width: 300px; height: 300px" (load)="loaded()"
[ngStyle]="{'display': imgVisible ? 'block' : 'none'}">
In back-end:
imgVisible = true;
and when changing data, also hide image:
this.imgVisible = false;
next, when image is loaded, show the image (be careful! when old and new images have the same URL, this event is not raised; if it is the case you need to conditionally hide image)
loaded(): void {
this.imgVisible = true;
}
Complete code for solution: https://stackblitz.com/edit/angular-ewptj7
I'm not a big fan of this kind solutions. It could be difficult to apply when you have more images.
All better solution are welcome.

Replace element with razor textarea

I want to take a tag and replace with a #Html.textarea() razor html helper but it doesn't look as if JQuery can replace DOM elements with html helpers. How do I go about this?
using(#Html.BeginForm())
{
<a id="clickme">Edit</a>
<div>#Model.username</div>
}
How can I replace this div with #Html.Textarea ? JQuery could do it with div and input tags.
jQuery cannot replace a tag with #Html.TextArea() !
The TextArea helper method is a C# method, which gets executed when razor tries to render the view. This happens in your web server. jQuery is a client side library and anything you do with jQuery happens at client side, in your browser.
But all these helper methods ultimately generate some HTML for DOM elements. That means, you can use jQuery to manipulate visibility of that.
If you are trying to do something like an inline edit, you can use a script like this , to start with
First, render the text area along with your label div, but have it hidden initially. Also wrap the label,edit link and the hidden input inside a container div which we can use later to help with our jQuery selectors.
#using (#Html.BeginForm())
{
<div class="edit-item">
Edit
<div class="edit-label">#Model.FirstName</div>
#Html.TextAreaFor(a => a.FirstName,
new { style = "display:none;", #class = "edit-text" })
</div>
<div class="edit-item">
Edit
<div class="edit-label">#Model.UserName</div>
#Html.TextAreaFor(a => a.UserName,
new { style = "display:none;", #class = "edit-text" })
</div>
}
Now when the user clicks edit, you have to toggle the visibility of the label and hidden input and update the value of label after user done editing the value in the input element.
$(function () {
$("a[data-mode]").click(function (e) {
e.preventDefault();
var _this = $(this);
var c = _this.closest(".edit-item");
c.find(".edit-text").toggle();
c.find(".edit-label").toggle();
if (_this.attr("data-mode") === 'label') {
_this.attr("data-mode", 'edit');
_this.text("done");
} else if (_this.data("mode") === 'edit') {
c.find(".edit-label").text(c.find(".edit-text").val());
_this.text("edit");
_this.attr("data-mode", 'label');
}
});
});
This is a head start. You can optimize this code as needed.
Here is a working jsfiddle for your reference

Prevent screen from moving when clicking on <a href=></a>

I'm using <a href> element along with :target css selector to show a <div> which by default is set to display:none. Problem is, that when I click on the link to show that <div>, it is automatically scrolling down my site towards that <div>.
Is there a way to stop the screen movement?
Unfortunately I am not yet proficient in anything besides CSS and HTML.
You can use event.preventDefault() to avoid this. Something like this:
$('a.yourclass').click(function(e)
{
//your code
e.preventDefault();
});
OR:
link
in the link enter:
Link here
You'll need JS anyway:
// (in jQuery)
$el.on('click', function(e) {
// find current scroll position
var pos = document.body.scrollTop || document.documentElement.scrollTop;
// let normal action propagate etc
// in the next available frame (async, hence setTimeout), reset scroll posiion
setTimeout(function() {
window.scrollTo(0, pos);
}, 1);
})
I don't know if this will flicker the screen. It might. It's a horrible hack either way.
In my Chrome, there's no flicker: http://jsfiddle.net/rudiedirkx/LEwNd/1/show/
There are two ways to tell the browser we don't want it to act:
The main way is to use the event object. There's a method
event.preventDefault().
If the handler is assigned using on (not by
addEventListener), then we can just return false from it.
Example:
Click here
or
here
This is a bit of a hack but you could use a basic css work around:
CSS only Example
#div1 {
height: 0;
overflow:hidden;
}
#div1:target {
height: auto;
margin-top: -110px;
padding-top: 110px;
}
#div2 {
background:red;
}
Click to show
<div id="div1">
<div id="div2">Content</div>
</div>
If you need it to be a little more flexible you can add some js...
More Flexible Example with JS
$('a').click(function () {
$('#div1').css({
'margin-top': 0 - $('#div1').position().top + $(window).scrollTop(),
'padding-top': $('#div1').position().top - $(window).scrollTop()
});
});
Basically you're pulling the top of div1 up with the negative margin and then pushing div2 back down with the padding, so that the top of div1 rests at the top of the window... Like I said its a hack but it does the trick.
Those links are anchor-links and by default made for those jumps :) You could use JS to prevent the default behaviour in some way. For example using jQuery:
$('a').click(function(e){e.preventDefault();});
or by default add return false; to the links
Avoid using :target all together and just use onclick event.
function myFunction()
{
document.getElementById('hiddenDiv').style.display = 'block';
return false;
}

AngularJS ng-href and svg xlink

I'd like some input on using xml namespaced attributes with angular.
The problem is angular comes with a couple of directives to handle writing attributes such as href and src when angular has parsed the expresssions (otherwise the browser will try to load {{mymodel.myimage}} as a url)
https://github.com/angular/angular.js/blob/master/src/ng/directive/booleanAttrs.js#L329
The problem I'm facing is that I'm using angular to output svg together with D3 and since angular doesn't have a way to output xlink:href I was stuck.
I created a custom directive that outputs xlink:href
app.directive('ngXlinkHref', function () {
return {
priority: 99,
restrict: 'A',
link: function (scope, element, attr) {
var attrName = 'xlink:href';
attr.$observe('ngXlinkHref', function (value) {
if (!value)
return;
attr.$set(attrName, value);
});
}
};
});
Full demo: http://plnkr.co/edit/cMhGRh
But it seems that if I don't manually add xlink:href to the element, the svg image will not render.
Any suggestions on how to best handle xml namespaces / svg together with angular would be greatly appreciated.
You can use ng-attr-<some attribute>
ng-attr-xlink:href="{{xxx}}" works for me.
Note that you also need an empty xlink:href="" as initial value. – Derek Hsu
If, like me, you're looking for a way to add images to svg, you can do so adding:
xlink:href="" ng-href="{{ foo }}"
Example:
http://jsbin.com/sigoleya/1/edit?html,js,output
Where I found the solution:
https://github.com/angular/angular.js/issues/7697
I ran into a similar problem when trying to output a value for xlink:href that's tied to the model. Based on the user's chosen <option> in a <select> control, I was trying to show a dynamic SVG icon via the xlink:href attribute of the <use> element.
I found a thread about this in the GitHub Issues for AngularJS. Based on the discussion there, it appears that because a viable workaround exists, they've effectively tabled a fix by moving it to the Backlog milestone.
What ultimately worked for me was inspired by this JSBin:
http://jsbin.com/sigoleya/1/edit?html,js,output
Here's the code I used in my template:
<svg class="icon" data-ng-class="category.iconName">
<use xlink:href="" data-ng-href="{{'#' + category.iconName}}">
</svg>
Given a category.iconName of icon-music, for example, Angular sets the xlink:href dynamically to #icon-music, which references the <svg id="icon-music"> element further up on the same page.
As others have noted, what's key is setting a blank xlink:href="" attribute on the element where you call the ngHref directive. Attribute order does not seem to matter. Using ng-attr-xlink:href="{{xxx}}" (as mentioned in Derek Hsu's answer) did not work for me.
All of this assumes Angular 1.3.36.
I solved the same problem with the following modules:
Module for SVGs:
var app = angular.module('Svgs', []);
angular.forEach([
{ ngAttrName: 'ngXlinkHref', attrName: 'xlink:href' },
{ ngAttrName: 'ngWidth', attrName: 'width' },
{ ngAttrName: 'ngHeight', attrName: 'height' }
], function (pair) {
var ngAttrName = pair.ngAttrName;
var attrName = pair.attrName;
app.directive(ngAttrName, function (IeHelperSrv) {
return {
priority: 99,
link: function (scope, element, attrs) {
attrs.$observe(ngAttrName, function (value) {
if (!value) return;
attrs.$set(attrName, value);
if (IeHelperSrv.isIE) element.prop(attrName, value);
});
}
};
});
});
Module for IE detection:
angular.module('IeHelper', []).factory('IeHelperSrv', function () {
return {
isIE: checkForIE.isIE,
}
});
var checkForIE = {
init: function () {
this.isIE = (navigator.userAgent.indexOf('MSIE') != -1);
}
};
checkForIE.init();
HTML:
<!-- image has initial fake source, width and height to force it to render -->
<image xlink:href="~/Content/Empty.png" width="1" height="1"
ng-xlink-href="{{item.imageSrc}}"
ng-width="{{item.width}}" ng-height="{{item.height}}"
ng-cloak
/>
For anyone else having this problem due to Angular/Angular UI Router in HTML5 mode, I came up with a straightforward fix to enable svg sprite icons to work with their xlink:href attribute and the tag.
Gist is here: https://gist.github.com/planetflash/4d9d66e924aae95f7618c03f2aabd4a3
app.run(['$rootScope', '$window', function($rootScope, $window){
$rootScope.$on('$locationChangeSuccess', function(event){
$rootScope.absurl = $window.location.href;
});
<svg><use xlink:href="{{absurl+'#svgvID'}}"></use></svg>
I ran into this problem where I was using Ajax to load the svg spritesheet onto the page. If I had a on the page before the spritesheet was loaded, it would fail and would not resolve once the spritesheet was avaialble. Any added to the dom after the spritesheet was loaded were fine. I had to delay putting the items in the dom until after the spritesheet finished loading.
This only affected IOS. All other browsers didn't care about the order.
This took me more time than I would've wanted. Around 20-30 minutes.
If I understand correctly, any failed loading on image element will render that element useless in the future. I believe it's something similiar #GeekyMonkey is saying. If angular binding system has set xlink:href initially to null, Image element wont work anymore, even if we have valid value in the future.
Here is solution, notice how I have wrapped image element inside g element, using ng-if directive. That makes sure we will bind against image only when a correct value is available.
<g ng-if="vm.svgMap.background != null">
<image
ng-attr-xlink:href="{{vm.svgMap.background.image | trusted}}"
ng-attr-width="{{vm.svgMap.background.width}}"
ng-attr-height="{{vm.svgMap.background.width}}"
xlink:href=""
width="1"
height="1"
x="0"
y="0"></image>
</g>
As others said, the order of attributes are important as well. To ensure that angularJS allows us to bind image element, we'll also have to trust that resource, I've done it through filter (it's the one in xlink:href attribute):
(function() {
'use strict';
angular.module('myTool').filter('trusted', TrustedFilter);
function TrustedFilter($sce) {
return function(url) {
return $sce.trustAsResourceUrl(url);
};
};
}());