How to setup data binding to an SVG image using knockout.js? - javascript

I'm trying to get a click event on a SVG to fire using knockout.js:
HTML
<img id="the-image" src="img/image.svg" data-bind="????????" />
SVG
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect x="100" fill="#707070"
width="20"
height="200"
data-bind="click: $root.open" />
</svg>
this doesn't work if the SVG file is given as source for the img element, it does however work if I just paste it into the img element.
Is there a way to setup the binding so that the data-context is accessible for the SVG?

SVG elements are not added to the DOM when you use an img element to display the image, therefore knockout.js is unable to bind to those elements. The answers to this question contain some solutions that might help you: How do you access the contents of an SVG file in an <img> element?.

Related

How can I change svg color on click text tag

I have below tags inside a SVG:
<g>
<use x="10" y="14" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:actuate="onRequest" id="PL-1-02" xlink:show="replace" xlink:href="#sfseat" />
</g>
<rect x="43" y="18" width="25" style="fill:rgb(192,192,192); stroke:none;" rx="2" ry="2" height="30" />
<text x="18" y="35" style="stroke:rgb(0,0,0); stroke-width:0.5;">
02
</text>
When I click on the text tag then I have to change the color of svg.
Please suggest how can I get g element on click text tag in javascript.
Interestingly, this can be solved without any Javascript. SMIL animations have the ability to be triggered by events. You can use a simple <set fill="freeze"> tag as a child of the rectangle to change the value of an attribute or CSS property permanently. The element to be clicked needs to have an id attribute, and then you can add a begin="myId.click" to the <set>:
<svg viewBox="0 15 100 50">
<rect x="43" y="18" width="25" rx="2" ry="2" height="30"
style="fill:rgb(192,192,192); stroke:none;">
<set attributeName="fill" to="rgb(192,0,0)"
begin="trigger02.click" fill="freeze" />
</rect>
<text id="trigger02" x="18" y="35"
style="stroke:rgb(0,0,0); stroke-width:0.5;cursor:pointer">
02
</text>
</svg>
As suggested in Radical Edward's answer you could edit the fill attribute of the SVG (or, probably better, the rect) element – you'll probably get cleaner results and better portability by separating the styles though.
Use classnames for styling
Add a style element and define your styles separately:
<svg>
<style>
rect {
stroke: none;
fill: rgb(192,192,192);
}
rect.active {
fill: rgb(255, 20, 20);
}
</style>
<!-- The rest of your SVG -->
<!-- The rect element with the inline style removed -->
<rect
x="43"
y="18" width="25"
rx="2"
ry="2"
height="30"
/>
</svg>
Listen to click events on the text element
In order to indentify the text element and the rect, we'll give both of them a unique ID:
<rect
id="rect1"
x="43"
y="18"
width="25"
rx="2"
ry="2"
height="30"
/>
<text
id="text1"
x="18"
y="35"
style="stroke:rgb(0,0,0); stroke-width:0.5;"
>
Then we'll add an event listener on the text element:
var textElement = document.querySelector('#text1');
var rect = document.querySelector('#rect1');
textElement.addEventListener('click', function() {
rect.classList.add('active');
});
The above will work down to IE11, if you're targeting modern browsers only or using a transpiler like Babel you can use a more modern ES6+ syntax with const and arrow functions; for older browsers (IE10 and below) you'll need to polyfill or use other workarounds, see the chapter "Legacy Internet Explorer and attachEvent" on MDN.
Alternative: Modify the fill attribute's value
Instead of using a style element, you could also hard-code the rect's fill attribute and then modify it:
<rect fill="rgb(195,195,195)" ... />
textElement.addEventListener('click', function() {
rect.setAttribute('fill', 'rgb(255,20,100)');
});
edit (as per OP's comments)
If you don't have access to the contents of the svg and mutliple SVGs you should at least wrap it in an identifiable container (i.e. with an ID or unique classname) to make sure you're working on the right one – if that's possible
<div class="clickable-svg-container">
<svg .../>
</div>
You'll then have to find the elements you want to watch and/or modify by first selecting your containers (or all the svgs on the page) and then searching inside that:
var containers = document.querySelectorAll('.clickable-svg-container');
// Alternative: Pick all the SVG elements from the current document
// var svgs = document.querySelectorAll('svg');
// Now loop over your selected elements:
containers.forEach(function(container, index) {
var rect = container.querySelector('rect');
var text = container.querySelector('text');
var group = container.querySelector('g');
// now you can set up your event listener as seen above
text.addEventListener(...);
});
Now you can use the rest of the above guide to react to click events. Note that you can still (a) declare the styles for the active class globally (e.g. in the HTML's <head /> and use the classname to style the modified element or (b) modify the fill attribute directly on each element.
Read more on the MDN Web Docs for:
Element.querySelector
Document.querySelector
Document.querySelectorAll
The DOMStrings used in the above methods are just strings; you may use any valid CSS-Selector, here's a nice overview and list
let totalUseTags=svg.getElementsByTagName('use');
for(let i=0;i<totalUseTags.length;i++){
let useParentElement=totalUseTags[i];
let textElement=this.getSiblingTextElementOfUseTag(totalUseTags[i].parentElement.nextElementSibling);
useParentElement.parentElement.appendChild(textElement)
}
getSiblingTextElementOfUseTag(g){
let i=1;
while (g) {
if(g.nodeName=='text'){
return g;
}
g = g.nextElementSibling;
i++;
}
}
first find out all the use tag elements.
find their parent g tag element.
find their next text tag sibling element.
append that text element to the g tag.

How to extract data <d> in svg through javascript

I wrote an icon component through reactjs to parse the svg files. I use storybook to show my icon component. Now I need help for writing a command line to parse the svg file to the component. I need to script the element in in a svg file and I have no idea how to achieve it.
I am planning to write it in javascript. And here is one example of my svg files.
<svg viewBox="0 0 1024 1024" p-id="3378" width="200" height="200">
<path d="..." p-id="3379"></path>
</svg>
May anyone gives me some ideas on how to achieve it. I am bad at extracting data from a file.
For the specific question from the title, use the following JS code:
document.querySelector("object")
.contentDocument.querySelectorAll("path[path-id='3379']")[0].getAttribute("d")
The SVG should be referenced using the object element (replace width/height attribute content with proper values):
<object
type="image/svg+xml"
data="./logo.svg"
width="480"
height="240"
></object>
It is assumed that there is a single object element in your html. Otherwise tag them with an id attribute and use #<the_id_goes_here> in the first selector:
document.querySelector("#<the_id_goes_here>")
.contentDocument.querySelectorAll("path[path-id='3379']")[0].getAttribute("d")

How to change part of an inline SVG's appearance when hovering over another outside source div or text element using javascript?

I have been looking for an HTML/CSS only solution to this issue for about 2 weeks now and nothing has come from any research or experimentation. I have come to the understanding that this is not possible with CSS so I was wondering if anyone could provided me a Javascript example that could possibly work for my case as javascript isn't my strong suit at the moment. My issue is that I have a multi-component SVG element. It is inline in the HTML and within a section tag. There is also a set of text elements in the section above the SVG. What I am trying to make happen is that when a portion of the SVG is hovered, the corresponding text element and the hovered SVG piece both change color. Vice versa if the text element is hovered, the corresponding SVG piece will change color as well.
<section>
<h1>Section 1</h1><h1>Section 2</h1>
<svg id="World_Map"...<path class="_russia_"...</svg>
</section>
It is probably important to note that the SVG has multiple paths with unique ID's and these are the pieces that I am trying to get working in this fashion.
Yes, without the SVG elements being siblings or ancestors to your text elements this is very difficult (if not impossible) to do with CSS. I have seen some pretty tricky and amazing things done with CSS selectors, though, so I wouldn't rule it out that someone more clever than me could get it to work. :)
Anyway, I have a working example of what you asked for using JavaScript. It actually makes use of jQuery because there is the .hover() binding, which cleanly allows you to bind a single function to the mouseenter and mouseleave events simultaneously, and the .toggleClass() method, which will add a class to an element if it doesn't exist or remove the class from the element if it's already there.
Since you did not post any functional code, here is a JSFiddle with my simple example.
https://jsfiddle.net/davecripps/o6mwjokj/
$(function() {
$("h1").hover(function() {
$("#map" + $(this).attr("id").substring(4)).toggleClass("mapHover");
});
$("rect").hover(function() {
$("#text" + $(this).attr("id").substring(3)).toggleClass("textHover");
});
});
.textHover,
h1:hover {
color: red;
cursor: pointer;
}
.mapHover,
rect:hover {
fill: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<section>
<h1 id="textRussia">Russia (yellow)</h1>
<h1 id="textSpain">Spain (blue)</h1>
<h1 id="textFrance">France (green)</h1>
<svg width="340" height="100" viewbox="0 0 340 100">
<rect id="mapRussia" x="0" y="0" width="100" height="100" fill="yellow" />
<rect id="mapSpain" x="120" y="0" width="100" height="100" fill="blue" />
<rect id="mapFrance" x="240" y="0" width="100" height="100" fill="green" />
</svg>
</section>
You should be able to understand the methodology and apply to your code.

Multiple svg with same IDs

Can i put multiple svgs in a html page and use the same IDs in all of them?
<div>
<svg height="0" width="0">
<clipPath id="svgPath"> ........
</svg>
<svg height="0" width="0">
<clipPath id="svgPath"> ........
</svg>
<svg height="0" width="0">
<clipPath id="svgPath"> ........
</svg>
</div>
Since the specs define the id attribute as unique per document, you should refactor the IDs or use an alternative, e.g. embedding via img or object tag.
<img src="my.svg" height="100" alt="alternative Text">
<object type="image/svg+xml" data="my.svg" width="100" height="100"></object>
If you need the SVGs to be inline you should consider using an SVG injector, which changes the IDs to unique strings when inserting the SVGs into the HTML document
SVGInject adds a random string to the end of the ID for elements defined in the <defs> section. For example, svgPath may become something like svgPath-Dcs83KsE. Other SVG injectors add a running number to the ID.
Both methods were developed so the same SVG can be injected multiple times into a HTML document without creating an ID conflict. But of course it also works for different SVGs with conflicting IDs.
You can't write multiple id attribute in your html. Id attribute must be unique in html document.
The id attribute specifies its element's unique identifier (ID). The value must be unique amongst all the IDs in the element's.
https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#the-id-attribute
You need to use class attribute for your purpose.

Confused about SVG styling with d3.js

The most d3.js examples use SVG to draw the diagrams, etc. This means for instance rect instead of div. This then again means, that CSS properties like drop-shadow cannot be applied.
The alternative approach is to define filters in <defs>. The same is true if you want to fill the rect with a gradient color. Is that right so far?
So I define some filter and gradients, but it seems very redundant to redefine these definitions for every page again. Wouldn't it be possible to reference all these definitions from a separate svg-file?
I tried things like: filter: url(.../my_file.svg) but it does not seem to work, but isn't that how it's supposed to be?
Say you have a file called filters.svg, and it contains a few filter defitions. It looks something like this:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<filter id="my_filter" x="0%" y="0%" width="100%" height="100%">
...
</filter>
</svg>
If you were using my_filter in that same document, you'd just refer to it using #my_filter. But you're not. You want to use it in another document. What you have to do then is refer to it in those documents as /filters.svg#my_filter. Like this:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="0" y="0" width="100" height="100" fill="url(/filters.svg#my_filter)" />
</svg>
This is of course assuming that both documents are accessible from the same domain, and that they're located in the right places, etc. I don't know if this will work in CSS stylesheets, but it will work across SVG documents. Check out the section on Linking in the SVG Specification for more details.

Categories

Resources