Both click() and dispatchEvent does not trigger automatic click in reactjs - javascript

I have a react component that has an SVG image. I have divided the SVG into multiple react box. I have query selector which selects all the react box and JS click event to auto click that react.
I tried working with both click and dispatch event. But none of them works in my scenario.
Below is the section of the code I am working on.
componentDidMount() {
var element = document.querySelectorAll("square");
for(var i = 0; i<element.length; i++) {
element[i].dispatchEvent(new Event('click'));
}
}
render(){
return (
<React.Fragment>
<div className="col-12">
<svg viewBox="0 0 100 100">
<image xlinkHref={imageFile} height="100%" width="100%" />
<g><rect className="square" x="10" y="10" width="20" height="10" fillOpacity=".2" onClick={() =>console.log("clicked")}></rect> </g>
<g><rect className="square" x="30" y="10" width="20" height="10" fillOpacity=".4" onClick={() =>console.log("clicked")}></rect> </g>
</svg>
</div>
</React.Fragment>
)
}
I also tried using the click() function and did not work for SVG images and also is there a way we could automate a click in each square every 10 seconds?

You forgot a . in the query selector so the node list was actually empty.
If you want to automate a click in each square every 10 seconds, this code does the trick:
const elements = document.querySelectorAll(".square");
const intervalsIdentifiers = Array.from(elements).map(x => setInterval(() => x.dispatchEvent(new Event('click')), 10000));
The dispatchEvent method is indeed the only way, because the rect element doesn't have a click method (only HTML elements do, not SVG elements) as demonstrated below:
console.log('click' in SVGRectElement.prototype); // false
console.log(HTMLElement.prototype.hasOwnProperty('click')); // true
console.log(HTMLButtonElement.prototype instanceof HTMLElement); // true (a button has the click method)
console.log(SVGRectElement.prototype instanceof HTMLElement); // false
The full working code (native JavaScript but should work as well with React in the componentDidMount hook):
const elements = document.querySelectorAll(".square");
const intervalsIdentifiers = Array.from(elements).map(x => setInterval(() => x.dispatchEvent(new Event('click')), 10000));
<div className="col-12">
<svg viewBox="0 0 100 100">
<image xlink:Href="https://img-19.ccm2.net/8vUCl8TXZfwTt7zAOkBkuDRHiT8=/1240x/smart/b829396acc244fd484c5ddcdcb2b08f3/ccmcms-commentcamarche/20494859.jpg" height="100%" width="100%" />
<g><rect class="square" x="10" y="10" width="20" height="10" fillOpacity=".2" onclick="console.log('clicked')"></rect> </g>
<g><rect class="square" x="30" y="10" width="20" height="10" fillOpacity=".4" onclick="console.log('clicked')"></rect> </g>
</svg>
</div>

Related

Extract Text From SVG [duplicate]

I have this javascript...
window.writeText = function(form) {
var text;
form.catnumber2.value = "PING";
text = document.getElementByName('cat2Number').innerHtml;
return alert(text);
};
But I get no alert box as expected.
The svg does not show up when I view source via view/developer/view source (I'm in chrome) BUT when I use view/developer/developer tools....I can see the following svg....
<svg height="594">
<g ID="MasterG">
<text name="cat2Number">"$1234"</text>
</g>
</svg>
Any idea what I'm doing wrong? Why is it I can't see the svg code in "view source" but I can in "developer tools" ? Is that the cause of my problem? Is that why my alert box won't "alert"?
After a couple of seconds of googling and finding this https://stackoverflow.com/a/9602772/1217408
I created this JSFiddle example of using textContent: http://jsfiddle.net/hVyTJ/1/
The original http://jsfiddle.net/hVyTJ/ uses standard DOM traversal to get to the text element from the root SVG element. While the update targets the text element directly by ID.
As for finding attribute values you can use getAttributeNS as described here: http://www.carto.net/svg/manipulating_svg_with_dom_ecmascript/
EDIT:
As pointed out by Phrogz, a simple getAttribute call is often sufficient. Read the comment for more details.
you can invoke text() to return the text content of an svg:text element.
// assume svgCont is an svg element
var label = svgCont.append("svg:text").text("hello, world!");
// print the text content to the console
console.log( label.text() );
Without all the unnecessary discussion:
Grab your SVG element:
svg = document.getElementById("my_svg_id");
Grab the inner text from the SVG:
var text = svg.textContent
For the ones who need only the displayed text, you can use the Selection API and its Range interface.
Simply using .textContent would also grab all text nodes that aren't displayed:
const svg = document.querySelector("svg");
console.log(svg.textContent);
<svg>
<defs>
<desc>This text is not displayed, it shouldn't be grabbed</desc>
<!-- same for all the new-lines in the markup -->
</defs>
<rect width="100%" height="100%" fill="red" />
<circle cx="150" cy="100" r="80" fill="green" />
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
</svg>
Since SVG elements don't have an innerText property, we need to iterate ourselves over all text nodes, and check if when selecting them we get a BBox.
const svg = document.querySelector("svg");
const range = new Range();
let textContent = "";
const walker = document.createTreeWalker(svg, NodeFilter.SHOW_TEXT, null);
while(walker.nextNode() && walker.currentNode) {
range.selectNode(walker.currentNode);
if (range.getClientRects().length) {
textContent += walker.currentNode.textContent;
}
}
console.log(textContent);
<svg>
<defs>
<desc>This text is not displayed, it shouldn't be grabbed</desc>
<!-- same for all the new-lines in the markup -->
</defs>
<rect width="100%" height="100%" fill="red" />
<circle cx="150" cy="100" r="80" fill="green" />
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
</svg>
just using the dom method
const svg = document.querySelector(`[data-uuid="live_map_svg"]`);
const shape = svg.querySelector(`text`);
const text = shape.innerHTML;
// const text = shape.textContent;
setTimeout(() => {
svg.insertAdjacentHTML(`beforebegin`, text);
}, 1000);
.svg-box{
width: 500px;
height: 500px;
background: #ccc;
color: red;
font-size: 16px;
}
[data-uuid="live_map_svg"]{
font-size: 16px;
}
<div class="svg-box">
<svg
data-uuid="live_map_svg" id="live_map_svg"
width="100%" height="100%"
viewBox="0 0 100 100"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<text x="30" y="50" fill="#369acd">A Area</text>
</svg>
</div>

React - How to show skeleton placeholders until Array map function ended

I have a country list component that contains country phone codes, country names and their flags using map() function so it takes a bit long to load. I need to get the information if map() function ended or still working then use it for showing then hiding placeholders. How could I achieve that?
I couldn't find proper solutions on Internet or couldn't use them. Like when using Promise(all) in a React Component, I've been having hardness to figure out how syntax should be.
<CountryList /> component:
// Packages I used for countries
import { getCountries, getCountryCallingCode } from "react-phone-number-input"
import countryNames from "react-phone-number-input/locale/en.json"
import ReactCountryFlag from "react-country-flag"
// The array comes from package
const countries = getCountries()
<CountryList>
{countries.map((country) => (
<CountryItem key={country} value={country}>
<ReactCountryFlag countryCode={country} svg />
<span>
{countryNames[country]}
<span>+{getCountryCallingCode(country)}</span>
</span>
</CountryItem>
))}
</CountryList>
<CountryItemSkeleton /> component:
// The package I used for skeleton placeholder
import ContentLoader from "react-content-loader"
<CountryItemSkeleton>
<CountryItem>
<ContentLoader>
<rect x="0" y="0" rx="3" ry="3" width="40" height="30" />
<rect x="52" y="8" rx="7" ry="7" width="248" height="14" />
</ContentLoader>
</CountryItem>
<CountryItem>
<ContentLoader>
<rect x="0" y="0" rx="3" ry="3" width="40" height="30" />
<rect x="52" y="8" rx="7" ry="7" width="248" height="14" />
</ContentLoader>
</CountryItem>
<CountryItem>
<ContentLoader>
<rect x="0" y="0" rx="3" ry="3" width="40" height="30" />
<rect x="52" y="8" rx="7" ry="7" width="248" height="14" />
</ContentLoader>
</CountryItem>
</CountryItemSkeleton>
Everything here is synchronous so you cannot wait for or monitor the map() progress.
What you can try though is loading the country list in an effect hook so that it's populated after your component is mounted. On the initial render you can use your skeleton component
const [countries, setCountries] = useState([]);
// run once on mount
useEffect(() => {
setCountries(getCountries());
}, []);
// or even with a small delay
useEffect(() => {
const timer = setTimeout(setCountries, 200, getCountries());
return () => {
clearTimeout(timerId);
};
}, []);
return countries.length === 0 ? (
<CountryItemSkeleton />
) : (
<CountryList>
{countries.map((country) => (
<CountryItem key={country} value={country}>
{/* etc */}
</CountryItem>
))}
</CountryList>
);

How do I use SVGs for background images on buttons in React

First timer on stack overflow so I'll try my best.
I'm trying to dynamically add SVGs as background images for my buttons.
Created react app using create-react-app.
I have 3~ files in question: poland.svg, Flag.jsx and exampleComponent.jsx
exampleComponent.jsx
import React from 'react';
import Flag from './Flag.jsx';
const exampleComponent = (props) => {
const backgroundImage = {
backgroundImage: `url(${Flag(props.nation)})`
}
return(
<button style={backgroundImage}>
<div />
</button>
)
}
export default exampleComponent;
Flag.jsx
import Poland from './poland.svg';
import US from './us.svg';
const Flag = (nation) => {
let path = "";
if (nation === "Poland") path = Poland;
else if (nation === "US") path = US;
return path;
}
export default Flag;
poland.svg
<svg
height="100%"
width="100%"
>
<defs />
<rect fill="#ffffff" width="100%" height="50%" />
<rect transform="translate(0, 50px)" fill="#ff0000" width="100%" height="50%" />
</svg>
I'm getting the file path /static/media/poland.49b43928.svg
When I inspect the element in chrome the correct file path is shown but the SVG isn't being loaded!
IM STILL A NOOB! so please be harsh! ;D
Update! So the file was being sent in XML format.. I wish I knew more to explain the details but all I did to fix this was add:
xmlns="http://www.w3.org/2000/svg"
to the svg tags like so:
<svg
height="100%"
width="100%"
xmlns="http://www.w3.org/2000/svg"
>
<defs />
<rect fill="#ffffff" width="100%" height="50%" />
<rect transform="translate(0, 50px)" fill="#ff0000" width="100%" height="50%" />
</svg>
If the svg files are similar, there may be a problem with matching the
<use xlink ID:href="#xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx"/>,
where xxxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxxxxxxxx is the ID, it must be unique for each file.
See the link, it may help
https://github.com/SilverFox70/svg-react-loader

How to switch position of D3 SVG elements

I have some SVG elements on my page that I created with D3. All are children of one parent SVG. Each contains some other D3 elements like paths and text. On the click of a button, I want two of these child SVGs to switch positions, so they move up or down on the page (all are placed above/below each other).
I already tried creating groups ("g") instead of the child SVGs and accessing/changing their positions. However, I can't seem to access the y position of the element.
I also tried using "insertAfter" but this only works with divs, not with SVGs (however, I'm looking for a similar behaviour).
$(".move_up").click(function() {
var svg = $("#second_child"); //ID is actually retrieved from an attribute of the button
svg.insertBefore(svg.prev()); //does obviously not work
});
HTML for move up button (one per child SVG exists):
<a class="move_up">
<span class="grey glyphicon glyphicon-chevron-up" title="Move up"></span>
</a>
HTML for SVG:
<div>
<svg id="parent">
<svg id="first_child"
<path></path>
<rect></rect>
<text></text>
...
</svg>
<svg id="second_child"
<path></path>
<rect></rect>
<text></text>
...
</svg>
<rect></rect>
<text></text>
...
</svg>
</div>
I want the first and second child SVGs to switch positions, when the move up (or respectively a move down) button is used.
This is what I ended up doing:
let group = $("#first_group");
let next_group = $('#second_group");
let diff = 58; // Height of a group
let translate_y = 0;
let translate_y_next = 0;
if (next_group.offset()) {
if (group.attr("transform")) {
let string = group.attr("transform");
translate_y = parseInt(string.substring(string.indexOf("(")+1, string.indexOf(")")).split(",")[1]);
}
if (prev_group.attr("transform")) {
let string_next = prev_group.attr("transform");
translate_y_next = parseInt(string_next.substring(string_next.indexOf("(")+1, string_next.indexOf(")")).split(",")[1]);
}
group.attr("transform", `translate(0, ${translate_y + diff})`);
next_group.attr("transform", `translate(0, ${translate_y_next - diff})`);
}
Works similar for a "Move up" button. Just make sure to change the sign in the last two lines!
May not be super elegant, but does the job.
You are using an SVG as wrapper and the positions are different to html. In SVG You need to define the X and Y position.
let ids = ['ex1', 'ex2', 'ex3', 'ex4', 'ex5']
let btn = document.getElementById('move')
const sortArrayAsYouWish = (array) => {
array.sort(() => Math.random() - 0.5);
}
const changeOrder = () => {
let posY = 35
sortArrayAsYouWish(ids) // change order
ids.forEach((id, i) => {
let $el = document.getElementById(id)
$el.style.transform = `translate(0, ${posY*i}px)`
})
}
btn.onclick = changeOrder
changeOrder()
svg {
width: 500px;
height: 340px;
border: solid 1px #ccc;
}
g {
transition: transform 0.4s;
}
text {
fill: #fff;
text-anchor: middle;
}
#ex2 rect {
fill: blue;
}
#ex3 rect {
fill: yellow;
}
#ex4 rect {
fill: red;
}
#ex5 rect {
fill: cyan;
}
<div><button id="move">Move</button></div>
<svg>
<g id="ex1">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 1</text>
</g>
<g id="ex2">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 2</text>
</g>
<g id="ex3">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 3</text>
</g>
<g id="ex4">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 4</text>
</g>
<g id="ex5">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 5</text>
</g>
</svg>

Adding Javascript events to SVG <def> elements instead of directly on the children?

I know that it is possible to attach plain-old Javascript event handlers to elements within an SVG tag. I am looking to duplicate very many instances of an object, so I am planning to use the <def> and <use> tags to simplify things a bit. However each of my children items will need to handle "click" and other events. I could define those events for each child, but it would be nice if I could somehow define them ONCE in the initial items, and then just "reuse" that click event. Is this possible?
https://jsfiddle.net/khyp1o0w/
<svg width="400" height="400" viewBox="0 0 400 400">
<defs>
<rect id="someDef" x="0" y="0" width="60" height="30" fill="#f00" />
<!-- ^^ would like to define an event here... -->
</defs>
<use x="150" y="150" xlink:href="#someDef" />
<use x="250" y="250" xlink:href="#someDef" />
<!-- ^^ ... that is used by these guys -->
</svg>
EDIT: Adding example SVG.
IIRC elements targeted by a use element should be internally copied into the <use> element just like if the <use> were an iframe, and the content were cloned.
So this means that events attached on the original node won't be copied onto the copies, unless this event is part of the node markup (inlined). But chrome don't really follow specs here and won't even make it work... So here is an example for FF :
// this won't work
document.getElementById('someDef').addEventListener('click', function() {
this.setAttribute('stroke', randomColor());
});
<svg width="400" height="400" viewBox="0 0 400 400">
<defs>
<script type="application/javascript">
function randomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[~~(Math.random() * 15)];
}
return color;
}
</script>
<!-- this will work in FF -->
<rect id="someDef" onclick="this.setAttribute('fill',randomColor())" x="0" y="0" width="60" height="30" fill="#f00" />
</defs>
<use x="150" y="150" xlink:href="#someDef" />
<use x="250" y="250" xlink:href="#someDef" />
</svg>
You could of course use event delegation by listening for click events on your root svg element, but this would work only for direct target elements, not for nested ones :
var svg = document.querySelector('svg');
svg.addEventListener('click', function(e) {
var usedTargetID = e.target.getAttributeNS("http://www.w3.org/1999/xlink", 'href');
switch (usedTargetID) {
case '#direct':
clickDirect(e);
break;
case '#nested':
clickNested(e);
break;
default:
return;
}
});
function clickDirect(e) {
// what you seem to want
e.target.setAttribute('fill', randomColor());
}
function clickNested(e) {
// will set both nested...
e.target.setAttribute('fill', randomColor());
}
function randomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[~~(Math.random() * 15)];
}
return color;
}
<svg width="400" height="400" viewBox="0 0 400 400">
<defs>
<rect id="direct" x="0" y="0" width="60" height="30" />
<g id="nested">
<!-- you can only access this -->
<!-- not its content individually -->
<rect x="80" y="0" width="60" height="30" />
<rect x="20" y="70" width="60" height="30" />
</g>
</defs>
<use x="150" y="50" xlink:href="#direct" />
<use x="250" y="150" xlink:href="#nested" />
</svg>
Or you could also add an event per element, but this would have the same limitations as delegation...

Categories

Resources