How to use innerHTML for Accordion (PrimeReact)? - javascript

In my React project, I am using the PrimeReact library to display some Accordions. But I need to create them dynamically and this is the current result:
As you see, the html tags are also shown in the answers of the faqs. I tried using innerHTML and dangerouslySet... but to no success.
Here is my relevant code:
createAccordions = () => {
const allFAQs = this.state.allFAQs;
let accordions = [];
for (const faq of allFAQs) {
const accordion = <AccordionTab header={faq.question} dangerouslySetInnerHTML={{__html: `<p>yo man</p>`}}></AccordionTab>;
accordions.push(accordion);
}
return accordions
}
render() {
return (
<div className="component">
<h1 style={{textAlign: 'center'}}>{this.state.pageTitle}</h1>
<div className="p-grid">
<div className="p-col-3">
</div>
<div className="p-col-6">
<Accordion>
{this.createAccordions()}
</Accordion>
</div>
<div className="p-col-3">
</div>
</div>
</div>
)
}
How can I properly set the innerhtml for the accordions?

I took a look at the source and found that the library tries to render the children you pass to it. I see you tried dangerouslySetInnerHTML, but set it on the AccordionTab itself instead of the child passed into it.
I set up a quick demo on CodePen. I passed in a div container to set the innerHTML on, and it seems to work!
<AccordionTab header="Demo">
<div dangerouslySetInnerHTML={{ __html: "<ul><li>I am HTML</li></ul>" }}></div>
</AccordionTab>

Related

check for only selected element if that has text in it but not its child

i am trying to check if a selected element have text inside it , the problem is when i am trying to checking value by innerText its also returning values from child which is why boolean function is returning true but what i am expecting to do is just verify one element and avoiding child's i hope i have exlpained it correct so what can be a proper way to achive this
const main = document.querySelector(".main")
const first = main.querySelector(".selected")
const second = main.querySelector(".another")
const third = main.querySelector(".another-one")
function hasText(el){
return el.innerText != ""
}
console.log(hasText(first),hasText(second),hasText(third)) // true true true expected result is = [true false,true]
<div class="main">
<div class="selected">
i have text inside me
</div>
<div class="another">
<div class="child">i am inside a child</div>
</div>
<div class="another-one">
i am inside another one
<div class="child">i am inside a child</div>
</div>
</div>
There are several options to achieve your goal. You could just check if el has children elements and in case .children.length > 0 just consider it as a text container and measure its innerText length.
But in case those parent elements are also supposed to optionally contain both children elements and text, you should check every single childNode of theirs, verify if that's a text portion and consider that text to be part of parent content.Those nodes will be text portions sparse in between legit children elements.
There's also the textContent property of Node that could give you an information similar to that returned by my function grabPureTextContent but there are caveats that I preferred to cut off implementing my own logic.
https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
Here's a demo adding such function to your code and using its logic to determine if a given element has text content or not.
I added a third case (.special) showing what happens when there's mixed content.
const main = document.querySelector(".main")
const first = main.querySelector(".selected")
const second = main.querySelector(".another")
const third = main.querySelector(".special")
/*
Returns the pure text content defined in el
looping through its childNodes and concatenating content,
(if child is text only) before returning the whole string.
There's also the option to use textContent property of Node
https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
But here the algorithm is more clear on what's going on
*/
function grabPureTextContent(el){
let content = '';
for(childText of el.childNodes){
if (childText.constructor.name == 'Text'){
content += childText.nodeValue.trim();
}
}
return content;
}
function hasText(el){
if (grabPureTextContent(el).length > 0)
return true;
return false;
}
console.log(hasText(third)) //=> true
console.log(hasText(second)) //=> false
console.log(hasText(third)) //=> true
.main > div {
border: solid 1px gray;
margin-bottom: 1em;
}
.container {
background: lightgreen;
}
.child {
background: lightpink;
}
<div class="main">
<div class="container selected">
i have text inside me
</div>
<div class="container another">
<div class="child">i am inside a child</div>
</div>
<!-- special case having both children and text -->
<div class="container special">
<div class="child">i am inside a child</div>
TEXT HERE after child
</div>
</div>
I would test if some tags inside the string. If not you can use your check if is empty.
const main = document.querySelector(".main")
const first = main.querySelector(".selected")
const second = main.querySelector(".another")
const third = main.querySelector(".another2")
function hasText(el) {
const pattern = /<.*>.*<\/.*>/;
return pattern.test(el.innerHTML) ? false : (el.innerText != "");
}
console.log(hasText(first),hasText(second), hasText(third)) // true true expected result is = [true false true]
<div class="main">
<div class="selected">
i have text inside me
</div>
<div class="another">
<div class="child">i am inside a child</div>
</div>
<div class="another2">
i am inside a child
</div>
</div>

Find elements of similar hierarchy by JavaScript (for web scraping)

For example, when I select one of the p.item-title elements below, all p.item-title elements should be found (not by the class name). Also, when I select one of the table elements below, all similar tables should be found. I need this for web scraping.
<div>
<div>
<p class="item-title">...</p>
<table>...</table>
</div>
</div>
<div>
<div>
<p class="item-title">...</p>
<table>...</table>
</div>
</div>
jQuery's siblings() method is similar in concept, but it finds similar elements under the same parent node. Is there any method or library to find similar elements from different parent nodes?
Just do querySelectorAll by the path (hierarchy) you want:
var allElements = document.querySelectorAll("div > div > p");
allElements.forEach(p => console.log(p));
<div>
<div>
<p class="item-title">Text 1</p>
<table>...</table>
</div>
</div>
<div>
<div>
<p class="item-title">Text 2</p>
<table>...</table>
</div>
</div>
Try this:
jQuery.fn.addEvent = function(type, handler) {
this.bind(type, {'selector': this.selector}, handler);
};
$(document).ready(function() {
$('.item-title').addEvent('click', function(event) {
console.log(event.data.selector);
let elements = document.querySelectorAll(event.data.selector);
elements.forEach(e => console.log(e));
});
});
Thanks to Jack, I could create a running script.
// tags only selector (I need to improve depending on the use case)
function getSelector(element){
var tagNames = [];
while (element.parentNode){
tagNames.unshift(element.tagName);
element = element.parentNode;
}
return tagNames.join(" > ");
}
function getSimilarElements(element) {
return document.querySelectorAll(element);
}

Data Driven CSS Grid Vue Component

I want to create a Grid component that accepts the number of columns from the user, accepts data and renders all it's children into consecutive cells.
Something like this.
<Grid :cells="12" :columns="6">
<div>Child1 Cell1</div>
<div>Child2 Cell2</div>
<div>Child3 Cell3</div>
<div>Child4 Cell4</div>
<div>Child5 Cell5</div>
<div>Child6 Cell6</div>
</Grid>
In the Grid.vue component in the template, this is what I expect to do.
<div class="nugget-grid-item" v-for="cell of cells" :key="cell">
{cell}
</div>
This will render something like this on the UI.
The dashed border on each cell is due to the nugget-grid-item CSS class, but CSS is not relevant here, so let's ignore that.
What I am not able to figure out is how do I get this Grid component to display the following.
Isn't there something like this.children from React in Vue?
What you need are slots. See docs here. As you'll see slots allow a parent component to pass DOM elements into a child component. A basic look at them could go like this:
//ChildComponent.vue
<template>
<div>
<p>I'm the child component!</p>
<!-- Content from the parent gets rendered here. -->
<slot></slot>
</div>
</template>
And then you inject content into the slot tags like this:
//ParentComponent.vue
<template>
<div>
<child-component>
<p>I'm injected content from the parent!</p>
<p>I can still bind to data in the parent's scope, like this! {{myVariable}}</p>
</child-component>
</div>
</template>
Slots can get pretty complex and do a lot of things so are well worth looking into.
Further to your below comment, you can put a v-for in the grid. This outputs what you seem to be after. I've put an input in to accept the users number of columns as you said and it then renders that number of cells. You can of course use multiple slots and named slots and scoped slots but I'll leave it up to you how you expand on this.
//Grid.vue
<template>
<div class="cell">
<slot></slot>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
.cell {
height: 40px;
width: 60px;
border: 1px solid gray;
}
</style>
and parent:
<template>
<div class="content">
<label>Enter number of columns</label>
<input v-model.number="col" type="number">
<Grid v-for="(n, i) in col" :key="i" >
<div>Child{{n}} Cell{{n}}</div>
</Grid>
</div>
</template>
<script>
import Grid from '#/components/admin/Grid'
export default {
layout: 'admin',
components: {
Grid
},
data: () => ({
col: 4
}),
}
</script>

How to iterate through DOM elements in react and add/remove classes?

I am beginner to react and I am unable to iterate through div elements. Each of these div elements (shown in the code) have a common className="step" initially but once the button Next is clicked I want 1st div to have className="step current" .
Once Next is clicked again 1st div element should have className="step done"(remove current and append done) and 2nd div element would have classname="step current"
I was able to toggle the className from "step" to "step current" in the following code but I am facing difficulty to traverse the div elements and add or remove the class.
class NavBar extends React.Component{
state = {
addClass : false
}
handleClick=()=>{
this.setState({addClass: !this.state.addClass});
}
render(){
let arrowClass = ["step"];
if(this.state.addClass){
arrowClass.push("current");
}
return(
<div id="navbar-div">
<div className="arrow-steps clearfix">
1. <div className={arrowClass.join(' ')}>
<span> Step1</span>
</div>
2. <div className={arrowClass.join(' ')}>
<span>Step2</span>
</div>
3. <div className={arrowClass.join(' ')}>
<span> Step3</span>
</div>
4. <div className={arrowClass.join(' ')}>
<span>Step4</span>
</div>
5. <div className={arrowClass.join(' ')}>
<span>Step5</span>
</div>
</div>
<div className="nav clearfix">
Previous
<a href="#" className="next pull-right" onClick={this.handleClick}>Next</a>
</div>
</div>
);
}
}
ReactDOM.render(
<NavBar />,
document.getElementById("root")
);
.current {
font-weight: bold;
}
.done {
color: #aaa;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
The classes step, current and done are defined in the css file.
Rather than writing the steps explicitly, put them in an array, and then remember where you are within that array with an indexing variable. Here's the minimal-changes approach to doing that with your code (see comments):
class NavBar extends React.Component{
state = {
steps: ["Step1", "Step2", "Step3", "Step4"],
current: 0 // <== Step1 is the current step
}
prevClick = () => {
// Move to previous step, note we use the callback version of setState,
// see https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
this.setState(({current}) => ({current: Math.max(0, current - 1)}));
}
nextClick = () => {
// Move to next step
this.setState(({steps, current}) => ({current: Math.min(steps.length - 1, current + 1)}));
}
render() {
// Get the steps and the current index
const {current, steps} = this.state;
// Render them, checking the position of the step (`index`) relative to `current`
// and outputting the relevant class name.
// I changed your `div`s to an ordered list so we get automatic numbering
return (
<div id="navbar-div">
<div>{current}</div>
<ol className="arrow-steps clearfix">
{steps.map((step, index) =>
<li key={index} className={`step ${index < current ? 'done' : index == current ? 'current' : ''}`}>{step}</li>
)}
</ol>
<div className="nav clearfix">
<a href="#" className="prev" onClick={this.prevClick}>Previous</a>
<a href="#" className="next pull-right" onClick={this.nextClick}>Next</a>
</div>
</div>
);
}
}
ReactDOM.render(
<NavBar />,
document.getElementById("root")
);
.current {
font-weight: bold;
}
.done {
color: #aaa;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Obviously, that just shows the basic approach, you'll want to modify it. If you have any information other than just the step text, you might make the array entries objects rather than just strings.
(Note: Using index as the key on those lis is only valid if you don't add/remove entries in the array of steps.)
Side note: As Murali Krishna pointed out, you had class rather than className on the div containing the previous/next links and on those links; I've changed those to className above.
Although you can do that but there's a more React'ish way. That is to use state to store the class you wanna add. So when you change the state (using setState) it will auto re-render and set the class.
Eg.
class NavBar extends React.Component{
state = {
status: "current"
}
handleClick=()=>{
this.setState(prevState=>{
if(prevState.status === 'current'){
return {status:"done"}
}else{
return {status:"current"}
}
})
}
render(){
return <button className={"step " + this.state.status} onClick={this.handleClick}>Hello</button>
}

Import different components react from different files

How can I use component from another JS file in ReactJS.It says "Header is not defined".BTW I am using ReactJS without NodeJS.Here's the code:
<script type="text/babel" src="../Assets/js/react/Dashboard_components.js"></script>
//Here in Dashboard_components.js
var Dashboard_comp=React.createClass(
{
componentDidMount:function()
{
$('.ui.modal').modal();
},
render:function()
{
return(
<div>
//Header exists in Base component js
<Header/>
<div className="ui two columns grid">
<div className="three column wide">
<SideNav/>
</div>
<div className="thirteen column wide">
<Stats/>
<Class_boxes/>
</div>
</div>
</div>
)
}
})
At the top of your Dashboard_components.js file, and above the
var Dashboard_comp=React.createClass( line, add the line
var Header = require('./yourpath/to/header.js')
If you're using ES6 you can simply
import Header from './path/header'
Hmm..Seems like no-one got the answer so I am answering my own question.
Suppose there is a Header component in a Base_component.js file.So in order to use it in Other html's.Use this:
window.Header=Header
It would make component publicaly available for other files as well not the same js.

Categories

Resources