Vue.js 2 Multiple Properties defined in one Single-File component - javascript

I am working on a Laravel 5.5 & Vue.js 2.x project, after several digging searchs and questions I came to components. But still I have a warning message when the page renders:
[Vue warn]: Property or method "trimestral" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
(found in < Root >)
I have the following Single-File component in a Vue file: FormExpenses.vue:
<template>
<div class="input-group">
<input class="form-control" type="number" min="0" name="expenses" id="expenses" v-model="expenses">
<div class="input-group-btn">
<select class="form-control" name="expenses_range" id="expenses_range" v-model="expensesRange" :disabled="expenses < 1">
<option value="" disabled="">Elija el rango de expensas</option>
<option value="mensual">Mensual</option>
<option value="trimestral">Trimestral</option>
<option value="cuatrimestral">Cuatrimestral</option>
<option value="semestral">Semestral</option>
<option value="anual">Anual</option>
</select>
</div>
</div>
</template>
<script>
module.exports = {
props: {
amount: {
type: Number,
default: 0
},
range: {
type: String,
default: '',
}
},
data: function() {
return {
expenses: this.amount,
expensesRange: this.range,
}
},
}
</script>
I have registered the component "correctly" since some data seems to work. This is in the Vue instance file. Besides, I have defined the following element which seems it renders well in the form:
<expenses-form
:amount="#if(old('expenses')) {{ old('expenses') }}#elseif(isset($property) && $property->expenses) {{ $property->expenses }}#endif"
:range="#if(old('expenses_range')) {{ old('expenses_range') }}#elseif(isset($property) && $property->expenses_range) {{ $property->expenses_range }}#endif"
></expenses-form>
This seems to be working because it shows the following while rendering:
As seen, the first field is being rendered with their functions and data, but not the second one, despite according to Vue.js documentatiob seems to be ok.
Any comment is appreciated.

Vue expects range to be a string, but you're using v-bind (:range and Vue treats trimestral as variable, which is not defined), when passing a prop, instead of :range="..." use range="".
More you can read in docs here: https://v2.vuejs.org/v2/guide/components.html#Literal-vs-Dynamic

Related

Interpolation in a Vue component that creates HTML

I am building a web app using Vue 2.6.x. In the prototype that was created we have a line like this repeated many times:
<label class="title">On Time</label><span class="score float-right">Score: {{ score.ap_ontime }}
The only part of this whole line that changes is the data within the {{ }}. I would like to turn this into a component that I can call many times. For example:
<MyLabel title="On Time" score="score.ap_ontime" />
...so that I don't have to type this score interpolation over and over in a long HTML file. I know how pass props to the component and add text to the template of the component:
<template>
...
<label class="title">{{title}}</label>
...
</template>
..but what I can't figure out is how to take a string (e.g.score.ap_ontime) and have the template inject something that will render that value from a score plain old JavaScript object that exists in the application and is imported into the template. The original code I show at the top of this post renders fine and reacts to changes in the JavaScript object I just can't figure out how to do this in a component that creates HTML and Vue template syntax based on props.
Your new component should look something like this:
<template>
<label class="title">{{ caption }}</label>
<span class="score float-right">Score: {{ onTime }}</span>
</template>
<script>
export default
{
name: 'MyNewComponent',
props:
{
caption:
{
type: String,
default: ''
},
onTime:
{
type: [String, Number],
default: 0
}
}
}
</script>
Then you can call your component in this way:
<my-new-component :caption="Big title" :on-time="score.ap_ontime" />

Vue select change event firing before I trigger any action

I have a Vue app that seems to fire the change event even before I change the selection, recently tried the #input instead but still the same thing happens(as shown below)
Please note I have tried #change and #input and the event still fires on loading of the controls
Now this was working before I made css changes to scope the component, so that it doesn't effect the surrounding css. But cant fathom why this would make any difference.
Does anyone know why when adding the options tag and contents would make the change event fire?
<div class="form-group" :class="{formError: errors.has('formSection')}">
<label for="formSection">Section*</label>
{{ formModel }}
<select
v-model="formModel.idSection1"
class="form-control"
id="formSection"
name="formSection"
#input="onChangeSectionLevel1">
<option v-for="sectionLevel1 in formModel.sectionLevel1"
v-bind:value="sectionLevel1.value"
v-bind:key="sectionLevel1.id">
{{ sectionLevel1.value }}
</option>
</select>
<span v-if="errors.has('formSection')">This field is required</span>
</div>
As soon as I add in the options tag which loops through the items the onChangeSectionLevel1 function gets called. I thought it might be vee-validate but taken this out and still happens.
methods: {
onChangeSectionLevel1() {
alert("changed");
...
}
}
Update:
I have noticed that if I print out the object that is being bound, I get this which missing the idSection1 item.
{
"idSection2": null,
"idSection3": null,
}
If I then just put a dummy option as below then I can see my 3 data items including the idSection1 that is missing if I loop through with the v-for
<select
v-model="formModel.idSection1"
class="form-control"
id="formSection"
name="formSection"
#change="onChangeSectionLevel1">
<option>Hello World</option>
</select>
The data item still has the idSection1 listed
{
"idSection1": null,
"idSection2": null,
"idSection3": null
}
Many thanks in advance
Not really an answer, but the code above is find and works as expected in js fiddle
https://jsfiddle.net/andrewp37/a4obkhd5/1/
Code:
<div id="app">
<label for="formSection">Section*</label>
{{ formModel }}
<select
v-model="formModel.idSection1"
class="form-control"
id="formSection"
name="formSection"
#change="onChangeSectionLevel1">
<option v-for="sectionLevel1 in formModel.sectionLevel1"
v-bind:value="sectionLevel1.value"
v-bind:key="sectionLevel1.id">
{{ sectionLevel1.value }}
</option>
</select>
</div>
new Vue({
el: "#app",
data: {
formModel: {
idSection1: null,
sectionLevel1: [
{
id: 1,
value: "Section 1"
}
]
}
},
methods: {
onChangeSectionLevel1() {
alert("changed");
}
}
})
I had noticed that with lots of breakpoints added, the model was being replaces after the page was mounted.

Element Ui component is not rerendering on vue component prop change

I have a parent component and multiple child components, which use the same prop. This prop is an array of keys for a dropdown menu in element.js.
When the children render the first time, they contain no data. However, once the keys from arrive using vuefire the children get the dropdown menu items. However, the element dropdown menu is not rerendered as it should have been.
However using the vue dev tools, I can see that the dropdown menu entries have been passed down as a key. When vue does a hot reload, because of a file change, the keys will load.
Once the entries are loaded, I can select the entry and everything works as expected.
I also had the same results using the vuetify dropdown and the HTML dropdown. Both have the same issue.
parent
<template>
<div class="setup">
<h1>Setup</h1>
<div class="selectIngredients" v-for="number in 6">
<setupSelection :bottle="number" :ingredients="options" />
</div>
</div>
</template>
<script>
import {db} from "#/firebaseConfig"
import setupSelection from '#/components/setupSelection';
export default {
components: {
setupSelection,
},
firestore: {
options: db.collection('ingredients'),
},
};
</script>
child
<template>
<div class="ingredientSelector">
<h3>Select for Pump <span>{{bottle}}</span></h3>
<el-select v-model="selected" clearable placeholder="Select" >
<el-option
v-for="ingredient in ingredients"
v-bind:key="ingredient.text"
v-bind:label="ingredient.text"
v-bind:value="ingredient">
</el-option>
</el-select>
<!-- <v-select
v-model="selected"
:items="ingredients"
label="Select a favorite activity or create a new one"
></v-select> -->
<!-- <select v-model="selected" v-for="ingredient in ingredients">
<option :value="ingredient.value">{{ingredient.text}}</option>
</select> -->
</div>
</template>
<script>
import {db} from "#/firebaseConfig";
export default {
props: {
ingredients: { required: true },
bottle: { type: Number, required: true },
},
data() {
return {
selected: ''
}
},
},
};
</script>
I expected the dropdown menu to update once the client received them.
Thank you!
I haven't used Vuefire myself but I read the following in the documentation:
Make sure to create any property added to firestore in data as well
https://github.com/vuejs/vuefire/tree/master/packages/vuefire#firestore-option
Similar advice is given here:
https://vuefire.vuejs.org/vuefire/binding-subscriptions.html#declarative-binding
In your example you don't have options in the parent's data. This would, presumably, leave it non-reactive, leading to the symptoms you describe.
Use a data property for your items, and set them after the options are loaded.
data() {
return {
options: []
}
},
created() {
db.collection('ingredients').then(data=> this.options = data}
}
The promise returned from db.collection('ingredients') is not reactive.
Even better approach would be to set options: null, and show a loading indicator until it is an array.

Changing vue instance variables from within component

I've been teaching myself Vue.js, and have been utilising components to increase modularity.
One thing that I am struggling with is manipulating variables in the Vue instance from within the component. I have got it working well with v-model within a component by passing the variable in the jade file as a prop
eg loginform(slot="login-form" v-bind:form-submit="loginSubmit" v-bind:login-data="loginData")
Where loginData contains variables username & password which are 'v-modelled' to the inputs within the component. Then in the component template:
<input type="password" class="text-field" v-model="formData.password" />
However I have a tooltip component that I am wanting to use twice: One for the username field & one for the password field. The visibility of these tooltips are given by tooltips.username.vis and tooltips.password.vis respectively.
I can't seem to pass that variable as a prop in order to manipulate without getting the avoid manipulating props warning, despite v-model within the component not giving these warnings. The tooltip component is given below:
Vue.component('tooltip', {
props: ['show', 'message', 'click'],
template:
<transition name="shrink">
<div v-show="show" v-on:click="click" class="tooltip">
<div class="tooltip-arrow"></div>
<div class="tooltip-container">{{message}}</div>
</div>
</transition>
});
Does anyone have any idea on how I can achieve the desired affect (Hiding the tooltip on mouse click). I have tried passing a method as the click prop that has different arguments based on whether the tooltip is for the username or password input, however I get click undefined warnings. I could make two seperate functions but I would rather not explicitly write two functions that do the same thing.
You shouldn't attempt to modify props from within a component as Vue's warnings tell you, changes to props do not flow up from the component to the prop so any changes will be overwritten.
For what you're trying to achieve you should look into Vue's Custom Events https://v2.vuejs.org/v2/guide/components-custom-events.html
HTML
<div id="app">
<form>
<div>
<label>Username</label>
<input type="username" v-model="formData.username" />
<tooltip :show="tooltips.username.vis"
:message="tooltips.username.message" #tooltip:hide="tooltips.username.vis = false" />
</div>
<div>
<label>Password</label>
<input type="text" v-model="formData.password" />
<tooltip :show="tooltips.password.vis"
:message="tooltips.password.message" #tooltip:hide="tooltips.password.vis = false" />
</div>
</form>
</div>
JS
Vue.component('tooltip', {
props: ['show', 'message'],
template: `<transition name="shrink">
<div v-show="show" class="tooltip" #click="hide">
<div class="tooltip-arrow"></div>
<div class="tooltip-container">{{message}}</div>
</div>
</transition>`,
methods: {
hide () {
this.$emit('tooltip:hide');
},
}
});
new Vue({
el: "#app",
data: {
formData: {
username: '',
password: ''
},
tooltips: {
username: {
message: 'Fix your username',
vis: true
},
password: {
message: 'Fix your password',
vis: true
}
}
}
});
https://jsfiddle.net/10fjkoob/12/

Array change detection for an array of complex objects in Vue JS 2

Update
Vue JS 3 will properly handle this: https://blog.cloudboost.io/reactivity-in-vue-js-2-vs-vue-js-3-dcdd0728dcdf
Problem:
I have a vue component that looks like this:
sub-comp.vue
<template>
<div>
<input type="text" class="form-control" v-model="textA">
<input type="text" class="form-control" v-model="textB">
<input type="text" class="form-control" v-model="textC">
</div>
</template>
<script>
export default {
props: {
textA: {
type: Number,
required: false
},
textB: {
type: Number,
required: false
},
textC: {
type: Number,
required: false
}
}
}
</script>
I have a parent component that looks like this:
layout-comp.vue
<template>
<div>
<button #click="addItem">Add</button>
<ul>
<li v-for="listItem in listItems"
:key="listItem.id">
<sub-comp
:textA="listItem.item.textA"
:textB="listItem.item.textB"
:textC="listItem.item.textC"
/>
</li>
</ul>
</div>
</template>
import subComp from '../sub-comp.vue'
export default {
components: {
subComp
},
data() {
return {
listItems: []
}
},
methods: {
addItem: function () {
var item = {
textA: 5,
textB: 100,
textC: 200
}
if (!item) {
return
}
this.length += 1;
this.listItems.push({
id: length++,
item: item
});
}
}
</script>
The thing is, anything I do to edit the textboxes, the array doesn't get changed, even though the reactive data shows that it changed. For example, it will always be as
{
textA: 5,
textB: 100,
textC: 200
}
Even if I changed textB: 333, the listItems array still shows textB: 100. This is because of this:
https://v2.vuejs.org/v2/guide/list.html#Caveats
Due to limitations in JavaScript, Vue cannot detect the following changes to an array
Question:
I'm wondering how do I update the array? I also want the change to occur when leaving the textbox, using the #blur event. I'd like to see what ways this can be done.
I read these materials:
https://codingexplained.com/coding/front-end/vue-js/array-change-detection
https://v2.vuejs.org/v2/guide/list.html
But it seems my example is a bit more complex, as it has indexes associated, and the arrays have complex objects.
Update 4/12/2018
Found out that in my addItem() that I had:
item = this.conditionItems[this.conditionItems.length - 1].item);
to
item = JSON.parse(JSON.stringify(this.conditionItems[this.conditionItems.length - 1].item));
I was thinking the sync modifier in the answer below was causing problems because it duplicated all items. But that's not the case. I was copying a vue object (including the observable properties), which caused it to happen. The JSON parse and JSON stringify methods only copies the properties as a normal object, without the observable properties. This was discussed here:
https://github.com/vuejs/Discussion/issues/292
The problem is that props flow in one direction, from parent to child.
Setting the value using v-model in child won't affect parent's data.
Vue has a shortcut to update parent's data more easily. It's called .sync modifier.
Here's how.
In sub-comp.vue
<template>
<div>
<input type="text" class="form-control" :value="textA" #input="$emit('update:textA', $event.target.value)" >
<input type="text" class="form-control" :value="textB" #input="$emit('update:textB', $event.target.value)">
<input type="text" class="form-control" :value="textC" #input="$emit('update:textC', $event.target.value)">
</div>
</template>
<script>
export default {
// remains the same
}
</script>
add .sync when you add the props
<sub-comp
:textA.sync="listItem.item.textA" // this will have the same effect of v-on:update:textA="listItem.item.textA = $event"
:textB.sync="listItem.item.textB"
:textC.sync="listItem.item.textC"
/>
update:
if you have reactivity problem, don't use .sync, add a custom event and use $set
<sub-comp
:textA="listItem.item.textA" v-on:update:textA="$set('listItem.item','textA', $event)"
/>

Categories

Resources