I use Vuex with my Vue components. When my components have static fields that are editedtable, they are easily handled with computed properties:
computed: {
text: {
get() {
return ...
},
set(value) {
this.$store.commit...
},
},
},
<input type="text" v-model="text">
However, how should this be done when I render a list of options that need to be bound?
options = [
{
value: ...,
text: ...,
},
{
value: ...,
text: ...,
},
...
];
<input type="text" v-model="option.text" v-for="option in options">
You have to define mutations for the options, like "addOption", "editOption", etc...
Than optionally ( but recommended ) define an options component. Bind events to call the mutations.
A good simple example provided by vuex is available here:
https://github.com/vuejs/vuex/tree/dev/examples/todomvc
Particularly look at the todo component. It is in charge of a single item from the todo list.
https://github.com/vuejs/vuex/blob/dev/examples/todomvc/components/Todo.vue
and how they work with the list in the app component.
https://github.com/vuejs/vuex/blob/dev/examples/todomvc/components/App.vue
And these are their mutations. See how they edit and add and delete list items.
https://github.com/vuejs/vuex/blob/dev/examples/todomvc/store/mutations.js
I can't have a shorter answer, moving to vuex ( vue + redux methodology ) requires a shift in thinking and architecture.
You can model these options as data in your vue instance and use them in HTML as you have mentioned.
var vm = new Vue({
data: {
options: [
{
value: ...,
text: ...,
},
{
value: ...,
text: ...,
},
...
]
}
})
And in HTML:
<input type="text" v-model="option.text" key="option.value" v-for="option in options">
I have added a key attribute as well along with v-for so that it can track each node’s identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item. An ideal value for key would be the unique id of each item.
Related
I have a dynamic component being injected in the slot of another one and I pass the props object to it. But when I update the data (an array) which has been assosiated with the prop (dataT: this.tableData), that prop isn't being updated inside the component.
It seems like I have a deal with two different objects but the array was passed by the reference, wasn't it?
This is the main component
<template>
<Button #click="addWindows"></Button>
<Window v-for="window in windows" :key="window.id">
<component :is="window.name" v-bind="window.props" #onDeleteRow="handleDeleteRow"></component>
</Window>
</template>
<script>
export default{
data(){
return{
windows:[],
tableData:[
{
id: '0',
name: 'dog'
},
{
id: '1',
name: 'cow'
},
{
id: '2',
name: 'cat'
}
]
}
},
methods:{
addWindows(){
this.windows = [
{
id: 0,
name: 'Component1',
props: {
dataT: this.tableData
}
},
{
id: 1,
name: 'Component2',
props: {}
}];
},
handleDeleteRow(id){
this.tableData = this.tableData.filter(r => r.id != id);
}
}
}
</script>
I expect updating dataT prop in Component1 when I modify this.tableData in the main component.
Original answer based on an earlier version of the question
If you make windows a computed property it can depend on tableData:
export default {
data() {
return {
tableData: [
{
id: '0',
name: 'dog'
},
{
id: '1',
name: 'cow'
},
{
id: '2',
name: 'cat'
}
]
}
},
computed: {
windows () {
return [
{
id: 0,
name: 'Component1',
props: {
dataT: this.tableData
}
}, {
id: 1,
name: 'Component2',
props: {}
}
]
}
}
}
If you can't make all of it a computed property, e.g. because you need to be able to modify it, then keep it as data and just use the computed property to create the array needed in your template. In that case the computed property would just be merging together different parts of the data into the correct form.
In your original code, the line dataT: this.tableData won't work because this.tableData doesn't exist yet, it'll just be undefined. There's no lazy evaluation here, it needs to resolve to the correct object at the point it hits that line.
Even if it was able to get access to the correct object it wouldn't help because in handleDeleteRow you're reassigning tableData to point to a different object. Passing 'by reference' has nothing to do with the name you use to identify the object, it refers to a reference in memory.
Incidentally, v-on also supports an object syntax, just like v-bind, so you could make onDeleteRow optional in a similar fashion.
Update based on the edited question
When you write this in addWindows:
props: {
dataT: this.tableData
}
This will assign the current value of this.tableData to dataT. That current value will be an array and as arrays are reference types any modifications made to that array will apply no matter what identifier is used to reference it.
However, this line...
this.tableData = this.tableData.filter(r => r.id != id);
... does not modify that array. Instead it assigns a totally new array to this.tableData. This will have no effect on the array referenced by dataT, which is unchanged.
There are several ways you could approach solving this, including using a computed property. However, a property getter might provide a convenient sleight-of-hand:
addWindows () {
const vm = this;
this.windows = [
{
id: 0,
name: 'Component1',
props: {
get dataT () {
return vm.tableData
}
}
},
{
id: 1,
name: 'Component2',
props: {}
}
];
}
This will always evaluate to the current value of tableData. Vue's reactivity should be fine with this extra indirection, it just sees it as equivalent to accessing tableData directly.
TL;DR
The issue is with your binding. Use the following:
<component
:is="window.name"
:dataT="window.props.dataT"
#onDeleteRow="handleDeleteRow">
</component>
Explanation
the v-bind attribute specifies what prop is bound to what value (or reference). In your case, you didn't specify what values you're binding to what props, thus the component props weren't bound as expected.
I'm having a form in vue js with select drop downs, I'm trying to use https://sagalbot.github.io/vue-select/ this library to execute this, according to the documentation I've to pass an array into this and I'm calling a axios method to get the options of the drop downs. I'm getting the data in following format:
And with following description:
I want to only show the names in the option and get values as ID of that particular row, how can I achieve this.
My code look something like this: https://jsfiddle.net/eemdjLex/1/
<div id="app">
<v-select multiple :options="model.data"></v-select>
</div>
import vSelect from 'vue-select';
Vue.component('v-select', vSelect)
const app = new Vue({
el: '#app'
router: router,
data () {
return {
model: {},
columns: {},
}
}
methods: {
fetchIndexData() {
var vm = this;
axios.get('/companies').then(response => {
Vue.set(vm.$data, 'model', response.data.model)
Vue.set(vm.$data, 'columns', response.data.columns)
}
}
});
It is not working proper but you get the idea what I'm trying to do.
v-select appears to return the entire option as the value when using v-model so I might use a pair of computed values here.
new Vue({
el:"#app",
data:{
serverData,
selected: null
},
computed:{
// serverData is a stand in for your model.data.
// map over that to build your options
selectOptions(){
return this.serverData.map(d => ({label: d.name, value: d.id}))
},
// selectedOption is just a short computed to get the id value
// from whatever option was selected. You could also just use
// "selected.id" in whatever needs the id instead if needed.
selectedOption(){
if (this.selected)
return this.selected.value
else
return null
}
}
})
Example.
Looking at the README on the GitHub page for vue-select, I'm seeing that you can pass the <v-select> component an options property as an array of objects with label and value keys.
I would make a computed property to take your model.data and format it this way:
computed: {
options() {
let data = this.model.data;
let options = [];
// do whatever you need to do to format the data to look like this object:
// options = [{ label: 'foo', value: 1 }, { label: 'bar', value: 2 }]
return options;
}
}
Then pass this computed property to the <v-select> instead:
<v-select multiple :options="options"></v-select>
The official select docs may help you
your v-select component should look like
new Vue({
template: `
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>`,
el: 'v-select',
props: [ 'options' ]
data: {
selected: ''
}
})
I can't find how to create a variable from another variable value.
I get this variable from AJAX call (using vue-resources):
langs: {1:'fr', 2:'en', 3:'ar'},
But, I don't know how many properties langs will have.
How can I get this variable dynamically:
newVar: {
has_lang: '1',
values: [{ is: 'fr', val:''}, { is: 'en', val:''}, { is: 'ar', val:''}]
},
So in newVar, the fr, en, and ar properties have been generated automatically using the values in langs.
I am planning to use the values array to bind to the values of my md-input inputs using v-model:
<md-input-container v-for="value in newVar.values">
<label>#{{ attribute.attribute }} #{{ value.is }}</label>
<md-input v-model="value.val"></md-input>
</md-input-container>
Is doing this manipulation the best practice? Because I can't find a solution which uses v-model for inputs that are generated from a v-for loop.
Use a watcher to set the newVar property whenever the langs property changes. You'll need to also do this during the mounted lifecycle event if you are passing in the langs variable as a prop or defining it within data().
In your case, I'd add a method to call at both points:
data() {
return {
langs: {1:'fr', 2:'en', 3:'ar'},
newVar: {},
};
},
methods: {
updateValues() {
let newVar = {
has_lang: (this.langs !== {}) ? '1' : '0',
values: [],
};
for (let lang in this.langs) {
newVar.values.push({ is: this.langs[lang], val: '' });
}
this.newVar = newVar;
}
},
watch: {
langs() {
this.updateValues();
}
},
mounted() {
this.updateValues();
}
Here's a CodePen.
What's the best/correct way to update a nested array of data in a store using redux?
My store looks like this:
{
items:{
1: {
id: 1,
key: "value",
links: [
{
id: 10001
data: "some more stuff"
},
...
]
},
...
}
}
I have a pair of asynchronous actions that updates the complete items object but I have another pair of actions that I want to update a specific links array.
My reducer currently looks like this but I'm not sure if this is the correct approach:
switch (action.type) {
case RESOURCE_TYPE_LINK_ADD_SUCCESS:
// TODO: check whether the following is acceptable or should we create a new one?
state.items[action.resourceTypeId].isSourceOf.push(action.resourceTypeLink);
return Object.assign({}, state, {
items: state.items,
});
}
Jonny's answer is correct (never mutate the state given to you!) but I wanted to add another point to it. If all your objects have IDs, it's generally a bad idea to keep the state shape nested.
This:
{
items: {
1: {
id: 1,
links: [{
id: 10001
}]
}
}
}
is a shape that is hard to update.
It doesn't have to be this way! You can instead store it like this:
{
items: {
1: {
id: 1,
links: [10001]
}
},
links: {
10001: {
id: 10001
}
}
}
This is much easier for update because there is just one canonical copy of any entity. If you need to let user “edit a link”, there is just one place where it needs to be updated—and it's completely independent of items or anything other referring to links.
To get your API responses into such a shape, you can use normalizr. Once your entities inside the server actions are normalized, you can write a simple reducer that merges them into the current state:
import merge from 'lodash/object/merge';
function entities(state = { items: {}, links: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities);
}
return state;
}
Please see Redux real-world example for a demo of such approach.
React's update() immutability helper is a convenient way to create an updated version of a plain old JavaScript object without mutating it.
You give it the source object to be updated and an object describing paths to the pieces which need to be updated and changes that need to be made.
e.g., if an action had id and link properties and you wanted to push the link to an array of links in an item keyed with the id:
var update = require('react/lib/update')
// ...
return update(state, {
items: {
[action.id]: {
links: {$push: action.link}
}
}
})
(Example uses an ES6 computed property name for action.id)
I receive a complex JSON from the server. Let it be next:
var data = [{
name: "name1",
items:[
{
name:"name11",
subItems:[{
name:"name111",
children[
{id:1,name:"child1111",status:"good"},
{id:2,name:"child1112",status:"bad"},
{id:3,name:"child1113",status:"good"}
]},
{
name:"name112",
children[
{id:4,name:"child1121",status:"good"}]
}]
},
{
name:"name12",
subItems:[{
name:"name121",
children[
{id:5,name:"child1211",status:"bad"}]
}]
}]
},
{
name: "name2",
items:[
{
name:"name21",
subItems:[{
name:"name111",
children[
{id:7,name:"child2111",status:"good"}
]}]
}]
}];
So I have the list of objects each one contains name and items properties. Items is property of the similar list of objects each one contains name and subItems properties. subItems property same to previous and has name and children properties. children is list of my entities. I use mapping for filling my ViewModel.
First of all I can't image how to set as key id in my entity. I am wondering how to "dive" to it. Moreover, I need to extend my entity. Add the compute property like next example:
computProp: ko.computed(function() {return name+status;})
I don't want to create js classes, because I don't see benefits of mapping on this case. I can implement manual mapping in this case. It would be more clear for me.
So any idea, suggesting or critics are welcome.
PS: I have searched/read similar topics
You must explicit declare the children viewmodel to get this behaviour, but you still benefit from getting all the mapping done
http://jsfiddle.net/uXMhA/
ChildViewModel = function(data) {
ko.mapping.fromJS(data, {}, this);
this.computProp = ko.computed(function() {
return this.name() + this.status();
}, this);
};