Passing on:click event into dynamically created <svelte:component/> - javascript

I basically need to be able to trigger something within one or more components (that
are being dynamically added via svelte:component) when an icon/button within the parent
component is clicked. e.g. I need to hook the parts denoted with ** below:-
<script>
let charts = [
ChartA,
ChartB,
ChartC
];
</script>
{#each charts as chart, i}
<div class="wrapper">
<div class="icon" on:click={**HowToPassClickEventToComponent**}></div>
<div class="content">
<svelte:component this={charts[i]} {**clickedEvent**}/>
</div>
</div>
{/each}
I was able to get something working by unsing an array of props but each
component is notified when the array changes so this is not very clean.
I have searched both Google and StackOverflow as well as asking this question within the Svelte Discord channel with currently no luck.
Svelte Repl showing the problem
This seems like such a simple requirement but after a couple of days I remain stuck so any advice on how to pass events into dynamic components is much appreciated.

You could do it like this:
<script>
import ChartA from './ChartA.svelte'
import ChartB from './ChartB.svelte'
import ChartC from './ChartC.svelte'
let charts = [
ChartA,
ChartB,
ChartC
];
let events = [];
</script>
<style>
.icon{
width:60px;
height:30px;
background-color:grey;
}
</style>
{#each charts as chart, i}
<div class="wrapper">
<div class="icon" on:click={e=>events[i] = e}>Click</div>
<div class="content">
<svelte:component this={charts[i]} event={events[i]}/>
</div>
</div>
{/each}
Passing events around as data would be a bit unusual though. It would be more idiomatic to set some state in the parent component in response to the event, and pass that state down.
Alternatively, if the child components do need to respond to events themselves you could take this approach...
<script>
import ChartA from './ChartA.svelte'
import ChartB from './ChartB.svelte'
import ChartC from './ChartC.svelte'
let charts = [
ChartA,
ChartB,
ChartC
];
let instances = [];
</script>
<style>
.icon{
width:60px;
height:30px;
background-color:grey;
}
</style>
{#each charts as chart, i}
<div class="wrapper">
<div class="icon" on:click={e => instances[i].handle(e)}>Click</div>
<div class="content">
<svelte:component
this={charts[i]}
bind:this={instances[i]}
/>
</div>
</div>
{/each}
...where each child component exports a handle method:
<script>
let event;
export function handle(e){
event = e;
};
</script>

Related

React Dom is covering the entire page

I created a code to display the colors inside of an array, "d", and it's working quite fine. I used document.getElementById to display the colors.
Here's the problem:
The only thing displaying on screen is the list of colors. I created like four divs with different colours to use for something else but that isn't showing again.
If I remove the imported react dom and the getElementById code, all my blueDv, PinkDv div etc., come back up.
Is there a way to display the result of the array and still show the divs?
Where am I getting it wrong? Why is the document.getElementById covering the entire page?
import React from "react";
import REACTDOM from "react-dom";
import "./JsShuffle.css";
const JsShuffle = () => {
let d = ['Blue', 'pink', 'green', 'yellow'];
REACTDOM.render(d, document.getElementById('root'));
return (
<div className= "jsshuffle">
<div className= "blueDv">
</div>
<div className= "pinkDv">
</div>
<div className= "greenDv">
</div>
<div className= "yellowDv">
<div id="root"></div>
</div>
</div>
);
}
export default JsShuffle;

Move component to another DOM node using ember-maybe-in-element

I have a use case where I can move a child component to different DOM location within same page/route dynamically.
home.hbs
<div class="container">
<div class="content">
<!-- Place where I want to place Child -->
</div>
</div>
<Parent></Parent>
Parent.hbs
<h1>This is parent component</h1>
<Child></Child>
child.hbs
Hello World
child.js
const mainContainer = document.querySelector('.container .content');
const myElm = this.element.querySelector('[data-child-content]');
mainContainer.appendChild(myElm);
I want to use ember-maybe-in-element addon instead of using appendChild.
The in-element helper renders the block content outside of the regular flow, into a DOM element given by its destinationElement positional argument.
child.js
get destinationElement() {
return document.querySelector('.container .content');
}
child.hbs
{{#in-element this.destinationElement}}
<div>Hello World</div>
{{/in-element}}

How do you create a reusable Vue component with d3?

I'm trying to create a Chart.vue component based on D3. I need to be able to add multiple instances to a single page and keep each one separate.
I've tried to assign an ID generated with uuid to the div wrapping my component in the template:
<template>
<div :id=this.id>
<svg></svg>
</div>
</template>
The ID is created when the component is created.
<script>
import * as d3 from "d3";
import { v4 as uuidv4 } from "uuid";
export default {
...
created () {
this.id = uuidv4()
},
...
The chart is re-rendered when there is an update to the data passed in as props from the parent App.vue. To select the "correct" <svg> element that is owned by a particular instance of Chart I use the unique this.id in my renderChart method:
methods: {
renderChart(chart_data) {
const svg_width = 1000;
const svg_height = 600;
const svg = d3
.select("#" + this.id)
.select("svg")
.attr("width", svg_width)
.attr("height", svg_height);
...
Proceeding to add all the axes, data, etc.
If I add two such components to my App.vue template:
<template>
<div id="app">
<form action="#" #submit.prevent="getIssues">
<div class="form-group">
<input
type="text"
placeholder="owner/repo Name"
v-model="repository"
class="col-md-2 col-md-offset-5"
>
</div>
</form>
<Chart :issues="issues" />
<Chart :issues="issues" />
</div>
</template>
I see that they are added to the DOM with some uuid that's been created. When the data is updated and the renderChart function executes, both components get a copy of the "issues" data, but I only see one chart being created.
I'm quite novice with JavaScript, Vue and D3, so perhaps going about this the wrong way, but it seems like this should work?
Any help is appreciated.
Well, I seem to have found a solution, although I don't fully understand it, and I'm not sure why the initial approach didn't work (note: original approach did seem to work sometimes, but the behaviour was unpredictable).
To solve, I pass a unique ID to the component from the parent template as a prop and add it as the Chart component <div> id.
In App.vue:
<template>
<div id="app">
<form action="#" #submit.prevent="getIssues">
<div class="form-group">
<input
type="text"
placeholder="owner/repo Name"
v-model="repository"
class="col-md-2 col-md-offset-5"
>
</div>
</form>
<Chart id="chart1" :issues="issues" />
<Chart id="chart2" :issues="issues" />
</div>
</template>
Now I need to add id in the props of the Chart.vue and set a variable in the data() section.
<template>
<div :id=chart_id>
<svg></svg>
</div>
</template>
<script>
import * as d3 from "d3";
export default {
name: 'Chart',
props: ["issues", "id"],
data() {
return {
chart: null,
chart_id: this.id
};
},
...
I'm not sure why the uuid approach didn't work, but this seems more robust.

Materialize Css Parallex is not a function

I am trying to use the parallax within a react component. I have gotten this to work in the past. This is a MeteorJs project as well
I get a console error:
$(...).parallax is not a function
My component:
import React from 'react';
import $ from 'jquery';
export default class Index extends React.Component{
render(){
$(document).ready(function(){
$('.parallax').parallax();
});
return(
<div className="container">
<div className="parallax-container">
<div className="parallax"><img src="images/parallax1.jpg"/></div>
</div>
<div className="section white">
<div className="row container">
<h2 className="header">Parallax</h2>
<p className="grey-text text-darken-3 lighten-3">Parallax is an effect where the background
content or image in this case, is moved at a different speed than the foreground content while
scrolling.</p>
</div>
</div>
<div className="parallax-container">
<div className="parallax"><img src="images/parallax2.jpg"/></div>
</div>
</div>
)
}
}
My client main.js file:
import '../imports/startup/client/routes';
import '../node_modules/materialize-css/dist/js/materialize.min';
import '../node_modules/materialize-css/js/parallax';
The error message is telling you that .parallax() isn't a function in this line of code:
``
$('.parallax').parallax();
```
Which means that $('.parallax') is returning an object (usually a html element). It is not surprising that .parallax() is not a function, because it's just a html element.
Looking at the documentation, I think you are missing this initialisation code:
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.parallax');
var instances = M.Parallax.init(elems, options); });

Container structure issue with JQuery - Containing containers

I'm finishing up a memory game for school and I'd really like the cards to flip with a CSS animation, which on it's own is pretty straight forward. However I'm pretty new to JavaScript and JQuery which is leading to some trouble with achieving the proper container structure I need to make the cards flip when they are clicked.
Presently the game pieces generate within the board as follows:
const generate=(cards)=>{
cards.forEach(function(card, i) {
$(".gameBoard")
.append($("<div>").addClass("front")//
.append($("<div>").addClass("back").append($("
<img>").attr("src", cards[i]))));
});
};
OR:
<div class="gameBoard>
<div class="front"></div>
<div class="back"><img src="cards"></div>
</div>
But in order for the animation to function properly both the front and back divs need to exist in the same container like this:
<div class="gameBoard>
<div class="flip">
<div class="front></div>
<div class="back"><img src="cards></div>
</div>
</div>
How can I add the div I need (.flip) but have it contain the front and back divs, not just append on to the other divs being generated within the .gameboard container.
Thanks.
It's much simpler to create your DOM using template literals rather than jQuery methods. That way you just describe the HTML as you're accustomed to.
const generate=(cards)=>{
cards.forEach(function(card, i) {
$(".gameBoard").append(`
<div class=flip>
<div class=front></div>
<div class=back><img src="${cards[i]}"</div>
</div>
`);
});
};
generate([
"https://dummyimage.com/180x120/f00/fff.png&text=one",
"https://dummyimage.com/180x120/0f0/fff.png&text=two",
"https://dummyimage.com/180x120/00f/fff.png&text=three",
]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<div class=gameBoard></div>
You'll notice the ${cards[i]}, which lets you perform string interpolation by executing at runtime the code in the braces.
Here's a vanilla JS version.
const generate=(cards)=>{
var gb = document.querySelector(".gameBoard");
cards.forEach(card =>
gb.insertAdjacentHTML("beforeend", `
<div class=flip>
<div class=front></div>
<div class=back><img src="${card}"</div>
</div>
`)
);
};
generate([
"https://dummyimage.com/180x120/f00/fff.png&text=one",
"https://dummyimage.com/180x120/0f0/fff.png&text=two",
"https://dummyimage.com/180x120/00f/fff.png&text=three",
]);
<div class=gameBoard></div>
It also uses card instead of cards[i], and an arrow function for the callback.
And this one performs a single append.
const generate=(cards)=>{
document.querySelector(".gameBoard")
.insertAdjacentHTML("beforeend", cards.map(card =>
` <div class=flip>
<div class=front></div>
<div class=back><img src="${card}"</div>
</div>`).join(""));
};
generate([
"https://dummyimage.com/180x120/f00/fff.png&text=one",
"https://dummyimage.com/180x120/0f0/fff.png&text=two",
"https://dummyimage.com/180x120/00f/fff.png&text=three",
]);
<div class=gameBoard></div>

Categories