I am quite new to VueJs so forgive me for this question but I am very frustrated.
I have an app pre-configured with vue router and the views I want working fine, but whenever I try to display a card component with properties my site doesnt crash it just goes blank.
// here is my app.vue
[1]: https://i.stack.imgur.com/m7Zl3.png
// here is my router index[2]
[2]: https://i.stack.imgur.com/QGlwQ.png
// here is the component & props ...view farther down
<template>
<div>
<slot>
<b-card
bg-variant="primary"
text-variant="white"
header="Primary"
class="text-center"
>
<section class="header">
<h1>{{ id }} + {{ ipAddress }}</h1>
</section>
<section class="body">
<b-card-text>
<p1> {{ inputs }} + "," + {{ outputs }}</p1>
<p2> {{ publishers }} + "," + {{ subscribers }} </p2>
<p3> {{ frequency }} + "," + {{ messageType }} </p3>>
</b-card-text>
</section>
</b-card>
</slot>
</div>
</template>
<script>
// use camelCase // [ iD, ipAddress, inputs, outputs, publishers,
// subscribers, messageType, isActive, frequency]
export default {
name: nodeCard,
props: {
iD: {
type: Number,
required: true,
},
ipAddress: {
type: Number,
required: true,
// validator: function (value) {
// return value.length >= 8 || value.length <= 12;
// },
},
inputs: {
type: String,
required: false,
default: none,
},
outputs: {
type: String,
required: false,
default: none,
},
publishers: {
type: Object,
required: false,
default: none,
},
subscribers: {
type: Object,
required: false,
default: none,
},
messageType: {
type: Object,
required: true,
},
isActive: {
type: Boolean,
required: false,
default: none,
},
frequency: {
type: String,
required: false,
default: none,
},
},
data() {},
};
</script>
here is the view page
<template>
<div id="card">
<b-card-group deck>
<nodeCard
v-for="topics in NNV"
:key="topics.iD"
:name="topics.ipAddress"
:inputs="topics.inputs"
:outputs="topics.outputs"
:publishers="topics.publishers"
:subscribers="topics.subscribers"
:frequency="topics.frequency"
:messageType="topics.messageType"
>
</nodeCard>
</b-card-group>
</div>
</template>
<script>
import nodeCard from "../components/NodeCard.vue";
export default {
name: NNV,
components: nodeCard,
data() {
return {
topics: [
{
iD: 123,
ipAddress: 1234,
inputs: "Core Vue basics you have to know",
outputs: "Vue",
publishers: "polog",
subscribers: "Migos",
frequency: "offset",
messageType: "quavo",
},
],
};
},
};
</script>
As you are new to Vuejs so it is obvious to make such mistakes, so don't worry.
Replace your code with the below one and try to understand that which things were causing the issue, all were related to spelling mistakes and the difference between variable and String.
For example-
component's name is a String, we should not write it without the single or double comma, like below-
export default { name: "component_name" }
not
export default { name: component_name }
Whenever import any component, write it like this-
components: {component_name}
not
components:component_name
When pass props, use the same name on both sides, i.e if the prop key is "iD", it will be used in another component as iD only, not "id"
Correct code is-
View page-
<template>
<div id="card">
<b-card-group deck>
<nodeCard
v-for="(topic, index) in topics"
:key="index"
:id="topic.iD"
:ipAddress="topic.ipAddress"
:inputs="topic.inputs"
:outputs="topic.outputs"
:publishers="topic.publishers"
:subscribers="topic.subscribers"
:frequency="topic.frequency"
:messageType="topic.messageType"
>
</nodeCard>
</b-card-group>
</div>
</template>
<script>
import nodeCard from "#/components/NodeCard.vue";
export default {
name: "NNV",
components: { nodeCard },
data() {
return {
topics: [
{
iD: 123,
ipAddress: 1234,
inputs: "Core Vue basics you have to know",
outputs: "Vue",
publishers: "polog",
subscribers: "Migos",
frequency: "offset",
messageType: "quavo",
},
],
};
},
};
</script>
NodeCard.vue-
<template>
<div>
<slot>
<b-card
bg-variant="primary"
text-variant="white"
header="Primary"
class="text-center"
>
<section class="header">
<h1>{{ id }} + {{ ipAddress }}</h1>
</section>
<section class="body">
<b-card-text>
<p1> {{ inputs }} + "," + {{ outputs }}</p1>
<p2> {{ publishers }} + "," + {{ subscribers }} </p2>
<p3> {{ frequency }} + "," + {{ messageType }} </p3>>
</b-card-text>
</section>
</b-card>
</slot>
</div>
</template>
<script>
// use camelCase // [ iD, ipAddress, inputs, outputs, publishers,
// subscribers, messageType, isActive, frequency]
export default {
name: "NodeCard",
props: {
id: {
type: Number,
required: true,
},
ipAddress: {
type: Number,
required: true,
// validator: function (value) {
// return value.length >= 8 || value.length <= 12;
// },
},
inputs: {
type: String,
default: null,
},
outputs: {
type: String,
default: null,
},
publishers: {
type: String,
default: null,
},
subscribers: {
type: String,
default: null,
},
messageType: {
type: String,
required: true,
},
isActive: {
type: Boolean,
default: null,
},
frequency: {
type: String,
default: null,
},
},
};
</script>
Related
How can I get all input values from dynamic form as a single object with key-values pairs? Where key is input name and value is user input.
I have a custom input component. In app component I dynamically render inputs from array:
<template>
<div>
<form #submit.prevent="submit">
<ul>
<li v-for="input in inputFields" :key="input.label">
<base-input
:placeholder="input.placeholder"
:type="input.type"
:name="input.name"
v-model="userInput[input.name]"
></base-input>
</li>
</ul>
<button type="submit">Console.log input</button>
</form>
</div>
</template>
<script>
import BaseInput from "./components/BaseInput.vue";
export default {
components: { BaseInput },
data() {
return {
inputFields: INPUT_FIELDS,
userInput: {
name: "",
phone: "",
email: "",
location: "",
address: "",
comment: "",
},
};
},
methods: {
submit() {
console.log("userInput:", this.userInput);
},
},
};
</script>
Then in base-input component:
<template>
<input
:type="type"
:name="name"
:placeholder="placeholder"
#input="$emit('update:modelValue', $event.target.value)"
:value="modelValue[name]"
/>
</template>
<script>
export default {
emits: ["update:modelValue"],
props: {
label: {
type: String,
required: false,
},
placeholder: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
name: {
type: String,
required: false,
},
modelValue: {},
},
};
</script>
Expected behavior: click on submit --> get object like in console:
{
name: "user name",
phone: "1111",
email: "test#test.com",
location: "World",
address: "",
comment: "no",
},
Actual behavior: I can only type 1 letter in a field and no more.
Here's a codesandbox: https://codesandbox.io/s/optimistic-alex-dzw0mp?file=/src/App.vue
You were close. You need to remove [name] from the end of :value="modelValue[name]" in the base-input component. Since you are already scoping to name in the parent component it is wrong to also look for a [name] prop on the value of modelValue within your child component.
See it working here: https://codesandbox.io/s/unruffled-cohen-bivefc?file=/src/components/BaseInput.vue
Your component template should be:
<input
:type="type"
:name="name"
:placeholder="placeholder"
#input="$emit('update:modelValue', $event.target.value)"
:value="modelValue"
/>
I have a dropdown that I want to give a validation.
So here is my dropdown component:
<template>
<div class="custom-select" :tabindex="tabindex" #blur="open = false">
<div class="selected" :class="{ open: open }" #click="open = !open">
{{ selected.name }}
</div>
<div class="items" :class="{ selectHide: !open }">
<div v-if="defaultValue != ''">{{defaultValue}}</div>
<div v-for="(option, i) of options" :key="i" #click=" selected = option; open = false; $emit('input', option);">
{{ option.name }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
options: {
type: Array,
required: true,
},
defaultValue: {
type: String,
required: false,
default: "Choose an option",
},
tabindex: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
open: false,
selected: this.setDefaultValue(),
};
},
mounted() {
this.$emit("input", this.selected);
},
methods: {
setDefaultValue () {
if (this.defaultValue == '' && this.options.length > 0) {
return this.options[0];
}
return { name: this.defaultValue};
}
}
};
</script>
and in my parent component, I am using this dropdown, the fetched value from API call and filled with variations. So what I am trying to do is if the value is not selected (default: "Choose an option"), I want to give an error message which is saying, the dropdown is mandatory.
<Dropdown
:options="getVariations"
class="select"
#input="getSelected"
/>
<script>
import Dropdown from "../components/Dropdown";
import apiHelper from "../helpers/apiHelper";
export default {
components: {Dropdown},
data() {
return {
selected: "",
variationId: "",
selectedObject: null
};
},
computed: {
getVariations() {
return this.product.attributes.variations
}
},
methods: {
getSelected(opt) {
this.selectedObject = opt;
this.selected = opt.description;
this.variationId = opt.id;
}
},
};
</script>
This is a simple example of what you are asking for:
// v-model property
selectedVModel: null
// html
<select id="testselect" v-model="selectedVModel" required>
<option :value="null">Default</option>
<option value="bar">Option 1</option>
<option value="foo">Option 2</option>
<option value="baz">Option 3</option>
</select>
<div v-if="!selectedVModel">Error! Choose something!</div>
You can handle the error inside of your child component. In this example, if the v-model as selectedVModel is empty, the <div> with v-if="!selectedVModel" will be shown. As selectedVModel is null it will automatically select <option :value="null">Default</option>. If you chosse one of the options selectedVModel get´s a value so v-if="!selectedVModel" results into false, no error then. If you select Default again, the value turns to null again which results into true at v-if="!selectedVModel", so the error will be visible again.
Combine this simple example with your code and let me know if it helped you.
If you want to validate in your parent component, try to check selectedObject.id :
Vue.component('Dropdown', {
template: `
<div class="custom-select" :tabindex="tabindex" #blur="open = false">
<div class="selected" :class="{ open: open }" #click="open = !open">
{{ selected.name }}
</div>
<div class="items" :class="{ selectHide: !open }">
<div v-if="defaultValue != ''">{{defaultValue}}</div>
<div v-for="(option, i) of options" :key="i" #click=" selected = option; open = false; $emit('input', option);">
{{ option.name }}
</div>
</div>
</div>
`,
props: {
options: {
type: Array,
required: true,
},
defaultValue: {
type: String,
required: false,
default: "Choose an option",
},
tabindex: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
open: false,
selected: this.setDefaultValue(),
};
},
mounted() {
this.$emit("input", this.selected);
},
methods: {
setDefaultValue () {
if (this.defaultValue == '' && this.options.length > 0) {
return this.options[0];
}
return { name: this.defaultValue};
}
}
})
new Vue({
el: '#demo',
data() {
return {
selected: "",
variationId: "",
selectedObject: null,
product: {
attributes: {
variations: [{id: 1, name: 'name1', description: 'desc1'}, {id: 2, name: 'name2', description: 'desc2'},{id: 3, name: 'name3', description: 'desc3'},]
}
}
};
},
computed: {
getVariations() {
return this.product.attributes.variations
}
},
methods: {
getSelected(opt) {
this.selectedObject = opt;
this.selected = opt.description;
this.variationId = opt.id;
},
submit() {
if (!this.selectedObject.id) alert('pls select option')
else console.log(this.selectedObject)
}
},
})
Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<button #click="submit">submit</button>
<Dropdown
:options="getVariations"
class="select"
#input="getSelected"
/>
</div>
I am facing an issue where I need to display one select field two times in the form and while saving the form it will save the data in an array.
What I have done is created a form and added a select form and I want it to display two times (two select form) and it will be able to select different values for two select displays
I have created a sandbox here
Any ideas are much appreciated.
You could create new variable to second value with same options as first select input and save it as array.
<template>
<div>
<b-form-select
class="mb-2 mr-sm-2 mb-sm-0"
:options="optQuality"
v-model="slcQuality"
#input="changeQuality"
>
</b-form-select>
<div>slcQuality: {{ slcQuality }}</div>
<b-form-select
class="mb-2 mr-sm-2 mb-sm-0"
:options="optQuality"
v-model="slcQuality2"
#input="changeQuality"
>
</b-form-select>
<div>slcQuality: {{ slcQuality2 }}</div>
<div>
<button #click="submit">Submit</button>
</div>
<div>submitted Data: {{ JSON.stringify(submittedData) }}</div>
</div>
</template>
<script>
export default {
data() {
return {
optQuality: [
{ value: 1, text: "Original" },
{ value: 2, text: "Kw-1" },
{ value: 3, text: "Kw-2" },
],
slcQuality: null,
slcQuality2: null, // new variable
submittedData: [],
};
},
methods: {
changeQuality() {
console.log("test");
console.log(this.slcQuality);
},
submit() {
const data = [this.slcQuality, this.slcQuality2]; //save data as array
this.submittedData = data;
console.log(data);
},
},
};
</script>
EDIT
To avoid massive code you could use an array of objects as variable or nested array like this, then loop twice in template (nested v-for).
<template>
<div>
<div v-for="(quality, i) in slcQualities" :key="i">
<div v-for="(selection, j) in quality.values" :key="j">
<div>{{ selection.name }}</div>
<b-form-select
class="mb-2 mr-sm-2 mb-sm-0"
:options="quality.options"
v-model="selection.value"
#input="changeQuality"
/>
<div>slcQuality: {{ quality.value }}</div>
</div>
</div>
<div>
<button #click="submit">Submit</button>
</div>
<div>submitted Data: {{ JSON.stringify(submittedData) }}</div>
</div>
</template>
<script>
// array of data
const qualities = [
{
options: [
{ value: 1, text: "Original" },
{ value: 2, text: "Kw-1" },
{ value: 3, text: "Kw-2" },
],
values: [
{ name: "Select 1-1", value: null },
{ name: "Select 1-2", value: null },
],
},
{
options: [
{ value: 1, text: "Original" },
{ value: 2, text: "Kw-3" },
{ value: 3, text: "Kw-4" },
],
values: [
{ name: "Select 2-1", value: null },
{ name: "Select 2-2", value: null },
],
},
];
export default {
data() {
return {
slcQualities: qualities,
submittedData: [],
};
},
methods: {
changeQuality() {
console.log("test");
console.log(this.slcQuality);
},
submit() {
const data = this.slcQualities.map((i) => i.values.map((j) => j.value)); //map the values
this.submittedData = data;
console.log(data);
},
},
};
</script>
Here's the sandbox
Can anyone help with this problem? How can I emit two properties from child component to parent on select input change? I can submit the value, see below, but would like to emit the value and the name property of segmentLocations object. This is the child component:
<template>
<div class="container">
<div>
<select v-model="selectedSegmentValue" v-on:change="$emit('selectLocation', $event.target.value)">
<option selected value="">Choose your location...</option>
<option v-for="segmentLocation in segmentLocations"
:value="segmentLocation.value"
:key="segmentLocation.value">
{{ segmentLocation.name }}>
</option>
</select>
</div>
</div>
</template>
<script>
export default {
data() {
return {
segmentLocations: [
{ value: "Residential", name: 'Residential building' },
{ value: "Workplace", name: 'Workplace' },
{ value: "Hospitality", name: 'Hospitality or Retail' },
{ value: "Real Estate", name: 'Real Estate' },
{ value: "Commercial Parking", name: 'Commercial Parking' },
{ value: "Fleets", name: 'Fleets' },
{ value: "Cities & Governments", name: 'Cities & Governments' },
{ value: "Corridor", name: 'Highway, Corridor or Petrol Station' }
],
}
}
};
</script>
And this is the parent:
<template>
<Segments
v-on:selectLocation="quote.selectedSegmentValue = $event"
:selectedValue="quote.selectedSegmentValue">
</Segments>
</template>
<script>
export default {
data() {
return {
quote: {
selectedSegmentValue: "",
selectedSegmentName: ""
},
};
},
</script>
I think the existing answers and mine share a similar technique, but I created a couple of simplified sample components based on your components.
Child component:
<template>
<div class="emit-two-properties">
<div class="form-group">
<label for="segment-location">Segment Location</label>
<select class="form-control" id="segment-location"
v-model="segmentLocation" #change="selectSegmentLocation">
<option v-for="(segLoc, index) in segmentLocations" :key="index"
:value="segLoc">{{ segLoc.name }}</option>
</select>
</div>
</div>
</template>
<script>
export default {
data() {
return {
segmentLocation: {},
segmentLocations: [
{ value: "Residential", name: 'Residential building' },
{ value: "Workplace", name: 'Workplace' },
{ value: "Hospitality", name: 'Hospitality or Retail' },
{ value: "Real Estate", name: 'Real Estate' },
{ value: "Commercial Parking", name: 'Commercial Parking' },
{ value: "Fleets", name: 'Fleets' },
{ value: "Cities & Governments", name: 'Cities & Governments' },
{ value: "Corridor", name: 'Highway, Corridor or Petrol Station' }
],
}
},
methods: {
selectSegmentLocation() {
this.$emit('select-segment-location-event', this.segmentLocation);
}
}
}
</script>
Parent component:
<template>
<div class="parent">
<h4>Parent.vue</h4>
<div class="row">
<div class="col-md-6">
<form #submit.prevent="submitForm">
<emit-two-properties #select-segment-location-event="updateSegmentLocation" />
<button class="btn btn-secondary">Submit</button>
</form>
<p><span>Selected Segment Location Value:</span>{{ segmentLocation.value }}</p>
<p><span>Selected Segment Location Name:</span>{{ segmentLocation.name }}</p>
</div>
</div>
</div>
</template>
<script>
import EmitTwoProperties from './EmitTwoProperties'
export default {
components: {
EmitTwoProperties
},
data() {
return {
segmentLocation: {}
}
},
methods: {
updateSegmentLocation(segLoc) {
this.segmentLocation = segLoc;
}
}
}
</script>
you can create a method to get name and value from event.target (remove value from the end of child emit):
changeSelectedSegment(selected){
this.selectedSegmentName = selected.name
this.selectedSegmentValue = selected.value
}
in the parent change v-on:selectLocation to v-on:selectLocation="changeSelectedSegment($event)"
you can define a method like this (this method emit an object with name and value properties to parent
)
methods: {
selectLocation(event){
if(event.target.value !== ''){
const item = this.segmentLocations.find( item => item.value === event.target.value)
this.$emit('selectLocation', {
name: item.name,
value: event.target.value
})
}
}
},
and change this line :
<select v-model="selectedSegmentValue" v-on:change="$emit('selectLocation', $event.target.value)">
to this:
<select v-model="selectedSegmentValue" v-on:change="selectLocation">
I have two components "number-iput" and "basket input". How i can watch props from number-input (value) in basket-input?
Number Input component:
<template lang="pug">
.field
.number-input
button.number-input__btn(#click.prevent="minus")
i.i.i-minus
input(type="number" v-model="value" #input="valuecheck")
button.number-input__btn(#click.prevent="plus")
i.i.i-plus
</template>
<script>
export default {
name: "number-input",
props: {
value: {
type: Number,
default: 1
},
min: {
type: Number,
default: 1
},
max: {
type: Number,
default: 999999999
},
current: {
type: Number,
default: 1
}
},
methods: {
plus() {
if(this.value < this.max) this.value++;
},
minus() {
if(this.value > this.min) this.value--;
},
valuecheck() {
if(this.value > this.max) this.value = this.max
}
},
watch: {
value: function() {
if(parseInt(this.value) > parseInt(this.max)) this.value = this.max
}
}
}
</script>
Basket-input
<template lang="pug">
.basket-item
a.basket-item__image(href="")
img(:src="image", :alt="title")
.basket-item__info
span.basket-item__caption {{ code }}
a.basket-item__title(href="") {{ title }}
span.basket-item__instock(v-if="instock && instock > 0") В наличии ({{ instock }} шт)
span.basket-item__instock(v-else) Нет в наличии
.basket-item__numbers
number-input(min="1" max="99" :value="numberofitems")
.basket-item__lastcol
.column-price(v-if="price")
b {{ numberofitems * price }} ₽
span(v-if="numberofitems > 1") {{ numberofitems }} × {{ price }} ₽
button.basket-item__remove Удалить товар
</template>
<script>
export default {
name: 'basketitem',
props: {
image: {
type: String,
required: true
},
title: {
type: String,
required: true
},
code: {
type: String,
required: true
},
instock: {
type: Number
},
price: {
type: Number
},
numberofitems: {
type: Number,
default: 1
}
},
}
</script>
In basket input i need to watch number-input(value) and write it in numberofitems prop..
Im trying all, but my knowledge of vue is too low for that (
Props are mechanism to pass data only in one way - that means you can pass a value from parent to child but child is not allowed to change the value. If you do, Vue will warn you.
Way around it to use events. The principle is wildly know as "props-down, events-up".
You pass value to Child via prop
When Child want to change the value, instead of changing it directly, it will emit the event with new value
Parent component needs to handle that event and change its internal value (change will propagate into child item via prop)
You can read about various ways to do it for example here
Computed properties can help with that:
<template>
<input type="number" v-model="internalValue" />
</template>
<script>
export default {
props: {
value: {
type: Number,
default: 1
}
},
computed: {
internalValue: {
get: function() {
return this.value
},
set: function(newValue) {
this.$emit('valueChanged', newValue)
}
}
}
}
</script>
And in your parent component:
<template>
<mycomponent :value="value" #valueChanged="value = $event"/>
<mycomponent :value="value" #valueChanged="onValueChanged"/>
</template>
<script>
export default {
data: {
value: 0
},
methods: {
onValueChanged(newValue) {
this.value = newValue;
}
}
}
</script>
Notes
with larger and more complicated applications it can be better to use some solutions with shared global state like Vuex
Your Basket-input component has same problem because its receives numberofitems via prop