Binding input parameters to URL params in VueJS - vue.js

Is there an easy way to bind the input parameters, to allow people to bookmark the Javascript calculation?
var app = new Vue({
el: '#app',
data: {
// dummy data
disksize: 100,
cost: 0.05,
items: [{
freq: "daily",
qty: 3,
ratio: 5
},
{
freq: "weekly",
qty: 0,
ratio: 10
},
{
freq: "yearly",
qty: 0,
ratio: 20
}
],
},
computed: {
initialCost() {
return Number(this.disksize * this.cost)
},
subtotalSize() {
return this.items.map((item) => {
return Number(item.qty * item.ratio / 100 * this.disksize)
});
},
subtotalCost() {
return this.items.map((item) => {
return Number(item.qty * item.ratio / 100 * this.disksize * this.cost)
});
},
subTotals() {
return this.items.reduce((subTotals, item) => {
return Number(subTotals + item.qty * item.ratio / 100 * this.disksize * this.cost)
}, 0);
},
total() {
return Number(this.initialCost + this.subTotals)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p><label>Size of disk: <input type=number v-model.number="disksize">GB</label></p>
<p><label>EBS cost: <input type=number step="0.001" v-model.number="cost">per GB-month of data stored</label></p>
<p><label>Cost of initial EBS disk: <input readonly :value="initialCost.toFixed(2)">USD per month</label></p>
<h3>
EBS snapshots
</h3>
<p>
EBS snapshots are incremental, so if you created a snapshot hourly each snapshot would only be backing up the changes that had been written to the volume in the last hour.
</p>
<table title="Retention">
<thead align="left">
<th>Frequency</th>
<th>Quantity</th>
<th>Ratio</th>
<th>Size</th>
<th>Cost per month</th>
</thead>
<tbody>
<tr v-for="(item, index) in items">
<td><input readonly :value="item.freq.charAt(0).toUpperCase() + item.freq.slice(1)" size="10"></td>
<td><input type="number" min="0" max="100" v-model.number="item.qty" size="10"></td>
<td><input type="number" min="0" max="100" v-model.number="item.ratio" size="3">%</td>
<td><input type="number" step="0.01" :value="subtotalSize[index].toFixed(2)" readonly size="10">GB</td>
<td><input type="number" step="0.01" :value="subtotalCost[index].toFixed(2)" readonly size="10">USD</td>
</tr>
</tbody>
</table>
<p>
<label>Total
{{initialCost.toFixed(2)}} initial cost + {{subTotals.toFixed(2)}} snapshots = <strong>{{total.toFixed(2)}} USD per month</strong>
</label>
</p>
</div>
I don’t want to use npm et al. Just prepackaged URLs like https://unpkg.com/vue-router/dist/vue-router.js ... if that's the solution. I'm not sure.
https://example.com/?disk=100&quantity=3&ratio=5
Quantity/Ratio can actually repeat, not sure what the at looks like in URL params. Any hints?

If I understand your question correctly, you want to do that allow an url like:
https://example.com/?disk=100&quantity=3&ratio=5
will passdisk,quantity and ratio props to your component.
This is achievable using vue router. One possible way is to use it in function mode:
You can create a function that returns props. This allows you to cast
parameters into other types, combine static values with route-based
values, etc.
const router = new VueRouter({
routes: [
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
]
})

If you simply want to get the values of a URL parameter and pass them along to a component, you can make use of Javascript's URLSearchParams() method (this won't work in IE 11, but a polyfill might be available).
function getURLParam (param) {
const queryString = window.location.search // query string from URL
const params = new URLSearchParams(queryString) // parse query string into object
return params.get(param) // get the value of the param name passed into function
}
Experiment with the code above. I can't be sure of the exact implementation you need, but this should suffice as a good starting point.

Related

How can I get the :value from input with Vuejs?

Hi I have list like this:
<tr v-for="(post, index) in posts" v-bind:index="index">
<td>{{ post.rut }}</td>
<td>{{ post.names }} {{ post.father_lastname }} {{ post.mother_lastname }}</td>
<td>
<input type="number" class="form-control" id="exampleInputEmail1" v-bind:value="post.employee_id" #input="form.amount[post.employee_id]" placeholder="Ingresa el monto">
</td>
</tr>
I defined in v-bind:value="" a initial value for every input of the list, then I need to send that data with axios but when I do that it does not send anything I mean I can not catch the vale for every input why? because it displays the value.. my axios is:
onSubmit(e) {
this.loading = true; //the loading begin
e.preventDefault();
let currentObj = this;
const config = {
headers: { 'content-type': 'multipart/form-data' }
}
let formData = new FormData();
formData.append('amounts', JSON.stringify(this.form.amount));
axios.post('/api/payroll_management/store?api_token='+App.apiToken, formData, config)
.then(function (response) {
currentObj.success = response.data.success;
})
.catch(function (error) {
console.log(error);
});
}
so I wonder how can I get the data from the inputs? if it returns empty this.form.amount
Thanks
Since each post has an amount value that gets changed with the <input>, it is easier to have that value be part of the post item itself.
This is done using v-model="post.amount" (see documentation) on the <input> of each post.
This way, there is a single place where the amount value is and where it gets updated.
Then when you submit the form, you can get the an array of these amount values by using a computed property (see documentation).
For better understanding what is happening, I highly recommend going through VueJS's documentation, since it's very readable and explains everything quite well.
Now, bringing it all together, have a look at this example:
new Vue({
el: "#app",
data: {
posts: [
{
rut: "A",
names: "Name Name",
father_lastname: "Lastname",
mother_lastname: "Lastname2",
employee_id: 5,
amount: 5, // Default value here
},
{
rut: "B",
names: "Name Name",
father_lastname: "Lastname",
mother_lastname: "Lastname2",
employee_id: 2,
amount: 2, // Default value here
},
],
},
computed: {
// Make form data a computed object.
form: function() {
// Get only the amount values from the posts.
// The inputs will update those automatically,
// so these will change as well.
let amount = this.posts.map((post, idx) => {
return parseInt(post.amount); // parseInt() is optional here
});
return {
amount: amount,
// ... other stuff
};
},
},
methods: {
getFormData: function() {
console.log( JSON.stringify(this.form.amount) );
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<h2>Posts:</h2>
<table>
<tr v-for="(post, index) in posts" v-bind:index="index">
<td>{{ post.rut }}</td>
<td>{{ post.names }} {{ post.father_lastname }} {{ post.mother_lastname }}</td>
<td>
<input type="number" class="form-control" v-model="post.amount">
</td>
</tr>
</table>
<br/>
<button v-on:click="getFormData()">Console log form data</button>
</div>

vue2 list return mixed string or component

In loop like this, I mostly just iterate over item values as strings, but sometimes need to return rendered component, for example build link element, or dropdown menu, for that table cell - need to find a way to return other component output instead of raw html string
<tr class="listing-item listing-item-category">
<td v-for="td in headeritems">{{val(td.k)}}</td>
</tr>
Is that even possible? I've found no mention of this, how should the method code go to return other component output? I know I would have to use v-html, but how to get it?
Assume we have a list like this:
headerItems: [
{
type: 'text',
value: 'Some text'
},
{
type: 'img',
props: {
src: 'http://some-where....'
}
},
{
type: 'my-component',
value: 'v-model value',
props: {
prop1: 10,
prop2: 'Blah bla',
},
events: {
myEvt: () => console.log('myEvt has fired')
}
},
],
So, We can render it:
<tr>
<td
v-for="(item, i) in headerItems" :key="i"
>
<div v-if="item.type === 'text'"> {{ item.value }}</div>
<component
v-else
:is="item.type"
v-model="item.value"
v-bind="item.props"
v-on="item.events"
/>
</td>
</tr>

Change v-model value without changin the actual data

So i've this data
data: () => ({
products: [
{ id: 1, name: "Prod 1", price: 2, stock: 5 },
{ id: 2, name: "Prod 2", price: 3, stock: 6 }
]
})
Template
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products" :key="product.id">
<td>{{ product.id }}</td>
<td>{{ product.name }}</td>
<td>
<input
type="text"
class="form-control"
v-model="product.price"
#paste.prevent
/>
</td>
<td>
<input
type="text"
class="form-control"
maxlength="999"
v-model="product.stock"
#paste.prevent
#keypress="onlyNumber($event)"
#input="handleInputStock($event.target.value)"
#blur="updateStock($event.target.value, product.id)"
/>
</td>
</tr>
</tbody>
</table>
So what I want is that when the user hit delete/backspace from the stock input field the value cannot be empty (blank) or it must be greater than or equal to zero. but without changing the products.stock value. this is because I need the product.stock value to compare with the changed value (stock input field) before sending to the server. So if stock value is equal to product.stock don't send to server otherwise send and update stock value.
so here's what i've done so far.
prevent the stock value empty but not working
handleInputStock(value) {
return +value.replace(/[^0-9]/g, "");
},
update stock
updateStock(stock, productId) {
const productStock = this.products.find(product => product.id == productId).stock;
if (!(stock == productStock)) {
// do ajax
}
},
onlyNumber
onlyNumber(e) {
const charCode = e.which ? e.which : event.keyCode;
if (charCode > 31 && (charCode < 48 || charCode > 57)) {
e.preventDefault();
}
},
Personally this feels like a higher level question to which your flow of product editing needs tweaking. Here is what I can think of:
User enters all the information.
User hits submit button.
Check whether of not the stock count is empty or 0.
Return an error message if it is.
Submit and update otherwise.
It might be worth looking into vuelidate that handles such validation in JavaScript. Meanwhile, we are also coming up with a tool called CRUDS DS (a WIP) that handles such situation with ease.
The best way is to create a ProductComponent and watch every product separately inside its own component, as shown below:
Product.vue
<ProductComponent
v-for="product in products"
:product="product"
:key="product.id" />
ProductComponent.vue
<template>
<tr>
<td>{{ product.name }}</td>
<td>
<input
type="text"
class="form-control"
v-model="product.price"
#paste.prevent
/>
</td>
<td>
<input
type="text"
class="form-control"
maxlength="999"
v-model="product.stock"
#paste.prevent
#keypress="onlyNumber($event)"
#blur="updateStock($event.target.value, product.id)"
/>
</td>
</tr>
</template>
<script>
export default {
props: {
product: {
type: Object,
default: {},
},
},
data: () => ({ actual_stock: "" })
// this is for handle stock cannot be empty or GTE:0
// also you dont need handleInputStock anymore
watch: {
product: {
handler(val) {
this.actual_stock = val.stock;
},
immediate: true,
},
"product.stock": function (newVal, oldVal) {
this.product.stock = +newVal;
},
},
methods: {
updateStock(stock, productId) {
if (!(stock == this.actual_stock)) {
// do ajax
}
}
}
}
</script>
If you want to handle it on parent side, you may use $emit to send an event upwards.
Can we have two versions of products? One for the server, one for v-models.
var server_products = [
{ id: 1, name: "Prod 1", stock: 5 },
{ id: 2, name: "Prod 2", stock: 6 }
]
//...
data: () => ({
products = server_products
})
updateStock(stock, productId) {
server_products.forEach((product) => {
if(product.id === productId && stock !== product.stock){
product.stock = stock
// do ajax
}
})
},
//...
If not than you can use vue's watch property so vue finds changes to the array for you.
//...
data: () => ({
products: [
{ id: 1, name: "Prod 1", stock: 5 },
{ id: 2, name: "Prod 2", stock: 6 }
]
}),
watch: {
'products': {
handler: function(newValue) {
// do ajax
},
deep: true
}
}
//...

Vue.js How to recalculate the displayed data after changing the select-option?

The task - when changing the unit of measurement, consider the total weight of the products.
Now the data in the array is changing, but new data is displayed on the screen only when click on other input fields
There is a table with data from the array, with the fields: Product name, net weight, quantity.
There is a select in the table with a choice of unit of measure - kg, grams, in order to understand in which units the "net weight" is set
for each value there is a conversion coefficient conversion_num from a separate array
Now, when changing the unit of measure, the unitChange function is launched, which changes the data in the array, but the changes are not displayed on the screen.
I tried with computed function, it work same.
What am I doing wrong?
HTML:
<tr v-for="(comp, i) in st">
<td>{{comp.name}}</td>
<td>
<input v-model.number="comp.weight_netto">
</td>
<td>
<select #change="unitChange($event.target.value, i)" v-model="comp.unit_id">
<option v-for="val in ul" :value="val.unit_id" v-text="val.name" ></option>
</select>
</td>
<td>
<input v-model.number="comp.quantity">
</td>
<td>{{itemqty(i)}}</td>
</tr>
JS:
methods: {
unitChange(value, i){
for (var j=0; j<this.ul.length; j++){
if (this.ul[j].unit_id==value){
this.st[i].conversion_num=this.ul[j].conversion_num;
break;
}
}
},
itemqty(i){
return (this.st[i].weight_netto*this.st[i].quantity)/this.st[i].conversion_num;
}
}
Don't mutate your products!
You'll end up with rounding errors: changing from one unit to another and back to initial will result in a different value from initial, because of the two roundings.
And it also makes things more difficult for you as everywhere you want to use the product you have to take into account its current unit.
A much cleaner solution is to leave the weight and unit unchanged (in the unit of your choice) and use a method to parse the displayed value, based on currently selected unit.
Your products remain unchanged (neither weight values or weight units change).
Proof of concept:
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
data: () => ({
products: [
{ id: 1, name: 'one', weight: 20},
{ id: 2, name: 'two', weight: 17}
],
units: [
{label: 'kgs'},
{label: 'lbs', factor: 2.20462},
{label: 'oz', factor: 35.274}
],
currentUnit: {label: 'kgs'}
}),
methods: {
convertToUnit(weight) {
return (Math.round(weight * (this.currentUnit.factor || 1) * 100) / 100) +
this.currentUnit.label
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Current unit:
<select v-model="currentUnit">
<option v-for="(unit, key) in units" :value="unit" :key="key">{{ unit.label }}</option>
</select>
<div v-for="(product, k) in products" :key="k">
name: <span v-text="product.name"></span>,
weight: <span v-text="convertToUnit(product.weight)"></span>
</div>
</div>
You should use Vue.set
Also some things can be refactored
Example
const table = Vue.component('product-table', {
data() {
return {
products: [{
name: 'one',
weight_netto: 1.23,
quantity: 4,
conversion_num: .420,
unit_id: 1
},{
name: 'two',
weight_netto: 3.21,
quantity: 5,
conversion_num: .69,
unit_id: 2
}],
units: [{
unit_id: 1,
conversion_num: .420,
name: 'kg'
},
{
unit_id: 2,
conversion_num: .69,
name: 'lb'
}
]
}
},
template: `
<table>
<tbody>
<tr :key="product.name" v-for="product in products">
<td>{{product.name}}</td>
<td>
<input v-model.number="product.weight_netto">
</td>
<td>
<select #change="unitChange($event.target.value, product)" v-model="product.unit_id">
<option :key="unit.unit_id" v-for="unit in units" :value="unit.unit_id" v-text="unit.name" ></option>
</select>
</td>
<td>
<input v-model.number="product.quantity">
</td>
<td>{{itemqty(product)}}</td>
</tr>
</tbody>
</table>`,
methods: {
unitChange(unit_id, product) {
unit_id = Number(unit_id);
const { conversion_num } = this.units.find(pr => pr.unit_id === unit_id);
Vue.set(product, 'conversion_num', conversion_num);
},
itemqty({ weight_netto, quantity, conversion_num}) {
return ((weight_netto * quantity) / conversion_num).toFixed(2);
}
}
})
new Vue({
el: '#container'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="container">
<product-table>
</product-table>
</div>

changing a single value using v-model / full table is redrawn

I was building an editable table, which began to crawl to a halt when the number of rows started to run in the 100's. This led me to investigate what was going on.
In the example below, when changing the value in the input, the whole table is redrawn, and the ifFunction() function is trigged 4 times.
Why is this happening? Shouldn't Vue be capable of just redrawing the respective cell? Have I done something wrong with the key-binding?
<template>
<div id="app">
<table border="1" cellpadding="10">
<tr v-for="(row, rowKey) in locations" :key="`row_+${rowKey}`">
<td v-for="(column, columnKey) in row" :key="`row_+${rowKey}+column_+${columnKey}`">
<span v-if="ifFunction()">{{ column }}</span>
</td>
</tr>
</table>
<input v-model="locations[0][1]">
</div>
</template>
<script>
export default {
data() {
return {
locations: [
["1","John"],
["2","Jake"]
], // TODO : locations is not generic enough.
}
},
methods: {
ifFunction() {
console.log('ifFunction');
return true;
},
}
}
</script>
The data property defines reactive elements - if you change one part of it, everything that's depending on that piece of data will be recalculated.
You can use computed properties to "cache" values, and only update those that really need updating.
I rebuilt your component so computed properties can be used throughout: created a cRow and a cCell component ("custom row" and "custom cell") and built back the table from these components. The row and the cell components each have a computed property that "proxies" the prop to the template - thus also caching it.
On first render you see the ifFunction() four times (this is the number of cells you have based on the data property in Vue instance), but if you change the value with the input field, you only see it once (for every update; you may have to click "Full page" to be able to update the value).
Vue.component('cCell', {
props: {
celldata: {
type: String,
required: true
},
isInput: {
type: Boolean,
required: true
},
coords: {
type: Array,
required: true
}
},
data() {
return {
normalCellData: ''
}
},
watch: {
normalCellData: {
handler: function(value) {
this.$emit('cellinput', {
coords: this.coords,
value
})
},
immediate: false
}
},
template: `<td v-if="ifFunction()"><span v-if="!isInput">{{normalCellData}}</span> <input v-else type="text" v-model="normalCellData" /></td>`,
methods: {
ifFunction() {
console.log('ifFunction');
return true;
},
},
mounted() {
this.normalCellData = this.celldata
}
})
Vue.component('cRow', {
props: {
rowdata: {
type: Array,
required: true
},
rownum: {
type: Number,
required: true
}
},
template: `
<tr>
<td
is="c-cell"
v-for="(item, i) in rowdata"
:celldata="item"
:is-input="!!(i % 2)"
:coords="[i, rownum]"
#cellinput="reemit"
></td>
</tr>`,
methods: {
reemit(data) {
this.$emit('cellinput', data)
}
}
})
new Vue({
el: "#app",
data: {
locations: [
["1", "John"],
["2", "Jake"]
], // TODO : locations is not generic enough.
},
methods: {
updateLocations({
coords,
value
}) {
// creating a copy of the locations data attribute
const loc = JSON.parse(JSON.stringify(this.locations))
loc[coords[1]][coords[0]] = value
// changing the whole locations data attribute to preserve
// reactivity
this.locations = loc
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table border="1" cellpadding="10">
<tbody>
<tr v-for="(row, i) in locations" is="c-row" :rowdata="row" :rownum="i" #cellinput="updateLocations"></tr>
</tbody>
</table>
<!-- <input v-model="locations[0][1]">
<input v-model="locations[1][1]">-->
{{locations}}
</div>