vue js 2 sorting a table - vue.js

I have 2 questions with vue.js 2 and a fiddle here: https://jsfiddle.net/tmun9cxa/1/
When you click a column header, why does my sorting not work? What is the solution?
How do I go about making the search input field search only the pn column?
A lot of the examples ive found are using vue1 and out of date.
<input type="text" value="" v-model="search" placeholder="Search">
<table style="text-align: center;">
<thead>
<tr>
<th v-for="column in columns">
<a
href="#"
v-on:click="sort(column.shortcode)">{{column.label}}
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(product) in products">
<td>{{product.pn}}</td>
<td>{{product.od}}</td>
<td>{{product.id}}</td>
<td>{{product.thickness}}</td>
<td>{{product.lo}}</td>
<td>{{product.weight}}</td>
</tr>
</tbody>
</table>
javascript here
var vm = new Vue({
el: '#app',
data: {
currentSort: 'pn',
currentSortDir: 'asc',
search: '',
columns: [
{ label: 'P/N', shortcode: 'pn' },
{ label: 'OD (De,mm)', shortcode: 'od' },
{ label: 'ID (De,mm)', shortcode: 'id' },
{ label: 'Thickness (De,mm)', shortcode: 'thickness' },
{ label: 'LO', shortcode: 'lo' },
{ label: 'Weight (kg/1000)', shortcode: 'weight' },
], // columns
products: [
{
pn: 170158,
od: 13,
id: .44,
thickness: 1,
lo: .45,
weight: .7
},{
pn: 1803561,
od: 12,
id: .8,
thickness: .7,
lo: .11,
weight: .5
},{
pn: 170149,
od: 9,
id: .64,
thickness: .6,
lo: .75,
weight: .3
},{
pn: 150149,
od: 15,
id: .22,
thickness: .3,
lo: .55,
weight: .9
},
], // products
},
methods: {
sort:function(col) {
//console.log( 'current: '+this.currentSort );
//console.log( 'col: '+col );
//var colthing = col;
// if you click the same label twice
if(this.currentSort == col){
console.log( 'same col: '+col );
// sort by asc
this.products = this.products.sort((a, b) => {
return a.col >= b.col;
});
}else{
this.currentSort = col;
console.log( 'diff col: '+col );
// sort by desc
this.products = this.products.sort((a, b) => {
return a.col <= b.col;
});
} // end if
}, // sort
}, // methods
}); // vue

the column sorting , as pointed out, was not working because you need to use a[col] instead of a.col
Also, you should consider using a computed value instead of modifying the original data. This makes filtering easier too.
here is the updated script (note that <tr v-for="(product) in products"> needs to be <tr v-for="(product) in showProducts"> for this to work)
var vm = new Vue({
el: '#app',
data: {
currentSort: 'pn',
currentSortDir: 'asc',
search: '',
columns: [
{ label: 'P/N', shortcode: 'pn' },
/// more columns ...
], // columns
products: [
//.... objects
], // products
},
computed: {
showProducts() {
return this.products.filter(a => {
console.log(a.pn)
return (a.pn + '').includes(this.search)
})
.sort((a, b) => {
if (this.currentSortDir === 'asc') {
return a[this.currentSort] >= b[this.currentSort];
}
return a[this.currentSort] <= b[this.currentSort];
})
},
},
methods: {
sort:function(col) {
// if you click the same label twice
if(this.currentSort == col){
this.currentSortDir = this.currentSortDir === 'asc' ? 'desc' : 'asc';
}else{
this.currentSort = col;
console.log( 'diff col: '+col );
} // end if
}, // sort
}, // methods
}); // vue
finally, the fiddle: https://jsfiddle.net/tmun9cxa/2/

Related

how to multi filtering vue.js

just new in vue.js.
I have an array of objects which is my products, How to multi filter it?
HTML
<input type="checkbox" required v-model="selectedCategory" value="1"> // Category 1
<input type="checkbox" required v-model="selectedCategory" value="2"> // Category 2
<div>
<div v-for="product in filteredProducts">
<a href="#">
<div class="prdct-frame__img-holder">
<img :src="product.productImage">
</div>
<p>{{ product.productName }}</p>
</a>
</div>
</div>
SCRIPT
<script>
var vm = new Vue({
el: "#main",
data: {
products: [
{
productName: "Product 1",
productID: 1,
productImage: //via.placeholder.com/200x200,
categoryID: true,// 1
colorID: 1, //blue
sizeID: 1, //large
},
{
productName: "Product 2",
productID: 2,
productImage: //via.placeholder.com/200x200,
categoryID: true, // 1
colorID: 2, //red
sizeID: 2, //medium
},
{
productName: "Product 3",
productID: 3,
productImage: //via.placeholder.com/200x200,
categoryID: true, // 2
colorID: 3, //green
sizeID: 3, //small
},
{
productName: "Product 4",
productID: 4,
productImage: //via.placeholder.com/200x200,
categoryID: true, // 2
colorID: 4, //green
sizeID: 3, //small
},
],
selectedCategory: [],
},
computed: {
filteredProducts: function() {
var vm = this;
var category = vm.selectedCategory;
return vm.products.filter((product) => {
var keys = Object.keys(product);
var matchFilter = false;
category.forEach((key) => {
if(product[key] === true) {
matchFilter = true;
}
});
return matchFilter;
});
},
}
});
</script>
This Code works for filtering by category, if I filter by Category 1 it display all the products under Category 1 which is right. Now I want to filter it too by color and size.
For example if choose Category 1 and choose color is red and choose size is medium
The expected out put will be :
Product name : Product 2
Color : red
Size : medium
You should use an Array of filters:
data()
{
return {
filtration:
[
{
key: 'categoryID',
value: 2
},
{
key: 'colorID',
value: 2
},
{
key: 'sizeID',
value: 2
},
]
}
},
computed: {
filteredProducts: function() {
const arrFilters = this.filtration;
return this.products.filter((product) => {
return arrFilters.every((filter) => {
return product[filter.key] === filter.value;
});
});
},
}

Vuejs : suggestions component in table row

I am trying to use autocomplete component "v-suggestions", from https://www.npmjs.com/package/v-suggestions
Want to use in tab row in a loop, since i want to create multiple select boxes, please see the code
<tr
v-for="ingredient in ingredients"
v-bind:key="ingredient.id"
>
<!-- https://www.npmjs.com/package/v-suggestions -->
<td>
<suggestions
v-model="query.suggestions"
:options="options"
:onInputChange="onSelectIngredient">
</suggestions>
</td>
<td>
<input
type="text"
class="form-control"
id=""
v-model="items.quantity"
/>
</td>
<td>
export default {
data() {
let ingredients_auto = ['Onion', 'Salt', 'Oil', 'Sugar']
return {
items: [
{ id: 0, item_name: "x", quantity: "" },
{ id: 1, item_name: "y", quantity: "" },
{ id: 2, item_name: "y", quantity: "" }
],
query: '',
ingredients_auto: ingredients_auto,
ingredients_selected: null,
options: {}
};
methods: {
onSelectIngredient (query) {
if (query.trim().length === 0) {
return null
}
// return the matching countries as an array
return this.ingredients_auto.filter((item) => {
return item.toLowerCase().includes(query.toLowerCase())
})
}
}
I am getting below error in console , any idea why I am see this issue , I think there are issue with v-model, not sure how to fix this
vue.runtime.esm.js?2b0e:1888 TypeError: Cannot use 'in' operator to search for 'suggestions' in Salt
at Proxy.set (vue.runtime.esm.js?2b0e:1076)
at callback (eval at ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"d52508de-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/AddDish.vue?vue&type=template&id=646fb311&scoped=true& (app.b02fcecff20360dbbc44.hot-update.js:11), <anonymous>:189:45)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at VueComponent.invoker (vue.runtime.esm.js?2b0e:2179)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at VueComponent.Vue.$emit (vue.runtime.esm.js?2b0e:3888)
at VueComponent.query (v-suggestions.js?c4f6:1)
at Watcher.run (vue.runtime.esm.js?2b0e:4568)
at flushSchedulerQueue (vue.runtime.esm.js?2b0e:4310)
at Array.eval (vue.runtime.esm.js?2b0e:1980)
You had couple of mistakes I have rectified go through this
<template>
<table>
<tr
v-for="ingredient in ingredients"
v-bind:key="ingredient.id"
>
<td>
<suggestions
v-model="queries[ingredient.id]"
:options="options"
:onInputChange="onSelectIngredient">
</suggestions>
<!-- note the queries array, each item in it binds to a unique item in ingredients array -->
</td>
<td>
<input
type="text"
class="form-control"
id=""
v-model="ingredient.quantity"
/>
</td>
</tr>
</table>
</template>
<script>
import suggestions from 'v-suggestions'
export default {
name: 'demo',
components: {
suggestions
},
// using component locally, but if want to use globally use Vue.use(suggestions)
data() {
let ingredients_auto = ['Onion', 'Salt', 'Oil', 'Sugar']
let q = [];
for(var i=0; i<ingredients_auto.length; i++) {
q.push('');
}
//since we want queries array to be same length as of ingredients/ingredients_auto
return {
dish_name: "",
ingredients: [
{ id: 0, item_name: "Salt", quantity: "" },
{ id: 1, item_name: "Pepper", quantity: "" },
{ id: 2, item_name: "Oil", quantity: "" }
],
error: "",
selected:[],
units: ['gms', 'pound', 'kgs', 'ml', 'nos', 'liter'],
query: '',
ingredients_auto: ingredients_auto,
ingredients_selected: null,
options: {},
queries: q
};
},
methods: {
onSelectIngredient (query) {
if (query.trim().length === 0) {
return null
}
// return the matching countries as an array
return this.ingredients_auto.filter((item) => {
return item.toLowerCase().includes(query.toLowerCase())
})
}
}
}
</script>
Note that I have made a dynamic array q which I copy to queries because you want each individual textbox to behave independently so typing in one textbox should not affect the text contained in other textboxes
#viney see the
I didn't added that portion, let me add
export default {
data() {
let ingredients_auto = ['Onion', 'Salt', 'Oil', 'Sugar']
return {
dish_name: "",
ingredients: [
{ id: 0, item_name: "Salt", quantity: "" },
{ id: 1, item_name: "Pepper", quantity: "" },
{ id: 2, item_name: "Oil", quantity: "" }
],
error: "",
selected:[],
units: ['gms', 'pound', 'kgs', 'ml', 'nos', 'liter'],
query: '',
ingredients_auto: ingredients_auto,
ingredients_selected: null,
options: {}
};

Change table values on change with Vue

I want to change table values when a user selects a value from the dropdown menu. By default, the values are in Metric. If a user selects Standard then I want to run some math on each value and to convert metric to standard. Also being able to switch back to Metric.
https://jsfiddle.net/tmun9cxa/13/
html
<div class="form-filter">
<select name="type" v-model="filter_unit" v-on:change="onSelectUnitChange">
<option value="metric">Metric</option>
<option value="standard">Standard</option>
</select>
</div><!-- /filter -->
<table style="text-align: center;">
<thead>
<tr>
<th v-for="column in columns">
<a
href="#"
v-on:click="sort(column.shortcode)">{{column.label}}
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(product) in showProducts">
<td>{{product.pn}}</td>
<td>{{product.od}}</td>
<td>{{product.id}}</td>
<td>{{product.thickness}}</td>
<td>{{product.lo}}</td>
<td>{{product.weight}}</td>
</tr>
</tbody>
</table>
</div><!-- /app -->
Javascript
var vm = new Vue({
el: '#app',
data: {
currentSort: 'pn',
currentSortDir: 'asc',
category: 'all',
filter_unit: 'metric',
search: '',
columns: [
{ label: 'P/N', shortcode: 'pn' },
{ label: 'OD (De,mm)', shortcode: 'od' },
{ label: 'ID (De,mm)', shortcode: 'id' },
{ label: 'Thickness (De,mm)', shortcode: 'thickness' },
{ label: 'LO', shortcode: 'lo' },
{ label: 'Weight (kg/1000)', shortcode: 'weight' },
], // columns
products: [
{
pn: 170158,
od: 13,
id: .44,
thickness: 1,
lo: .45,
weight: .7,
category: 'chrome',
},{
pn: 1803561,
od: 12,
id: .8,
thickness: .7,
lo: .11,
weight: .5,
category: 'chrome',
},{
pn: 170149,
od: 9,
id: .64,
thickness: .6,
lo: .75,
weight: .3,
category: 'stainless',
},{
pn: 150149,
od: 15,
id: .22,
thickness: .3,
lo: .55,
weight: .9,
category: 'chrome',
},
], // products
},
computed: {
showProducts(){
return this.products
.filter(a => {
return (a.pn + '').includes(this.search)
})
.sort((a, b) => {
if (this.currentSortDir === 'asc') {
//console.log( this.currentSort );
return a[this.currentSort] >= b[this.currentSort];
}
return a[this.currentSort] <= b[this.currentSort];
})
}
},
methods: {
sort:function(col) {
// if you click the same label twice
if(this.currentSort == col){
// sort by asc
this.currentSortDir = this.currentSortDir === 'asc' ? 'desc' : 'asc';
}else{
this.currentSort = col;
}
}, // sort
onSelectUnitChange:function(){
if(this.filter_unit == 'standard'){
// change values of OD using this equation. current OD value * 0.0393701
// change table header OD(De,mm) to OD(De,in)
// also will be doing a similar process to ID and Thickness
console.log('standard change');
}
},
}, // methods
}); // vue
You may add the logic on your computed property and check the v-model of the dropdown. I've updated your sample see https://jsfiddle.net/tmun9cxa/74
With my example I didn't change your existing computed but you can simply add your logic to that
filteredProducts() {
let filteredProducts = []
let _product
this.showProducts.forEach(product => {
_product = product
// do the logic here
if (this.filter_unit === 'metric') {
_product.displayWeight = _product.weight * 25
} else if (this.filter_unit === 'standard') {
_product.displayWeight = _product.weight + 10
}
filteredProducts.push(_product)
})
return filteredProducts
}
Update:
Another option is to use Vue filters. I've updated your example using filters http://jsfiddle.net/eywraw8t/274069
filters: {
convertOd(val, type) {
if (type === 'metric') {
return val * 0.0393701
} else if (type === 'imperial') {
return val
}
}
}
or
Vue.filter('convertOd', function (val, type) {
if (type === 'metric') {
return val * 0.0393701
} else if (type === 'imperial') {
return val
}
})
and to use it in html
<td>{{ product.od | convertOd(filter_unit) }}</td>
You could use computed properties but your code could work as it is.
I just applied conversions on values in the onSelectUnitChange function and it worked.
onSelectUnitChange:function(){
if(this.filter_unit == 'standard'){
this.products.forEach(p => {
p.od *= 0.0393701
p.id *= 0.0393701
p.thickness *= 0.0393701
})
this.columns[1].label = "OD(De,in)"
this.columns[2].label = "ID(De,in)"
this.columns[3].label = "Thickness(De,in)"
} else {
this.products.forEach(p => {
p.od /= 0.0393701
p.id /= 0.0393701
p.thickness /= 0.0393701
})
this.columns[1].label = "OD(De,mm)"
this.columns[2].label = "ID(De,mm)"
this.columns[3].label = "Thickness(De,mm)"
}
}

Remove category_id property from ColumnValue object in Vue template

I want to remove category_id from {{ columnValue }}, but what is the best way to to that, because i need category_id in the first part ?
<table>
<tr>
<td v-for="columnValue, column in record">
<template v-if="editing.id === record.id && isUpdatable(column)">
<template v-if="columnValue === record.category_id">
<select class="form-control" v-model="editing.form[column]">
<option v-for="column in response.joins">
{{ column.category }} {{ column.id }}
</option>
</select>
</template>
<template v-else="">
<div class="form-group">
<input class="form-control" type="text" v-model= "editing.form[column]">
<span class="helper-block" v-if="editing.errors[column]">
<strong>{{ editing.errors[column][0]}}</strong>
</span>
</div>
</template>
</template>
<template v-else="">
{{ columnValue }} // REMOVE category_id here!
</template>
</td>
</tr>
</table>
And the view (its the number under group i want to remove):
The DataTable view
The script:
<script>
import queryString from 'query-string'
export default {
props: ['endpoint'],
data () {
return {
response: {
table: null,
columntype: [],
records: [],
joins: [],
displayable: [],
updatable: [],
allow: {},
},
sort: {
key: 'id',
order: 'asc'
},
limit: 50,
quickSearchQuery : '',
editing: {
id: null,
form: {},
errors: []
},
search: {
value: '',
operator: 'equals',
column: 'id'
},
creating: {
active: false,
form: {},
errors: []
},
selected: []
}
},
filters: {
removeCategoryId: function (value) {
if (!value) return ''
delete value.category_id
return value
}
},
computed: {
filteredRecords () {
let data = this.response.records
data = data.filter((row) => {
return Object.keys(row).some((key) => {
return String(row[key]).toLowerCase().indexOf(this.quickSearchQuery.toLowerCase()) > -1
})
})
if (this.sort.key) {
data = _.orderBy(data, (i) => {
let value = i[this.sort.key]
if (!isNaN(parseFloat(value)) && isFinite(value)) {
return parseFloat(value)
}
return String(i[this.sort.key]).toLowerCase()
}, this.sort.order)
}
return data
},
canSelectItems () {
return this.filteredRecords.length <=500
}
},
methods: {
getRecords () {
return axios.get(`${this.endpoint}?${this.getQueryParameters()}`).then((response) => {
this.response = response.data.data
})
},
getQueryParameters () {
return queryString.stringify({
limit: this.limit,
...this.search
})
},
sortBy (column){
this.sort.key = column
this.sort.order = this.sort.order == 'asc' ? 'desc' : 'asc'
},
edit (record) {
this.editing.errors = []
this.editing.id = record.id
this.editing.form = _.pick(record, this.response.updatable)
},
isUpdatable (column) {
return this.response.updatable.includes(column)
},
toggleSelectAll () {
if (this.selected.length > 0) {
this.selected = []
return
}
this.selected = _.map(this.filteredRecords, 'id')
},
update () {
axios.patch(`${this.endpoint}/${this.editing.id}`, this.editing.form).then(() => {
this.getRecords().then(() => {
this.editing.id = null
this.editing.form = {}
})
}).catch((error) => {
if (error.response.status === 422) {
this.editing.errors = error.response.data.errors
}
})
},
store () {
axios.post(`${this.endpoint}`, this.creating.form).then(() => {
this.getRecords().then(() => {
this.creating.active = false
this.creating.form = {}
this.creating.errors = []
})
}).catch((error) => {
if (error.response.status === 422) {
this.creating.errors = error.response.data.errors
}
})
},
destroy (record) {
if (!window.confirm(`Are you sure you want to delete this?`)) {
return
}
axios.delete(`${this.endpoint}/${record}`).then(() => {
this.selected = []
this.getRecords()
})
}
},
mounted () {
this.getRecords()
},
}
</script>
And here is the json:
records: [
{
id: 5,
name: "Svineskank",
price: "67.86",
category_id: 1,
category: "Flæskekød",
visible: 1,
created_at: "2017-09-25 23:17:23"
},
{
id: 56,
name: "Brisler vv",
price: "180.91",
category_id: 3,
category: "Kalvekød",
visible: 0,
created_at: "2017-09-25 23:17:23"
},
{
id: 185,
name: "Mexico griller 500 gram",
price: "35.64",
category_id: 8,
category: "Pølser",
visible: 0,
created_at: "2017-09-25 23:17:23"
},
{
id: 188,
name: "Leverpostej 250 gr.",
price: "14.25",
category_id: 9,
category: "Pålæg",
visible: 1,
created_at: "2017-09-25 23:17:23"
},
}]
.. and so on......
I would recommend using a filter in Vue to remove the property, such as:
new Vue({
// ...
filters: {
removeCategoryId: function (value) {
if (!value) return ''
delete value.category_id
return value
}
}
})
An then use this in your template:
{{ columnValue | removeCategoryId }}
Update: I misunderstood the scope of the loop. This works, and I verified on jsfiddle: https://jsfiddle.net/spLxew15/1/
<td v-for="columnValue, column in record" v-if="column != 'category_id'">

Vue.js How to calculate totals?

How can I calculate the total amount from an array?
I pass data to the child component as prop, and I am stuck here. When I console log prop, it returns a very complicated object . I tried this.values.reduce() function but it does not work.
<template>
<tr v-for="value in values" >
<th scope="row">{{$index+1}}</th>
<td>{{value.name}}</td>
<td>-</td>
<td>${{value.total}}</td>
</tr>
<tr>
<th></th>
<td><strong>Total:{{total}}</strong></td>
<td>-</td>
<td>-</td>
</tr>
</template>
<script>
export default {
props: ['values'],
ready: function() {
}
}
</script>
In case anyone else is in the same situation as me I thought I'd add this answer. I needed to get the values from nested objects then push them to an array before reducing them:
total: function(){
let total = [];
Object.entries(this.orders).forEach(([key, val]) => {
total.push(val.price) // the value of the current key.
});
return total.reduce(function(total, num){ return total + num }, 0);
}
This uses ES7 .entries to loop through the object which looked like this:
orders = {
1: {title: 'Google Pixel', price: 3000},
2: {title: 'Samsung Galaxy S8', price: 2500},
3: {title: 'iPhone 7', price: 5000}
}
You can then display the total in your template with:
<span> {{total}} </span>
var payments = new Vue({
el: "#payments",
data: {
payments: [
{ name: "houseRent", amount: 1000, is_paid: true },
{ name: "houseRent", amount: 1500, is_paid: true },
{ name: "houseRent", amount: 1200, is_paid: false },
{ name: "houseRent", amount: 1070, is_paid: true },
{ name: "houseRent", amount: 1040, is_paid: false }
]
},
computed: {
totalAmount: function () {
var sum = 0;
this.payments.forEach(e => {
sum += e.amount;
});
return sum
}
}
});`
As you proposed, you could use the Array#reduce function. Starting from this example on SO, you could adapt it to your needs and only add value.total to the sumtotal.
To compute the total of all values, you can use computed properties, which will display as {{ total }} in your template:
<script>
export default {
props: {
values: {
type: Array,
default: []
},
}
ready: function() {
},
computed: {
total: function() {
if (!this.values) {
return 0;
}
return this.values.reduce(function (total, value) {
return total + Number(value.total);
}, 0);
}
}
}
</script>
Note: This will of course only work, if value.total is a unitless number (e.g. 1, not '1 USD'). Otherwise you would need to strip the in the reduce function as well.