I am not a vue.JS programmer, but have been asked to jump in and make a few changes to an existing app. I am trying to use a computed property to show/hide a DIV.
The problem is the computed property is not being called after the form POST (I put a console.log statement inside the computed method to verify it's not even being called). The findLoginProvider method is invoked successfully and the localStorage items are set... but the DIV is not hidden.
Like I said, I am not a vue.JS programmer and cannot figure out why this isn't working... this is a simplified example of the real page. I really want computed properties to work because I have multiple DIV's to show/hide based on a few computed properties (so manually settings the visibility of a specific DIV is not desired)
HTML:
<div v-if="showFindLoginProvider" :class="{'disabled': isLoading}">
<form #submit.prevent="findLoginProvider" method="POST">
<div>
<label class="block text-gray-700">Email Address</label>
<input
type="email"
name="email"
v-model="email"
placeholder="Enter Email Address"
autofocus
autocomplete
required
/>
</div>
<button type="submit">Continue</button>
</form>
</div>
Methods:
findLoginProvider() {
this.isLoading = true;
UserService
.getLoginProvider(this.email)
.then((response) => {
if (response?.status === 200) {
localStorage.setItem("login_email", this.email);
localStorage.setItem("login_provider", JSON.stringify(response.data));
} else {
this.isError = "Error retrieving login provider";
}});
this.isLoading = false;
},
Computed:
showFindLoginProvider() {
console.log("showFindLoginProvider")
return !this.isLoggedIn && (!this.hasLoginEmail || !this.hasLoginProvider);
},
According to the docs
A computed property will only re-evaluate when some of its reactive dependencies have changed
By saying that in your fnc findLoginProvider you need to change reactive variable to fire showFindLoginProvider run
findLoginProvider() {
this.isLoading = true;
UserService
.getLoginProvider(this.email)
.then((response) => {
if (response?.status === 200) {
// will tell computed to run
this.isLoggedIn = true
// rest of your code
} else {
this.isError = "Error retrieving login provider";
}
});
this.isLoading = false;
},
Related
I'm trying to make an autocomplete dropdown menu in vue and can't get the api response to render on the page. I'm making an api call on every keystroke in the input. You can see in the handleOnChange method that I'm trying to set the response to the results variable that is binding to the list above.
When I console log the results right after I make the api call AND set it to the data binding variable it logs as if everything is working fine. However, it does not render the actual data on the screen.
Here is my code
<template>
<div>
<input type="text" value="" v-model="address" placeholder="Start typing an address..." #input="methods.handleOnChange(address)"/>
<ul v-if="results.length !== 0">
<li v-for="result in results">
{{ result.streetLine || ""}}
</li>
</ul>
<p v-model="results">
{{results}}
</p>
</div>
</template>
<script>
const SmartyStreetsSDK = require("smartystreets-javascript-sdk");
const SmartyStreetsCore = SmartyStreetsSDK.core;
const Lookup = SmartyStreetsSDK.usAutocompletePro.Lookup;
const credentials = new SmartyStreetsCore.SharedCredentials([website-key]);
const clientBuilder = new SmartyStreetsCore.ClientBuilder(credentials).withLicenses(["us-autocomplete-pro-cloud"]).withBaseUrl("https://us-autocomplete-pro.api.smartystreets.me/lookup");
const client = clientBuilder.buildUsAutocompleteProClient();
export default {
name: 'Autocomplete',
data () {
return {
address: "",
results: [{streetLine: "testing"}],
methods: {
handleOnChange: function(address) {
//TODO: need to be able to access "results" from in here
console.log("this: ", this);
if (address) {
const lookup = new Lookup(address);
client.send(lookup).then((response) => {
console.log(response.result);
this.results = response.result;
console.log("databinding: ", this.results);
}).catch(console.log)
}
}
}
}
}
}
</script>
Vue.set()
As discussed in the comments, Vue.set was able to do it.
See documentation: https://v2.vuejs.org/v2/api/#Vue-set
Arguments are:
{Object | Array} target
{string | number} propertyName/index
{any} value
It replaces the value at target[propertyName/index] with value and forces reactivity on the value(s).
In your case it should be this instead of this.results = response.result;:
Vue.set(this, "results", response.result);
I have the following vue component where I am changing the class of the parent row based on whether or not an input is focused
<template>
<div class="form form--login">
<div class="form__row" :class="{entered: emailEntered}">
<label class="form__label" for="login-form-email">Email address</label>
<input type="text" class="form__control form__control--textbox" name="email-address" id="login-form-email" #focus="emailEntered = true" #blur="handleBlur($event, emailEntered)">
</div>
<div class="form__row" :class="{entered: passwordEntered}">
<label class="form__label" for="login-form-password">Password</label>
<input type="password" class="form__control form__control--textbox form__control--password" name="password" id="login-form-password" #focus="passwordEntered = true" #blur="handleBlur($event, passwordEntered)">
</div>
</div>
</template>
<script>
export default {
name: 'login-form',
data() {
return {
emailEntered: false,
passwordEntered: false,
}
},
methods: {
handleBlur(e, enteredBool) {
if (e.currentTarget.value.trim() === '') {
// this doesn't do anything - I can do an if else statement to change this.passwordEntered or this.emailEntered based on the name of the current target, but how do I change the value by passing it into the method?
enteredBool = false;
}
},
}
}
</script>
but it doesn't seem to change the variable that is passed into the method - how do I pass a data variable into the method and change it's value? Or should I be doing it in a different way? I don't really want to be doing an if else statement as I may have a form that has got a lot more inputs on and I think that would be really inefficient to maintain
I also thought that I could do something in the #bur as you can do #blur="passwordEntered = false", but I wasn't sure how to check if the field was empty or not
In order to change the variable, you need to refer it using this
handleBlur(e, enteredBool) {
if (e.currentTarget.value.trim() === '') {
this[enteredBool] = false; //Change added
}
},
and the way you pass it should be like
#blur="handleBlur($event, 'emailEntered')" //Added single quotes
and
#blur="handleBlur($event, 'passwordEntered')" //Added single quotes
Using Vue3 and Vuex4
I got an input field:
<input :class="invalid.includes(item.attribute) ? 'invalidInput' : 'validInput'" type="text" :id="item.attribute" :name="item.attribute" :placeholder="item.default_value" min="0" step="any" :value="item.value" #input="validate(item.attribute, $event)" class="p-1">
I change the value of "invalid" like this. Just checking for the validity of a regex and adding/removing the attribute to the array.
VALIDATE_INPUT: (state, data) => {
var regex = /(?=.*\d)^\$?(([1-9]\d{0,2}(,\d{3})*)|0)?(\.\d{1,2})?$/;
switch (data.attribute) {
case 'invoice_amount_f':
if (!regex.test(data.value)) {
state.validations.push(data.attribute)
} else {
let index = state.validations.findIndex(el => el === data.attribute);
if (index > -1) {
state.validations.splice(index, 1);
}
}
break;
default:
break;
}
}
The action calling the mutation is called like:
const validate = (attribute, event) => {
store.dispatch('validate', {
attribute: attribute,
value: event.target.value
});
}
Computed:
var invalid = computed({
get() {
return store.getters.getValidationState;
}
});
When now typing something into the input field the text in the field ain't chaning. This seems to happen cause I use the value of invalid inside the template. Why is that?
EDIT: It seems to have something to do with the scope I am using it in.
<h3>{{ invalid }}</h3>
<div v-if="nestedListItems && Object.keys(nestedListItems).length !== 0">
<draggable v-model='nestedListItems' item-key="id" class=" w-12/12 bg-white m-auto border" animation="150">
When rendering it outside of draggable it's absolutely fine. Inside it crashes my store.
You need to provide an object to the :class, see here:
https://v3.vuejs.org/guide/class-and-style.html#binding-html-classes
I suggest you create a variable in the script containing the boolean e.g. isValid and then apply it like this:
:class="{invalidInput : !isValid, validInput: isValid }"
I have many components that are basically a form that, on submit, makes a request to the server and disables its input elements until a response is received. I'd like to not have to care about this disabling every time and factor it out into something reusable. Is there a good way to do that?
For concreteness, here is a minimal example:
<form v-on:submit.prevent="send">
<fieldset :disabled="isDisabled">
<div>
<label>Name</label>
<input v-model="u.name">
</div>
<div>
<label>Email</label>
<input type="email" v-model="u.email">
</div>
</fieldset>
</form>
As you can see, handling this isDisabled state clutters up the component:
data () {
return {
u: {
name: '',
email: '',
},
isDisabled: false
}
},
methods: {
send: function () {
this.isDisabled = true
api.post('/users/create', {
name: this.u.name,
email: this.u.email
}).then(response => {
this.isDisabled = false
<do something>
}).catch(error => {
alert(error)
this.isDisabled = false
})
}
}
One idea was to make a generic Form component parametrized by the required fields and REST endpoint, passed in by props. However, the forms and their send functions vary considerably, and might also include conditional inputs so this seems difficult to me.
It sounds like you want a mixin, but all it would do is declare the isDisabled data item (which I would recommend you call saving so that it better indicates program state).
Since you set it to false in both the resolve and reject phases of the Promise, you can move it to the finally phase, which would help the perceived clutter a bit.
You could possibly have a directive in your mixin that would find all the form elements in the form and disable them when saving, and re-enable them afterward, so the markup in the template would just be
<form v-disable-elements="saving">
I'm quite happy with the way I ended up doing it. My form component is basically just
<template>
<form v-on:submit.prevent="send">
<fieldset :disabled="submitting">
<slot></slot>
<div class="submit">
<input type="submit" :value="submitText">
</div>
</fieldset>
</form>
</template>
plus some code for displaying error messages. The component is parametrized by props, notably the endpoint to send data to, the payload, and the text in the button.
With slots, the actual form lives in the parent component and the computation of the payload is also done there, it is easy to have forms that contain very different inputs, unlike my first idea of passing the fields, their types and their name in the payload themselves as props. For concreteness, I usually make a computed property like this:
formdata: function () {
return {
endpoint: '/events/create',
submitText: 'Create event',
payload: {
description: this.ev.description,
date: this.date
}
}
}
and pass it with
<sync-form v-bind="formdata">
The send method in the form component takes care of disabling/un-disabling, and emits an ok or err event depending on the response. Optionally, the parent component can pass in a prop that tells the form if the input is valid and can be submitted.
I created a FormContainer.vue which also passes the data as described here https://v2.vuejs.org/v2/guide/components-slots.html#Scoped-Slots
<template>
<form
#submit.prevent="submit">
<slot v-bind:formInfo="formInfo"></slot>
</form>
</template>
<script>
export default {
props: {
onSubmit: Function
},
data () {
return {
formInfo: {
submitting: false
}
}
},
methods: {
async submit () {
try {
this.formInfo.submitting = true
const response = await this.onSubmit()
this.$emit('onSuccess', response)
} catch (err) {
console.log('err', err)
this.$emit('onError', err)
}
this.formInfo.submitting = false
}
}
</script>
In a child I can do
<template>
<form-container
:onSubmit="onSubmit"
#onSuccess="onSuccess"
#onError="onError"
v-slot="{formInfo}">
<input type="text" v-model="email" />
<button>{{ formInfo.submitting ? 'Submitting...' : 'Submit' }}</button>
</form-container>
</template>
<script>
import FormContainer from './FormContainer'
export default {
components: {
FormContainer
},
data () {
return {
email: ''
}
},
methods: {
onSubmit () {
// Your axios or grahql call or whatever
},
onSuccess (res) {
// Whatever you want to do after the submit succeeded
console.log('Submitted!', res)
},
onError (err) {
// Whatever you want to do after the submit failed
console.log('Submit failed!', err)
}
}
</script>
In angularjs I had the following:
app.directive('ngEnter', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
scope.$apply(function (){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
});
And the html was:
<input type="text" ng-model="searchText" class="form-control"
placeholder="Search"
ng-enter="search($event, searchText)">
So basically once I have finished typing my text to search on, when I pressed the enter key the search function on my controller would run.
How would I do this in Aurelia?
I am still learning about its features so any help would be appreciated.
I think an alternative to the angular ngEnter would be:
import {customAttribute, inject} from 'aurelia-framework';
#customAttribute('enter-press')
#inject(Element)
export class EnterPress {
element: Element;
value: Function;
enterPressed: (e: KeyboardEvent) => void;
constructor(element) {
this.element = element;
this.enterPressed = e => {
let key = e.which || e.keyCode;
if (key === 13) {
this.value();//'this' won't be changed so you have access to you VM properties in 'called' method
}
};
}
attached() {
this.element.addEventListener('keypress', this.enterPressed);
}
detached() {
this.element.removeEventListener('keypress', this.enterPressed);
}
}
<input type="password" enter-press.call="signIn()"/>
The simplest way would be to wrap the input in a form element and bind to the submit event of the form.
<form role="form" submit.delegate="search()">
<div class="form-group">
<label for="searchText">Search:</label>
<input type="text" value.bind="searchText"
class="form-control" id="searchText"
placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
This will give you the same behavior you have built above. I'm still working to create an example of a custom attribute to do this, but honestly, this is how I would recommend you do this specific thing (capturing when the user presses enter).
This does not seem to be supported out of the box. While not perfect, here is what I intend to do:
Add a hidden submit button at the top of my form with a click.delegate attribute.
Run some code in my VM when its clicked. This code will decide what to do with the enter keypress based on any custom logic I need.
Hope that helps,
Andrew
EDIT:
You could also add a keypress event delegate:
<input keypress.delegate="doSomething($event)" />
And define doSomething() as:
doSomething(event) {
if(event.which == 13) {
alert('Your code goes here!');
}
event.preventDefault();
}
This will be a little cleaner when you have many inputs with differing enter keypress behaviours.
Because of keypress.delegate and keypress.trigger in an <input> block entering text by default, your function must return true to avoid it, like this:
doSomething(event) {
if(event.which == 13) {
console.log('Your code goes here!');
event.preventDefault();
return false;
}
return true;
}