Vue2: Binding v-model via computed property? - javascript

I have this component that shows a whole bunch of different components. Like so:
computed: {
...mapGetters('forms', ['formErrors']),
input: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
},
},
component() {
const components = {
'ls-text-field': () =>
import('../../../../common/ls-text-field.vue'),
'simple-date-picker': () =>
import('../../../../common/simple-date-picker.vue'),
select: 'v-select',
combobox: 'v-combobox',
};
return components[this.setting.component];
},
attributes() {
const attrs = {
'ls-text-field': {
label: this.setting.name,
},
'simple-date-picker': {},
select: {
label: 'Select this foo',
items: this.setting.options,
},
combobox: {
'return-object': true,
items: this.productList,
loading: this.loading_product_list,
'item-value': 'sku',
'item-text': 'sku',
label: this.setting.name,
name: this.setting.key,
},
};
return {
...attrs[this.setting.component],
'error-messages': this.formErrors(this.setting.key),
};
},
},
and the Template looks something like this:
<template>
<v-col md="4" cols="12">
<component
:is="component"
v-bind="attributes"
v-model="input"
:search-input.sync="searchSku"
/>
But you'll notice I had to do v-model in the template and not in the computed property. I suppose there is NO way to do this:
attributes() {
const attrs = {
'ls-text-field': {
label: this.setting.name,
},
'simple-date-picker': {},
select: {
label: 'Select this foo',
items: this.setting.options,
},
combobox: {
'return-object': true,
items: this.productList,
loading: this.loading_product_list,
'item-value': 'sku',
'item-text': 'sku',
label: this.setting.name,
name: this.setting.key,
'v-model': this.item.info.someKey // This doesn't seem possible
},
};

Related

How to use react-select-async-paginate library on change of different select?

I am trying to implement 2 select box
1st select box will be the simple select box.
2nd select box will have infinite scroller functionality and for this, I am using the react-select-async-paginate library.
Issue Explanation
AsyncPaginate is the component of react-select-async-paginate library. It uses loadOptions function attribute to load the option into the select box and it expect return value as {options: [], hasMore: false}.
In my code, in the loadOptions attribute, I am calling the loadHostOptions function to get the options. And on change of the first dropdown, I am calling loadHostOptions function. But in this case, correct options are not reflected in the 2nd dropdown.
Can anyone help me with how to load options on the change of the first dropdown?
Here is codesandbox
Code
import React from "react";
import { AsyncPaginate } from "react-select-async-paginate";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
firstSelectVal: "",
value: null
};
}
firstSelectChange = (event) => {
this.setState({ firstSelectVal: event.target.value });
if (event.target.value) {
this.loadHostOptions("java", []);
}
};
onPagiChange = (event) => {
this.setState({ value: event});
};
loadHostOptions = async (search, prevOptions) => {
if (search === "java") {
const responseJSON = {
results: [
{
value: 1,
label: "Java"
}
],
has_more: false
};
return {
options: responseJSON.results,
hasMore: responseJSON.has_more
};
} else {
const responseJSON = {
results: [
{
value: 1,
label: "Java"
},
{
value: 2,
label: "C++"
},
{
value: 3,
label: "Python"
},
{
value: 4,
label: "Node"
},
{
value: 5,
label: "Go, Lang"
}
],
has_more: false
};
return {
options: responseJSON.results,
hasMore: responseJSON.has_more
};
}
};
render() {
return (
<div>
<h1>react-select-async-paginate</h1>
<h2>1st Selectbox</h2>
<select
id="lang"
onChange={this.firstSelectChange}
value={this.state.firstSelectVal}
>
<option value="">Select</option>
<option value="java">Java</option>
</select>
{this.state.firstSelectVal ? (
<>
<h2>2nd Selectbox</h2>
<AsyncPaginate
value={this.state.value}
loadOptions={(search, prevOptions) =>
this.loadHostOptions(search, prevOptions)
}
onChange={(e) => this.onPagiChange(e)}
/>
</>
) : (
""
)}
</div>
);
}
}
export default App;
You will have to change your onPagiChange function to
onPagiChange = (event) => {
this.setState({ value: event});
};
That is set the entire event as value instead of event.value
Update
The actual issue is whenever you click on the AsyncPaginate field it will call loadHostOptions function and pass the current value to the search argument. In that case whatever you type in will be the value of search.
So if you want to use the value of first Select box to filter option for the second one, you will have to directly use the this.state.firstSelectVal directly inside the loadHostOptions function. Like this
loadHostOptions = async (search, prevOptions) => {
if (this.state.firstSelectVal === "java") {
const responseJSON = {
results: [
{
value: 1,
label: "Java"
}
],
has_more: false
};
return {
options: responseJSON.results,
hasMore: responseJSON.has_more
};
} else {
const responseJSON = {
results: [
{
value: 1,
label: "Java"
},
{
value: 2,
label: "C++"
},
{
value: 3,
label: "Python"
},
{
value: 4,
label: "Node"
},
{
value: 5,
label: "Go Lang"
}
],
has_more: false
};
return {
options: responseJSON.results,
hasMore: responseJSON.has_more
};
}
};

Vue and Jexcel events and computed fields

I am using the vue wrapper for jexcel and am attempting to trigger the undo function from the toolbar computed field. I cannot seem to access the instance of the spreadsheet. it throws a this.undo is undefined error
<template lang="html">
<div class="wrapper-jexcel">
<button class="" #click="getData(jExcelObj)">Data</button>
<button class="" #click="jExcelObj.undo()">Undo</button>
<input
type="button"
value="Add new row"
#click="jExcelObj.insertRow()"
/>
<div id="spreadsheet" ref="spreadsheet"></div>
</div>
</template>
import jexcelStyle from "jexcel/dist/jexcel.css"; // eslint-disable-line no-unused-vars
import jexcel from "jexcel"; // eslint-disable-line no-unused-vars
import db from '#/firebase/init'
import firebase from 'firebase'
export default {
name: "workbook",
data() {
return {
workbookid: this.$route.params.workbookid,
myCars: [],
columns: [
{ type: "text", title: "Car", width: "120px" },
{
type: "dropdown",
title: "Make",
width: "250px",
source: ["Alfa Romeo", "Audi", "BMW", "Honda", "Porshe"]
},
{ type: "calendar", title: "Available", width: "250px" },
{ type: "image", title: "Photo", width: "120px" },
{ type: "checkbox", title: "Stock", width: "80px" },
{
type: "numeric",
title: "Price",
width: "120px",
mask: "$ #.##,00",
decimal: ","
},
{ type: "color", width: "100px", render: "square" }
]
};
},created() {
this.getworkbook()
},
methods: {
onchange(){
console.log('change');
},
insertRowc() {
console.log(this);
// this.spreadsheet.insertRow();
},
undo(){
console.log('test');
jExcelObj.undo();
},
getData(payload) {
console.log(this.myCars);
console.log(payload);
// this.myCars = payload.data
}
},
computed: {
jExcelOptions() {
var self = this;
return {
data: this.myCars,
columns: this.columns,
search: true,
//fullscreen: true,
minDimensions: [20, 40],
defaultColWidth: 100,
allowComments: true,
toolbar: [
{ type:'i', content:'undo', onclick:function() { return jExcelObj.undo(); } },
{ type:'i', content:'redo', onclick:function() { this.redo(); } },
{ type:'i', content:'save', onclick:function () { test.download(); } },
{ type:'select', k:'font-family', v:['Arial','Verdana'] },
{ type:'select', k:'font-size', v:['9px','10px','11px','12px','13px','14px','15px','16px','17px','18px','19px','20px'] },
{ type:'i', content:'format_align_left', k:'text-align', v:'left' },
{ type:'i', content:'format_align_center', k:'text-align', v:'center' },
{ type:'i', content:'format_align_right', k:'text-align', v:'right' },
{ type:'i', content:'format_bold', k:'font-weight', v:'bold' },
{ type:'color', content:'format_color_text', k:'color' },
{ type:'color', content:'format_color_fill', k:'background-color' },
]
};
}
},
mounted: function() {
//console.log(this.jExcelOptions);
//console.log(this.$refs["spreadsheet"]);
const jExcelObj = jexcel(this.$refs["spreadsheet"], this.jExcelOptions);
// Object.assign(this, jExcelObj); // pollutes component instance
Object.assign(this, { jExcelObj }); // tucks all methods under jExcelObj object in component instance
// console.log(this.jExcelObj);
}
};
should i be passing the instance into the computed method? I am struggling to understand how to manage instances of a wrapper plugin and accessing the methods.
You can get the instance using that:
var yourTableInstance = document.getElementById('spreadsheet').jexcel;
yourTableInstance.undo();
yourTableInstance.getData();
mounted: function() {
let spreadsheet = jspreadsheet(this.$el, options);
Object.assign(this, spreadsheet);
}

How to set pre-selected value in vue js dropdownlist

I'm working on two vue component.sending parent component array data to child component using props.now i want to set pre-selected value in child component dropdownlist.
Here is my code sample:
props:{
// pre-selected value based on this.
userdata:{
type:[Array,Object],
required:true,
},
roles:{
type:[Array,Object],
required:true,
},
},
data(){
return{
mutableRoles:[],
}
},
and this is my view part:
//i want set pre-selected value on this dropdownlist
<select multiple v-model="mutableRoles" class="form-control">
<option v-for="(role,index) in roles" v-bind:value="role.id" >{{role.name}}</option>
</select>
I have seen many example where show only using string. but in my case both are array.
Try this:
const CurrentRole = Vue.component("current-role", {
template: `
<div>
<label>Options</label>
<select v-model="roleId" #change="changeValue">
<option v-for="v in roles" :key="v.id" :value="v.id">{{v.title}}</option>
</select>
</div>
`,
props: {
userdata: {
type: [Array, Object],
required: true,
},
roles: {
type: [Array, Object],
required: true,
}
},
data: _ => ({
roleId: null
}),
methods: {
changeValue() {
this.userdata.role = this.roles.find(e => e.id == this.roleId)
},
},
mounted() { // for initial state
this.roleId = this.userdata.role.id
},
watch: {
userdata(v) { // for changes on parent
if (v) this.roleId = v.role.id
}
}
})
new Vue({
el: "#app",
data: {
rlist: [{
id: 1,
title: "a"
}, {
id: 2,
title: "b"
}, {
id: 3,
title: "c"
}],
user: {
role: {
id: 3,
title: "c"
}
}
},
methods: {
changeUser() {
this.user = {
role: {
id: 1,
title: "a"
}
}
}
}
})
<script src="https://unpkg.com/vue#2.5.22/dist/vue.js"></script>
<div id="app">
<p>User: {{user}}</p>
<current-role :userdata="user" :roles="rlist">
</current-role/>
<button #click="changeUser">change user</button>
</div>
The select is tailored for primitive values, therefore you'll need to add helper functions.
Higher level vue frameworks such as vue-material, vuetify, element and muse-ui tend to offer components to cope with such problems with a higher abstraction level.
EDIT:
I changed the snippet in order to make it closer to your situation.

Tree-view of Editable Text Areas in Vue.js

I have this fiddle:
https://jsfiddle.net/pnqzspoe/12014/
I want to modify it a bit and want to display each node as a text area containing the corresponding text. Further, I want to give an option to 'reply' to it. This would mean insertion of a new text area into which we can enter text.
Here is the code:
<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, index) in model.children"
:key="index"
: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>
var data = {
name: 'My Tree',
children: [
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
},
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
]
}
]
}
// 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: 'new stuff'
})
}
}
})
// boot up the demo
var demo = new Vue({
el: '#demo',
data: {
treeData: data
}
})
What would be the template for this use-case?
If I don't understand your question wrongly...
Replace
{{model.name}}
with
<textarea v-model="model.name"></textarea>
should work?

How to get states from a Vuex store from within a Vuetify list, VueJs

I have a Vue file that looks like so :
import store from '#/store'
export default{
name: 'myList',
data: () => ({
show: true,
listContent: [{
name: '1',
icon: 'person',
value: function () {
return store.state.myStore.settings.one
}
}, {
name: '2',
icon: 'person',
value: function () {
return store.state.myStore.settings.two
}
}, {
name: '3',
icon: 'person',
value: function () {
return store.state.myStore.settings.three
}
}
]
})
}
The part that's not working is getting the 'value' from the 'listContent'.
{
name: '3',
icon: 'person',
value: function () {
return store.state.myStore.settings.three
}
}
In my code, I have imported the view as if I were to put :
this.$store.state.myStore.settings.one
Inside the value function, 'this' would refer to the object
{
name: '3',
icon: 'person',
value: function () {
return store.state.myStore.settings.three
}
}
And I wouldnt be able to get the store. However, my code still doesn't work. I need to get access to the store inside the listContent.
The list is rendered like so :
<v-data-table :items="listContent" hide-actions hide-headers>
<template slot="items" slot-scope="props">
<td>{{ props.item.name }}</td>
<td class="text-xs-right" v-text="props.item.value()"> </td>
</template>
</v-data-table>
Either I have referenced the store incorrectly, or the template is incorrect. Any ideas ?
Why do you want the value to be a function that returns the state value. You can just assign it to state value using this.$store.state.myStore.settings.one
For this to work make the data option a normal function instead of an arrow function so that this still represents the vue instance
export default {
name: "myList",
data() {
return {
show: true,
listContent: [
{
name: "1",
icon: "person",
value: this.$store.state.myStore.settings.one
},
{
name: "2",
icon: "person",
value: this.$store.state.myStore.settings.two
},
{
name: "3",
icon: "person",
value: this.$store.state.myStore.settings.three
}
]
};
}
};
May be this will help. Long one, but it works.
const myModule = {
state: {
test: "modulle",
settings: {
one: "This is one",
two: "This is two",
three: "This is three"
}
}
};
const store = new Vuex.Store({
modules: { myModule }
});
new Vue({
el: "#app",
store,
data() {
return {
listContent: [
{
name: "1",
icon: "person",
value: null
},
{
name: "2",
icon: "person",
value: null
},
{
name: "3",
icon: "person",
value: null
}
]
};
},
watch:{
'$store.state.myModule.settings.one':{
immediate:true,
handler:function(value){
this.listContent[0].value = value;
}
},
'$store.state.myModule.settings.two':{
immediate:true,
handler:function(value){
this.listContent[1].value = value;
}
},
'$store.state.myModule.settings.three':{
immediate:true,
handler:function(value){
this.listContent[2].value = value;
}
},
}
});

Categories