I have the following (simplified) HTML in my React project:
const { title } = this.props;
if(title) {
return (
<div class="someClass" onClick={someFunction} title={title}>
//Whatever..
</div>
)
}
else {
return (
<div class="someClass" onClick={someFunction}>
//Whatever..
</div>
)
}
Note that the if and else return values are identical except for the title attribute.
My question is, how can I prevent this repetition, but only add title when required.
I know I can add title to every element and just leave it blank if not required, but I do not want title attributes all over my HTML unless they are actually doing something.
I have tried:
var element = (
<div class="someClass" onClick={someFunction} title={title}>
//Whatever..
</div>
)
if(title)
element.title = //Whatever
But this did not work.
I have also tried
element.setAttribute('title', title);
But this did not work either.
Both the attempts above crashed the page.
Any ideas? Surely it can be done....
you can use:
title = {title?title:null}
if there is title in your props this will add the attribute title with the specified value to the element, otherwise no title attribute will be added.
In the React world with JSX you should use className instead of class.HTML doesn't exist in React the syntax that look like HTML is called JSX which just a syntactic sugar.
When you code hits a return statement everything that comes after will never be executed.So instead of doing:
if(condition){
return ...
}
else{
return ...
}
do can do:
if(condition){
return (<div>...</div>);
}
return (<div>...</div>)
So you don't need the else
Stop doing manual DOM manipulation like in Vanilla Javascriptby doing element.setAttribute('title', title); you don't need that in React.Please take your time to learn React basics.
Related
I want to find a clickable element using cypress. The clickable element always includes the text "Login" and is inside the container div. However, the catch is that I don't know if the clickable element is modeled using an <button>, <a>, or <input type="submit">.
My HTML sometimes look like this:
<nav>
<button>Wrong Login Button</button>
</nav>
<div class='container'>
...
<div>
<h2>Login</h2>
...
<button>Login</button>
</div>
</div>
And sometimes like this example:
<div class='container'>
...
<div>
<h2>Login</h2>
...
<div>
<div>
<a>Login</a>
</div>
</div>
</div>
</div>
It is sometimes a <button>, sometimes a <a> or <input type="submit">. And sometimes there are also more nested divs in there.
How can I find the clickable element (button, a or submit) element using cypress?
I only want to find the clickable element, so if there is a heading with the text "Login", it should be ignored.
This is nearly the answer:
cy.get('.container').contains('Login')
However, I somehow need to ignore all elements other than (button, a, and input[type=submit]). Because otherwise it would return the Login
I need something like:
cy.get('.container').find('button OR a OR input[type=submit]').contains('Login')
I thought of writing a custom command that can be used as the example below:
cy.get('.container').findClickable('Login')
The implementation could look similar as the example below:
Cypress.Commands.add('findClickable', {prevSubject: ['element']}, (
subject: Cypress.Chainable<HTMLElement>,
text: string,
options?: Partial<Cypress.Loggable & Cypress.Timeoutable & Cypress.Withinable & Cypress.Shadow>,
) => {
const buttons = cy.wrap(subject).find('button').contains(text)
const links = cy.wrap(subject).find('a').contains(text)
const submits = cy.wrap(subject).find('input[type=submit]').contains(text)
if (buttons.length + links.length + submits.length !== 1) {
throw new DOMException(
`Didn't find exactly one element. Found ${buttons.length} buttons, ${links.length} links, and ${submits.length} submits.`)
}
if (buttons.length === 1)
return buttons[0]
else if (links.length === 1)
return links[0]
else
return submits[0]
})
I know that the code above does not work because of many reasons. First, there is no .length attribute and second, cypress would fail because of the timeout of the first find('buttons') method.
Does anyone know how to implement the custom command the right way?
You can use the within and contains command with a combination of selector and text.
cy.get('.container').within(() => {
cy.contains('button', 'Login').click()
cy.get('input[type=submit]').click()
cy.contains('Login').click()
})
For Multiple Login, First we are checking the button is enabled or disabled, then we are clicking on it.
cy.contains('Login').each(($ele) => {
if($ele.is(":enabled")){
cy.wrap($ele).click()
}
})
You can also do, this is a more optimized code for the above snippet.
cy.contains('Login').filter(':enabled').click()
It is possible to search for multiple selectors when separating them with a comma:
cy.get('button, a, input[type=submit]')
So, search for all clickable elements and then filter for the clickable elements containing the required text.
Cypress.Commands.add('findClickable', {prevSubject: ['element']}, (
subject: Cypress.Chainable<HTMLElement>,
text: string,
_options?: Partial<Cypress.Loggable & Cypress.Timeoutable & Cypress.Withinable & Cypress.Shadow>,
) =>
cy.wrap(subject)
.find('button, a, input[type=submit]')
.filter(':contains(' + text + ')'),
)
Usage:
cy.get('.container').findClickable('Login')
I have function who return html
renderSuggestion(suggestion) {
const query = this.query;
if (suggestion.name === "hotels") {
const image = suggestion.item;
return this.$createElement('div', image.title);
} else {
let str = suggestion.item.name;
let substr = query;
return this.$createElement('div', str.replace(substr, `<b>${substr}</b>`));
}
},
But<b> element not render in browser as html element. Its display like string...
How I display this <b> element?
Tnx
That is because when you provide a string as the second argument of createElement, VueJS actually inserts the string as a text node (hence your HTML tags will appear as-is). What you want is actually to use a data object as the second argument, which give you finer control over the properties of the created element:
this.$createElement('div', {
domProps: {
innerHHTML: str.replace(substr, `<b>${substr}</b>`)
}
});
Of course, when you are using innerHTML, use it with caution and never insert user-provided HTML, to avoid XSS attacks.
You can also create a component and use v-html to render the output.
Declare props for your inputs:
export default {
props: {
suggestion: Object,
query: String
}
};
And use a template that uses your logic in the template part
<template>
<div class="hello">
<div v-if="suggestion.name === 'hotels'">{{suggestion.item.title}}</div>
<div v-else>
<div v-html="suggestion.item.name.replace(this.query, `<b>${this.query}</b>`)"/>
</div>
</div>
</template>
This allows for greater flexibility when using more complex layouts.
A working example here
Provide more detail(possibly a picture) of how it's not showing. Consider using a custom CSS class to see the div and what's happening to it.
bold {
border-style: black;
font-weight: bold;
}
then just use the "bold" class instead of "b".
The content of my Vue app is fetched from Prismic (an API CMS). I have a rich text block, some parts of which are wrapped inside span tags with a specific class. I want to get those span nodes with Vue and add to them an event listener.
With JS, this code would work:
var selectedSpanElements = document.querySelectorAll('.className');
selectedSpanElements[0].style.color = "red"
But when I use this code in Vue, I can see that it works just a fraction of a second before Vue updates the DOM. I've tried using this code on mounted, beforeupdate, updated, ready hooks... Nothing has worked.
Update: Some hours later, I found that with the HTMLSerializer I can add HTML code to the span tag. But this is regular HTML, I cannot access to Vue methods.
#Bruja
I was able to find a solution using a closure. The folks at Prismic reminded/showed me.
Of note, per Phil Snow's comment above: If you are using Nuxt you won't have access to Vue's functionality and will have to go old-school JS.
Here is an example where you can pass in component-level props, data, methods, etc... to the prismic htmlSerializer:
<template>
<div>
<prismic-rich-text
:field="data"
:htmlSerializer="anotherHtmlSerializer((startNumber = list.start_number))"
/>
</div>
</template>
import prismicDOM from 'prismic-dom';
export default {
methods: {
anotherHtmlSerializer(startNumber = 1) {
const Elements = prismicDOM.RichText.Elements;
const that = this;
return function(type, element, content, children) {
// To add more elements and customizations use this as a reference:
// https://prismic.io/docs/vuejs/beyond-the-api/html-serializer
that.testMethod(startNumber);
switch (type) {
case Elements.oList:
return `<ol start=${startNumber}>${children.join('')}</ol>`;
}
// Return null to stick with the default behavior for everything else
return null;
};
},
testMethod(startNumber) {
console.log('test method here');
console.log(startNumber);
}
}
};
I believe you are on the right track looking into the HTML Serializer. If you want all your .specialClass <span> elements to trigger a click event that calls specialmethod() this should work for you:
import prismicDOM from 'prismic-dom';
const Elements = prismicDOM.RichText.Elements;
export default function (type, element, content, children) {
// I'm not 100% sure if element.className is correct, investigate with your devTools if it doesn't work
if (type === Elements.span && element.className === "specialClass") {
return `<span #click="specialMethod">${content}</span>`;
}
// Return null to stick with the default behavior for everything else
return null;
};
I have an html template that i'm using template literals for. The function looks like the below
// postCreator.js
export const blogPostMaker = ({ title, content, address, id }, deletePost) => {
const innerHtml = `
<blog-post>
<h1 class='title'>${title}</h1>
<p class='content'>${content}</p>
<p class='address'>${address}</p>
<button onclick='${() => deletePost(id)}'>Delete</button>
</blog-post>
`
return innerHtml
}
//Blog.js
postHelper.getSeriesOfPosts(10)
.then(resp => {
resp.forEach(post => (blogDiv.innerHTML += blogPostMaker(post, postHelper.deletePost)))
})
What I can't figure out is how to get the onclick to work. I've tried passing in an anon function in Blog.js to the postCreator as well with no luck.
Any ideas?
If you don't want to expose the event callback globally, you should attach it in the JS part of the code with addEventListener() :
// postCreator.js
export const blogPostMaker = ({ title, content, address, id }) =>
`
<blog-post id='${id}'>
<h1 class='title'>${title}</h1>
<p class='content'>${content}</p>
<p class='address'>${address}</p>
<button>Delete</button>
</blog-post>
`
//Blog.js
postHelper.getSeriesOfPosts(10).then(resp => {
resp.forEach(post => {
blogDiv.innerHTML += blogPostMaker(post)
blogDiv.querySelector(`blog-post[id="${post.id}"] > button`)
.addEventListener('click', () => postHelper.deletePost(post.id))
})
Note: it's not the most efficient way to do it but it keeps your file structure.
Instead I would create the <button> directly with createElement() and then add it to DOM with appendChild(), or I would use a DocumentFragment or a <template> in order to avoid querySelector() on all of the blogDiv.
If you absolutely want to use inline JS without exposing the helper you'll need to define your as a component (for example a Custom Element).
Miroslav Savovski's solution works but they did not explain why, so I thought I would add this answer with the reasoning behind that and a step-by-step of how it is actually working, and why the OP's solution was not working initially.
TLDR? Scroll to the last two code snippets.
With template literals when you put a function inside of them it executes that function, so let's say we have a simple function like this that just returns a string of 'blue':
const getBlueColor = () => 'blue'
And then we call this function inside of a template literal like this:
<div>${getBlueColor()}</div>
What happens is that the getBlueColor() is called right when that code is executed.
Now lets say we wanted to do this onclick instead like this:
<div onclick="${getBlueColor()}"></div>
What is happening here is that getBlueColor is not executed onclick, it's actually executed whenever this template literal is executed.
The way we fix this is to prevent the template literal from executing this function by simply removing the template literal:
<div onclick="getBlueColor()"></div>
But now let's say you want to pass in some parameters to a function like getOppositeColor(color) like this:
<div onclick="getOppositeColor(color)"><div>
This will not work because color won't be defined. So you need to wrap that with a template literal and in a string like this:
<div onclick="getOppositeColor('${color}')"><div>
Now with this you will be calling the onclick when the user clicks the button, and you will be passing it a string of the color like this:
getOppositeColor('blue')
const markUp = `
<button onclick="myFunction()">Click me</button>
`;
document.body.innerHTML = markUp;
window.myFunction = () => {
console.log('Button clicked');
};
I have the following ReactJS component:
class Menu1item extends React.Component{
render(){
return (
<div>
{this.props.glyph}{this.props.value}
</div>
)
}
}
The component is used like below:
<Menu1item glyph={menu.glyph1} value={menu.menu1}/>
And menu is like below:
menu={menu1: 'YOU', glyph1: '<span class="icon"></span>'}
The component is rendered like below:
I wonder how I can actually render the span element.
Applied the suggested solutions and now it works fine:
Try removing quotes around '<span class="icon"></span>'.
This should do it:
const menu = {
menu1: 'YOU',
glyph1: (<span class="icon"></span>)
};
Explanation:
You are passing a string literal to the glyph prop, so it is literally rendering the string '<span class="icon"></span>', as expected. If you pass a <span> instead, then it should render a <span> like you want it.
Remember, you are using JSX, so you can write HTML-style tags without quotes, and they will be compiled to React.createElement() calls.
EDIT:
Since class is a reserved word, React uses the className prop instead. So you should actually write
const menu = {
menu1: 'YOU',
glyph1: (<span className="icon"></span>)
};
Thanks to #novaline for catching this.
You need dangerouslySetInnerHTML:
render() {
return (
<div>
<span dangerouslySetInnerHTML={{__html: this.props.glyph}}></span>
{this.props.value}
</div>
)
}