child not updating based on props in Vue.js - javascript

I have something like this in my parent scope:
<form-error :errors="errors"></form-error>
<textarea class="form-control" name="post" cols="30" rows="8" #keydown="errors.clear('post')" v-model="post"></textarea>
Note the #keydown event, where I am clearing out the errors, by calling
method on a class.
With :errors="errors" I am passing instance of the below Errors class,
into <form-error> child component:
class Errors {
constructor() {
this.errors = {};
}
get(field) {
if (this.errors[field]) {
return this.errors[field][0];
}
}
clear(field) {
delete this.errors[field];
}
has(field) {
return this.errors.hasOwnProperty(field);
}
}
And in <form-error> child component I have this:
<template>
<div v-if="errors.has('post')" class="alert alert-danger" v-text="errors.get('post')"></div>
</template>
<script>
export default {
props: ['errors']
};
</script>
Now, while v-text="errors.get('post')" works fine, and I am getting error
displayed, the v-if="errors.has('post')" part doesn't work at all.
I am assuming errors is passed the right way as props, otherwise that
errors.get('post') wouldn't work.
Question is, why when parent triggers that #keydown event, and I see the
errors object is being emptied properly (Vue addon for chrome), the v-if
part doesn't update, thus hiding the div?
As you can see, the <FormError> child component is being updated to reflect the change in errors when I start typing, but still v-if doesn't trigger.
Edit
What's even more confusing, docs say:
Note that objects and arrays in JavaScript are passed by reference, so if the
prop is an array or object(as in my case), mutating the object or array itself inside the
child will affect parent state.
Although of course I am not mutating the object from with in my child, but the
important part is that object changes in parent should be reflected in child.

Are you getting any error. As I can see Map.has has poor support in browsers. You can try using any of following alternatives:
post in errors
<template>
<div v-if="'post' in errors" class="alert alert-danger" v-text="errors.get('post')"></div>
</template>
errors['post']
<template>
<div v-if="errors['post'] !== undefined" class="alert alert-danger" v-text="errors.get('post')"></div>
</template>

Related

Vue3 defineprops usage with v-model in child compoent

As I understand you can not change the props value directly in the child component.
But I find out that this will work, I want to know the reason behind it.
For reference: I am using vue3+vite
For example:
<template>
<input v-model="price"/>
</template>
<script lang="ts" setup>
defineProps({
price : Number
});
</script>
this can change the props value based on the input. with no warning or error
but if I write this way
<template>
<input v-model="props.price"/>
</template>
<script lang="ts" setup>
const props = defineProps({
price : Number
});
</script>
there will be a warning in the console.
notice I didn't write any computed to handle the change of the props.
Is it a bad practice?
Both should issue warning. The reasoning is that the parent will not usualy be aware of the change unless it is mutated there. It also allows the parent to validate the change. The idea is that only the owner of the data should modify it.
So emit an event instead. The conventional way to write the code is.
<input :value="price" #input='$emit("input", $event)'/>
// or
<input :value="price" #update:value='$emit("update:value", $event)'/>
// or
<input :value="price" #input='$emit("update:value", $event)'/>
You can access both because Vue automatically exposes both the props object itself and all the props' properties into the template.

Can't get Bootstrap-vue form validation working

I am using Vue CLI and Bootstrap and am having trouble with the form validation currently when the page loads all the input fields load as invalid. I can see why this is happening because the input fields are getting a class of is-invalid. I have fixed this by passing the state prop a value of null when it is false. It does not seem like the default behavior should be to run the validation when the page loads but maybe it is. I believe I have everything set up correctly as far as structure and proper classes I followed the bootstrap-vue docs.
My Code
<b-form
#submit.prevent="addReview"
name="review-form"
class="needs-validation"
novalidate
>
<div class="name">
<label class="sr-only" for="form-input-name">Name</label>
<b-input
id="form-input-name"
class="form-inputs mb-2 mr-sm-2 mb-sm-0"
v-model="name"
placeholder="Name"
required
:state="isEmpty(this.name) ? true : null" <---- My problem is here
></b-input>
...
</b-form>
My problem is I need 3 results from this ternary which obviously isn't possible. I need null on load to remove the error messages then false to display error on validation and true to display valid input. I have been struggling with this for days so any help with any aspect of this setup would be greatly appreciated if you want more code let me know. The submit button adds a class of was-validated which does display any error messages that are associated with empty inputs but doesn't validate the inputs.
Question
How do I validate inputs while still keeping form error messages hidden on load.
You aren't bound to just using a ternary statement in the :state prop - you can hook :state up to a computed property directly, which will achieve three things (this is similar to what is shown in the documentation):
We can have more than two conditions, breaking out of the limitations of the ternary statement.
The computed property will analyze user input on the fly, ensuring real-time validation of the form input.
Our template code will be cleaner and more readable (important).
I'm working loosely off of your example, but something like the following should solve your issue:
<template>
<div id="app">
<img width="25%" src="./assets/logo.png" style="margin-bottom: 15px;">
<b-form>
<div class="name">
<label class="sr-only" for="form-input- name">Name</label>
<b-input v-model="name" id="form-input-name" :state="isNameStateValid"></b-input>
</div>
</b-form>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
name: ""
};
},
methods: {
isValid() {
return this.name.length > 3 ? true : false; //your validation criteria goes here
}
},
computed: {
isNameStateValid() {
if (this.name) {
return this.isValid(this.name);
}
return null;
}
}
};
</script>
In the above, you would have a method that would check for your specific validation criteria (isValid, isEmpty, etc.).
Our new computed property, isNameStateEmpty, will use that method's return value, returning false for a validation failure (triggering BootstrapVue's failed validation state), true for a validation pass, or null in the event that this.name does not have a current value (examples being a fresh page load, or a user clearing the input field, making it blank).
See a working Codesandbox of this behavior here.
Because the input's v-model (v-bind:value and #change) is set to our "name" data property, every character change within the input field will reactively update our data property (this.name).
Because our isNameStateValid computed property has a dependency of this.name, it will reevaluate on every change of the this.name data property - ensuring real-time validation using BootstrapVue's validation state.
Hopefully that helps.

Binding a parameter to an ember component and handle it from the controller

I want to create the following component in ember (templates / components / echo-hlabel-tf.hbs)
<div id="echo-hlabel-tf">
<span id="tf-label">{{label}}</span>
<span id="tf-value">
<input type="text" value={{textValue}}
{{action "validateRegExp" textValue regExp post on="focusOut"}}
{{action "showText" textValue post on="mouseUp"}}
/>
</span>
</div>
Just a textfield and a label placed in the same row . I have created the functions in the controller of the component (components / echo-hlabel-tf.js):
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
validateRegExp(text,regExpStr){
let regExpObj = new RegExp(regExpStr)
if(regExpObj.test(text)){
console.log('entry matches')
}else{
console.log('entry is wrong');
}
},
showText(text){
console.log(text);
}
}
});
I want to use this component from another template (template / programmers.hbs) :
<ul>
{{people-list title="List of programmers" people=model}}
</ul>
<div>
{{echo-hlabel-tf
label="Textfield horizontal"
textValue="..."
regExp="^([a-z0-9]{5})$"
}}
</div>
The thing is that , even the actions are fired as the events trigger, the variable that represents the value of the input (textValue) always stands the same ("...") . seems that the binding between the outer template, and the components is created at the beginning, but if I put more letters in the textfield , the value of textValue remains the same and doesn't change . I would like to, as I'm adding letters to the textBox, print them with the console with the method showText, but I always print "..."
You are using pure html5 input that does not do two way binding by itself. That is even if you type something, it is not reflected to textValue attribute. You can use ember's builtin input component as in this twiddle. I believe this will solve your problem.

Interact with object passed to Angular2 component as Input

(to save some reading -- the problem boiled down to a missing 'this' when referencing the #Input'ed variables)
I have a parent class - this will (eventually) display a list of "QuestionComponents".
The relevant part of the parent class template is as follows:
<question [(justText)]="testText" [(justObj)]="testObj" [(myQuestion)]="singleQuestion"></question>
Suffice it to say I'm getting the "singleQuestion" object from a http service call. the Question class, of which "singleQuestion" is an instance, has a basic "toString" method for easier output debugging.
testText, textObj, and singleQuestion are all just objects of increasing complexity for me as I tested. For now it's very basic and only has one "question" as I work on my understanding.
The child "QuestionComponent" looks like this:
#Component ({
selector:
'question',
templateUrl:
'./app/components/question/question.component.html',
directives: [FORM_DIRECTIVES],
})
export class QuestionComponent {
#Input() myQuestion:USGSQuestion
#Input() justText:string
#Input() justObj:object
constructor() {
}
onClick() {
myQuestion.generalInfo.label = "changed";
console.log("change attempted");
}
}
Note that as time moves on, I'll need to be able to do some heavy interaction with the objects that are passed into the QuestionComponent. In fact, my preference was to just pass the Question object into the constructor, but passing data into the component constructor doesn't seem to work for some reason. (I digress) To attempt to check how I can interact with passed data , I built the onClick button and tried to change something in one of the #Input'ed variables.
The template for the child object is as so:
<div *ngIf="!justText">
no text passed
</div>
<div *ngIf="justText">
<b>Here is the passed Text:</b><br>
{{justText}} <br>
</div>
<br>
<div *ngIf="!justObj">
no obj passed
</div>
<div *ngIf="justObj">
<b>Here is the passed JSON:</b><br>
Foo: {{justObj.foo}} <br>
Baz: {{justObj.baz}}
</div>
<br>
<div class="question">
<i>I am a question spot</i>
<div *ngIf="!myQuestion">
Loading Question...
</div>
<div *ngIf="myQuestion">
<b>Here is the passed question:</b><br>
{{myQuestion}} <br>
</div>
</div>
<button (click)="onClick()">Clickit</button>
How do I get a reference to the #Input'ed objects inside the class? (in this case, how would I modify "myQuestion" in the 'onClick()' function? ... and, secondarily, how would I ensure changes to the objects got passed to the view and updated?
I should note I already tried to pass the value from the 'view' back through the onClick call, like so:
(change button to:)
<button (click)="onClick(myQuestion)">Clickit</button>
(and change onClick to:)
onClick(q) { q.generalInfo.label = "changed"; }
This did not work.
I'm an idiot.
After a few hours of research and searching (Before asking) it all came down to adding "this".
... as in "myQuestion.generalInfo.label = "changed";" should have been "this.myQuestion.generalInfo.label = "changed";"
Some days you gotta love programming, eh?

Aurelia Custom Elements data binding

I am using a custom-element with my Aurelia app. When I am binding data from my view-model with the custom-element, it is working fine. Even if I make some changes in the data in the custom-element control, the change is also reflected to the data in my view model, thanks to the two-way data binding.
However, if I make some changes in the data from the view model (using javascript), the data is not updated in the custom-element. I have replicated this problem for a simpler setting.
Simple View Model
export class Playground {
public testObj: any;
counter = 0;
constructor() {
this.testObj = {
prop1: "This is prop1 value"
, collection2: [{ id: "item21" }]
}
}
updateProp1() {
alert("before update: "+this.testObj.prop1);
this.testObj.prop1 = "Sayan " + this.counter++;
alert(this.testObj.prop1);
}
verifyChange() {
alert(this.testObj.prop1);
}
}
Simple View
<template>
<h1>
Playground
</h1>
<div >
<div repeat.for="item of testObj.collection2">
<div class="row">
<div class="col-sm-4">
<input type="text" class="form-control" placeholder="prop1"
value.bind="$parent.testObj['prop1']">
</div>
</div>
</div>
<button click.delegate="updateProp1()" class="btn btn-primary"> Update Prop1 </button>
<button click.delegate="verifyChange()" class="btn btn-primary"> Verify Change </button>
</div>
</template>
Now whenever I click Verify Change after changing the value in textbox, the changed value comes in the alert. But, if I click the Update Prop1 button, the prop1 value gets updated, but the change doesn't reflect in the view.
I am not sure exactly what I am missing.
Note: Instead of using $parent.testObj['prop1'], if $parent.testObj.prop1 is used, the databinding works as it should. However, my actual custom-element is of generic kind and the property name is not known before hand, hence it seems that I need to keep using $parent.testObj['prop1'] notation instead of dot notation: $parent.testObj.prop1.
At pointer/suggestion/feedback is appreciated.
Update: If anyone can point out the the difference between the dot notation and indexer notation w.r.t. aurelia data-binding (I have checked this already), that will be of great help.
This was an issue in earlier builds of the aurelia/binding module. Here's the related issue(s):
https://github.com/aurelia/binding/issues/75
https://github.com/aurelia/binding/pull/76
I tried your view/view-model in the latest version of aurelia and everything worked. Here's a screencast of what I saw: http://screencast.com/t/KqcuqXWjkU2
Make sure you have the latest version of the aurelia components installed- update steps here: http://blog.durandal.io/2015/05/01/aurelia-may-status-and-releases/

Categories

Resources