I have a column for buttons to toggle a modal. The problem is, I don't want to display the button for every single row. I only want to display the button on the first entry of the color.
Note that the colors are unpredictable (you don't know what colors will be displayed beforehand).
For example,
color toggler
black +
red +
red //don't display it here
yellow +
blue +
blue //don't display it here
blue //don't display it here
orange +
red +
black +
black //don't display it here
blue +
I have try to go through the document and some example, but I can't seem to find a solution to it (maybe something that I missed ?).
What I did was storing the first color in the state. Then I did with the theCheckFunc:
let flag = true
if (nextColor !== this.state.color)
this.setState({color: nextColor})
flag = false
return flag
Then in the columns I did.
Cell: props => (this.theCheckFunc(props) && <div onClick={somefunc}> + <div>)
However, everything seems to be frozen. The browser doesn't even respond.
Any good suggestion on how to do this ?
Don't use state with this, since you don't want to re-render based on new input. Instead, compute the array as part of the render.
For example, assuming that when you get to your render statement, you have a random array of colors like this:
['red', 'red', 'black', 'purple', 'purple']
Then this function could create the array you need with the data for render:
function getTableRowData(arr) {
let tableRowData = []
arr.forEach((color, n) => {
let toggler = true
if (n !== 0 && arr[n - 1] === color) {
toggler = false
}
tableRowData.push({ color, toggler, })
})
return tableRowData
}
Then you can iterate over the tableRowData in your render return and have it display the way you want to.
First set your color control variables in state or in class wherever you choose. In this example i'm choosing to control them over state.
constructor(props) {
super(props);
this.state = {
firstRedAlreadyHere: false,
firstBlueAlreadyHere: false,
firstGrayAlreadyHere:false,
....
...
}
}
then open a function to prepare a table. Later Use that function in render() to put table on component.
function putValuesToTable()
{
let table = [];
for (let i = 0; i < (YOUR_LENGTH); i++) {
{
let children = []; /* SUB CELLS */
/* IF RED COLOR IS NEVER CAME BEFORE, PUT A BUTTON NEAR IT */
if(!this.state.firstRedAlreadyHere)
children.push(<td>
<SomeHtmlItem></SomeHtmlItem></td> <td><button </button></td>)
/* ELSE DON'T PUT BUTTON AND CHANGE STATE. */
else
{
children.push(<SomeHtmlItem></SomeHtmlItem>);
this.state.firstRedAlreadyHere = true;
}
table.push(<tr>{children}</tr>);
}
}
return table;
}
I am changing state directly instead of this.setState(); because I don't want to trigger a refresh :). In render function, call putValuesToTable like this
render()
{
return (<div>
<table>
<tbody>
<tr>
<th>SomeParameter</th>
<th>SomeParameter2</th>
</tr>
{this.putValuesToTable}
</tbody>
</table>
</div>);
}
Use this example to extend your code according to your aim.
Related
I'm currently trying to render a specific class across two lightning-badge components that is suppose to change both badges from inverse to success, but am getting this instead:
When the value on the left badge equals the value on the right (so in this case both are 3), they should both be green, otherwise they should both be grey. They should never be seperate colours.
The value on the left increases as the user saves a record and is checked on status of "Completed". For some reason only the class on the second badge is being updated with the new class that includes slds-theme_success. I may be missing something small, but just haven't been able to figure it out. Please see code below:
badgeClass = "slds-badge_inverse slds-var-m-horizontal_x-small slds-col";
get patientsCompleted() {
if(this.records) {
let completedArr = this.records.filter(value => value.fields.Status__c.value == "Completed");
if(completedArr.length === this.patientsTotal) {
this.badgeClass = "slds-badge_inverse slds-theme_success slds-var-m-horizontal_x-small slds-col";
}
return completedArr.length;
}
};
get patientsTotal(){
if(this.records) {
return this.records.length;
}
};
<span class="slds-col_bump-left">
<div class="slds-grid slds-gutters">
<lightning-badge class={badgeClass} label={patientsCompleted}></lightning-badge>
<div class="slds-col"> of </div>
<lightning-badge class={badgeClass} label={patientsTotal}></lightning-badge>
</div>
</span>
Have you tried moving badgeClass to a getter? Something like this:
get patientsCompleted() {
if(this.records) {
let completedArr = this.records.filter(value => value.fields.Status__c.value == "Completed");
// No longer needed
// if(completedArr.length === this.patientsTotal) {
// this.badgeClass = "slds-badge_inverse slds-theme_success slds-var-m-horizontal_x-small slds-col";
// }
return completedArr.length;
}
};
get patientsTotal(){
if(this.records) {
return this.records.length;
}
};
get badgeClass() {
let baseClass = "slds-badge_inverse slds-var-m-horizontal_x-small slds-col";
return this.patientsCompleted === this.patientsTotal ? `${baseClass} slds-theme_success` : `${baseClass}`
}
I suspect LWC field tracking has some precautionary mechanism and didn't trigger the update.
I am not sure but perhaps if 0 records are available you want the badges to remain gray? In that case include this.patientsTotal > 0 in the get badgeClass() {...}.
Happy coding.
I have a react app in which I'm trying to make each table row fade out whenever a corresponding button to that table row is clicked. The idea is that when a delete button on that row is clicked the whole row will fade out and the rest of the rows below it will move up (without creating any empty space) and if it is the last row that is being removed it will just fade out and table will become smaller.
Currently I'm trying to use refs and CSS to implement this feature, however right now if, for example, I click on "Delete" button in the second row - this row will be removed immediately without any animation, but at the same time the very last row's content will fade out according to the CSS styling that I set up and there will be an empty table row left there. Following that, any other element will be removed without any fade out animation.
So far I have done the following:
1) A component that create dynamic table rows (any necessary data, including the ref, are passed through useContext() from the component with the business logic):
function TableData() {
const { dataset, handleDeletion, wrapperRef } = React.useContext(Context)
return dataset.map((item, index) => {
const { id, name, year, color, pantone_value } = item
return (
<tr key={id} ref={wrapperRef}>
<td>{year}</td>
<td>{name}</td>
<td style={{ backgroundColor: color, width: "100px" }} />
<td>
<button
value={id}
type="button"
class="btn btn-outline-danger"
onClick={handleDeletion}>
<i class="far fa-trash-alt"></i>
</button>
</td>
</tr>
)
})
}
2) In the main component with business logic I set up the ref:
const wrapperRef = React.createRef()
3) And this is a function that handle a click of a delete button:
function handleDeletion(event) {
const id = event.currentTarget.value
const wrapper = wrapperRef.current
wrapper.classList.add('fade')
setDataset(prevDataset => prevDataset.filter(item => item.id !== parseInt(id)))
}
4) Simple CSS animation:
.fade {
opacity: 0;
transition: opacity 5000ms;
}
Could you please let me know what am I doing wrong here and what would need to be fix in order to make it work as planned?
Best regards,
Konstantin
P.S. I'm attaching the full code to this post as well (just in case) and here is the link to the pen with this project.
UPDATE:
1) I added the following className into my 'tr' component:
<tr key={id} className={toDelete && itemToDelete === id ? 'fade' : ''}>
2) In the main component I added the following state hooks:
const [ toDelete, setToDelete ] = React.useState(false)
const [ itemToDelete, setItemToDelete ] = React.useState('')
3) And changed the delete function to the following (with adding an extra useEffect that is applied with those hooks being changed):
function handleDeletion(event) {
const id = event.currentTarget.value
setToDelete(true)
setItemToDelete(id)
//setDataset(prevDataset => prevDataset.filter(item => item.id !== parseInt(id)))
}
React.useEffect(() => {
if (toDelete !== false && itemToDelete !== '') {
setTimeout(() => {
setDataset(prevDataset => prevDataset.filter(item => item.id !== parseInt(itemToDelete)))
}, 500)
}
}, [toDelete, itemToDelete])
I tried to do it different ways, including using setTimeout/setInterval, doing it without a separate useEffect hook in the handleDeletion function, etc. But still can't get the desired result, it is removing it without applying fading animation.
You create wrapperRef and you overwrite it with every map iteration so that it holds the reference on the last table row. That is why you see the last row fade out.
You would probably need an array of refs so that you have the ref for every row. Depending on how big the table is maybe you should look for other solutions because it not recommended to overuse refs
This approach is probably the best.
In addition to Darko's answer, you can also delete the row from the DOM after fading out.
setTimeout(function() { that.setState({ isDeleted: true })}, 500);
https://codepen.io/primaryobjects/pen/qBaMvrq
I have a page that displays 3 images, and the user is expected to tap on one, then tap the Next button to continue.
So basically, I am simply adding some CSS to the image when it is tapped.
BUT... my code is ugly, and doesn't keep track of whether they ALREADY have one selected.
onPlanTap: function (args) {
const planImage = args.object;
const planImageSrc = planImage.src;
const planId = plan.id;
this.set("nextButtonOn", true);
var n = planImageSrc.search("off");
// Found, it is off - turn on
if (n > 0) {
var newOnSrc = planId + "-off.png";
planImage.src = newOnSrc;
this.set("currentPlan",planId);
FancyAlertService.showFancySuccess("Plan Secected!", "You have chosen the FREE plan.", "Ok");
}
else {
// It's already on, turn off
var newOnSrc = planId + "-on.png";
planImage.src = newOnSrc;
this.set("currentPlan","");
}
}
[ oh, the css I am adding, simply adds a thick white border to the image ]
I can't figure out how to only have one selected.
Is there some sort of "toggle" feature in NS I am missing, or would I have to write the logic myself? If that's the case, can anyone give me a nudge with some code?
Wrap your images inside some custom object.
class MyImage {
image;
isSelected:boolean;
}
Add data binding:
<ListView [items]="myDataItems" class="list-group">
<ng-template let-item="item" let-i="index">
<Image (tap)="selectImage(item)" class="item.isSelected ? styleA : styleB"></Image>
</ng-template>
</ListView>
And in your component file:
class MyComponent {
myDataItems: Array<MyImage>
selectImage(item: MyImage) {
//deselect all Items
//select this item
}
}
I'm playing around with a webapp using React. My code currently creates a list of buttons that can change the color of a circle, depending on the randomly-generated button the user clicks on. To enhance my code, when the user clicks the button, I want all of the buttons colors to change, and therefore need to update all of the button's onClick functions, since they pass their color as an argument to the function that changes the circle's color. Below is the solution I currently have: it requires me to remove every button, and then completely reconstruct the button. Just using button.onclick = function() { newOnclickFunction} does not work, and I have not been able to find the answer on my own. Any help would be greatly appreciated; I'm almost certain there's a better a way to do it than this.
let reflipPalleteCompletely = () => {
let everyButtonPossible = document.getElementsByClassName("colorChangeButton")
for( var button of everyButtonPossible){
button.style.backgroundColor = randomColor()
let myParent = button.parentElement
myParent.removeChild(button)
let freshButton = document.createElement("button", {value: "Click", className: "colorChangeButton", })
freshButton.innerHTML = 'Click'
freshButton.className = 'colorChangeButton'
freshButton.style.backgroundColor = randomColor()
let newOnclickFunction = () => { changeToNewColor(button.style.backgroundColor); reflipPalleteCompletely() }
freshButton.onclick = function() { newOnclickFunction() }
myParent.appendChild(freshButton)
The short answer is: if each button is supposed to change color to reflect the color value it represents, and that set of colors is re-randomized every time a button is pressed, then you can't and shouldn't avoid re-render of the buttons.
However, Mike is right: you're not using React properly if you're writing your own element-creation scripts.
Your component might look like this:
const buttonCount = 10
class Demo extends React.Component {
constructor(props) {
super(props)
this.state = {
currentColor: getRandomColor()
}
}
setColor = (newColor, event) => {
this.setState({
currentColor: newColor
})
}
render() {
let {
currentColor
} = this.props
let colorChoices = Array(buttonCount)
.fill()
.map(() => getRandomColor())
return (
<div className="Demo">
<div className="the-shape" style={{ backgroundColor: currentColor }} />
<ol className="color-choices">
{
colorChoices.map( color => (
<button key={ color }
style={{ backgroundColor: color }}
onClick={ this.setColor.bind(this, color) }
>
{ color }
</button>
))
}
</ol>
</div>
)
}
}
This all depends on you having a getRandomColor function that can generate a color. And it doesn't address making sure that the choices don't include the current color (although you could easily do that by e.g. generating 2n colors, filtering out the currentColor, then taking the first n, or somesuch).
If you really hate redrawing the buttons, you could save their refs and then have the setColor method iterate through them and modify their styles.
But the point of React is to avoid procedural mutation of the DOM in favor of declaring the desired DOM and letting the React engine figure out an efficient mutation strategy.
A direct answer to the question you asked: "what's a more optimal way to change out an HTML element's.onClick element?" might be: find a pattern that doesn't require you to change the function every time.
Instead of having this:
let newOnclickFunction = () => { changeToNewColor(button.style.backgroundColor); reflipPalleteCompletely() }
Try something like this instead:
function onClickButton(event) {
let button = event.target
let color = button.style.backgroundColor
changeToNewColor(color)
}
This way, the desired color value isn't baked into the onClick function. Instead, the function examines the button whose click invoked it, and uses its background as the argument to changeToNewColor.
With some clever CSS, you could write the desired color to a data- prop on each button, and have the browser do the work of calculating background-color from that. Then you could use event delegation on some ancestor element that contains all the buttons, that listens for a click on any element with that data- prop and does the same work as above. This way, you don't even have a click function on each button.
I use following code to set background color for the row, based on the value changed but it didn't work, the row color didn't change:
MyGrid.onCellChange.subscribe(function (e, args)
{
var data = Grid.getData();
if(data[args.row].IsDeleted == true)
{
args.row.cssClasses += 'MyBlueColor'; //Set Css Class
}
}
<style type="text/css">
.MyBlueColor
{
background-color: blue;
}
</style>
args.row is the row index, not the row itself
check out what is passed as the event args when onCellChange is triggered
To change the cssClass of a row based on the data item, you will need to implement a custom getItemMetadata function to return custom metadata for that row. Check out how this is implemented in slick.groupitemmetadataprovider.js
So as a hack just to make sure it works, you could replace the getItemMetadata function in slick.dataview.js with your own custom function that would look something like:
function getItemMetadata(i) {
var item = rows[i];
if (item === undefined) {
return null;
}
if (item.IsDeleted) {
return {
cssClasses: 'MyBlueColor'
};
}
return null;
}
This will obviously break the original column definitions, but once you figure out how the getRowMetadata function works, you should be able to create your own metadata provider similar to slick.groupitemmetadataprovider.js and even return custom metadata for each column!