onChange event doesn't change state in <select> React - select

As the title, I have an onChange event for the <select> element in React, but it doesn't fire when I change the option in the dropdown.
I set the initial state, update the state with this.setState() and test it with 2 onChange event, 1 for the text input, and the other for the <select>.
Only the state of the text input is updated. It means only the text input called the onChange event.
Can anyone help me figure out why this happens?
ServicesPage = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
var accountId = Meteor.userId();
return {
services: Services.find({ accountId: accountId }).fetch(),
sites: Sites.find({ accountId: accountId }).fetch()
}
},
getInitialState() {
return {
selected: "0",
test: 'Hello'
}
},
handleChangeLocation(e) {
this.setState({ selected: e.target.value });
},
handleChange(event) {
this.setState({ test: event.target.value});
console.log(this.state.test);
},
componentDidMount() {
$('select').material_select();
console.log(this.state.selected);
},
render() {
var sitesList = [];
for (var i = 0; i < this.data.sites.length; i++) {
sitesList.push(<option key={i} value={this.data.sites[i]._id}>{this.data.sites[i].name}</option>);
}
return (
<div>
<input type="text" value={this.state.test} onChange={this.handleChange} />
<h4>Services Page</h4>
<div className="row">
<div className="col s12 m6 l6">
<select value={this.state.selected} onChange={this.handleChangeLocation}>
<option value="0">Choose location</option>
{sitesList}
</select>
</div>
</div>
</div>
)
}
})

material_select inserts it's own DOM elements and hides the existing <select> hierarchy. The React elements don't know anything about this since they work only at the level of the virtual DOM. Therefore material_select undermines the connection between the React virtual DOM and the real DOM.
This will be the case for almost all JQuery plugins, so they are mostly incompatible with React. You probably want to use the material-ui React components instead.

Related

Vue form select emit event when the same element is selected

When I attach the '#change' property to a form input, e.g.
<select v-model="data" #change="handler">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
It is only triggered when a different option is selected. How do I trigger the handler function even when the same option is chosen again?
The event that's triggered on a <select> when selecting the already selected <option> is mouseup.
Therefore, you'll need an additional prop to store the select's state (isOpen). You'll call a helper method (let's name it mouseUp) and only call handler() when the select is closing.
For all the other input cases (keypress and whatnot), rely on #input.
I added another helper prop to avoid triggering handler() twice:
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: () => ({
data: 1,
isOpen: false,
changed: false
}),
methods: {
mouseUp(e) {
if (this.isOpen) {
this.$nextTick().then(() => {
if (!this.changed){
this.handler(e);
}
});
this.isOpen = false;
} else {
this.changed = false;
this.isOpen = true;
}
},
handler(e) {
this.changed = true;
console.log(`handler(${e.target.value})`);
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<select v-model="data" #mouseup="mouseUp" #input="handler">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
</div>
The logic might seem cumbersome but, because #input is triggered before #mouseup, I'm only calling handler() from mouseUp() if #input has not been triggered and the <select> is closing.
The only case of <select> closing and handler() not being fired is pressing ESC key when select is open. But I'm not sure you should be calling it in that case (user wants to cancel any change). If you want to call it in that case as well, it's quite easy to listen to that specific case.

What is the best way to post form with multiple components using Vue js

as I'm on my Vue spree (started recently but so far I'm really enjoying learning this framework) couple of questions rised up. One of which is how to post form from multiple components. So before I continue forward I wanted to ask you what are you thinking about this way of structuring and point me in right direction if I'm wrong.
Here it goes.
I'm working on a SPA project using ASP.NET CORE 2.1 and Vue JS Template (with webpack)(https://github.com/MarkPieszak/aspnetcore-Vue-starter) and my project is structured in several containers, something like this:
In my app-root i registered several containers
<template>
<div id="app" class="container">
<app-first-container></app-first-container>
<app-second-container></app-second-container>
<!--<app-third-container></app-third-container>-->
<app-calculate-container></app-calculate-container>
<app-result-container></app-result-container>
</div>
</template>
<script>
// imported templates
import firstContainer from './first-container'
import secondContainer from './second-container'
import calculateContainer from './calculateButton-container'
//import thirdContainer from './third-container'
import resultContainer from './result-container'
export default {
components: {
'app-first-container': firstContainer,
'app-second-container': secondContainer,
// 'app-third-container': thirdContainer,
'app-calculate-container': calculateContainer,
'app-result-container': resultContainer
}
}
</script>
In my first container I'm having several dropdowns and two input fields with my script file where I'm fetching data from API and filling dropdowns and input fields with fetched data.
Something like this ( entered some dummy code for demonstration)
<template>
<div>
<h1>Crops table</h1>
<p>This component demonstrates fetching data from the server. {{dataMessage}}</p>
<div class="form-row">
<div class="form-group col-md-6">
<label for="exampleFormControlSelect1" class="col-form-label-sm font-weight-bold">1. Some text</label>
<select class="form-control" id="exampleFormControlSelect1" v-model="pickedCropType" #change="getCropsByType()">
<option v-for="(cropType, index) in cropTypes" :key="index" :value="cropType.id" :data-imagesrc="cropType.imgPath">{{ cropType.name }}</option>
</select>
</div>
<div class="form-group col-md-6">
<label for="exampleFormControlSelect2" class="col-form-label-sm font-weight-bold">2. Some text</label>
<select class="form-control" id="exampleFormControlSelect2">
<option v-for="(crop, index) in cropSelectList" :key="index" :value="crop.id">{{ crop.name }}</option>
</select>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
export default {
data() {
return {
cropTypes: null,
cropSelectList: null,
crops: null,
pickedCropType: null,
}
},
methods: {
loadPage: async function () {
try {
//Get crop types and create a new array with crop types with an added imgPath property
var cropTypesFinal = [];
let responseCropTypes = await this.$http.get(`http://localhost:8006/api/someData`);
responseCropTypes.data.data.forEach(function (element) {
cropTypesFinal.push(tmpType);
});
} catch (err) {
window.alert(err)
console.log(err)
}
},
getCropsByType: async function () {
//Get crops by crop type
let responseCrops = await this.$http.get(`http://localhost:8006/api/crop/Type/${this.pickedCropType}`);
var responseCropsData = responseCrops.data.data;
this.cropSelectList = responseCropsData;
}
},
async created() {
this.loadPage()
}
}
</script>
And in my second container I have different dropdowns and different input fields with different scripts etc.
So, my questions are:
1.) I'm having required data form field in first container and in second container I'm having additional data and my submit button is separated in third container (app-result-container). So, is this proper and logical way of structuring containers if not can you point me in right direction?
2.) Is it smart to input script tag in every container where I'm processing/fetching/submitting some data for that particular container? Should I put scripts tag in separated file and keep structure clean, separating html from js file.
Example:
import { something } from 'something'
export default {
data () {
return {
someData: 'Hello'
}
},
methods: {
consoleLogData: function (event) {
Console.log(this.someData)
}
}
}
3.) Can I send input values from one container to another (In my particular case from first and second container to app-calculate-container(third container))?
How to on submit return results container with calculated imported values
If you want components to communicate or share data with one another, you will need to either emit an event from one component up to the parent and pass it down via props, or use some kind of state management model, like Vuex, where each of your components can listen to the store.
Take a look at this code sandbox: https://codesandbox.io/s/8144oy7xy2
App.vue
<template>
<div id="app">
<child-input #input="updateName" />
<child-output :value="name" />
</div>
</template>
<script>
import ChildInput from "#/components/ChildInput.vue";
import ChildOutput from "#/components/ChildOutput.vue";
export default {
name: "App",
components: {
ChildInput,
ChildOutput
},
data() {
return {
name: ""
};
},
methods: {
updateName(e) {
this.name = e.target.value;
}
}
};
</script>
ChildInput.vue
<template>
<input type="text" #input="changeHandler">
</template>
<script>
export default {
name: "ChildInput",
methods: {
changeHandler(e) {
this.$emit("input", e);
}
}
};
</script>
ChildOutput.vue
<template>
<p>{{ value }}</p>
</template>
<script>
export default {
name: "ChildOutput",
props: {
value: {
type: String,
default: ""
}
}
};
</script>
What's going on?
The ChildInput component is a text field and on every change inside it, fires an event (emits using this.$emit() and passes the whole event up).
When this fires, App is listening to the change, which fires a method that updates the name data property.
Because name is a reactive data property and is being passed down as a prop to the ChildOutput component, the screen re-renders and is updated with the text written.
Neither ChildInput nor ChildOutput knows about one another. It's the parent that listens to the event passed to it, then passes the new prop down.
This way of working is fine and simple to understand, but I would strongly recommend looking at Vuex, as this method can get messy and complicated when you go beyond trivial tasks.

Angular 4 Create Dynamic formArray inside array using reactive forms

Here, we are creating dynamically form array's inside array.
Below is the sample structure of expected result given below.
{
"optionsRadios": null,
"Package_Title": null,
"HotelData": [
{
"Htitle": "",
"HDescription": "",
"hotelStar": "",
"RoomData": [
{
"Hotel_Room_Type": ""
},
{
"Hotel_Room_Type": ""
}
]
}
]
}
I want to create HotelData Dynamically, within that HotelData array i want to create RoomData array fields also dynamically.
I created HotelData fields by the following codes:
export class AddPackageComponent implements OnInit {
ngOnInit() {
this.invoiceForm = this._formBuild.group({
Package_Title: [],
HotelData: this._formBuild.array([this.addRows()])
})
}
addRows() {
return this._formBuild.group({
Htitle: [''],
HDescription: [''],
hotelStar: ['']
});
}
addHotel() {
const control: FormArray = this.invoiceForm.get(`HotelData`) as FormArray;
control.push(this.addRows());
}
}
You are on the right track, we just need to add some more code...
addRows need the form array RoomData, and here we also initially push an empty form group of room. If you don't want that, modify it.
addRows() {
let group = this._formBuild.group({
...
RoomData: this._formBuild.array([])
});
// push formgroup to array initially
this.addRoom(group.controls.RoomData)
return group;
}
addRoom looks like this:
addRoom(hotel:any) {
let group = this._formBuild.group({
Hotel_Room_Type: ['']
})
hotel.push(group)
}
addRoom is also the method we are calling from template when we want to add a new room to a hotel. Remember to pass the current hotel as parameter from template.
As for adding a new hotel, your addHotel stays the way you have it now.
Then over to your template, the relevant part should look something like this:
<div formArrayName="HotelData">
<div *ngFor="let hotel of invoiceForm.get('HotelData').controls; let i = index" [formGroupName]="i" >
<!-- form controls here -->
<button (click)="addRoom(hotel.get('RoomData'))">Add Room</button>
<div formArrayName="RoomData">
<div *ngFor="let room of hotel.get('RoomData').controls; let j = index" [formGroupName]="j">
<!-- form controls here -->
</div>
</div>
</div>
</div>
Finally, here's a Demo: http://plnkr.co/edit/7tcLcnzALew3oKGenjwK?p=preview

React onClick keeps triggering

When I trigger the onClick even the event keeps triggering for about 1000+ times. I can't seem to figure where this is coming from. I have changed the onClick to an onMouseover to see if it keeps triggering but then the event only triggers once.
I'm using : react 0.13.3
Any idea's?
var React = require('react');
var AppActions = require('../../actions/app-actions.js');
var FileAmount = React.createClass({
getInitialState: function() {
return {
amount : this.props.amount,
config : this.props.config
};
},
handleClick: function(e){
var name = e.target.name;
if(name === 'decrease'){
if(this.state.amount > 1){
this.setState({
amount : (this.state.amount - 1)
});
AppActions.updateAmount(this.props.index, (this.state.amount - 1))
}
}else{
this.setState({
amount : (this.state.amount + 1)
});
AppActions.updateAmount(this.props.index, (this.state.amount + 1))
}
},
handleChange: function(e){
var amount = e.target.value;
this.setState({
amount : amount
});
AppActions.updateAmount(this.props.index, amount)
},
render: function() {
var config = this.state.config
return (
<div className="file-amount">
<span className="file-amount-text"> {config.filelist_quantity}: {this.state.amount} {config.filelist_pieces}</span>
<div className="file-amount-fields">
<i className="file-amount-decrease icon" name="decrease" onClick={this.handleClick} />
<input className="file-amount-input" type="number" value={this.state.amount} onChange={this.handleChange} />
<i className="file-amount-increase icon" name="increase" onClick={this.handleClick} />
</div>
</div>
);
}
});
module.exports = FileAmount;
I left a comment on the original post, but on second inspection, it looks quite possible that you pass down props.amount from a Flux Store. If that is the case you're creating an infinite loop.
handleClick increments state.amount, then after the AppAction is called, the Store updates the component with props.amount, then the onChange fires because it is tied to state.amount and then onChange changes state.amount and changes props.amount when it calls AppActions.updateAmount.
Every time props or state are updated, React will call the render() method. If there is any way that props or state get updated while the render() executes, then you are likely going to run into an infinite loop.
Perhaps adding a e.preventDefault(); to your handleClick method will stop this loop from being started.
I removed the added javascript for the browser-sync proxy settings. That somehow screwed around with my react.

How to trigger ng-model value in angularjs if browser auto fill scripts filled out the forms [duplicate]

When submitting a form in AngularJS and use the browser remember password functionality, and in a subsequent login attempt you let the browser fill in the login form with the username and password, the $scope model won't be changed based on the autofill.
The only dirty hack I found is to use the following directive:
app.directive("xsInputSync", ["$timeout" , function($timeout) {
return {
restrict : "A",
require: "?ngModel",
link : function(scope, element, attrs, ngModel) {
$timeout(function() {
if (ngModel.$viewValue && ngModel.$viewValue !== element.val()) {
scope.apply(function() {
ngModel.$setViewValue(element.val());
});
}
console.log(scope);
console.log(ngModel.$name);
console.log(scope[ngModel.$name]);
}, 3000);
}
};
}]);
The problem is that the ngModel.$setViewValue(element.val()); doesn't change the model nor the view based on the element.val() returned value. How can I accomplish that?
Apparently this is a known issue with Angular and is currently open
I'm not sure what you could do here besides some sort of work around like you're trying. It seems you're on the right track. I couldn't get my browser to try to remember a password for your plunk, so I'm not sure if this will work but have a look:
app.directive('autoFillSync', function($timeout) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
var origVal = elem.val();
$timeout(function () {
var newVal = elem.val();
if(ngModel.$pristine && origVal !== newVal) {
ngModel.$setViewValue(newVal);
}
}, 500);
}
}
});
<form name="myForm" ng-submit="login()">
<label for="username">Username</label>
<input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
<label for="password">Password</label>
<input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
<button type="submit">Login</button>
</form>
I think you just need to simplify your approach a bit. The one thing I definitely recommend is to check ngModel.$pristine and make sure you're not overwriting some poor user's input. Also, 3 seconds is probably too long. You shouldn't have to call $apply() in a $timeout, BTW, it should queue a $digest for you automatically.
The real catch: Will your browser beat Angular to execution? What about my browser?
This is probably an unwinnable war, which is why Angular (or Knockout) hasn't been able to solve it readily. There's no guarantee of the state of the data in your input at the time of the directive's initial execution. Not even at the time of Angular's initialization.... So it's a tricky problem to solve.
Here is a solution that is far less hacky than other solutions presented and is semantically sound AngularJS: http://victorblog.com/2014/01/12/fixing-autocomplete-autofill-on-angularjs-form-submit/
myApp.directive('formAutofillFix', function() {
return function(scope, elem, attrs) {
// Fixes Chrome bug: https://groups.google.com/forum/#!topic/angular/6NlucSskQjY
elem.prop('method', 'POST');
// Fix autofill issues where Angular doesn't know about autofilled inputs
if(attrs.ngSubmit) {
setTimeout(function() {
elem.unbind('submit').submit(function(e) {
e.preventDefault();
elem.find('input, textarea, select').trigger('input').trigger('change').trigger('keydown');
scope.$apply(attrs.ngSubmit);
});
}, 0);
}
};
});
Then you simply attach the directive to your form:
<form ng-submit="submitLoginForm()" form-autofill-fix>
<div>
<input type="email" ng-model="email" ng-required />
<input type="password" ng-model="password" ng-required />
<button type="submit">Log In</button>
</div>
</form>
You don't have to use a $timeout or anything like this. You can use an event system.
I think it's more Angularish and does not depend on jQuery or custom event catching.
For example on your submit handler:
$scope.doLogin = function() {
$scope.$broadcast("autofill:update");
// Continue with the login.....
};
And then you can have an autofill directive like this:
.directive("autofill", function () {
return {
require: "ngModel",
link: function (scope, element, attrs, ngModel) {
scope.$on("autofill:update", function() {
ngModel.$setViewValue(element.val());
});
}
}
});
Finally, your HTML will be like:
<input type="text" name="username" ng-model="user.id" autofill="autofill"/>
No need to hack anymore! Angular dev tbosch made a polyfill that triggers a change event when the browser changes form fields without triggering a change event:
https://github.com/tbosch/autofill-event
For now they won't build this into the Angular code, as this is a bugfix for the browser, and also works without Angular (e.g. for plain jQuery apps).
"The polyfill will check for changes on document load and also when an input is left (only in the same form). However, you can trigger the check manually if you want to.
The project has unit tests as well as semi automatic tests, so we finally have a place to collect all the different use case together with the required browser settings.
Please note: This polyfill works with plain AngularJS apps, with AngularJS/jQuery apps but also with plain jQuery apps that do not use Angular."
It can be installed with:
bower install autofill-event --save
Add the script autofill-event.js after jQuery or Angular in your page.
This will do the following:
after DOMContentLoaded: check all input fields
a field is left: check all other fields in the same form
API (to manually trigger the check):
$el.checkAndTriggerAutoFillEvent(): Execute the check for all DOM elements in the given jQuery / jQLite element.
How it works
Remember all changes to input elements by the user (listening for change events) and also by JavaScript (by intercepting $el.val() for jQuery / jQLite elements). That changed value is stored on the element in a private property.
Checking an element for auto fill: Compare the current value of the element with the remembered value. If it's different, trigger a change event.
Dependencies
AngularJS or jQuery (works with either one or both)
More info and source on the github page.
Original Angular Issue #1460 on Github can be read here.
Dirty code, check if issue https://github.com/angular/angular.js/issues/1460#issuecomment-18572604 is fixed before using this code.
This directive triggers events when field is filled, not only before submit (it's necessary if you have to handle input before submit)
.directive('autoFillableField', function() {
return {
restrict: "A",
require: "?ngModel",
link: function(scope, element, attrs, ngModel) {
setInterval(function() {
var prev_val = '';
if (!angular.isUndefined(attrs.xAutoFillPrevVal)) {
prev_val = attrs.xAutoFillPrevVal;
}
if (element.val()!=prev_val) {
if (!angular.isUndefined(ngModel)) {
if (!(element.val()=='' && ngModel.$pristine)) {
attrs.xAutoFillPrevVal = element.val();
scope.$apply(function() {
ngModel.$setViewValue(element.val());
});
}
}
else {
element.trigger('input');
element.trigger('change');
element.trigger('keyup');
attrs.xAutoFillPrevVal = element.val();
}
}
}, 300);
}
};
});
Seems like clear straight ahead solution. No jQuery needed.
UPDATE:
Model is updated only when model value isn't equal to actual input
value.
Checking doesn't stop on first autofill. In case if you wish to use
another account for example.
app.directive('autofillable', ['$timeout', function ($timeout) {
return {
scope: true,
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
scope.check = function(){
var val = elem[0].value;
if(ctrl.$viewValue !== val){
ctrl.$setViewValue(val)
}
$timeout(scope.check, 300);
};
scope.check();
}
}
}]);
Solution 1 [Using $timeout]:
Directive:
app.directive('autoFillSync', function($timeout) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, model) {
var origVal = elem.val();
$timeout(function () {
var newVal = elem.val();
if(model.$pristine && origVal !== newVal) {
model.$setViewValue(newVal);
}
}, 500);
}
};
});
HTML:
<form name="myForm" ng-submit="login()">
<label for="username">Username</label>
<input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
<label for="password">Password</label>
<input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
<button type="submit">Login</button>
</form>
Solution 2 [Using angular events]:
Ref: Becko's answer
Directive:
app.directive("autofill", function () {
return {
require: "ngModel",
link: function (scope, element, attrs, ngModel) {
scope.$on("autofill:update", function() {
ngModel.$setViewValue(element.val());
});
}
};
});
HTML:
<form name="myForm" ng-submit="login()">
<label for="username">Username</label>
<input type="text" id="username" name="username" ng-model="username" autofill/><br/>
<label for="password">Password</label>
<input type="password" id="password" name="password" ng-model="password" autofill/><br/>
<button type="submit">Login</button>
</form>
Solution 3 [Using relay method calls]:
Directive:
app.directive('autoFill', function() {
return {
restrict: 'A',
link: function(scope,element) {
scope.submit = function(){
scope.username = element.find("#username").val();
scope.password = element.find("#password").val();
scope.login();//call a login method in your controller or write the code here itself
}
}
};
});
HTML:
<form name="myForm" auto-fill ng-submit="submit()">
<label for="username">Username</label>
<input type="text" id="username" name="username" ng-model="username" />
<label for="password">Password</label>
<input type="password" id="password" name="password" ng-model="password" />
<button type="submit">Login</button>
</form>
Well, the easiest way it's to emulate the browser's behavior, so if there is a problem with the change event, just fire it yourself. Much simpler.
Directive:
yourModule.directive('triggerChange', function($sniffer) {
return {
link : function(scope, elem, attrs) {
elem.on('click', function(){
$(attrs.triggerChange).trigger(
$sniffer.hasEvent('input') ? 'input' : 'change'
);
});
},
priority : 1
}
});
HTML:
<form >
<input data-ng-model="user.nome" type="text" id="username">
<input data-ng-model="user.senha" type="password" id="password" >
<input type="submit" data-ng-click="login.connect()" id="btnlogin"
data-trigger-change="#password,#username"/>
</form>
You can do some variations, like putting the directive on the form and firing the event on all inputs with the .dirty class on form submit.
This is jQuery way :
$(window).load(function() {
// updates autofilled fields
window.setTimeout(function() {
$('input[ng-model]').trigger('input');
}, 100);
});
This is Angular way :
app.directive('autofill', ['$timeout', function ($timeout) {
return {
scope: true,
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
$timeout(function(){
$(elem[0]).trigger('input');
// elem.trigger('input'); try this if above don't work
}, 200)
}
}
}]);
HTML
<input type="number" autofill />
Here's another workaround that's less hacky, but requires some extra code in the controller.
HTML:
<form ng-submit="submitForm()" ng-controller="FormController">
<input type="text" ng-model="username" autocomplete-username>
<input type="submit">
</form>
Directive (CoffeeScript):
directives.directive 'autocompleteUsername', ->
return (scope, element) ->
scope.getUsername = ->
element.val()
Controller:
controllers.controller 'FormController', [->
$scope.submitForm = ->
username = $scope.getUsername?() ? $scope.username
# HTTP stuff...
]
This is the only solution I've found that allowed all of my Angular' validations to work as designed including disable/enable of submit button. Installs with bower and 1 script tag. Bazinga!
https://github.com/tbosch/autofill-event
Changing the model value, instead of using a timeout function worked for me.
Here is my code:
module.directive('autoFill', [ function() {
return {
require: 'ngModel',
link:function(scope, element, attr, ngModel) {
var origVal = element.val();
if(origVal){
ngModel.$modelValue = ngModel.$modelValue || origVal;
}
}
};
}]);
One-liner workaround in the submit handler (requires jQuery):
if (!$scope.model) $scope.model = $('#input_field').val();
I force a $setValue(val()) on submit: (this works without jQuery)
var ValidSubmit = ['$parse', function ($parse) {
return {
compile: function compile(tElement, tAttrs, transclude) {
return {
post: function postLink(scope, element, iAttrs, controller) {
var form = element.controller('form');
form.$submitted = false;
var fn = $parse(iAttrs.validSubmit);
element.on('submit', function(event) {
scope.$apply(function() {
var inputs = element.find('input');
for(var i=0; i < inputs.length; i++) {
var ele = inputs.eq(i);
var field = form[inputs[i].name];
field.$setViewValue(ele.val());
}
element.addClass('ng-submitted');
form.$submitted = true;
if(form.$valid) {
fn(scope, {$event:event});
}
});
});
scope.$watch(function() { return form.$valid}, function(isValid) {
if(form.$submitted == false) return;
if(isValid) {
element.removeClass('has-error').addClass('has-success');
} else {
element.removeClass('has-success');
element.addClass('has-error');
}
});
}
}
}
}
}]
app.directive('validSubmit', ValidSubmit);
I am very new to Angularjs, but I found a simple solution to that problem=>
Force Angular to reevaluate expression... by changing it!
(of course you need to remember the initial value to revert to initial state)
Here is the way it goes in your controller function for submitting the form:
$scope.submit = function () {
var oldpassword = $scope.password;
$scope.password = '';
$scope.password = oldpassword;
//rest of your code of the submit function goes here...
where of course, the value entered in your password input has been set by windows and not by user.
You can try this code :
yourapp.directive('autofill',function () {
return {
scope: true,
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
var origVal = elem.val();
if (origVal != '') {
elem.trigger('input');
}
}
}
});
A minor modification to this answer (https://stackoverflow.com/a/14966711/3443828): use an $interval instead of a $timeout so you don't have to race the browser.
mod.directive('autoFillSync', function($interval) {
function link(scope, element, attrs, ngModel) {
var origVal = element.val();
var refresh = $interval(function() {
if (!ngModel.$pristine) {
$interval.cancel(refresh);
}else{
var newVal = element.val();
if (origVal !== newVal) {
ngModel.$setViewValue(newVal);
$interval.cancel(refresh);
}
}
}, 100);
}
return {
require: 'ngModel',
link: link
}
});
This is the solution I ended up using in my forms.
.directive('autofillSync', [ function(){
var link = function(scope, element, attrs, ngFormCtrl){
element.on('submit', function(event){
if(ngFormCtrl.$dirty){
console.log('returning as form is dirty');
return;
}
element.find('input').each(function(index, input){
angular.element(input).trigger('input');
});
});
};
return {
/* negative priority to make this post link function run first */
priority:-1,
link: link,
require: 'form'
};
}]);
And the form's template will be
<form autofill-sync name="user.loginForm" class="login-form" novalidate ng-submit="signIn()">
<!-- Input fields here -->
</form>
This way I was able to run any parsers/formatters I have on my ng-model and have the submit functionality transparent.
Solution without directives:
.run(["$window", "$rootElement", "$timeout", function($window, $rootElement, $timeout){
var event =$window.document.createEvent("HTMLEvents");
event.initEvent("change", true, true);
$timeout(function(){
Array.apply(null, $rootElement.find("input")).forEach(function(item){
if (item.value.length) {
item.$$currentValue = item.value;
item.dispatchEvent(event);
}
});
}, 500);
}])
This is a simple fix that works for all the cases I've tested in both Firefox and Chrome. Note that with the top answer (directive w/ timeout) I had issues with -
Browser back / forward buttons, don't re-fire page load events (so the fix doesn't apply)
Loading of credentials some time after page load. e.g. in Firefox, double click on the login box and select from stored credentials.
Need a solution that updates before form submission since I disable the Login button until valid input provided
This fix is obviously very dumb and hacky, but it works 100% of the time -
function myScope($scope, $timeout) {
// ...
(function autoFillFix() {
$timeout(function() {
$('#username').trigger('change');
$('#password').trigger('change');
autoFillFix(); }, 500);
})();
}
None of these solutions worked for my use case. I have some form fields that use ng-change to watch for change. Using $watch is no help as it is not triggered by autofill. Since I have no submit button there is no easy way to run some of the solutions and I was not successful using intervals.
I ended up disabling autofill - not ideal but a lot less confusing to the user.
<input readonly onfocus="this.removeAttribute('readonly');">
Found the answer here
If you are using jQuery you could do this on form submit:
HTML:
<form ng-submit="submit()">
<input id="email" ng-model="password" required
type="text" placeholder="Your email">
<input id="password" ng-model="password" required
type="password" placeholder="Password">
</form>
JS:
$scope.submit = function() {
$scope.password = $('#password').val();
}
If you want to keep it simple just get the value using javascript
In your angular js controller :
var username = document.getElementById('username').value;