recursion and rendering in Polymer 2 - javascript

The following code works as expected when the variable 'works' is set to true (in chromium) but doesn't when it's not. I'm trying to figure out why. Although I can just use the working one, probably I should understand better why the non-working one doesn't work. Why should there be any difference between pushing array items one at a time with this.push and setting the entire array with this.set?
'use strict'
var data = [{
id: 1,
children: [{
id: 2
}, {
id: 3,
children: [{
id: 4
}, {
id: 5,
children: [{
id: 6
}, {
id: 7
}]
}, {
id: 8
}]
}, {
id: 9,
children: [{
id: 10,
children: [{
id: 11
}, {
id: 12
}, {
id: 13
}]
}, {
id: 14
}, {
id: 15
}]
}]
}];
class Polymerology2Element extends Polymer.Element {
static get is() {
return 'polymerology2-element';
}
static get properties() {
return {
nodeData: {
type: Array,
value: [],
notify: true
},
node: {
type: null,
value: null,
notify: true
}
};
}
isOdd(num) {
return num % 2;
}
addNodes() {
var works = true;
if (works) {
if (this.node !== null) {
var arr = [];
for (var i = 0; this.node !== undefined &&
this.node !== null && this.node.children !== undefined && i < this.node.children.length; i++) {
arr.push(this.node.children[i]);
}
setTimeout(() => {
this.set('nodeData', arr)
}, 0);
}
} else {
if (this.node !== null) {
for (var i = 0; this.node !== undefined &&
this.node !== null && this.node.children !== undefined && i < this.node.children.length; i++) {
setTimeout(() => {
this.push('nodeData', this.node.children[i])
}, 0);
}
}
}
}
constructor() {
super();
if (Polymerology2Element.instance === undefined) {
this.nodeData = data;
this.node = null;
Polymerology2Element.instance = 0;
} else
Polymerology2Element.instance += 1;
this.instance = Polymerology2Element.instance;
}
connectedCallback() {
super.connectedCallback();
this.addNodes();
}
}
window.customElements.define(Polymerology2Element.is, Polymerology2Element);
:host {
display: block;
}
<base href="https://polygit.org/polymer+polymer+v2.3.1/components/" />
<script src="webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="polymer/polymer.html" />
<polymerology2-element></polymerology2-element>
<dom-module id="polymerology2-element">
<template>
<ul>
<template is="dom-repeat" items="[[nodeData]]" as="item">
<template is="dom-if" if="{{isOdd(item.id)}}">
<li>Instance: {{instance}}, Node: {{item.id}} (odd)</li>
<polymerology2-element node="[[item]]"></polymerology2-element>
</template>
<template is="dom-if" if="{{!isOdd(item.id)}}">
<li>Instance: {{instance}}, Node: {{item.id}} (even)</li>
<polymerology2-element node="[[item]]"></polymerology2-element>
</template>
</template>
</ul>
</template>
</dom-module>

Related

Pinia | Vue3 - Accessing array in deep object

In Pinia (base.js)
export const useBaseStore = defineStore('base', {
state: () => {
return {
areaLocal: [
{ id: 0, name: 'LAX' },
{ id: 1, name: 'SFO' },
{ id: 2, name: 'SAN' },
],
areaLocal2: {
area: [
{ id: 0, name: 'AAA'},
{ id: 1, name: 'BBB'},
{ id: 2, name: 'CCC'},
]},
}
},
getters: {
getAreaById: (state) => {
return (areaId) =>
state.areaLocal.find((areaLocal) => areaLocal.id === areaId)
},..
in Vue (area.vue)
<script setup>
import { computed, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useBaseStore } from '../stores/base'
const areaStore = useBaseStore()
const { getAreaById } = storeToRefs(areaStore)
const areaIndex = ref(0)
</script>
<template>
<div class="container">
<h2>AREA: {{ getAreaById(areaIndex).name }}</h2>
....
This, areaLocal, I get "AREA: LAX", which is correct,
but HOW I can write "getter" in the pinia for access to "areaLocal2", the object "area" then the array?
state.areaLocal2.area.find((areaLocal2.area) => areaLocal.id.area === areaId
The above throws an undefined error.
getArea2ById: (state) => (areaIa) => state.areaLocal2.area.find((area) => area.id === areaId)
You are already in area by the time you hit the find higher order function so no need to reference it again inside of it.

Need to change name of a key in a script that returns a sorted List

I have some code that loops thru a flat json doc and creates a hierarchal of the items based on level and position. It all works as expected but i have the need to change the child elements name from child to items. And thats where my problem starts. There is 2 porions in code where i set the child:
newparent = {
...oldparent,
child: mItems
};
} else {
newparent = {
...oldparent,
child: [child]
So I tried to change that to items but that does not work, when i change it, I get the items key on the initial items but not for any additional items under that parent.
Here is also link to a working sample Replit Testbed
My Sample Output
[
{
enabled: true,
guid: 'F56AAC06-D2EB-4E1C-B84D-25F72973312E',
level: 0,
name: 'Farms',
parent: 'F56AAC06-D2EB-4E1C-B84D-25F72973312E',
position: 0,
umid: 'F56AAC06-D2EB-4E1C-B84D-25F72973312E',
child: [
{
enabled: true,
guid: '144C0989-9938-4AEC-8487-094C23A5F150',
level: 1,
name: 'New Farm List',
parent: 'F56AAC06-D2EB-4E1C-B84D-25F72973312E',
position: 0,
umid: '144C0989-9938-4AEC-8487-094C23A5F150'
},
{
enabled: true,
guid: '8FBA7B0B-566E-47CD-885B-1C08B57F34F6',
level: 1,
name: 'Farm Lists',
parent: 'F56AAC06-D2EB-4E1C-B84D-25F72973312E',
position: 1,
umid: '8FBA7B0B-566E-47CD-885B-1C08B57F34F6'
},
{
enabled: true,
guid: 'FCD36DBD-0639-4856-A609-549BB10BEC1A',
level: 1,
name: 'Farm Upload',
parent: 'F56AAC06-D2EB-4E1C-B84D-25F72973312E',
position: 4,
umid: 'FCD36DBD-0639-4856-A609-549BB10BEC1A'
},
{
enabled: true,
guid: '4264DA98-1295-4A65-97C6-313485744B4D',
level: 1,
name: 'Campaign',
parent: 'F56AAC06-D2EB-4E1C-B84D-25F72973312E',
position: 4,
umid: '4264DA98-1295-4A65-97C6-313485744B4D',
child: [
{
enabled: true,
guid: '9CBDC6BB-5B3D-4F53-B846-AFE55F34C1E9',
level: 2,
name: 'New Campaign',
parent: '4264DA98-1295-4A65-97C6-313485744B4D',
position: 5,
umid: '9CBDC6BB-5B3D-4F53-B846-AFE55F34C1E9'
},
{
enabled: true,
guid: '281490B5-C67D-4238-9D52-DE1DFA373418',
level: 2,
name: 'Campaign List',
parent: '4264DA98-1295-4A65-97C6-313485744B4D',
position: 6,
umid: '281490B5-C67D-4238-9D52-DE1DFA373418'
}
]
}
]
}
]
Here is my Code
function menu_sorted(input) {
let max_level = 0;
var sorted_by_level = {};
input.forEach(i => {
if (sorted_by_level.hasOwnProperty("level_" + i.level.toString())) {
sorted_by_level["level_" + i.level.toString()].push(i);
} else {
sorted_by_level["level_" + i.level.toString()] = [i];
if (i.level > max_level) {
max_level = i.level;
}
}
});
for (level = max_level; level > 0; level--) {
sorted_by_level["level_" + level.toString()].forEach(child => {
const oldparent = sorted_by_level[
"level_" + (level - 1).toString()
].filter(p => p.guid === child.parent)[0];
const parentIndex = sorted_by_level[
"level_" + (level - 1).toString()
].findIndex(p => p.guid === child.parent);
let newparent;
// delete child.guid;
// delete child.parent;
if (oldparent.hasOwnProperty("child") && oldparent.child) {
var mItems = [...oldparent.child, child];
mItems.sort((a, b) => (a.position > b.position) ? 1 : -1);
newparent = {
...oldparent,
child: mItems
};
} else {
newparent = {
...oldparent,
child: [child]
};
}
sorted_by_level["level_" + (level - 1).toString()][
parentIndex
] = newparent;
});
}
sorted_by_level.level_0.sort((a, b) => (a.position > b.position) ? 1 : -1);
return sorted_by_level.level_0
}
console.log(util.inspect(menu_sorted(mydata.nofilter),false,null,true))
See my fork of your Replit:
https://replit.com/join/wleqdijkot-mattsenne
There are additional places you need to change "child" to "items", including lines 34, 35:
if (oldparent.hasOwnProperty("items") && oldparent.items) {
var mItems = [...oldparent.items, child];
Also the 2 changes you noted above. Note that we don't change the local 'child' variable, but every reference to 'items' being a property needs to accounted for as well.

VueJs v-for with filter checkbox becomes checked on next item

Please run code snipped to see problem replicated.
I have a boolAutoFilter function with following logic:
if filter is null all items pass test
if filter is true/false item passes if prop is equal to filter val
the problem is... when bool filter is set to false, and user clicks checkbox to change value of property from false to true, that item disappears and the checkbox of the next item becomes checked even through the prop val remains false.
function boolAutoFilter(item, boolFilters) {
var returnVal = true;
for (var prop in boolFilters) {
if (!item.hasOwnProperty(prop) || boolFilters[prop] === null)
continue;
returnVal = returnVal && ((item[prop] || false) === boolFilters[prop]);
}
return returnVal;
};
window.onload = function () {
window.app = new Vue(
{
el: '#app',
data: {
filters: {
boolFilters: {
active: false,
},
stringFilters: {},
},
items: [
{ id: 1, active: false },
{ id: 2, active: false },
{ id: 3, active: false },
{ id: 4, active: false },
{ id: 5, active: false },
{ id: 6, active: false },
{ id: 7, active: false },
{ id: 8, active: false }
]
},
methods:{
mainItemFilter: function (item) {
const boolFilters = this.filters.boolFilters;
var rVal = true;
rVal = rVal && boolAutoFilter(item, boolFilters);
return rVal;
}
},
computed: {
filteredItems() {
var vm = this;
return this.items.filter(function (item) {
return vm.mainItemFilter(item);
});
}
}
})
}
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<div id="app">
<table>
<tbody>
<tr v-for="item in filteredItems">
<td>
{{item.id}}
</td>
<td>
<input type="checkbox" v-model="item.active" />
</td>
</tr>
</tbody>
</table>
{{items}}
</div>
You need to add a unique key property to all the elements you loop over in the ``v-for`, or Vue will try to reuse the same instance of the component over again so any state changes that happens to one instance will be reflected in all the other instances.
Try changing your loop to something like <tr v-for="(item, index) in filteredItems" v-bind:key="item + "_" + index" />
(Use template literals instead of item + "_" + index), its just because markdown uses the same symbols I couldn't show whilst writing code block.
I think you are having issues because you are using the "active" property for filtering items and the checked state.
I added "checked" property to the objects in items array and updated the html to use item.checked instead of item.active and the checkboxes no longer disappear.
function boolAutoFilter(item, boolFilters) {
var returnVal = true;
for (var prop in boolFilters) {
if (!item.hasOwnProperty(prop) || boolFilters[prop] === null)
continue;
returnVal = returnVal && ((item[prop] || false) === boolFilters[prop]);
}
return returnVal;
};
window.onload = function () {
window.app = new Vue(
{
el: '#app',
data: {
filters: {
boolFilters: {
active: false,
},
stringFilters: {},
},
items: [
//updated with checked
{ id: 1, active: false, checked: false },
{ id: 2, active: false, checked: false },
{ id: 3, active: false, checked: false },
{ id: 4, active: false, checked: false },
{ id: 5, active: false, checked: false },
{ id: 6, active: false, checked: false },
{ id: 7, active: false, checked: false },
{ id: 8, active: false, checked: false }
]
},
methods:{
mainItemFilter: function (item) {
const boolFilters = this.filters.boolFilters;
var rVal = true;
rVal = rVal && boolAutoFilter(item, boolFilters);
return rVal;
}
},
computed: {
filteredItems() {
var vm = this;
return this.items.filter(function (item) {
return vm.mainItemFilter(item);
});
}
}
})
}
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<div id="app">
<table>
<tbody>
<tr v-for="item in filteredItems">
<td>
{{item.id}}
</td>
<td>
<!-- updated with checked -->
<input type="checkbox" v-model="item.checked" />
</td>
</tr>
</tbody>
</table>
{{items}}
</div>
Hope this helps you get on the right track.

rerender component when my state change state in ReactJS

I have a specific problem in my React-Redux app. so I display rerender my component, when my this.state.boolean change value. A few lines of code express more than a thousand words:
Please look on my method appendTable setstate boolean to false, and when this end operations, setState boolean to true. I would like to rerender my component only then my boolean state changing state.
class TableComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
columnStatic: [{ name: "customerName", title: "Client" }],
columnsDynamic: [],
columnBands: [],
columns: [],
tableColumnExtensions: [],
percentColumns: [],
boolean: true
};
this.handleDateWeek = this.handleDateWeek.bind(this);
this.appendTable = this.appendTable.bind(this);
this.Auth = new AuthService();
}
componentDidMount() {
this.handleDateWeek();
}
componentDidUpdate(prevProps) {
if (
this.props.isLoading === false &&
prevProps.isLoading !== this.props.isLoading
)
this.appendTable();
}
handleDateWeek() {
this.props.handleLastWeek(this.props.dateFrom, this.props.dateTo);
}
appendTable() {
this.setState(
{
columnStatic: [{ name: "customerName", title: "Klient" }],
columnsDynamic: [],
columnBands: [],
columns: [],
tableColumnExtensions: [],
percentColumns: [],
boolean: false
},
() => {
var i = 1;
var j = 1;
var k = 1;
var l = 0;
var dateArray = [];
this.props.dataApi.map(dA => {
dA.data.map(dat => {
if (dateArray.indexOf(dat.date) > -1) {
return;
}
dateArray.push(dat.date);
this.setState(prevState => ({
columnsDynamic: [
...prevState.columnsDynamic,
{ name: "ordersAmount" + i++, title: "Zamówienia" },
{ name: "earnings" + i++, title: "Obrót (brutto)" }
],
columnBands: [
...prevState.columnBands,
{
title: `${dat.date}`,
children: [
{ columnName: "ordersAmount" + j++ },
{ columnName: "earnings" + j++ }
]
}
],
percentColumns: [
...prevState.percentColumns,
`ordersAmount${l++ % 2 != 0 ? l : l++}`
],
tableColumnExtensions: [
...prevState.tableColumnExtensions,
{
columnName: "ordersAmount" + k++,
width: 90,
align: "right"
},
{
columnName: "earnings" + k++,
width: 150,
align: "right"
}
],
boolean: true
}));
});
});
}
);
}
...
return (
<Fragment>
<div className="tableContainerHead">
{this.props.isLoading ? (
<Loading />
) : (
<Fragment>
<Grid
rows={dataApi}
columns={columns.concat(columnStatic, columnsDynamic)}
>
<PercentTypeProvider for={percentColumns} />
...
);
You could add the method shouldComponentUpdate():
shouldComponentUpdate(nextProps, nextState) {
return (nextState.boolean !== this.state.boolean) ? true : false;
}
It should now re-render only when the boolean property has been updated.
I suggest you change the name of that property though, as Boolean is a reserved keyword and it is very close. Perhaps something more semantically descriptive?

Indeterminate checkboxes with Vue.js

I just started out working with Vue and I'm trying to visualise a nested list.
The list-items should contain triple-state checkboxes:
When a child item is checked, the parent item's checkbox should become 'indeterminate'. When all child-checkboxes are checked, the parent checkbox should also become checked.
When a parent item checkbox is checked, all child item checkboxes (also the ones nested deeper) should be selected too.
I kind of have a working solution (check out this pen or the code below) but the checkbox-logic is still flawed. For this example, checked boxes are green, indeterminate ones are orange and unchecked ones are red.
I've run out of ideas how to fix it. Could someone shed some light on how to accomplish this in Vue?
'use strict';
Vue.component("book-chapter", Vue.extend({
name: "book-chapter",
props: ["data", "current-depth"],
data: function() {
return {
checked: this.data.checked,
indeterminate: this.data.indeterminate || false
};
},
methods: {
isChecked: function() {
return this.checked && !this.indeterminate;
},
isIndeterminate: function(){
return this.indeterminate;
},
toggleCheckbox: function(eventData) {
if (this.currentDepth > 0){
if (!this.data.children) {
this.checked != this.children
} else {
this.indeterminate = !this.indeterminate;
}
}
if (eventData) {
// fired by nested chapter
this.$emit('checked', eventData);
} else {
// fired by top level chapter
this.checked = !this.checked;
this.$emit('checked', {
data: this.data
});
}
},
isRootObject: function() {
return this.currentDepth === 0;
},
isChild: function() {
return this.currentDepth === 2;
},
isGrandChild: function() {
return this.currentDepth > 2;
}
},
template: `
<div class='book__chapters'>
<div
class='book__chapter'
v-bind:class="{ 'book__chapter--sub': isChild(), 'book__chapter--subsub': isGrandChild() }"
v-show='!isRootObject()'>
<div class='book__chapter__color'></div>
<div
class='book__chapter__content'
v-bind:class="{ 'book__chapter__content--sub': isChild(), 'book__chapter__content--subsub': isGrandChild() }">
<div class='book__chapter__title'>
<span class='book__chapter__title__text'>{{data.title}}</span>
</div>
<div class='book__chapter__checkbox triple-checkbox'>
<div class='indeterminatecheckbox'>
<div
class='icon'
#click.stop="toggleCheckbox()"
v-bind:class="{'icon--checkbox-checked': isChecked(), 'icon--checkbox-unchecked': !isChecked(), 'icon--checkbox-indeterminate': isIndeterminate()}">
</div>
</div>
</div>
</div>
</div>
<book-chapter
ref='chapter'
:current-depth='currentDepth + 1'
v-for='child in data.children'
key='child.id'
#checked='toggleCheckbox(arguments[0])'
:data='child'>
</book-chapter>
</div>
`
}));
Vue.component("book", Vue.extend({
name: "book",
props: ["data"],
template: `
<div class='book'>
<book-chapter
:data='this.data'
:currentDepth='0'>
</book-chapter>
</div>
`
}));
var parent = new Vue({
el: "#container",
data: function() {
return {
book: {}
};
},
mounted: function() {
this.book = {
"title": "Book",
"children": [{
"title": "1 First title",
"children": [{
"title": "1.1 Subtitle"
}, {
"title": "1.2 Subtitle"
}]
}, {
"title": "2 Second title",
"children": [{
"title": "2.1 Subtitle",
"children": [{
"title": "2.1.1 Sub-Sub title"
}, {
"title": "2.1.2 Another sub-sub title"
}]
}]
}]
}
}
});
Update: fixed a bug found by #PhillSlevin. See pen here
Check this pen, is it what you want to achieve?
I think you can use either eventbus or vuex to solve this problem,
if you treated every 's section as a component.
'use strict';
var bus = new Vue();
var book = {
"title": "Book",
"children": [{
"title": "1 First title",
"children": [{
"title": "1.1 Subtitle"
}, {
"title": "1.2 Subtitle"
}]
}, {
"title": "2 Second title",
"children": [{
"title": "2.1 Subtitle",
"children": [{
"title": "2.1.1 Sub-Sub title"
}, {
"title": "2.1.2 Another sub-sub title"
}]
}]
}]
};
Vue.component('book', {
template: `
<div class="book__chapter">
<p :class="'book__title ' + status" #click="clickEvent">{{title}} {{parent}}</p>
<book v-for="child in children" :key="child" :info="child"></book>
</div>
`,
props: ['info'],
data() {
return {
parent: this.info.parent,
title: this.info.title,
children: [],
status: this.info.status,
};
},
created() {
const info = this.info;
if(info.children) {
info.children.forEach(child => {
child.status = "unchecked";
// use title as ID
child.parent = info.title;
});
this.children = info.children;
}
},
mounted() {
const vm = this;
bus.$on('upside', (payload) => {
const targetArr = vm.children.filter((child) => child.title === payload.from);
if (targetArr.length === 1) {
const target = targetArr[0];
target.status = payload.status;
if (vm.children.every(ele => ele.status === 'checked')) {
vm.status = 'checked';
} else if (vm.children.every(ele => ele.status === 'unchecked')) {
vm.status = 'unchecked';
} else {
vm.status = 'indeterminate';
}
bus.$emit('upside', {
from: vm.title,
status: vm.status,
});
}
});
bus.$on('downside', (payload) => {
if (payload.from === this.parent) {
if (payload.status === 'checked') {
vm.status = 'checked';
vm.children.forEach(child => child.status = 'checked');
} else if (payload.status === 'unchecked') {
vm.status = 'unchecked';
vm.children.forEach(child => child.status = 'unchecked')
}
bus.$emit('downside', {
from: vm.title,
status: vm.status,
})
}
});
},
methods: {
clickEvent() {
if (this.status === 'checked') {
this.status = 'unchecked';
this.children.forEach(child => child.status = 'unchecked');
} else {
this.status = 'checked';
this.children.forEach(child => child.status = 'checked');
}
const vm = this;
bus.$emit('upside', {
from: vm.title,
status: vm.status,
});
bus.$emit('downside', {
from: vm.title,
status: vm.status,
});
},
}
});
var parent = new Vue({
el: "#container",
data: function() {
return {
book
};
},
});
.book__title.unchecked::after {
content: '□';
}
.book__title.indeterminate::after {
content: '△';
}
.book__title.checked::after {
content: '■';
}
.book__chapter {
display: block;
position: reletive;
margin-left: 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.js"></script>
<div id="container">
<book :info="book" :parent="'container'"></book>
</div>

Categories