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

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>

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!

What is the most reliable way to fetching the scroped CSS attribute in vuejs?

In vuejs the elements are assigned an attribute starting 'data-v-***'
I could not find any docs about fetching this value so ended up using refs and grabbing the attributes of the main node:
<template>
<div class="m-colour-picker" ref="thisContainer">
...
</div>
</template>
const attributes = this.$refs.thisContainer.getAttributeNames();
let dataAttribute = '';
attributes.forEach((attribute: string) => {
if (attribute.substring(0, 5) === 'data-') {
dataAttribute = attribute;
}
});
But it feels a little forced.. is there a method in vue to fetch this already built in?
That has little to do with Vue.js. Data attributes for any element are automatically synced with it's internal dataset object.
Example:
console.log(foobar.dataset);
console.log(foobar.dataset.vFoo);
console.log(foobar.dataset.vBar);
// notice how data attributes containing more than the initial data- dash
// are automatically transformed to camel case:
// data-v-foo-bar ===> dataset.vFooBar
console.log(foobar.dataset.vFooBar);
// if all you care about is the names of the attributes:
console.log(Object.keys(foobar.dataset));
<div id="foobar" data-v-foo="bar" data-v-bar="baz" data-v-foo-bar="foobaz"></div>

Q-Tree How to add checkbox for all nodes

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.

Event handling after HTML injection with Vue.js

Vue is not registering event handler for HTML injected objects. How do I do this manually or what is a better way to work around my problem?
Specifically, I send a query to my server to find a token in text and return the context (surrounding text) of that token as it exists in unstructured natural language. The server also goes through the context and finds a list of those words that also happen to be in my token set.
When I render to my page I want all of these found tokens in the list to be clickable so that I can send the text of that token as a new search query. The big problem I am having is my issue does not conform to a template. The clickable text varies in number and positioning.
An example of what I am talking about is that my return may look like:
{
"context": "When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected",
"chunks": ['human events', 'one people', 'political bands']
}
And the resulting output I am looking for is the sentence looks something like this in psuedocode:
When in the Course of <a #click='search("human events")'>human events</a>, it becomes necessary for <a #click='search("one people")'>one people</a> to dissolve the <a #click='search("political bands")'>political bands</a> which have connected
This is what I have tried so far though the click handler is not registered and the function never gets called:
<v-flex xs10 v-html="addlink(context.context, context.chunks)"></v-flex>
and in my methods section:
addlink: function(words, matchterms){
for(var index in matchterms){
var regquery = matchterms[index].replace(this.regEscape, '\\$&');
var query = matchterms[index];
var regEx = new RegExp(regquery, "ig");
words = words.replace(regEx, '<a href=\'#\' v-on:click.prevent=\'doSearch("'+ query +'")\'>' + query + '</a>');
}
return words;
}
As I said, this does not work and I know why. This is just showing that because of the nature of the problem is seems like regex is the correct solution but that gets me into a v-html injection situation. Is there something I can do in Vue to register the event handlers or can some one tell me a better way to load this data so I keep my links inline with the sentence and make them functional as well?
I've already posted one answer but I've just realised that there's a totally different approach that might work depending on your circumstances.
You could use event delegation. So rather than putting click listeners on each <a> you could put a single listener on the wrapper element. Within the listener you could then check whether the clicked element was an <a> (using event.target) and act accordingly.
Here's one way you could approach it:
<template>
<div>
<template v-for="segment in textSegments">
<a v-if="segment.link" href="#" #click.prevent="search(segment.text)">
{{ segment.text }}
</a>
<template v-else>
{{ segment.text }}
</template>
</template>
</div>
</template>
<script>
export default {
data () {
return {
"context": "When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected",
"chunks": ['human events', 'one people', 'political bands']
}
},
computed: {
textSegments () {
const chunks = this.chunks
// This needs escaping correctly
const re = new RegExp('(' + chunks.join('|') + ')', 'gi')
// The filter removes empty strings
const segments = this.context.split(re).filter(text => text)
return segments.map(segment => {
return {
link: segment.match(re),
text: segment
}
})
}
},
methods: {
search (chunk) {
console.log(chunk)
}
}
}
</script>
I've parsed the context text into an array of segments that can then be handled cleanly using Vue's template syntax.
I've used a single RegExp and split, which will not discard matches if you wrap them in a capture group, (...).
Going back to your original example, v-html only supports native HTML, not Vue template syntax. So you can add events using onclick attributes but not #click or v-on:click. However, using onclick wouldn't provide easy access to your search method, which is scoped to your component.

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.