VueJS + Vue.Draggable + Vuex Store + Computed Variables - vue.js

Is there a way to use Vue.Draggable lists with computed variables in a Vuex Store? I'm fairly new to VueJS and this is my current component setup:
// Template
<draggable class="list-group" :list="list1" group="people">
<div
class="list-group-item"
v-for="(element, index) in list1"
:key="element.id"
>{{ element.name }} ({{ element.list }}) ({{ index }})</div>
</draggable>
// Script
computed: {
list1: {
return this.$store.getters.listItems.filter(item => item.list === 1)
}
}
// Store
const getters = {
listItems: (state) => {
return state.items
}
}
Without computed variables, everything works fine. When using computed variables, I can drag list items between lists, however the UI won't update because it's computed from the store.
Basically, what I need is to display multiple lists based on an input file (json). These lists have list items that can be dragged between different lists (with Vue.Draggable). Also, the user can create new list items / delete existing list items.
What's the best way to achieve this?
Thank you in advance.

In a complex draggable use-case, also using vuex, I ended up creating a variable in state called 'changes', and every time something changes, it gets incremented (through a vuex mutation). The data that's linked to dragging is in data, not in the store. I manually sync the two.
I use this - through a watcher - to trigger a rerender of my base component with completely fresh data.
This solution is ugly, and likely won't be necessary in vue3.

You probable need to do the following:
Have your draggable div into a new component
On this new component, have this #change.self="handleDragAndDrop($event)"
Handle the D&D
handleDragAndDrop: function (event){
const eventType = Object.keys(event)[0];
const chosenFrame = event[eventType].element;
store.dispatch(
"updateFramesOrder",
{
event: event,
listItemId: this.$props.itemId,
}
);
}
Finally in the store create the updateFramesOrder action that changes the list accordingly.
Hope it helps!

Related

Why in my nuxt-link doesn't reload page with same url?

If I’m on a page with the URL 'http://localhost:8080/item' and I’m clicking on the same link on this page, then the page does not reload.
I need to make that if I click on the same link, the page will reload.
My link:
<nuxt-link :to="/item">
Any insight will be welcome. Thanks!
Use key, something like:
<router-view :key="$route.params.yourCustomParam"/>
Also you can use something like:
<router-link :to="{ params: { yourCustomParam: Data.now } }" replace>link</router-link>
Remember to is passed router.push() and it accept an object also. Doing that, it is more declarative and controllable. I'm using this to decide if the page of component should be rerendered since they will based on id params obtained from URL entry, and my child component can still using nesting .
I recently tried to solve a similar issue and to overcome this I used Vuex with :key (ref).
Firstly, in your store you need a state property such as:
export const state = () => ({
componentUpdates: {
item: 0,
//can add more as needed
}
})
In general, you could use only one property across the app if you prefer it that way. Just remember that later on, the key value needs to be unique - that is in the case if you used this property for two or more components within one page, for example. In this case, you could do something like this :key="$store.getters.getComponentUpdates.item+'uniqueString'"
then a getter:
export const getters = {
getComponentUpdates(state) {
return state.updateComponent;
}
}
finally a mutatation:
export const mutations = {
updateComponent(state, payload) {
return state.componentUpdates[payload.update]++
}
}
Now we can utilise the reactive :key wherever needed.
But first in your nuxt-link lets add an event to trigger the mutation, note the usage of #click.native to trigger the click event:
<nuxt-link #click.native="$store.commit('updateComponent', { update: 'item'})" :to="/item">
Now in the item page, for example. Let's imagine there is a component that needs to be updated. In this case we would add :key to it:
<my-item :key="$store.getters.getComponentUpdates.item" />
That is it. As you can see this solution utilises the benefits of nuxt-link but also allows us to selectively update only parts of our page that need updates (we could update the entire page this way as well if needed).
In case if you needed to trigger the logic from mounted or initial load in general, then you could use computed property and :key to your div container, right inside the <template> of your page.
Add :key to the div:
<template>
<div :key="$store.getters.getComponentUpdates.item"></div>
</template>
Create computed property:
computed: {
updateItemPage() {
//run your initial instructions here as if you were doing it in mounted then return the getter
this.initialLoadMethod()
return this.$store.getters.getComponentUpdates.item
}
}
The final touch, which is not crucial but can be implemented in order to reset the state property:
export const mutations = {
updateComponent(state, payload) {
return state.componentUpdates[payload.update] >= 10
? state.componentUpdates[payload.update] = 0
: state.componentUpdates[payload.update]++
}
}

Mutiple Data Source for a component

I am in a process to support multiple data source to show data in a component via props & store.
The idea is to build a list component, which will load the data from the store if no props present. Otherwise, show the data from props. Thus, I am ensuring reusability in the context of search functionality and normal listing view.
Here is the code looks like,
<template>
<div>
<li v-for="user in this.dataSource" :key="user.age">
{{ user.name }}
</li>
</div>
</template>
<script>
export default {
props: {
userData: {
type: Array,
default: null,
}
},
created() {
// dispatch action to get user data
this.$store.dispatch("GET_USER_DATA");
},
data() {
return {
dataSource: this.userData !== null ? this.userData: this.$store.state.users
};
}
};
As of now, the store holds just static data. But in the context of REST it will be async in nature. So sometimes, I see no data.
So my question is that is this logic of dataSource can be improved further?
Thanks
Robin.
You should change dataSource to computed property. It will automatically re-computed when this.userData is changed or this.$store.state.users is changed
computed: {
dataSource() {
return this.userData !== null ? this.userData: this.$store.state.users
}
}
For more information about computed in Vuejs, please check the document
This is a bad approach anyways.
Store should be used to contain the data and components should just "show" it and manipulate it through actions and mutations.
So in this case, I'd remove the props, created and data part and add a getter to the store which should be implemented in a computed in the component. Nothing fancy here.
You shouldn't need to manually load the data with a proper store setup. Always make a getter and "load" it into the component with a computed.

bootstrap-vue editable table revert value if error

I am using the vue js and bootstrap-vue to make an editable table, the user is allowed to make changes on the table with v-on:change assisting to axios update the database. The restriction is that the Languages are Unique and cannot be an empty string, I cannot seem to be able to revert the value if the user makes a mistake. What is the recommended approach to this? Any help is greatly appreciated.
I have tried to "refresh" the table by doing:
this.languages = this.languages;
Does not seem to refresh the value in the table.
a section of vue component on the table:
<b-table striped hover :items="filtered" :fields="fields">
<template slot="name" slot-scope="data">
<b-form-input type="text" :value="data.item.name" v-on:change="updateLanguage($event,data.item.id)"></b-form-input>
</template>
</b-table>
in the methods of vue export default:
updateLanguage(e,id) {
if(e.trim()){
const find_langauge = this.languages.find(language=>{
return language.name.toLowerCase() === e.toLowerCase();
});
find_langauge ? this.languages = this.languages : console.log('no match');
} else {
console.log('cannot be empty');
this.languages = this.languages;
}
}
in computed:
filtered() {
return this.search ? this.languages.filter(language=>
language.name.toLowerCase().includes(this.search.toLowerCase())) : this.languages;
}
I cannot find any solution to refresh the :value, so I ended up with force re-render of the component. Not ideal, but at least the entire component got refreshed. Will appreciate any better way to approach this instead of re-rendering.
Vue doesn't retain "initial" values for inputs, as it relies on your data modal as the source of truth.
You would need to store the initial value in data, and when detecting a reset, copy that initial value back to the v-model associated with the input.
Also, if you have inputs in multiple rows, it is a good idea to set a unique key on the input, and or if you have a unique key field (where the value is unique per item row), set the primary-key prop on b-table to have it generate a unique Vue key per TR element.

Vue: How to perform reactive object change detection in v-for?

I read this documentation but cannot use the proposed solution.
I have a v-for loop over objects. These objects are changed dynamically over time and I need that change to show reactively in the v-for loop.
<b-row lg="12" v-for="data in objects" :key="data.id">
<div v-if="data.loading">
loading...
{{data.loading}}
</div>
<div v-else>
loaded, done
{{data.loading}}
</div>
</b-row>
In my methods, I have a for loop that downloads data for each object and changes the object value like this:
for(var i = 0; i<response.ids.length; i++){
var newId = response.ids[i].id
this.objects.newId = {"loading":true, "id": newId}
downloadSomething(newId).then(res => {
this.objects.newId = res[0] //<-- this change needs to be shown reactively.
})
}
According to Vue documentation, Object changes are not reactive:
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive
Vue propses some workaround like this:
Vue.set(vm.userProfile, 'age', 27)
UPDATE
But for my case, this just creates a new parameter in the object with the same ID and creates a duplicate key warning and other problems.
I also tried Vue.delete just before Vue.set but it is not actually deleting.
Is there a way to not replace the key/value pair but add more/change parameters to the first child of the root with the ID of newID
Thanks!
Solution: Replace this.objects.newId = res[0] with this.$set(this.objects, 'newId', res[0]) and it should work.
Explanation: this.$set is just an alias to Vue.set, available within any Vue instance. Bear in mind that vm (stands for view model) in the example is the same as this and equals to Vue component instance. Also due to ES5 JS restrictions, you have to set objects' properties explicitly via Vue.set/this.$set to manually trigger re-render. This problem will be resolved when Vue 3.0 is released.
Hope this helps, if you need any clarifications - feel free to ask.
Try that:
downloadSomething(newId).then(res => {
this.$set(this.objects, 'newId', res[0])
})
you need Vue.set() when you want to define a new property to an existing object (not directly to the data), and this function will assign the new property to the built-in watcher, and it will become reactive as well.
its all being explained in the docs: https://v2.vuejs.org/v2/guide/reactivity.html
in your case, it seems to be all you need to make it work. in my example:https://jsfiddle.net/efrat19/eywraw8t/484131/ an async function fetches the data and define a new property to contain te response.
new Vue({
el: "#app",
data: {
objects:{
property1:12
}
},
methods: {
fetchDataAndAddProperty(response){
fetch('https://free.currencyconverterapi.com/api/v6/countries').then(res =>
res.json()).then(data => Vue.set(this.objects,'property2',data))
}
}
})
and the template:
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<div lg="12" v-for="(val,key,index) in objects" :key="index">
{{key}}:{{val}}
</div>
<button #click="fetchDataAndAddProperty()">fetch</button>
</div>
as you can see in the fiddle, the new property becomes reactive and being displayed as well.

Vue.js dynamic components - passing data

I'm new to Vue.js. I followed the tutorial here - https://coligo.io/dynamic-components-in-vuejs/ - on dynamic components, to give me a dynamic layout I liked, for listing products and allowing the user to switch to an edit view when they click on one of the products in the table. So, I have a 'list-products' component and an 'edit-product' component, and which one is displayed is dependent on the state of 'currentView' in the main Vue instance.
<div id="content">
<keep-alive>
<component :is="currentView"></component>
</keep-alive>
</div>
The switching is all working fine when currentView is changed. What I haven't got figured out is how best to pass the product information to the edit component such that it ends up as data. I suppose the list and edit components are two sibling components of the main Vue instance, instantiated at the same time. What I need to do is when I click on a row in listing table, have the product object used for building that row made available to the edit component. I'm not sure how I do that (at least, in a proper Vue way). When the displayed component is switched (via the change in 'currentView'), is some event called for the newly (re)displayed component? If so, I could presumably call some function?
LATER: I have determined that the 'activated' hook is called when I switch to the edit-product component, which I imagine I should be able to use somehow. Now to figure that out.
You could use Vuex for that. Vuex is a Flux inspired state management library for Vue.
Your application basically has two different states: (1) no product selected (list-products component), and (2) any product selected (edit-product). When this is modeled with Vuex, the idea is to keep the currently selected product in a so-called store and let the components figure out their internal state depending on the store state. Here's an example:
Create a store to keep the application state:
const store = new Vuex.Store({
state: {
selectedProduct: null
},
getters: {
selectedProduct: state => state.selectedProduct
},
mutations: {
selectProduct: (state, data) => state.selectedProduct = data
}
});
Handle product selection in your list-products component:
methods: {
selectProduct(product) {
this.$store.commit('selectProduct', product);
}
}
Display the current product in edit-product:
Vue.component('edit-product', {
store,
template: '<div>{{selectedProduct.name}}</div>',
computed: Vuex.mapGetters(['selectedProduct'])
});
And finally switch the components depending on the state:
new Vue({
el: '#app',
store,
computed: Object.assign(Vuex.mapGetters(['selectedProduct']), {
currentView() {
return this.selectedProduct ? 'edit-product' : 'list-products'
}
})
});
Here's a basic working JSFiddle.