Here I am generating a dynamic tree structure using my json and angular2 - tree component and till every thing is fine now, I am unable to generate the selection events ad when ever we select the events that particular names have to be selected as objects if child is there and I tried this URL and in the documentation also I didn't find any methods for getting the selected valules so please, suggest me on that.
https://angular2-tree.readme.io/docs
below is my code
options = {
useCheckbox: true
};
nodes;
data = {
"info": {
"laptop": {
},
"config": {
"properties": {
"ram": {
},
"processor": {
},
"hdd": {
}
}
},
"link": {
},
"name": {
},
"company": {
"properties": {
"model": {
},
"maker": {
"type": "integer"
},
"country": {
"type": "text"
},
"enterprise": {
}
}
}
}
};
check(){
const results = Object.keys(this.data.info).map(k => ({
name: k,
children: this.data.info[k].properties
? Object.keys(this.data.info[k].properties).map(kk => ({ name: kk }))
: []
}));
this.nodes = results;
}
.html code
<button type="button" (click)="check()">click</button>
<hr>
<input id="filter" #filter (keyup)="tree.treeModel.filterNodes(filter.value)" placeholder="filter nodes" />
<button (click)="tree.treeModel.clearFilter()">Clear Filter</button>
<tree-root #tree [focused]="true" [options]="options" [nodes]="nodes"></tree-root>
stackblitz link
https://stackblitz.com/edit/angular-kh28sg
I don't know if there a better way You can access to the selected nodes using tree.treeModel.selectedLeafNodeIds. You must before check is isSelected or not
See the docs/API
For example if you has a button
<!--I like pass as argument the property "treeModel" of #tree ("reference variable")-->
<button (click)="click(tree.treeModel)">sendData</button>
<tree-root #tree [focused]="true" [options]="options" [nodes]="nodes"></tree-root>
Your function click can be like
click(tree:TreeModel)
{
console.log(tree.activeNodes);
Object.keys(tree.selectedLeafNodeIds).forEach(x=>{
let node:TreeNode=tree.getNodeById(x);
if (node.isSelected)
{
console.log("Selected:",node.data.name,
"Parent:",node.parent.data.name);
}
})
}
NOTE: I forked your stackblitz
Updated create an object with the response
click(tree: TreeModel) {
console.log(tree.activeNodes);
let result: any = {} //<--declare a variable
Object.keys(tree.selectedLeafNodeIds).forEach(x => {
let node: TreeNode = tree.getNodeById(x);
if (node.isSelected) {
console.log("Selected:", node.data.name,
"Parent:", node.parent.data.name);
if (node.parent.data.name) //if the node has parent
{
if (!result[node.parent.data.name]) //If the parent is not in the object
result[node.parent.data.name] = {} //create
result[node.parent.data.name][node.data.name] = true;
}
else {
if (!result[node.data.name]) //If the node is not in the object
result[node.data.name] = {} //create
}
}
})
console.log(result);
}
I couldn't get the accepted answer to work, my tree.getNodeById(x) would always return null
So I used the action mapping:
Add IActionMapping to your Tree component
actionMapping: IActionMapping = {
// ...
checkboxClick: (tree, node) => {
node.data.checked = !node.data.checked;
this.setCheckedNodes(node.id, node.data.checked);
}
}
Method to store the values in a service:
private setCheckedNodes(id: string, checked: boolean) {
if (!this.treeService.selectedIds) {
this.treeService.selectedIds = new Array<[string, boolean]>();
}
const checkedNode = this.treeService.selectedIds.find(cn => cn[0] === id);
if (checkedNode) {
if (checkedNode[1] !== checked) {
checkedNode[1] = checked;
this.treeService.selectedIds[id] = checkedNode;
}
} else {
this.treeService.selectedIds.push([id, checked]);
}
}
Then on some select event get or emit the values stored in the service:
export class treeService {
// ...
public selectedIds: Array<[string, boolean]>;
}
Probably want to clear them afterwards
Related
hoping this is a easy question but I can't seem to figure it out.
I'm attempting to append a key to a object that is held in state. This key and value pair don't exist prior which I think is whats giving me the problem. Anyways here's what I have so far:
const Reducer = (state, action) => {
switch (action.type) {
case "SET_STUDENT_TAGS":
const userID = action.payload.id;
let studentIndex = state.originalStudentList.students.findIndex(obj => obj.id === action.payload.id);
return {
...state,
originalStudentList: {
...state.originalStudentList,
students: {
[studentIndex]: {
tags: "test"
}
}
}
};
Now the issue i'm running into is that I can run findIndex once but any attempt after just crashes my app saying its not a function. Simiarly the set state logic in the reducer there doesn't work either as it can't find "tags". So I guess i'm at a loss of how to do this. Here's how the data that is getting fed in looks:
{
"students": [
{
"id": "1",
},
{
"id": "2",
}
]
}
here's the function from the input box:
const addTag = (event, student) => {
if (event.key === "Enter") {
if (event.target.value != "") {
dispatch({
type: "SET_STUDENT_TAGS",
payload: {
id: student.id,
value: event.target.value,
},
});
event.target.value = "";
} else {
return;
}
}
};
what I'd like to achieve:
{
"students": [
{
"id": "1",
"tags": ["cat", "dog"]
},
{
"id": "2",
"tags": ["cat", "parrot"]
}
]
}
So i'm at a lost as to how to do this correctly. Appreciate any help!
You're on the right track, but that syntax replaces the array with a regular object, which is why findIndex fails after the first time. For more info, see this answer.
And then to add a property to the specific student, you just replace it with an object spreading the original and adding the tags:
const Reducer = (state, action) => {
switch (action.type) {
case "SET_STUDENT_TAGS":
const userID = action.payload.id;
let studentIndex = state.originalStudentList.students.findIndex(obj => obj.id === action.payload.id);
let student = state.originalStudentList.students[studentIndex];
return {
...state,
originalStudentList: {
...state.originalStudentList,
students: [
state.originalStudentList.students.slice(0, studentIndex),
{
...student,
tags: action.payload.tags
},
state.originalStudentList.students.slice(studentIndex+1)
]
}
};
In my react app i'm coding a small photo gallery, with a GraphQL query i get all the images in a folder in this format:
{
"data": {
"allFile": {
"edges": [
{
"node": {
"childImageSharp": {
"fluid": {
"aspectRatio": 0.7518796992481203,
"originalName": "music_01.jpg"
}
}
}
},
{
"node": {
"childImageSharp": {
"fluid": {
"aspectRatio": 1.3333333333333333,
"originalName": "music_02.jpg"
}
}
}
},
{
"node": {
"childImageSharp": {
"fluid": {
"aspectRatio": 0.7518796992481203,
"originalName": "food_01.jpg"
}
}
}
}
]
}
},
"extensions": {}
}
it return about 50 entries, that (using links on the page) i need to filter out, something like a cateogry, where the category name is based on regex /category/ (or with indexOf) (music, foood and so) in the filename.
i was thinking to use a state to keep the original data separated from the filtered one, but it looks im not able to filter out the data to keep only the needed one.
my approach was something like
const Portofolio = ({data}) =>{
const [filtered, setFiltered]=useState();
function onFilterData(filter) {
//scroll through data and keep only the entries that match filter
//assign the kept data to filtered with setFiltered(keptdata)
}
return (
//render the gallery from filtered object
)
}
but im stuck on the filtered part and i cant get out of it!
any suggestion?
You can use useMemo to save the filtered list and avoid to use useEffect and setState and with this approach you save one render every time that your data changed.
You need something like this:
const filteredList = React.memo(() => {
return data.filter(({ childImageSharp: { fluid } }) => {
return fluid. originalName.indexOf(filter) > -1;
})
}, [data, filter])
data: is all your items list
filter: is the route that need to match with the item name
You use JS filter, check if your originalName exists and if it matches with your filter that comes from the fn argument
function onFilterData(filter = 'music') {
const edges = data?.allFile?.edges;
if (edges) {
const target = edges.filter(edge => {
const fileName = edge?.node?.childImageSharp?.fluid?.originalName;
if (fileName && fileName.includes(filter)) {
return true;
}
return false;
});
setFiltered(target);
}
}
I have the following code which is changing the value of the options key in the inputType multipleChoice object the main object is called cards and I need to set the state to the newTasksIdsArray
I want the multipleChoice.options to have the newArray values so I can setthe state to them
Update: more info
I guess what I am trying to say is I need the a variable to setlocalstroage eventually to be the entireObject with the optiosn array replaced with whatever would be in newTasksIdsArray which i have generated, just not included because It't probably not neccesary
Thanks!
I need my final result to be
[
{
"inputType":"multipleChoice",
"uniId":"bzR7bpwzjMxcBEdSF",
"label":"Preferred Method of Contact",
"value":"2813348004",
"multipleChoice":{
"options":NEW ARRAY GOES HERE
}
},
...
KEEP REST OF OBJECT
]
let test = [];
cards.map((card) => {
if (card.inputType === "multipleChoice") {
//console.log(card);
card.multipleChoice.options = newTasksIdsArray;
test.push(...cards);
}
});
console.log(test);
setCards(test)
//update localstorage with test as well
This is the cards object
[
{
"inputType":"multipleChoice",
"uniId":"bzR7bpwzjMxcBEdSF",
"label":"Preferred Method of Contact",
"value":"2813348004",
"multipleChoice":{
"options":[
{
"uniId":"gJ8N6sAJrZZvCcPkp",
"label":"Cell Phone",
"checked":false
},
{
"uniId":"Ha9rmssmRkGzpRTn7",
"label":"Email",
"checked":true
}
]
}
},
{
"inputType":"shortText",
"uniId":"AkvioWe6D2ahgDCbW",
"label":"First Name:",
"value":"Kanye",
"multipleChoice":{
}
},
{
"inputType":"phoneNumber",
"uniId":"xwbBBnT2D69QJHHuL",
"label":"Cell Phone Number",
"value":"2813348004",
"multipleChoice":{
}
},
{
"inputType":"email",
"uniId":"62fDs7JtTF4MxMvww",
"label":"Work Email",
"value":"kanye#usa.gov",
"multipleChoice":{
}
},
{
"inputType":"address",
"uniId":"pKAwHmRJKCcKMz8LN",
"label":"Home Address",
"value":"123 White House Avenue",
"multipleChoice":{
}
},
{
"inputType":"dropDown",
"uniId":"K3o689k8G2ZrWEfQc",
"label":"How did you find us?",
"value":"2813348004",
"dropDown":{
"uniId":"3r9gzPXXjidq9p4fw",
"options":[
{
"uniId":"7hGYT4jv89WxFveaj",
"label":"Google"
},
{
"uniId":"J2K2W6P4BR7ZEGEao",
"label":"Referral"
}
]
},
"multipleChoice":{
}
}
]
I guess the cards state was already updated by the reaasignment of the value in the map function so this just works fine to store to localstorage
cards.map((card) => {
if (card.inputType === "multipleChoice") {
card.multipleChoice.options = newTasksIdsArray;
}
});
window.localStorage.setItem("inputs", JSON.stringify(cards)
I am trying to update the roll property in an object which is nested in the players array.
My state looks like this:
players: [
{
"id": "44ufazeep0o",
"name": "test-player-1",
"roll": 0,
"totalWins": 0
},
{
"id": "eu8hutex7z9",
"name": "test-player-2",
"roll": 0,
"totalWins": 0
}
]
This is my action:
export const addRoll = (roll, id) => {
return {
type: ADD_ROLL,
roll,
id,
}
}
This is my reducer:
case ADD_ROLL:
return state.players.map((player) => {
if (player.id === action.id) {
return {
...player,
roll: action.roll,
}
}
return player;
});
...
I am watching state.players in an components like so:
import { connect } from 'pwa-helpers'; // connects a Custom Element base class to the Redux store
...
stateChanged(state) {
this.players = state.players;
}
render() {
return html`${this.players.map((player)=> html`<h1>${player.name}</h1>`)}`
}
Now, whenever I call store.dispatch(addRoll(this.roll, this.id)), this.players.map() returns undefined in the component where I am watching state.players.
Any input on why this might happen is much appreciated!
You have return an array instead of an object with players key in it from state after ADD_ROLL action is dispatched. Correct way to update it would be
case ADD_ROLL:
return {
...state,
players: state.players.map((player) => {
if (player.id === action.id) {
return {
...player,
roll: action.roll,
}
}
return player;
});
}
...
I am trying to create a tree with an add function using computed data. I used the tree-view example in vuejs official homepage and combined it with the computed function that I created but found no luck in implementing it. I've been trying to solve this for 4 days already and still no luck so I am here looking for help.
When you click the "+" in the end of the list it will trigger a call to the addChild function and it will successfully append the data. The data gets appended but the recursive component is not reactive.
https://jsfiddle.net/znedj1ao/9/
var data2 = [{
"id": 1,
"name":"games",
"parentid": null
},
{
"id": 2,
"name": "movies",
"parentid": null
},
{
"name": "iron-man",
"id": 3,
"parentid": 2
},
{
"id": 4,
"name": "iron-woman",
"parentid": 3
}
]
// define the item component
Vue.component('item', {
template: '#item-template',
props: {
model: Object
},
data: function () {
return {
open: false
}
},
computed: {
isFolder: function () {
return this.model.children &&
this.model.children.length
}
},
methods: {
toggle: function () {
if (this.isFolder) {
this.open = !this.open
}
},
changeType: function () {
if (!this.isFolder) {
Vue.set(this.model, 'children', [])
this.addChild()
this.open = true
}
},
addChild: function () {
this.model.children.push({
name: 'My Tres',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
})
}
}
})
// boot up the demo
var demo = new Vue({
el: '#demo',
data: {
treeData2: data2
},
computed: {
nestedInformation: function () {
var a= this.nestInformation(data2);
return a;
}
},
methods:
{
nestInformation: function(arr, parent){
var out = []
for(var i in arr) {
if(arr[i].parentid == parent) {
var children = this.nestInformation(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}
}
})
<!-- item template -->
<script type="text/x-template" id="item-template">
<li>
<div
:class="{bold: isFolder}"
#click="toggle"
#dblclick="changeType">
{{model.name}}
<span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
</div>
<ul v-show="open" v-if="isFolder">
<item
class="item"
v-for="model in model.children"
:model="model">
</item>
<li class="add" #click="addChild">+</li>
</ul>
</li>
</script>
<p>(You can double click on an item to turn it into a folder.)</p>
<!-- the demo root element -->
<ul id="demo">
<item
class="item"
:model="nestedInformation[1]">
</item>
</ul>
The Vue.js documentation that Abdullah Khan linked to in a comment above says:
Again due to limitations of modern JavaScript, Vue cannot detect property addition or deletion.
However, property addition is exactly what you are doing in your nestInformation method:
if(children.length) {
arr[i].children = children
}
The result is that the children property of each object is not reactive, so when this Array is pushed to in addChild, no re-render is triggered in the UI.
The solution will be to use Vue.set when creating the children Arrays so that they will be reactive properties. The relevant code in the nestInformation method must be updated to the following:
if (children.length) {
Vue.set(arr[i], 'children', children);
}
I have forked and modified your fiddle for reference.