React render to element by class - javascript

How i can render my component in few elements by class? I have one component, and i need show him in my page in differents tags, but with general logic
var FilterBox = React.createFactory(FilterBox),
ToRender = document.getElementsByClassName('filter-box')
React.render(FilterBox(), ToRender);
html:
<div class="filter-box"></div>
...
...
<div class="filter-box"></div>

you should be able to iterate over your classes and call React.render in each
for(var i;i < ToRender.length;i++{
React.render(FilterBox(), ToRender[i])
}

Related

Calling a function from a JS file not working

I'm using React to view my pages.
I came across this problem if I try to call a function from a .js file nothing happens.
Basically, I have a small program that has two columns. Each column has a <p> tag that contains Column 1 and Column 2. There is a button below that once you click on it, both Columns should switch.
index.js
import "../style.css";
//import "./java.js";
class index extends React.Component {
render() {
return(
<div className="container">
<div className="columns" id="columnsToSwitch">
<div className="column1" id="column1_id">
<p>Column1</p>
</div>
<div className="column2" id="column2_id">
<p>Column 2</p>
</div>
</div>
<div className="switch" id="switch_id" onClick={this.switchColumns}>Switch</div>
</div>
);
}
}
export default index;
java.js
const switchCol = document.querySelectorAll("div.columns");
const el = document.getElementById("switch_id");
if(el) {
el.addEventListener("click", switchColumns, false);
}
switchCol.forEach(switches => switches.addEventListener('click', switchColumns));
function switchColumns(){
const makeSwitch1 = document.getElementById('column1_id');
document.getElementById('column2_id').appendChild(makeSwitch1);
const makeSwitch2 = document.getElementById('column2_id');
document.getElementById('column1_id').appendChild(makeSwitch2);
}
Method 1:
I tried to import the .js file that contains the function.
Nothing is happening after clicking "Switch".
Method 2:
Using onClick within a tag.
<div className="switch" id="switch_id" onClick={this.switchColumns}>Switch</div>
I get a couple of errors,
Uncaught TypeError: Cannot read properties of undefined (reading 'switchColumns')
The above error occurred in the <index> component:
On This line:
const switchCol = document.querySelectorAll(".switchCol");
There's no elements with the class of 'switchCol' so you're going to get an empty NodeList which causes the forEach loop to not execute so there are no click events on the columns themselves.
In the forEach block:
switchCol.forEach(switches => {
switches.addEventListener("column1", switchColumns);
switches.addEventListener("column2", switchColumns);
});
"column1" and "column2" are not valid event listeners, and there doesn't need to be two event listeners for one element. I think you mean to write the following:
switchCol.forEach(switch => switch.addEventListener('click', switchColumns))
Now onto your main switching column function:
function switchColumns(){
const makeSwitch1 = document.getElementById('column1');
document.getElementById('column2').appendChild(makeSwitch1);
const makeSwitch2 = document.getElementById('column2');
document.getElementById('column1').appendChild(makeSwitch2);
}
Variables makeSwitch1 and makeSwitch2 are going to be undefined as you do not have any elements with an id of column1 and column2 respectfully. Which is causing your issue with the second fix you tried.

Why does querySelector not work with v-for?

I have a simple div with a v-for loop that displays items on a page. These items have a class on them which I would like to select using Javascript's querySelector method.
<div class="product-feed">
<div v-for="Product in ProductFeed" :key="Product.ProductID" class="product-item" >
<Product-Vue-Component :Product="Product"></Product-Vue-Component>
</div>
</div>
<script>
...
setup() {
async function loadFeed(){
await nextTick();
let element1 = document.querySelector('.product-feed');
console.log(`element1`, element1) // this works and displays the element
let element2 = document.querySelector('.product-item:last-of-type');
console.log(`element2`, element2) // this comes back as null
}
}
onMounted(()=> {
loadFeed();
})
}
...
</script>
Even though I am waiting for the DOM to render using nextTick(), the function loadFeed() cannot pick up on any item that is in the v-for loop.
I need to detect items in the v-for loop so that I can implement an infinite scroll feature where more items are loaded as the user scrolls to the bottom of the list of .product-item elements (hence :last-of-type pseudo selector)
Where is it going wrong and is it possible to select elements this way?
Try to set ref on div with class="product-item"
<div ref="el" v-for="Product in ProductFeed" :key="Product.ProductID" class="product-item" >
then in setup function :
const el = ref(null)
onMounted(() => {
loadFeed()
})
async function loadFeed(){
await nextTick();
let element1 = document.querySelector('.product-feed');
console.log(`element1`, element1) // this works and displays the element
console.log(el.value)
}
}
return {el}
Please, use refs for that just add ref attribute to the container ref="productFeed" and get this element with
import { ref } from 'vue'
...
const productFeed = ref(null)
...
return {
productFeed
}
Then you can use productFeed.value as a variable, containing dom element
Try to use refs like this:
<div
v-for="Product in ProductFeed" :ref="`product--${Product.id}`">
</div>

Conditional styling on class in Svelte

I'm trying to use Svelte to do some conditional styling and highlighting to equations. While I've been successful at applying a global static style to a class, I cannot figure out how to do this when an event occurs (like one instance of the class is hovered over).
Do I need to create a stored value (i.e. some boolean that gets set to true when a class is hovered over) to use conditional styling? Or can I write a function as in the example below that will target all instances of the class? I'm a bit unclear why targeting a class in styling requires the :global(classname) format.
App.svelte
<script>
// import Component
import Katex from "./Katex.svelte"
// math equations
const math1 = "a\\htmlClass{test}{x}^2+bx+c=0";
const math2 = "x=-\\frac{-b\\pm\\sqrt{b^2-4ac}}{2a}";
const math3 = "V=\\frac{1}{3}\\pi r^2 h";
// set up array and index for reactivity and initialize
const mathArray = [math1, math2, math3];
let index = 0;
$: math = mathArray[index];
// changeMath function for button click
function changeMath() {
// increase index
index = (index+1)%3;
}
function hoverByClass(classname,colorover,colorout="transparent")
{
var elms=document.getElementsByClassName(classname);
console.log(elms);
for(var i=0;i<elms.length;i++)
{
elms[i].onmouseover = function()
{
for(var k=0;k<elms.length;k++)
{
elms[k].style.backgroundColor=colorover;
}
};
elms[i].onmouseout = function()
{
for(var k=0;k<elms.length;k++)
{
elms[k].style.backgroundColor=colorout;
}
};
}
}
hoverByClass("test","pink");
</script>
<h1>KaTeX svelte component demo</h1>
<h2>Inline math</h2>
Our math equation: <Katex {math}/> and it is inline.
<h2>Displayed math</h2>
Our math equation: <Katex {math} displayMode/> and it is displayed.
<h2>Reactivity</h2>
<button on:click={changeMath}>
Displaying equation {index}
</button>
<h2>Static math expression within HTML</h2>
<Katex math={"V=\\pi\\textrm{ m}^3"}/>
<style>
:global(.test) {
color: red
}
</style>
Katex.svelte
<script>
import katex from "katex";
export let math;
export let displayMode = false;
const options = {
displayMode: displayMode,
throwOnError: false,
trust: true
}
$: katexString = katex.renderToString(math, options);
</script>
<svelte:head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex#0.12.0/dist/katex.min.css" integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X" crossorigin="anonymous">
</svelte:head>
{#html katexString}
If I understand it correctly you have a DOM structure with arbitrary nested elements and you would want to highlight parts of the structure that share the same class.
So you would have a structure like this:
<div>
<p>This is some text <span class="a">highlight</span></p>
<span class="a">Another highlight</span>
<ul>
<li>Some listitem</li>
<li class="a">Some listitem</li>
<li class="b">Some listitem</li>
<li class="b">Some listitem</li>
</ul>
</div>
And if you select an element with class="a" all elements should be highlighted regardles where they are in the document. This arbitrary placement makes using the sibling selector in css not possible.
There is no easy solution to this, but I will give you my attempt:
This is the full code with some explanation
<script>
import { onMount } from 'svelte'
let hash = {}
let wrapper
onMount(() => {
[...wrapper.querySelectorAll('[class]')].forEach(el => {
if (hash[el.className]) return
else hash[el.className] = [...wrapper.querySelectorAll(`[class="${el.className}"]`)]
})
Object.values(hash).forEach(nodes => {
nodes.forEach(node => {
node.addEventListener('mouseover', () => nodes.forEach(n => n.classList.add('hovered')))
node.addEventListener('mouseout', () => nodes.forEach(n => n.classList.remove('hovered')))
})
})
})
</script>
<div bind:this={wrapper}>
<p>
Blablabla <span class="a">AAA</span>
</p>
<span class="a">BBBB</span>
<ul>
<li>BBB</li>
<li class="a b">BBB</li>
<li class="b">BBB</li>
<li class="b">BBB</li>
</ul>
</div>
<style>
div :global(.hovered) {
background-color: red;
}
</style>
The first thing I did was use bind:this to get the wrapping element (in your case you would put this around the {#html katexString}, this will make that the highlight is only applied to this specific subtree.
Doing a querySelector is a complex operation, so we will gather all the related nodes in a sort of hashtable during onMount (this kind of assumes the content will never change, but since it's rendered with #html I believe it's safe to do so).
As you can see in onMount, I am using the wrapper element to restrict the selector to this section of the page, which is a lot faster than checking the entire document and is probably what you want anyway.
I wasn't entirely sure what you want to do, but for simplicity I am just grabbing every descendant that has a class and make a hash section for each class. If you only want certain classes you could write out a bunch of selectors here instead:
hash['selector-1'] = wrapper.querySelectorAll('.selector-1');
hash['selector-2'] = wrapper.querySelectorAll('.selector-2')];
hash['selector-3'] = wrapper.querySelectorAll('.selector-3');
Once this hashtable is created, we can loop over each selector, and attach two event listeners to all of the elements for that selector. One mouseover event that will then again apply a new class to each of it's mates. And a mouseout that removes this class again.
This still means you have to add hovered class. Since the class is not used in the markup it will be removed by Svelte unless you use :global() as you found out yourself. It is indeed not that good to have global classes because you might have unintended effect elsewhere in your code, but you can however scope it as I did in the code above.
The line
div > :global(.hovered) { background-color: red; }
will be processed into
div.svelte-12345 .hovered { background-color: red; }
So the red background will only be applied to .hovered elements that are inside this specific div, without leaking all over the codebase.
Demo on REPL
Here is the same adapted to use your code and to use a document-wide querySelector instead (you could probably still restrict if wanted by having the bind one level higher and pass this node into the component)
Other demo on REPL

Add a class to the Immediate Parent Element in Angular

I have an element where it has a class creates dynamically. I want to add a new class to it's parent element if the child element has a specific class.
<a [ngclass]="addClassHere"> //need to add class here if child has a specific class
<div [ngclass]="getScheduleDateColour(date.day)">{{date.day}}</div> //child class
</a>
the a element is created by the package and cannot access directly.
Assuming your getScheduleDateColour() returns different names of classes, say 'childClass1','childClass2' and so on, and the required is 'childClass1'.
Now you can set a new class to your parent according to your child's class by :
<a [ngclass]="childDiv.className == 'childClass1'? 'ReqParentClass' : '' ">
<div #childDiv [ngclass]="getScheduleDateColour(date.day)">{{date.day}}</div>
</a>
PS: If you want to add different parentClasses if child is not the required one, you can add it also in the ternary operator, like: 'childClass1'? 'ReqParentClass' : 'ElseThisWillBeTheParentClass'
One of the options you have is to use Template reference variables
for both your parent and child elements and change them pragmatically from your TS file
like so in html
<div #parentElem class="aquaDay">
<div #childElem class="aqua">day</div>
</div>
and use #ViewChild to bind them and change them as you want
#ViewChild("parentElem") parent: ElementRef;
#ViewChild("childElem") child: ElementRef;
changeColour() {
const childClass = this.child.nativeElement.className;
this.parent.nativeElement.className =
childClass === "aqua" ? "greenDay" : "aquaDay";
}
I created a simple demo here: https://stackblitz.com/edit/angular-ivy-422p1t?file=src/app/app.component.ts

Is it bad practice to create a web-component inside another and append it to said other?

Essentially I have a web component "x" and I dynamically create a form component inside the "x" which will be appended to "x".
I could just do it in the place I create "x", after creating "x", of course.
Basically this:
class X extends LitElement {
render() {
return html`
<div>
<slot name="form-component">${this.appendFormComponent()}</slot>
</div>
<slot></slot>
`
}
appendFormComponent() {
const formComponent = document.createElement('input')
formComponent.slot = "form-component"
this.append(formComponent)
}
// side note, is running this append inside the render function a terrible
// idea and where should I do it instead? I mean doing it in the render
// function does appear to work...
}
As you suspected, this is definitely a terrible idea because you are mixing imperative paradigm with declarative paradigm. However, if you really need to do this and since you are using LitElement, you can nicely abstract the declarative and imperative UI code using appropriate lifecycle methods:
class X extends LitElement {
render() {
return html`
<div>
<slot name='form-component'></slot>
</div>
<slot></slot>
`;
}
// Executed only once
firstUpdated() {
const formComponent = document.createElement('input');
formComponent.slot = 'form-component';
this.append(formComponent);
}
}
Also, the approach you are attempting is probably problematic. Your problem would be easily solved by render function only:
class X extends LitElement {
render() {
return html`
<div>
<slot name='form-component'>
<!-- Notice the use of INPUT TAG here -->
<input type='text' />
</slot>
</div>
<slot></slot>
`;
}
}
Using something like firstUpdated with document.createElement should be used to create UI components which have offset elements that break the UI as Function of State notion. Such components are date pickers, multi select dropdown, dialog boxes, etc. which directly append DOM elements to the body for managing Z-index and fixed positioning accurately.
Further, as per your comments, if you have a dynamic function which needs to be assigned to the input text, simply create a wrapper function like:
class X extends LitElement {
// Input change event handler
onChange() {
// A guard to check presence of dynamic function
if (this.someDynamicFuction) {
this.someDynamicFuction();
}
}
render() {
return html`
<div>
<slot name='form-component'>
<!-- Notice the use of INPUT TAG here -->
<input type='text' #change=${this.onChange} />
</slot>
</div>
<slot></slot>
`;
}
}

Categories

Resources