I have a simple form in Vue but need the value of an input to contain less than and greater than symbols along with a few pieces of data. This is causing Vue to break on compile since it's not valid. How can I make it work?
Here's an example:
<input type="hidden" id="DAT" name="DAT" :value="
<USERID>item.user_id</USERID>
"/>
new Vue({
data() {
return {
item: {
user_id: "123456"
}
}
},
template: '<input type="text" id="DAT" name="DAT" :value="`<USERID>${item.user_id}</USERID>`"/>'
}).$mount("#app")
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
Related
This is my first time experimenting with Vue.js so it's altogether possible I'm missing something very obvious.
Basically, I have a component that calculates the number of boxes needed for a certain quantity of a printed piece (I work in the print industry).
If needed, I have a button to create an additional component if the printed piece has multiple parts.
I'd like to have a reactive way to update the total number of boxes needed for all parts, but I just can't seem to get there.
Here's a link to my Gitlab repo with the code: https://gitlab.com/dsross/printutils
Any help would be appreciated.
I'm also using Browserify to write the build.js and build.css files referenced in index.html.
Here are my files, in case no one wants to look at the repo:
App.vue
<template>
<div id="app">
<div>
</div>
<div>
<calculator v-for="(part, index) in parts" :key="index"></calculator>
<br />
<br />
<div class="card shadow-sm">
<div class="card-body">
<button type="button" class="btn" #click="addPart()">Add Part</button>
<button type="button" class="btn" #click="totalBoxes">Total Boxes</button>
<span>Box Total (all parts): </span><span id="grandtotal"></span>
</div>
</div>
</div>
</div>
</template>
<script>
// import Hello from './components/Hello.vue'
import Calculator from './components/Calculator.vue'
export default {
name: 'app',
components: {
Calculator
},
methods: {
addPart: function () {
console.log("Adding part");
this.parts.push(Calculator);
},
totalBoxes: function () {
console.log("totalBoxes called");
let totalBoxes = 0;
let partTotals = document.querySelectorAll("#partBoxTotal");
for (var i = 0; i < partTotals.length; i++) {
totalBoxes += parseInt(partTotals[i].innerHTML);
}
this.totalBoxCount = totalBoxes;
document.getElementById("grandtotal").innerHTML = totalBoxes;
}
},
data: function () {
return {
parts: [Calculator],
totalBoxCount: 0
}
},
}
</script>
Calculator.vue
<template>
<div class="card shadow-sm" id="boxCalculator">
<div class="card-body">
<form>
<div class="form-group">
<p>Paper:
<select class="custom-select" v-model="paperWeight">
<option v-for="(mweight, paper) in mweights" :key="mweight" v-bind:value="paper">{{paper}}</option>
</select>
</p>
<br />
<br />
<p>Final Width:
<input class="form-control" type="text" v-model="finalWidth" id="finalWidth" value="">
</p>
<p>Final Height:
<input type="text" class="form-control" v-model="finalHeight" id="finalHeight" value="">
</p>
<p>Sheets Per Unit:
<input type="text" class="form-control" v-model="numberOfSheets" id="numberOfSheets" name="numberOfSheets"
value="">
</p>
<p>Quantity:
<input type="text" class="form-control" v-model="quantity" id="quantity" name='quantity'>
</p>
<p>Stitched:
<input type="checkbox" v-model="stitched" name="stitched" id="stitched" value="">
</p>
</div>
</form>
<div class="card">
<div class="card-body">
<div id='results'>
<p id="partWeightTotal">Part Total Weight: {{ totalWeight }}</p>
<p><span>Part Box Total: </span><span id="partBoxTotal">{{ boxQuantity }}</span></p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {
mWeights,
stitchedMultiplier,
maxArea
} from "../constants.js"
module.exports = {
data: function () {
return {
paperWeight: this.selected,
paperType: "",
finalWidth: "",
finalHeight: "",
numberOfSheets: "",
quantity: "",
stitched: "",
boxes: "",
mweights: mWeights
}
},
computed: {
squareInches: function () {
return this.finalHeight * this.finalWidth;
},
squareInchWeight: function () {
let mWeight = mWeights[`${this.paperWeight}`];
return (mWeight / 1000) / maxArea;
},
totalWeight: function () {
return ((this.squareInches * this.squareInchWeight) * this.numberOfSheets) * this.quantity;
},
boxQuantity: function () {
let boxes = this.totalWeight / 35;
if (this.stitched) {
this.boxes = Math.ceil(boxes * stitchedMultiplier);
// this.$root.$emit('box-change', this.boxes);
return this.boxes
} else {
this.boxes = Math.ceil(boxes);
// this.$root.$emit('box-change', this.boxes);
return Math.ceil(this.boxes);
};
},
},
}
</script>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>boxcalculator2</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../favicon.png">
<title>Box Calculator</title>
<!-- Bootstrap core CSS -->
<link href="dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="dist/sticky-footer.css" rel="stylesheet">
<link rel="stylesheet" href="dist/build.css">
</head>
<body>
<div class="container">
<div class='row'>
<div class='col'>
<div id="app"></div>
</div>
</div>
</div>
<script src="dist/build.js"></script>
</body>
</html>
If I understand correctly, you'd like the App's total box count to be updated automatically whenever the individual Calculators determine their box counts. One way to do this is to emit an event from Calculator when its box count changes, which could be monitored with a watcher.
There are a couple issues we'll address below:
It doesn't make sense (and is inefficient) to store Calculator -- a single-file-component definition -- in this.parts[]. Instead, it could store meaningful data points, such as Calculator's output.
Instead of DOM manipulation (i.e., querying the DOM for an element to get/set its value), opt for modeling the data in Vue, and using interpolation in the template. This lets Vue automatically display the updated value in the target element. It also obviates the element ID assignments (assuming they were used exclusively for DOM manipulation), simplifying the template for improved readability.
Storing Calculator output
In App, we must use this.parts[] to track the result of each part's calculation (which we'll capture below). We'll define each array element (i.e., a "part") to be:
{
boxes: 0 // box count for this part
}
This definition allows a computed property (which we'll define later), based on .boxes, to be reactive.
So, in addPart() and the data option:
// App.vue
export default {
// ...
methods: {
addPart() {
this.parts.push({ boxes: 0 });
}
},
data() {
return {
parts: [{ boxes: 0 }]
}
}
}
Notifying App of Calculator output
Typically, parents pass data to children via props, and children communicate data to parents with events. Alternatives include using a state management library, such as Vuex, but for the sake of simplicity, we'll use events here.
In Calculator, we want to notify the parent (App) of changes to the boxes value, so we'll add a watcher that emits an event (e.g., called boxes-changed) whenever boxes changes:
// Calculator.vue
export default {
//...
watch: {
boxes(value) {
this.$emit('boxes-changed', value);
}
}
}
In App, we'll listen to the boxes-changed event, and copy the event detail's value to the current part's boxes variable, where part is the current array element of parts[] being iterated.
// App.vue
<calculator v-for="(part, index) in parts" #boxes-changed="part.boxes = $event" :key="index"></calculator>
Breakdown of #boxes-changed="part.boxes = $event":
#boxes-changed="..." - listen to boxes-changed event emitted from <calculator>
part.boxes = $event - set part.boxes to value of event detail
Making totalBoxCount reactive
With the changes above, we have the tools needed to make App's totalBoxCount reactive:
Change totalBoxCount into a computed property that sums up the .boxes fields of this.parts[]. This property will be computed automatically whenever array elements of this.parts[] changes.
// App.vue
export default {
computed: {
totalBoxCount() {
// Note: Alternatively, use a simple for-loop to sum .boxes
return this.parts
.filter(p => p.boxes && !Number.isNaN(p.boxes) // get only parts that have a positive `.boxes` value
.map(p => p.boxes) // map object array into an integer array of `.boxes` values
.reduce((p,c) => p + c, 0); // sum all array elements
},
},
data() {
return {
parts: [],
// totalBoxCount: 0 // CHANGED INTO COMPTUED PROP ABOVE
}
}
}
In App's template, use string interpolation to display totalBoxCount:
<!--
<span>Box Total (all parts): </span><span id="grandtotal"></span>
--> <!-- DON'T DO THIS -->
<span>Box Total (all parts): {{totalBoxCount}}</span>
We might as well remove the Total Boxes button (previously used to manually trigger a calculation) from the template:
<!--
<button type="button" class="btn" #click="totalBoxes">Total Boxes</button>
--> <!-- DELETE -->
and its associated click-handler:
// App.vue
export default {
methods: {
// totalBoxes: function() { /* .. */ } // DELETE THIS
}
}
demo
About the issue
I am using Laravel 5.6.7 with vue.js. vee-validate is being used for validation
When the form loads, it shows validation error messages. User did not even click the submit button. Below is the screenshot.
Code
<template>
<div>
<form role="form">
<input v-validate data-vv-rules="required" type="text"
v-model="UpdateForm.First_Name">
<p v-if="errors.has('First Name')">{{ errors.first('First Name') }}</p>
<button type="button">
Update Profile
</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
UpdateForm: {
First_Name: ''
}
}
},
created() {
this.GetProfile();
},
methods: {
GetProfile() {
axios.post("some api url", {}).then(response => {
this.UpdateForm.First_Name = response.data.Data.First_Name;
});
}
}
}
</script>
Could I get rid of validation error messages on form load?
This is not the expected behavior. For initial validating you need to inform it with v-validate.initial.
Maybe you are defining this to happen when declaring v-validate or in other place.
Vue.use(VeeValidate);
new Vue({
el: '#demo'
})
.is-danger{
color: red;
}
<script src="https://unpkg.com/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vee-validate#latest/dist/vee-validate.js"></script>
<div id="demo">
<label>This one needs touching</label>
<input type="text" name="name" v-validate="'required'">
<div v-show="errors.has('name')" class="is-danger">Errors: {{ errors.first('name') }}</div>
<br/>
<label>This one does not need touching</label>
<input name="name2" v-validate.initial="'required'" type="text">
<div v-show="errors.has('name2')" class="is-danger">{{ errors.first('name2') }}</div>
</div>
Changed
this.editForm.First_Name = Data.User.First_Name;
to
if(Data.User.First_Name != null && Data.User.First_Name != "") {
this.editForm.First_Name = Data.User.First_Name;
}
and validation is working fine now. Basically the variable is not initialized.
I have a HTML input field to enter some information:
<div id="fruitForm">
<div class="inputArea">
<label for="fruit0">Enter Fruit Name</label>
<input id="fruit0"></input>
</div>
</div>
<button #click="newInputField">Add More Fruit Input Fields</button>
<button #click="submit">Submit Fruit</button>
And then I handle that click event:
export default {
data() {
return {
}
},
methods: {
newInputField() {
//Create another input area for users to input fruits
},
submit() {
//Do something here
}
}
}
When a user selects the Add More Fruit Input Fields button, it should create a new input area so that the HTML looks like this:
<div id="fruitForm">
<div class="inputArea">
<label for="fruit0">Enter Fruit Name</label>
<input id="fruit0"></input>
</div>
<div class="inputArea">
<label for="fruit1">Enter Fruit Name</label>
<input id="fruit1"></input>
</div>
</div>
<button #click="newInputField">Add More Fruit Input Fields</button>
<button #click="submit">Submit Fruit</button>
Now, I've been using traditional DOM manipulation methods via vanilla Javascript to accomplish this... stuff like this:
const inputArea = document.getElementsByClassName('inputArea');
And then I change the id's of the input field, and then I use appendChild to add the new input field to the DOM.
So my question is: how should I be cloning this element with vuejs? I feel I'm not approaching this in the vuejs way. Should I be approaching this like a list and using v-for? Or something else?
Avoid direct DOM manipulations with Vue. You can use data property as a model for your template. The answer would be yes, you can and probably should use v-for for what you call cloning:
var demo = new Vue({
el: '#demo',
data: {
counter: 0,
inputs: [{
id: 'fruit0',
label: 'Enter Fruit Name',
value: '',
}],
},
methods: {
addInput() {
this.inputs.push({
id: `fruit${++this.counter}`,
label: 'Enter Fruit Name',
value: '',
});
}
}
});
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="demo">
<div class="inputArea" v-for="input in inputs" :key="input.id">
<label :for="input.id">{{input.label}}</label>
<input :id="input.id" v-model="input.value"></input>
</div>
<button #click="addInput">Add input</button>
</div>
So I have 2 blocks of HTML, each containing 2 input fields and when submitting the form, I want to get all values from the inputs, and then create an object from the values...
As of know I've done it with plain vanilla JS and it works as it should, however if feels like to touching the DOM a bit to much, and also are very much depending on a specific DOM struckture, and therefore I was thinking there must be a better way, the VUE way so to speak, however im a bit stuck on how to do this the VUE way, which is why posting the question here in hope of getting some useful tips :)
HTML:
<form novalidate autocomplete="off">
<div class="input-block-container">
<div class="input-block">
<input type="text" placeholder="Insert name" name="name[]" />
<input-effects></input-effects>
</div>
<div class="input-block">
<input type="email" placeholder="Insert email address" name="email[]" />
<input-effects></input-effects>
</div>
</div>
<div class="input-block-container">
<div class="input-block">
<input type="text" placeholder="Insert name" name="name[]" />
<input-effects></input-effects>
</div>
<div class="input-block">
<input type="email" placeholder="Insert email address" name="email[]" />
<input-effects></input-effects>
</div>
</div>
<button class="button button--primary" #click.prevent="sendInvites"><span>Send</span></button>
</form>
JS:
methods: {
createDataObject() {
let emailValues = document.querySelectorAll('input[type="email"]');
emailValues.forEach((email) => {
let name = email.parentNode.parentNode.querySelector('input[type="text"]').value;
if(email.value !== "" && name !== "") {
this.dataObj.push({
email: email.value,
name
});
}
});
return JSON.stringify(this.dataObj);
},
sendInvites() {
const objectToSend = this.createDataObject();
console.log(objectToSend);
//TODO: Methods to send data to server
}
}
You can provide data properties for each of your inputs if you have static content.
data: function() {
return {
name1: '',
email1: '',
name2: '',
email2: ''
}
}
Then use them in your template:
<input type="text" placeholder="Insert name" v-model="name1" />
Access in method by this.name1
Try this
<div id="app">
<h1> Finds </h1>
<div v-for="find in finds">
<input name="name[]" v-model="find.name">
<input name="email[]" v-model="find.email">
</div>
<button #click="addFind">
New Find
</button>
<pre>{{ $data | json }}</pre>
</div>
Vue Component
new Vue({
el: '#app',
data: {
finds: []
},
methods: {
addFind: function () {
this.finds.push({ name: '', email: '' });
}
enter code here
}
});
When my form is submitted I wish to get an input value:
<input type="text" id="name">
I know I can use form input bindings to update the values to a variable, but how can I just do this on submit. I currently have:
<form v-on:submit.prevent="getFormValues">
But how can I get the value inside of the getFormValues method?
Also, side question, is there any benefit to doing it on submit rather than updating variable when user enters the data via binding?
The form submit action emits a submit event, which provides you with the event target, among other things.
The submit event's target is an HTMLFormElement, which has an elements property. See this MDN link for how to iterate over, or access specific elements by name or index.
If you add a name property to your input, you can access the field like this in your form submit handler:
<form #submit.prevent="getFormValues">
<input type="text" name="name">
</form>
new Vue({
el: '#app',
data: {
name: ''
},
methods: {
getFormValues (submitEvent) {
this.name = submitEvent.target.elements.name.value
}
}
}
As to why you'd want to do this: HTML forms already provide helpful logic like disabling the submit action when a form is not valid, which I prefer not to re-implement in Javascript. So, if I find myself generating a list of items that require a small amount of input before performing an action (like selecting the number of items you'd like to add to a cart), I can put a form in each item, use the native form validation, and then grab the value off of the target form coming in from the submit action.
You should use model binding, especially here as mentioned by Schlangguru in his response.
However, there are other techniques that you can use, like normal Javascript or references. But I really don't see why you would want to do that instead of model binding, it makes no sense to me:
<div id="app">
<form>
<input type="text" ref="my_input">
<button #click.prevent="getFormValues()">Get values</button>
</form>
Output: {{ output }}
</div>
As you see, I put ref="my_input" to get the input DOM element:
new Vue({
el: '#app',
data: {
output: ''
},
methods: {
getFormValues () {
this.output = this.$refs.my_input.value
}
}
})
I made a small jsFiddle if you want to try it out: https://jsfiddle.net/sh70oe4n/
But once again, my response is far from something you could call "good practice"
You have to define a model for your input.
<input type="text" id="name" v-model="name">
Then you you can access the value with
this.name inside your getFormValues method.
This is at least how they do it in the official TodoMVC example: https://v2.vuejs.org/v2/examples/todomvc.html (See v-model="newTodo" in HTML and addTodo() in JS)
Please see below for sample solution, I combined the use of v-model and "submitEvent" i.e. <input type="submit" value="Submit">. Used submitEvent to benefit from the built in form validation.
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app">
<form #submit.prevent="getFormValues">
<div class="form-group">
<input type="email" class="form-control form-control-user"
v-model="exampleInputEmail"
placeholder="Enter Email Address...">
</div>
<div class="form-group">
<input type="password" class="form-control"
v-model="exampleInputPassword" placeholder="Password"> </div>
<input type="submit" value="Submit">
</form>
</div>
<script>
const vm = new Vue({
el: '#app',
methods: {
getFormValues (submitEvent) {
alert("Email: "+this.exampleInputEmail+" "+"Password: "+this.exampleInputPassword);
}
}
});
</script>
</body>
</html>
The other answers suggest assembling your json POST body from input or model values, one by one. This is fine, but you also have the option of grabbing the whole FormData of your form and whopping it off to the server in one hit. The following working example uses Vue 3 with Axios, typescript, the composition API and setup, but the same trick will work anywhere.
I like this method because there's less handling. If you're old skool, you can specify the endpoint and the encoding type directly on the form tag.
You'll note that we grab the form from the submit event, so there's no ref, and no document.getElementById(), the horror.
I've left the console.log() there to show that you need the spread operator to see what's inside your FormData before you send it.
<template>
<form #submit.prevent="formOnSubmit">
<input type="file" name="aGrid" />
<input type="text" name="aMessage" />
<input type="submit" />
</form>
</template>
<script setup lang="ts">
import axiosClient from '../../stores/http-common';
const formOnSubmit = (event: SubmitEvent) => {
const formData = new FormData(event.target as HTMLFormElement);
console.log({...formData});
axiosClient.post(`api/my-endpoint`, formData, {
headers: {
"Content-Type": "multipart/form-data",
}
})
}
</script>