so i try to make a multiple grid radio with condition it select one radio button and save it as json with key: question and value: column where the radio buttoin selected
<template>
<v-radio-group class="mt-0" v-model="answer" :rules="answerRule">
<thead>
<tr>
<th>Pilihan</th>
<th v-for="(option, keys) in columns" :key="keys + 'A'">
{{ option.value }}
</th>
</tr>
<tr v-for="(option, keys) in rowsCols" :key="keys + 'C'">
<th>{{ option.value }}</th>
<td
v-for="(optioncol, keys) in option.columns"
:key="keys + 'B'"
>
<v-radio-group
v-model="answer"
#change="update"
:rules="answerRule"
>
<v-radio
class="radioA"
solo
v-bind:key="option.id"
:value="optioncol"
>
</v-radio>
</v-radio-group>
</td>
</tr>
</thead>
<tbody></tbody>
</v-radio-group>
</template>
here how it looks in the browser but it selected all row
[![enter image description here][1]][1]
here is my script on how to load the data and try to save the json...
export default {
props: ['question'],
data() {
return {
rows: this.question.options,
answer: [],
columns: this.question.optionsColumns,
answerRule: [],
}
},
methods: {
async update() {
try {
let payload = {
questionId: this.question.id,
value: this.answer,
questionName: this.question.question,
}
//update question options
await this.$store.commit('answers/update', payload)
} catch (err) {
this.$store.commit('alerts/show', {
type: 'error',
message: err.response
? this.$t(err.response.data.message)
: this.$t('SERVER_ERROR'),
})
}
},
},
beforeMount() {
if (this.question.required) {
this.answerRule.push(
(v) => v.length > 0 || this.$t('QUESTION_REQUIRED')
)
}
},
}
</script>
Any help on that? because i've try and still cannot figure it out
Here is the Data that i already change into new format :
row:[{id:1,value:"asdasd",columns:[a,b,c]},{id:2,value:"asdasd",columns:[a,b,c]}{id:3,value:"asdasd"}{id:1,value:"asdasd",columns:[a,b,c]}]
yet i still got the same problem
[1]: https://i.stack.imgur.com/6fsIF.png
Related
With my code is nothing wrong (i hope), although I feel it could be better written. I tried move logic from to computed property but unsuccessfully, i think table structure is not correct, but I'm out of ideas. Anyone can help ?
Unfortunately "tabl" comes from the server and i cant changes this variable
<template>
<movable-div>
<template #header>
<div class="header">
<h3>{{ name }}</h3>
<div #mousedown.stop="dragMouseDown">
<input type="text" v-model="search" placeholder="Search..." />
</div>
<div class="button-group">
<svg width="1.2em" height="1.2em" viewBox="0 0 10240 10240" #click="toggleTable()" :class="[showTable ? 'go' : 'back']">
<path some long svg code... />
</svg>
<p #click="showTableAttributes()">X</p>
</div>
</div>
<table v-if="showTable">
<tr>
<th v-for="head in tableHead" :key="head.Name">
{{ head.Name }}
</th>
</tr>
<tr
v-for="row in filteredRow"
:key="row.Key"
class="data"
#click="zoomToFeatureExtent(row)"
>
<td v-for="item in tableHead" :key="item.Name">
<p v-html="row.filter((obj) => obj.Key === item.Name)
.map((item) => item.Value)
.join()
.replace(search, `<span style='color:#1D7CA7'><b>${search}</b></span>`)">
</p>
</td>
</tr>
</table>
</template>
</movable-div>
</template>
<script>
export default {
props: ['olMap', 'LayerStyleName', 'name'],
data() {
return {
tableHead: null,
rows: null,
table: {
ColumnList: [{name: "ex1"},{name: "ex2"}],
Name: "Example",
RowList: [{Original:[{Key: "ex1", Value: "exampleValue"}]},
{Original:[{Key: "ex2", Value: "exampleValue"}]}]
},
showTable: true,
layer: null,
filteredRow: [],
search: null,
};
},
mounted() {
this.rows = this.table.RowList;
this.tableHead = this.table.ColumnList.filter((item) => item.Name !== 'geometry');
this.search = '';
},
inject: ['showTableAttributes'],
methods: {
toggleTable() {
this.showTable = !this.showTable;
},
zoomToFeatureExtent(value) {
let extent = value
.filter((item) => item.Key === 'geometry')
.map((item) => item.Value);
let view = this.olMap.getView();
view.fit(extent[0], this.olMap.getSize());
let res = view.getResolution();
if (res < 0.5) {
view.setResolution(0.9);
}
},
},
watch: {
search: function (val) {
this.filteredRow = [];
for (let row of this.rows) {
if (row.Original.map((obj) => obj.Value.toString().includes(val))
.filter((i) => (i === true ? i : null))
.join()) {
this.filteredRow.push(row.Original);
} else null;
}
},
},
};
</script>
It's good practice (IMHO) to have the template void of any complex logic. It makes the code more maintainable since you don't have functionality split between your template and your script. It also allows for better performance if you can offload methods to cached variables which prevents static parts of code from re-calculating needlessly.
The following is a good example of improvement potential
<tr
v-for="row in filteredRow"
:key="row.Key"
class="data"
#click="zoomToFeatureExtent(row)"
>
<td v-for="item in tableHead" :key="item.Name">
<p
v-html="row.filter((obj) => obj.Key === item.Name)
.map((item) => item.Value)
.join()
.replace(search, `<span style='color:#1D7CA7'><b>${search}</b></span>`)"
></p>
</td>
</tr>
I find reading this in the template is harder than in the script block (but YMMV), but performance wise you're doing extra loops. This script here does 3 loops: rows(filteredRow), columns(tableHead), then rows again(row.filter).
If you move the logic to a computed, you can simplify the logic and improve the performance. A computed will keep the data cached and update as needed, so if you change the value of search it will re-compute, but if an unrelated variable changes then it wouldn't, and the template wouldn't have to recalculate the values again. In your code, it seems like there's no much for other values that might change, but good practice anyway.
here's what that might look like (untested code)
computed: {
tableData() {
return this.filteredRow.map(row => {
const cols = [];
this.tableHead.forEach(item => {
let value = "";
if (col.Name === row.Key) {
let value = item.Value.replace(search, `<span style='color:#1D7CA7'><b>${search}</b></span>`)
}
cols.push(value)
});
return {...row, cols};
})
}
},
<table v-if="showTable">
<tr>
<th v-for="head in tableHead" :key="head.Name">
{{ head.Name }}
</th>
</tr>
<tr
v-for="row in tableData"
:key="row.Key"
class="data"
#click="zoomToFeatureExtent(row)"
>
<td v-for="(cell, i) in row.cols" :key="i">
<p v-html="cell"></p>
</td>
</tr>
</table>
this is my rightTableMenu template
<template>
<div>
<h1 align="center">{{ title }}</h1>
<v-alert type="info" icon="mdi-emoticon-sad" v-if="basketStatus">
Empty Basket, please add some to basket
</v-alert>
<div v-if="changeAlertStatus()">
<v-alert
type="success"
icon="mdi-emoticon-happy"
:value="alert"
transition="fade-transition"
>
thank you
</v-alert>
<v-simple-table>
<template v-slot:default>
<thead>
<tr>
<th class="text-left">Quantity</th>
<th class="text-left">Name</th>
<th class="text-left">Price</th>
</tr>
</thead>
<tbody>
<tr v-for="item in basket" :key="item.name">
<td>
<v-icon #click="increaseQuantity(item)">add_box</v-icon>
<span>{{ item.quantity }}</span>
<v-icon #click="decreaseQuantity(item)"
>indeterminate_check_box
</v-icon>
</td>
<td>{{ item.name }}</td>
<td>{{ (item.price * item.quantity).toFixed(2) }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
<v-divider color="black"></v-divider>
<v-row id="basket_checkout" style="margin: 0">
<v-col>
<p>Subtotal:</p>
<p>Delivery:</p>
<p>Total amount:</p>
</v-col>
<v-col class="text-right">
<p>${{ subTotalResult }}</p>
<p>$10</p>
<p class="font-weight-bold">${{ totalPriceResult }}</p>
</v-col>
</v-row>
<v-row>
<v-spacer></v-spacer>
<v-btn depressed class="orange" v-on:click="submitOrder">
<v-icon>shopping_basket</v-icon>
</v-btn>
</v-row>
</div>
</div>
</template>
as you see there are two alerts one is showing when there is not item inside the array basket by checking the following
basketStatus() {
return this.$store.getters.basket.length === 0;
},
which is computed property
my data property section is
data() {
return {
title: "Current Basket",
alert: false,
};
},
but for the second v-alert, I wanna to have the alert to be shown and disappear after few sec and so far I have done the following for it
async changeAlertStatus() {
if (this.$store.getters.basket.length !== 0) {
this.alert = true;
try {
const response = await setTimeout(() => {
this.alert = false;
}, 100);
console.log("this is the resonse " + response);
} catch (err) {
console.log("fetch failed", err);
}
} else {
this.alert = false;
}
},
which is a method
I am confused how to interject the function inside the div part without using v-if directive and my async changeAlertStatus gets in the infinite loop when I check it inside the console and the v-alert does not get disappear
any thoughts on that?
if there is more info needed , please let me know
thank you
just in case my leftTableMenu is follows
<template>
<div>
<div v-if="showError['situation']">
<!--
basically, when you close the alert, the value of the alert goes to false
so you need to turn it to true when there is an error :value="showError.situation" -->
<app-alert :text="showError.message" :value.sync="showError.situation"></app-alert>
</div>
<h1 align="center">{{ title }}</h1>
<v-simple-table od="menu-table">
<template v-slot:default>
<thead>
<tr>
<th class="text-left">Name</th>
<th class="text-left">Price</th>
<th class="text-left">Add</th>
</tr>
</thead>
<tbody>
<tr v-for="item in menuItems" :key="item.name">
<td>
<span id="id_name">{{ item.name }}</span>
<br />
<span id="menu_item_description">{{ item.description }}</span>
</td>
<td>{{ item.price }}</td>
<td>
<v-btn text v-on:click="addToBasket(item)">
<v-icon color="orange">1add_shopping_cart</v-icon>
<span></span>
</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</div>
</template>
<script>
export default {
name: 'LeftTableMenu',
data() {
return {
title: "Menu Items",
};
},
methods: {
addToBasket(item) {
this.$store.dispatch("addToBasket", item);
},
},
computed: {
showError() {
return this.$store.getters.showError;
},
menuItems() {
return this.$store.getters.menuItems;
},
},
};
You can add a watcher on your computed property to see if it's changed.
When it changes you can update your data to show or the "Success" alert and then set a timeout to hide it back again after some time.
Here is an updated example with some changed param names for clarity.
I changed the computed name to be emptyBasket
computed: {
emptyBasket() {
return this.$store.getters.basket.length === 0;
}
},
I added showSuccessAlert to data
data() {
return {
showSuccessAlert: false
};
},
And here it the watcher that updates the showSuccessAlert
watch: {
emptyBasket: {
immediate: true,
handler(newVal, oldVal) {
this.showSuccessAlert = !newVal;
setTimeout(() => {
this.showSuccessAlert = oldVal;
}, 5000);
}
}
}
The watcher will be triggered immediately (not sure you need it),
newVal and oldVal are representing the new and old state of emptyBasket.
So when newVal is false it means that the basket is not empty, hence the update of showSuccessAlert = !newVal
I created a simple working sandbox with your code.
Here is the link:
https://codesandbox.io/s/smoosh-cherry-ngpqu?file=/src/App.vue
Should probably be watching backStatus and then do your alert stuff
watch: {
// whenever question changes, this function will run
backStatus: function (newVal, oldVal) {
this.alert = newVal;
const response = setTimeout(() => {
this.alert = oldVal;
}, 100);
// swap the vals around if needed
}
}
maybe you might need immediate too, but that's up to how your want to display things.
https://v2.vuejs.org/v2/guide/computed.html#Watchers
Rather than calling changeAlertStatus in the v-if directive, can that just be bound to the this.alert property? Then, when the Add to Cart button is clicked, its callback can set this.alert to true, causing the alerts to display. Just after setting this.alert to true, register the setTimeout to revert it back to false
Example: (Please excuse the abstract-ness of it, I feel like this is some missing code from the original post, specifically the add to cart button)
<template>
<div id="app">
<div class="alerts" v-if="alert">
<div>Thank you</div>
</div>
<button #click="handleAddToCart">
Add to cart
</button>
</div>
</template>
<script>
module.exports = {
el: "#app",
data: {
alert: false,
basketStatus: false
},
methods: {
handleAddToCart() {
this.alert = true;
setTimeout(() => {
this.alert = false;
}, 3000);
}
}
};
</script>
You can achieve this timeout on alert using watch like the others guys said:
<template>
<div class="w-full">
<div class="w-full" v-for="item in cart" :key="item.id">
<p>{{item.name}}</p>
</div>
<div class="w-full p-2 bg-yellow" v-if="alert">
<p>Your cart is empty</p>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'CartList',
data() {
return {
cart: [],
alert: true
}
},
watch: {
cart(val) {
if(!val.length) {
this.alert = true
} else {
setTimeout(() => {
this.alert = false
}, 2000)
}
}
},
mounted() {
this.getCart()
},
methods: {
getCart() {
axios('/cart/get').then((response) => {
this.cart = response.data.cart
})
}
}
}
</script>
But you can add some extra code to your request function and add the timeout there too:
getCart() {
axios('/cart/get')
.then((response) {
if(response.data.cart.length) {
setTimeout( () => {
this.alert = false
}, 2000)
}
})
}
I am trying to create a table component in vue which will change order according to a property passed to this component :
sort: {
type: String,
default: "desc",
description: "Table data order"
}
if I pass here asc I want rows to be placed in a reversed order like that:
desc - [1,2,3] asc - [3,2,1]
So what I've done is just added a function which changes order of array in state data:
( that's where from I get all the data for rows )
data: {
type: Array,
default: () => [],
description: "Table data"
}
and it's actually work, but I can't get rid of feeling like I've done something in a wrong way and I would like to see your thoughts and solutions. Thanks in advance, whole component code is below.
<template>
<table>
<thead>
<tr>
<slot name="columns" :columns="columns">
<th v-for="column in columns" :key="column">{{ column }}</th>
</slot>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in data" :key="index">
<slot :row="item" :index="index">
<td
v-for="(column, index) in colsWithValue(item)"
:key="index"
>{{ itemValue(item, column) }}</td>
</slot>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "base-table",
props: {
columns: {
type: Array,
default: () => [],
description: "Table columns"
},
data: {
type: Array,
default: () => [],
description: "Table data"
},
sort: {
type: String,
default: "desc",
description: "Table data order"
}
},
computed: {
tableClass() {
return this.type && `table-${this.type}`;
},
colsWithValue() {
return row => {
return this.columns.filter(column => this.hasValue(row, column));
};
}
},
methods: {
hasValue(item, column) {
return item[column.toLowerCase()] !== "undefined";
},
itemValue(item, column) {
return item[column.toLowerCase()];
},
sortDataAsc(){
if(this.sort === "asc"){
this.data = this.data.reverse();
}
}
},
created(){
this.sortDataAsc();
}
};
</script>
If you are not sure if that is the best solution, you can check the documentation about this "Displaying Filtered/Sorted Results" at the link https://v2.vuejs.org/v2/guide/list.html#Displaying-Filtered-Sorted-Results
The best example is:
<li v-for="n in evenNumbers">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
I am trying to search or filter through 3 fields firstname, lastname and email in my Vue.js 2 application. I understand that Vue 2 does not come with a built in filter method unlike in Vue 1, hence I created a custom method which is only able to filter through just one field. How do I extend this to multiple fields? I have tried something like this filterBy(list, value1, value2, value3) but it does not work.
This is my code
<template>
<div class="customers container">
<input class="form-control" placeholder="Enter Last Name" v-
model="filterInput">
<br />
<table class="table table-striped">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="customer in filterBy(customers, filterInput)">
<td>{{customer.first_name}}</td>
<td>{{customer.last_name}}</td>
<td>{{customer.email}}</td>
<td><router-link class="btn btn-default" v-bind:to="'/customer/'+customer.id">View</router-link></td></tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'customers',
data () {
return {
customers: [],
filterInput:'',
}
},
methods: {
fetchCustomers(){
this.$http.get('http://slimapp.dev/api/customers')
.then(function(response){
this.customers = (response.body);
});
},
filterBy(list, value){
value = value.charAt(0).toUpperCase() + value.slice(1);
return list.filter(function(customer){
return customer.last_name.indexOf(value) > -1;
});
},
},
created: function(){
if (this.$route.params.alert) {
this.alert = $route.params.alert
}
this.fetchCustomers();
},
updated: function(){
this.fetchCustomers();
},
components: {
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
Extend your filterBy method to check more then just last_name
filterBy(list, value){
value = value.charAt(0).toUpperCase() + value.slice(1);
return list.filter(function(customer){
return customer.first_name.indexOf(value) > -1 ||
customer.last_name.indexOf(value) > -1 ||
customer.email.indexOf(value) > -1
});
},
But you can use computed to provide filtered results (it might perform better because it caches computations)
computed: {
filteredList() {
const value= this.filterInput.charAt(0).toUpperCase() + this.filterInput.slice(1);
return this.customers.filter(function(customer){
return customer.first_name.indexOf(value) > -1 ||
customer.last_name.indexOf(value) > -1 ||
customer.email.indexOf(value) > -1
})
}
}
and use it in your template
<tr v-for="customer in filteredList">
...
</tr>
The above method finds all strings STARTING with the word you are looking for and ignores all middle-sentence words.
This means that if you have a customer like Vincent Van Patten you will only find it by searching for Vincent or Vincent(space)Van. If you search for the word Van or Patten it will return an empty search because you are using indexOf inside filter.
This is why I would rather use JS includes():
computed: {
filteredList() {
const value= this.filterInput.charAt(0).toUpperCase() + this.filterInput.slice(1);
return this.customers.filter(function(customer){
return customer.first_name.includes(value) ||
customer.last_name.includes(value) ||
customer.email.includes(value)
})
}
}
Any search like Van or Patten will now match
just to make it more flexibel do some lowercase:
computed: {
filteredList() {
const value = this.filterInput.toLowerCase().slice(1);
return this.customers.filter(function(customer){
return customer.first_name.toLowerCase().indexOf(value) > -1 ||
customer.last_name.toLowerCase().indexOf(value) > -1 ||
customer.email.toLowerCase().indexOf(value) > -1
})
}
}
I have a table where the row elements are all populated by child components. There is a checkbox in each of these child components. Now I want to get all checked checkboxes at once. I could use prefs emit as two way binding and update an array or object on the parent but I am wondering if there is better way for this.
Here a short example for the template part:
<table>
<thead>
<tr>
<th> Check </th>
<th> Title </th>
</tr>
</thead>
<list-tbody v-for="element in elements" :element="element"> </list-tbody>
</table>
and this is the child component
<tbody>
<tr>
<td>
<input type="checkbox">
</td>
<td> {{element.title}} </td>
</tr>
</tbody>
As mentioned in the comments you could handle this in two ways:
Use Vuex and mutate the elements array from the child component.
Emit an event on each selection click event to the parent and the parent will update the elements array.
You prefer the second because you're not using Vuex and that's OK.
Once you're having the data "linked" between child component and parent you can use a filter method to only show the selected elements.
Please have a look at the demo below or the fiddle from my comment.
const listTbodyVuex = {
props: ['element'],
template: `
<tbody>
<tr>
<td>
<input type="checkbox" #click="selected">
</td>
<td> {{element.title}} </td>
</tr>
</tbody>
`,
methods: {
...Vuex.mapMutations(['changeSelection']),
selected(evt) {
//console.log('clicked', evt.target.checked, this.changeSelection)
// changeSelection mutation could be also called with-out mapping
// this.$store.commit('changeSelection', ...);
this.changeSelection({
id: this.element.id, selected: evt.target.checked
});
}
}
}
const listTbodyEvents = {
props: ['element'],
template: `
<tbody>
<tr>
<td>
<input type="checkbox" #click="selected">
</td>
<td> {{element.title}} </td>
</tr>
</tbody>
`,
methods: {
selected(evt) {
console.log('clicked', evt.target.checked)
this.$emit('selected', {
element: this.element,
newSelection: evt.target.checked
})
}
}
}
const store = new Vuex.Store({
state: {
elements: [
{
id: 0,
title: 'first',
selected: false
},
{
id: 1,
title: 'second',
selected: false
},
{
id: 2,
title: 'third',
selected: false
}
]
},
mutations: {
changeSelection(state, {id, selected}) {
let element = state.elements
.filter((element) => element.id === id)[0];
element.selected = selected;
//console.log('update element', JSON.parse(JSON.stringify(element)));
Vue.set(state.elements, element.id, element);
}
}
})
new Vue({
el: '#app',
store,
data() {
return {
elements: [
{
id: 0,
title: 'first',
selected: false
},
{
id: 1,
title: 'second',
selected: false
},
{
id: 2,
title: 'third',
selected: false
}
]
}
},
computed: {
...Vuex.mapState({
vuexElements: (state) => state.elements
})
},
components: {
listTbodyEvents,
listTbodyVuex
},
methods: {
updateElement(data) {
let element = this.elements
.filter((element) => element.id === data.element.id)[0];
element.selected = data.newSelection;
// console.log('update', element)
},
filterSelected(data) {
// console.log('filter', data.filter((item) => console.log(item.selected)))
return data.filter((item) => item.selected);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.0/vuex.js"></script>
<div id="app">
<h1>Example with vuex</h1>
<table>
<thead>
<tr>
<th> Check </th>
<th> Title </th>
</tr>
</thead>
<list-tbody-vuex v-for="element in elements" :element="element" :key="element.id"> </list-tbody-vuex>
</table>
<pre>only selected: {{filterSelected(vuexElements)}}</pre>
<pre>{{vuexElements}}</pre>
<hr/>
<h1>Example with events</h1>
<table>
<thead>
<tr>
<th> Check </th>
<th> Title </th>
</tr>
</thead>
<list-tbody-events v-for="element in elements" :element="element" :key="element.id" #selected="updateElement"> </list-tbody-events>
</table>
<pre>only selected: {{filterSelected(elements)}}</pre>
<pre>{{elements}}</pre>
</div>
You should really stick to emitting values to maintain separation of your components. That being said, you can do the following if you really wanted to grab the data all at once:
First, you'll need a data attribute in your child component that your checkbox uses with v-model. For the sake of this example, let's just call it checkbox_value. Once you've done that, you can do something like the following in your parent component method:
var checkbox_values = [];
this.$children.forEach(function(child) {
//you can check the child type here if you have other non-checkbox children
checkbox_values.push(child.$data.checkbox_value);
});
Of course, I wouldn't encourage you to do something like this, but you'll have to make that judgement call.
Note: The above will return values only. You could push object key/value pairs instead!