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)"
}
}
Related
I;m new on Vuejs and I'm currently working with composition API so I have an array like this:
const tabs = ref([
{
id: 1,
pdf: 'name1',
...
},
{
id: 2,
pdf: 'name2',
...
},
{
id: 3,
pdf: 'name3',
...
},
])
Then I have a div like this:
<div
v-for="tab in tabs"
:key="tab.name"
:href="tab.href"
class="px-12 pt-8 flex flex-col"
:class="[tab.current || 'hidden']"
#click="changeTab(tab)"
>
<div v-if="pdf != ''">
<div class="pt-4 font-bold underline">
<a :href="pdfSrc" target="_blank">See PDF</a>
</div>
</div>
</div>
And then I use computed to get current href value as:
props: {
tabs: {
type: Array as PropType<Array<any>>,
required: true,
},
},
computed: {
pdfSrc(): string {
return `/img/modal/pdf/${encodeURIComponent(this.tabs[0].pdf)}.pdf`
},
}
As you can see I always use tabs[0] so pdf value is always value name1 and I want to get depending of the selected tab
The tab method:
setup(props) {
const changeTab = (selectedTab: { id: number }) => {
props.tabs?.map((t) => {
t.id === selectedTab.id ? (t.current = true) : (t.current = false)
})
}
return {
changeTab,
}
},
How can I change static index 0 to dynamic one depending on the current tab?
I would suggest creating a new variable for tracking the selected tab.
const selectedTabId = ref(0);
Similar to tabs, this could be passed down in array and the value updated in changeTab function.
props: {
tabs: {
type: Array as PropType<Array<any>>,
required: true,
},
selectedTabId: {
type: Number
}
},
setup(props) {
const changeTab = (selectedTab: { id: number }) => {
selectedTabId = selectedTab.id
props.tabs?.map((t) => {
t.id === selectedTab.id ? (t.current = true) : (t.current = false)
})
}
return {
changeTab,
}
},
Finally in the computed use selectedTabId
computed: {
pdfSrc(): string {
return `/img/modal/pdf/${encodeURIComponent(this.tabs[this.selectedTabId].pdf)}.pdf`
},
}
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: {}
};
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/
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'">
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.