How to remap array only on changes? - arrays

I have component Page which contains components Editor and Preview.
Page contains array items.
[
{
value: 0,
text: 'Item 1'
},
...
]
Array items is passed to Editor & Preview like this:
<editor [items]="items"></editor>
<preview [items]="items"></preview>
Editor can add/delete/edit/reorder items.
Issue is preview needs this array in another format.
[
{
index: 0,
label: 'Item 1'
},
...
]
If I do like this
getRadioItems(): any[] {
const items = [];
for (let i = 0; i < this.items.length; i++) {
items.push({ index: this.items[i].value,
label: this.items[i].text });
}
return items;
}
and then
<radio-list [radioItems]="getRadioItems()"></radio-list>
It refreshes radio list hundreds times per second. You can't even change value because it will be reset on every refresh.
If it were without remapping - it would work fine.
What is correct way to remap items to radioItems in such case?

Have you tried setting the ChangeDetectionStrategy of the preview component to OnPush? Then change detection should only be run when the #Input() items is updated.

It is stupid solution, but it works.
getRadioItems(): any[] {
const newJson = JSON.stringify(this.items);
if (this.json === newJson) {
return this.cachedItems;
}
this.json = newJson;
this.cachedItems = [];
for (let i = 0; i < this.items.length; i++) {
this.cachedItems.push({ index: this.items[i].value,
label: this.items[i].text });
}
return this.cachedItems;
}

Related

Array: How to change specific values dependent on index (Rating function)

I'm sorry for the terrible title, but somehow I can't explain it better in one sentence.
What I want to do is a rating component in my Vue App. So if I click the 3rd star, the two stars before that one are set to "true" as well.
What I got:
const ratingsArray = [
{
name: 'rating1',
ratingCount: 1,
isClicked: ref(false)
},
{
name: 'rating2',
ratingCount: 2,
isClicked: ref(false)
},
{
name: 'rating3',
ratingCount: 3,
isClicked: ref(false)
},
{
name: 'rating4',
ratingCount: 4,
isClicked: ref(false)
},
{
name: 'rating5',
ratingCount: 5,
isClicked: ref(false)
},
]
I just got a toggle function to toggle isClicked:
function toggleClick(x) {
x.value = !x.value
}
This is my template
<template>
<div v-for="rating in ratingsArray"
:key="rating.name"
#click="toggleClick(rating.isClicked)"
:class="[rating.isClicked.value ? 'ratingBoxFilled' : 'ratingBox']">
</div>
</template>
How can I say, that if rating3 is clicked (so isClicked is true), rating1 and rating2 also got to be true?
It seems that I need to work with the index in my array. But somehow, I cannot create an idea. Maybe you guys can help me out. Thank you!
A simple loop would do the trick:
<template>
<div v-for="(rating, index) in ratingsArray"
:key="rating.name"
#click="toggleClick(index)"
:class="[rating.isClicked.value ? 'ratingBoxFilled' : 'ratingBox']">
</div>
</template>
function toggleClick(ratingIndex) {
for (let i = 0; i < ratingsArray.length; i++) {
// Set ratingsArray[i].isClicked to true if it's within the requested range
ratingsArray[i].isClicked.value = (i <= ratingIndex);
}
}
You guys are great. I didn't thought of an for loop.
So here is my final solution:
<div
v-for="(rating, index) in ratingsArray"
:key="rating.name"
#click="updateRating(index, rating.ratingValue)"
:class="[rating.isEnabled.value ? 'ratingBoxChecked' : 'ratingBox']">
</div>
function updateRating(ratingIndex: number, ratingValue: number) {
for (let i = ratingIndex; i < 5; i++) {
ratingsArray[i].isEnabled.value = false;
}
for (let i = 0; i <= ratingIndex; i++) {
ratingsArray[i].isEnabled.value = true;
}
console.log('Rating Value: ' + ratingValue)
}
First I clean all the enabled dots.
Then it will run until the given index and set the boolean value to true. Thats all.

Items in array are returned in only one JSX element using .map()

New to react and still learning. I have an assignment to create a filter component with three dropsdowns that takes information from a JSON file. The idea is the results in the second dropdown will be filtered once the first dropdown has a selected value. The JSON format is:
"destinations": [
{
"id": 8375,
"name": "Bordeaux",
"country": "France",
"category": "wine"
}, ETC
"seasonCategories": {
"spring": [
"wine",
"wonder",
"forest",
"adventure",
"food"
], ETC
I've created a function that feeds the data into the dropdown component and filters it, but it's not returning as I expect: it's creating only one JSX <option> element with the values of all array items listed inside. I need it to generate a new JSX element with the current value of every item in the array. If I call {el[index]} on the last map function I get the right value, so I'm lost as to why it's not generating each in their own <option> tag. The function I'm using is:
function funcCategories(src, val) {
return Object.keys(src)
.filter((flag) => {
return flag === val;
})
.map((el) => {
let v = [];
for (let i = 0; i < src[el].length; i++) {
v.push(src[el][i]);
}
return v;
})
.map((el) => {
return <option className="Dropdown__option">{el}</option>;
});
}
My Dropdown component:
import React from 'react';
class Dropdown extends React.Component {
constructor(props) {
super(props);
this.value = '';
}
render() {
return (
<div className="Dropdown__wrapper">
<label className="Dropdown__label">{this.props.label}</label>
<select
className="Dropdown__select"
value={this.props.value}
onChange={this.props.handleSelect}
>
<option className="Dropdown__option">{this.props.label}</option>
{this.props.func}
</select>
</div>
);
}
}
export default Dropdown;
For you visual thinkers, this is what I'm seeing in the window:
Dropdown result of my current code
I figured it out! I was pushing the array object in the .filter() method and not the elements of each. Not the cleanest code but it works:
let keys = Object.keys(src);
let j = [];
for (let i = 0; i < keys.length; i++) {
if (keys[i] === val) {
alert(keys[i] + ' === ' + src[keys[i]].length)
for (let h = 0; h < src[keys[i]].length; h++) {
j.push(src[keys[i]][h]);
}
}
}
return j.map((el) => {
return <option className="Dropdown__option">{el}</option>;
});

Remove item from state

I know this question has been asked a lot but I haven't seem to find a solution even tho I've tried different scenarios.
this.state = {
chartTypes: [{name: 'Bar', id: 1},{name: 'Line', id: 2},{name: 'Pie', id: 3},{name: 'Radar', id: 4} ],
selectedChart: [],
}
onSelect = (selectedList, selectedItem) => {
// selectedItem.name returns name of chartTypes, selectedItem.id returns id of chartTypes.
this.setState({
selectedChart: selectedItem.name
})
}
onRemove = (selectedList, removedItem) => {
// removedItem.name returns name of chartTypes, removedItem.id returns id of chartTypes.
}
The select option works fine but I just put it there so you can have a better understanding. onSelect puts every selectedItem.name into selectedChart. On the remove function, how may I remove item from this.state.selectedChart based on the value/index.
I think you can do something like this
let temp = this.state.selectedChart;
temp=temp.filter((item) => item !== removedItem.name);
this.setState({ selectedChart: temp });
var newArray = this.state.selectedChart.filter((el)=>
el.id !==removedItem.id
);
after filter
this.setState({selectedChart:newArray})
Just filter and set backed to state
onRemove = (selectedList, removedItem) => {
let filtered = this.state.chartTypes.filter(list=> list.id !==removedItem.id);
this.setState({ chartTypes: filtered });
}

Treeview checkbox act as radiobutton

Sample diagram:
I want this specific group of nodes to act as radiobutton(only 1 should be checked). I know I can handle this by hard coding conditions but I want to make it expandable in the future(adding more checkbox) by changing its json column 'Group'.
sample data:
[
{ id:"1", text: "Items", expanded: true, List: [
{ id:"2",text: "book" ,group: 1},//group for radiobutton actions
{ id:"3",text: "chair",group: 1 },
{ id:"4",text: "table",group: 1 },
{ id:"5",text: "mat", group: 0 },
{ id:"6",text: "decor", group: 0}
] }
]
I found this jsfiddle exmaple about group attribute for references.
This is the best(I think) way to do this:
1. Declare array of IDs w/c you want to grouped by:
var group = ["2","3","4"]; //in my example above
2. In the check event:
if (group.indexOf(dataItem.ID) > -1) { //if the ID you clicked exists in the group
group.splice(group.indexOf(dataItem.ID), 1); //remove the ID from the group
for (var i = 0, j = treeview.length; i < j; i++) {
for (var x = 0, y = treeview[i].List.length; x < y; x++) {
if (group.indexOf(treeview[i].List[x].ID) > -1) {
treeview[i].List[x].set("checked", false); //uncheck the members of the group
}
}
}
}

How to get the checked items from kendo treeview

HTML CODE:
<div>
<md-button ng-click="getCheckedItems()">TEST</md-button>
</div>
<div kendo-tree-view="tree"
k-data-source="treeData"
k-on-change="selectedItem = dataItem">
<span k-template>
<md-checkbox !important ng-click='click(dataItem)'>{{ dataItem.text}}</md-checkbox>
</span>
</div>
I want to get the checked items from the treeview and save it as string with ',' between 2 texts using the get function $scope.getCheckedItems = function(){}
Your question is not very clear, but in case if you want to get all the selected checkboxes inside your controller you can do like following.
$scope.getCheckedItems = function () {
var data = $scope.tree.dataSource._data;
for (var i = 0, j = data.length; i < j; i++) {
if (data[i].checked) {
//Item is checked
//You can get the properties using data[i]
console.log(data[i]);
}
}
};
I found out that i need to specify 'items' as child. Here is the working code:
for (var i = 0, j = data.length; i < j; i++) {
for (var x = 0, y = data[i].items.length; x < y; x++)
{
if (data[i].items[x].checked) {
//Item is checked
//You can get the properties using data[i]
console.log(data[i].items[x].text);
}
}
}
Sample data:
dataSource: [
{ text: "foo", expanded: true, items: [
{ text: "bar" }
] },
{ text: "baz", expanded: true, items: [
{ text: "qux" }
] }]

Resources