Q-Tree How to add checkbox for all nodes - vue.js

I have dynamic data which is fetched from server. I'm using Vue.js and Q-Tree by Quasar framework.
When the data is static the checkbox (tick-strategy = 'leaf') works.
But when data is fetched from server I cannot understand why the checkbox is not shown, and one more interesting thing - the checkbox will be visible when I expand level until leaf
the code is a standard:
In mount life circle I have promise which fetch the data.
data () {
return {
allData: []
}
}
mount () {
Promise.all(....)
.then (data => { this.allData = data;})
}
So and now in template I have:
<q-tree
:nodes="allData"
node-key="id"
#lazy-load...
:tick-strategy="leaf"
:ticked.sync="ticked"
...
So, now the nodes are shown without checkbox, and appears until expand to leaf.

Related

Stuck with $ref pointing to Proxy object with vue-router

I was using VueJS in browser mode and am now trying to switch my code to a VueJS SPA and vue-router. I've been stuck for hours with a $refs not working anymore.
To interact with my Google Charts, I was using an absolute reference to the graph (this.$refs.villesChart) to get selected data like that:
computed: {
eventsprox() {
let eventsprox = {
select: () => {
var selection = "";
if (this.$refs.villesChart) selection = this.$refs.villesChart1.chartObject.getSelection();
if (selection.length) {
var row = selection0[0].row + 1;
this.code_commune = this.dataprox[row][4];
this.changerville(this.code_commune, this.dataprox[row][0]);
}
return false;
},
};
return eventsprox;
}
HTML code for graph:
<GChart type="BarChart" id="villesChart" ref="villesChart" :data="dataprox" :options="optionsprox" :events="eventsprox"/>
I don't know why, but in browser mode, this.$refs.villesChart is a component:
[1]: https://i.stack.imgur.com/xJ8pV.png
but now it is a proxy object, and lost its chartObject attribute:
[2]: https://i.stack.imgur.com/JyXrL.png
I'm really confused. Do you have an idea why?
And if I use the proxy object, then I get a Vue warning "Avoid app logic that relies on enumerating keys on a component instance" and it is not working in production environment.
Thanks a lot for your help!!
After hours of testing different solutions, I finally found a solution working with Vue3 and Vue-google-chart 1.1.0.
I got rid of "refs" and put the events definition and code in the data section of my Vue 3 app (instead of computed) and accessed the chart data through a component variable I used to populate it.
Here is my event code where this.dataprox is my data table for the chart:
eventsprox: {
'click': (e) => {
const barselect = parseInt(e.targetID.split('#')[2]) + 1;
this.code_commune = this.dataprox[barselect][4];
this.nom_commune = this.dataprox[barselect][0];
this.changerville(this.code_commune, this.nom_commune);
}
},
My Gchart html code:
<GChart type="AreaChart" :data="datag" :options="optionsg" :events="eventsprox"/>
I hope it can help!

Vuejs - update array of an object which is in an array

I'm developing a helpdesk tool in which I have a kanban view.
I previously used nested serializers in my backend and I managed to have everything working with a single query but it's not scalable (and it was ugly) so I switched to another schema :
I query my helpdesk team ('test' in the screenshot)
I query the stages of that team ('new', 'in progress')
I query tickets for each stage in stages
So when I mount my component, I do the following :
async mounted () {
if (this.helpdeskTeamId) {
await this.getTeam(this.helpdeskTeamId)
if (this.team) {
await this.getTeamStages(this.helpdeskTeamId)
if (this.stages) {
for (let stage of this.stages) {
await this.getStageTickets(stage)
}
}
}
}
},
where getTeam, getTeamStages and getStageTickets are :
async getTeam (teamId) {
this.team = await HelpdeskTeamService.getTeam(teamId)
},
async getTeamStages (teamId) {
this.stages = await HelpdeskTeamService.getTeamStages(teamId)
for (let stage of this.stages) {
this.$set(stage, 'tickets', [])
}
},
async getStageTickets (stage) {
const tickets = await HelpdeskTeamService.getTeamStageTickets(this.helpdeskTeamId, stage.id)
// tried many things here below but nothing worked.
// stage.tickets = stage.tickets.splice(0, 0, tickets)
// Even if I try to only put one :
// this.$set(this.stages[this.stages.indexOf(stage)].tickets, 0, tickets[0])
// I see it in the data but It doesn't appear in the view...
// Even replacing the whole stage with its tickets :
// stage.tickets = tickets
// this.stages.splice(this.stages.indexOf(stage), 1, stage)
},
In getTeamStages I add an attribute 'tickets' to every stage to an empty list. The problem is when I query all the tickets for every stage. I know how to insert a single object in an array with splice or how to delete one object from an array but I don't know how to assign a whole array to an attribute of an object that is in an array while triggering the Vue reactivity. Here I'd like to put all the tickets (which is a list), to stage.tickets.
Is it possible to achieve this ?
If not, what is the correct design to achieve something similar ?
Thanks in advance !
EDIT:
It turns out that there was an error generated by the template part. I didn't think it was the root cause since a part of the view was rendered. I thought that it would have prevent the whole view from being rendered if it was the case. But finally, in my template I had a part doing stage.tickets.length which was working when using a single query to populate my view. When making my API more granular and querying tickets independently from stages, there is a moment when stage has no tickets attribute until I set it manually with this.$set(stage, 'tickets', []). Because of that, the template stops rendering and raises an issue. But the ways of updating my stage.tickets would have worked without that template issue.
I could update the stages reactively. Here is my full code; I used the push method of an array object and it works:
<template>
<div>
<li v-for="item in stages" :key="item.stageId">
{{ item }}
</li>
</div>
</template>
<script>
export default {
data() {
return {
stages: [],
};
},
methods: {
async getTeamStages() {
this.stages = [{ stageId: 1 }, { stageId: 2 }];
for (let stage of this.stages) {
this.$set(stage, "tickets", []);
}
for (let stage of this.stages) {
await this.getStageTickets(stage);
}
},
async getStageTickets(stage) {
const tickets = ["a", "b", "c"];
for (let ticket of tickets) {
this.stages[this.stages.indexOf(stage)].tickets.push(ticket);
}
},
},
mounted() {
this.getTeamStages();
},
};
</script>
It should be noted that I used the concat method of an array object and also works:
this.stages[this.stages.indexOf(stage)].tickets = this.stages[this.stages.indexOf(stage)].tickets.concat(tickets);
I tried your approaches some of them work correctly:
NOT WORKED
this.$set(this.stages[this.stages.indexOf(stage)].tickets, tickets)
WORKED
this.$set(this.stages[this.stages.indexOf(stage)].tickets, 0, tickets[0]);
WORKED
stage.tickets = tickets
this.stages.splice(this.stages.indexOf(stage), 1, stage)
I'm sure it is XY problem..
A possible solution would be to watch the selected team and load the values from there. You seem to be loading everything from the mounted() hook, and I suspect this won't actually load all the content on demand as you'd expect.
I managed to make it work here without needing to resort to $set magic, just the pure old traditional vue magic. Vue will notice the properties of new objects and automatically make then reactive, so if you assign to them later, everything will respond accordingly.
My setup was something like this (showing just the relevant parts) -- typing from memory here, beware of typos:
data(){
teams: [],
teamId: null,
team: null
},
watch:{
teamId(v){
this.refreshTeam(v)
}
},
methods: {
async refreshTeam(id){
let team = await fetchTeam(id)
if(!team) return
//here, vue will auomaticlly make this.team.stages reactive
this.team = {stages:[], ...team}
let stages = await fetchStages(team.id)
if(!stages) return
//since this.team.stages is reactive, vue will update reactivelly
//turning the {tickets} property of each stage reactive also
this.team.stages = stages.map(v => ({tickets:[], ...v}))
for(let stage of this.team.stages){
let tickets = await fetchTickets(stage.id)
if(!tickets) continue
//since tickets is reactive, vue will update it accordingly
stage.tickets = tickets
}
}
},
async mounted(){
this.teams = fetchTeams()
}
Notice that my 'fetchXXX' methods would just return the data retrieved from the server, without trying to actually set the component data
Edit: typos

How to keep the v-treeview state after page refresh?

I am trying to understand how to keep the state of the treeview after a page refresh.
For example, I have a treeview of folders. I click on a folder it should expand.
After that, I can click on that item inside the folder (is a link to a page, therefore the whole page is refreshed.)
The problem is the treeview closes the folders back up again and doesn't keep its previous state.
I need it to be static so even after page reload the treeview does not lose its state.
treeview
This bit of code is my treeview:
v-list.py-2(v-else, dense)
v-treeview(v-model="tree", :items="treeData", activatable, :open.sync="openIds", #update:open="getOpenIds", item-key="name", open-on-click)
template(v-slot:prepend="{item, open}")
v-list-item(v-if="item.isFolder", link, :href='`/` + item.locale + `/` + item.path', :input-value='path === item.path')
v-list-item-avatar(size="24", tile)
v-icon {{ open ? 'mdi-folder-open' : 'mdi-folder' }}
v-list-item-title {{item.title}}
v-list-item.noFolders(v-else, :href='`/` + item.locale + `/` + item.path', :key='`childpage-` + item.id', :input-value='path === item.path')
v-list-item-avatar(size="24", tile)
v-icon mdi-text-box
v-list-item-title {{item.title}}
Also, I should mention that the data is dynamic. I am generating the tree (as an Object) using a method.
async buildTree(items){
this.$store.commit(`loadingStart`, 'browse-load')
for(let item of items){
if(item.isFolder){
let result = await this.$apollo.query({
query: gql`
query ($parent: Int, $locale: String!) {
pages {
tree(parent: $parent, mode: ALL, locale: $locale) {
id
path
title
isFolder
pageId
parent
locale
}
}
}
`,
fetchPolicy: 'cache-first',
variables: {
parent: item.id,
locale: this.locale
}
})
item.children = _.get(result, 'data.pages.tree', [])
for(let kid of item.children){
if(item.isFolder){
//kid.name = ""
kid.children = []
}
}
this.buildTree(item.children)
console.log(this.treeData )
}
}
this.$store.commit(`loadingStop`, 'browse-load')
And that's actually the method that is supposed to save the tree state to the local storage.
getOpenIds(items){
const memory = JSON.stringify(items)
this.sessionIds = sessionStorage.setItem('items2', memory)
this.openIds = JSON.parse(sessionStorage.getItem('items2'))
console.log(this.openIds)
},
I have tried the suggestions but for some reason, the tree doesn't react to the local storage. IS there something to do with the fact that the method is async?
You can use one of these 2 possibilities below (there are probably more):
Local storage
You can use localStorage to keep a record of all your folders' state.
Then you can add a computed property to get the data from the local storage if it exists and apply it inside your component.
Next time you can add a JSfiddle with your example so someone could provide you with a detailed solution :)
How to use: when someone clicks on a folder you should have a v-on:click handler where you store in local storage the new state
function onClickHandler(folderName: string, isOpen: boolean) {
localStorage.setItem(folderName, isOpen);
}
The next thing you should do is get the state for each folder from local storage and apply it - if it doesn't exist just return false.
function getFolderState(folderName: string) {
localStorage.getItem(folderName);
}
Apply it on the v-list-item element and make sure to pass in the same folder name for both functions.
Vue dynamic component
There is a built-in feature in Vue that keeps your component alive instead of dismounting when it's removed from the DOM.
You can use the keep-alive element to wrap your component. It will result in keeping all the component's state when switching between screens.
<keep-alive>
<your-component />
</keep-alive>

Vue-Native checkbox change value

I want to be able to change the value of a checkbox by clicking on it. recentContacts are loading just fine, and specifying initial checked values in the computed function works well. The :on-press seems to change the value but does not reflect in the UI.
Please Help
Template
<nb-list>
<nb-list-item v-for="contact in recentContacts" v-bind:key="contact.uid">
<nb-checkbox :on-press="() => contact.checked =! contact.checked" :checked="contact.checked"></nb-checkbox>
<nb-text>{{contact.firstName}} {{contact.lastName}}</nb-text>
</nb-list-item>
</nb-list>
Code
export default {
computed: {
recentContacts() {
return store.state.admin.userData.recentContacts.map(rc => {
rc.checked = false;
return rc;
});
}
},
}
EDIT:
I am guessing because VUEX is imutable. I've got this to work by having recentContacts inside of the data attribute instead of computed just not how I want to do things.

element not updated when data changes

I have a vaadin-checkbox:
<vaadin-checkbox id=[[item.id]] disabled="true" checked="[[item.checked]]">[[item.description]]</vaadin-checkbox>
I defined my properties:
static get properties() {
return {
items: {
type: Array,
notify: true,
value: function() {
return [];
}
}
};
}
When I now change the data by pressing some button:
_selectItem(event) {
const item = event.model.item;
if (item.checked === true) {
this.$.grid.deselectItem(item);
} else {
this.$.grid.selectItem(item);
}
item.checked = !item.checked;
}
The state of the checkbox is still checked="true". Why isnt the checkbox getting updated? The same thing when I change the description of the item:
_selectItem(event) {
event.model.item.description = 'test';
}
The test description is never appearing. The checkbox is never getting updated.
The reason why the checkbox does not get updated by the button click handler is in the Polymer 2 data system. Polymer does not detect the change and does not update the template accordingly.
In order to make the change in a way that Polymer would detect it you have two options:
Use this.set(`items.${event.model.index}.checked`, !item.checked) if you can reliably assume that the index used by dom-repeat always matches that elements's index in the items array (it is not the case if you use sorting or filtering features of dom-repeat). See an example here https://jsfiddle.net/vlukashov/epd0dn2j/
If you do not know the index of the updated item in the items array, you can also use the Polymer.MutableData mixin and notify Polymer that something has changed inside the items array without specifying the index of the changed item. This is done by calling this.notifyPath('items') after making a change. However, this requires that your element extends the Polymer.MutableData mixin, and that dom-repeat has the mutable-data attribute set. See an example here: https://jsfiddle.net/vlukashov/epd0dn2j/24/
More information on this in the Polymer 2 docs.