I'm new to Angular and have trouble debugging my use of form validations. Namely I want to disable sumbit button if the form is invalid. My problem is that it stays invalid even if I've inserted apparently valid input. This goes so far as to be even invalid for empty form:
This is my template (original code is commented out):
<div>
<div class="row">
<div class="col-xs-6">
<a href ng-click="companiesCtrl.newEdit()" class="btn btn-primary">Uus asutus</a>
<table class="table">
<tbody>
<tr ng-repeat="company in companiesCtrl.list">
<td><a href ng-click="companiesCtrl.load(company)">{{company.name}}</a></td>
</tr>
</tbody>
</table>
</div>
<div class="col-xs-6">
<form id="editForm">
<!--<form ng-show="companiesCtrl.showEdit()" ng-submit="companiesCtrl.save()" name="editForm">-->
<!--<h4>Asutuse detailid</h4>-->
<!--<a ng-show="companiesCtrl.edit.id != 0" class="btn btn-primary" ng-href="../{{companiesCtrl.edit.id}}/product/list">Asutuse vaatele</a>-->
<!--<fieldset class="form-group">-->
<!--<input ng-model="companiesCtrl.edit.id"/>-->
<!--</fieldset>-->
<!--<fieldset class="form-group">-->
<!--<input placeholder="Asutuse nimi" title="Asutuse nimi" ng-model="companiesCtrl.edit.name" ng-required="true"/>-->
<!--</fieldset>-->
<!--<fieldset class="form-group">-->
<!--<input type="submit" class="btn btn-success" ng-disabled="editForm.$invalid" value="Salvesta" />-->
<!--</fieldset>-->
</form>
<span>{{editForm.$error}}</span>
<span>{{editForm.$invalid}}</span>
<span>{{editForm.$valid}}</span>
</div>
</div>
and this is the result from the "span"-s:
{"required":[{}]} true false
My question is, as I'm new to Angular, how to debug this, or what are the additional factors that can affect form "valid" status.
I managed to find the problem myself. I had multiple directives that were hidden by ng-show. All of them had a form named "editForm" this cause Angular to validate all the forms.
Just if someone ever needs this for debugging: I added following to my template:
<span ng-repeat="error in compForm.$error"><span ng-repeat="error2 in error">
{{companiesCtrl.logThis(error2)}}</span></span>
and the following to my controller:
this.logThis = function(toLog) {
console.log(toLog);
}
From log I saw that it was validating too many times and quessed my problem.
Related
I have a table that is generated from server side on a partial and rendered using ajax. Because of this I lose binding.
So my only option is to map everything to a JavaScript array and post it.
Is there a way to map everything using jQuery without having to iterate every row then every column and look for each input one by one?
Are there any other options? Perhaps I have to use something like Vue?
Here is my partial:
#model Project.Areas.Robotics.Models.ViewModels.DefectRecordViewModel
<form method="post">
<card>
<card-header icon="fas fa-thumbs-down" title="Peças NOK">
</card-header>
<card-body>
<div class="form-row">
<div class="form-group col-md-6">
<h4>
<span class="">
<b><i class=""></i>Registo</b> #Model.ProductionRecordId
</span>
</h4>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label asp-for="References"></label>
<select id="references-nok" asp-items="Model.References" class="custom-select custom-select-sm" onchange="selectReferences(this)">
<option value="">--</option>
</select>
</div>
</div>
<div class="table-responsive">
<table id="tblDefects" class="table table-sm" style="width:100%;">
<thead class="bg-olive">
<tr>
<th>#Html.DisplayNameFor(model => model.DefectCodes[0].DefectCodeId)</th>
<th>#Html.DisplayNameFor(model => model.DefectCodes[0].Quantidade)</th>
<th>#Html.DisplayNameFor(model => model.DefectCodes[0].Detalhes)</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.DefectCodes.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(model => model.DefectCodes[i].DefectCode)
<input type="hidden" asp-for="DefectCodes[i].DefectCodeId" />
<input type="hidden" class="reference-holder" asp-for="DefectCodes[i].ReferenceId" />
<input type="hidden" asp-for="DefectCodes[i].DefectCodeId" />
</td>
<td>
<input asp-for="DefectCodes[i].Quantidade" class="form-control form-control-sm" />
<span asp-validation-for="DefectCodes[i].Quantidade" class="text-danger"></span>
</td>
<td>
<input asp-for="DefectCodes[i].Detalhes" class="form-control form-control-sm" />
<span asp-validation-for="DefectCodes[i].Detalhes" class="text-danger"></span>
</td>
</tr>
}
</tbody>
</table>
</div>
</card-body>
<card-footer>
<div class="form-row">
<div class="form-group col-md-6">
<button type="button" data-url="#Url.Page("Index","AddDefectCode")" onclick="addDefectRecord(this)" class="btn btn-success btn-sm"><i class="fas fa-plus"></i> Adicionar</button>
<button type="button" onclick="hideDetails()" class="btn btn-danger btn-sm"><i class="fas fa-times"></i> Cancelar</button>
</div>
</div>
</card-footer>
</card>
</form>
I am unable to understand clearly what is Your problem but if You have large number of records and You don't want to load complete at once or want to reload your table without reload or all functionality like paging searching and filter you can use data table plugin
with server side record manipulation if this you are question you can reply this post so i can give you more example or i will give you some example .for your further understanding .
other wise you can elaborate your question with some screen shot so i can understand your roots of problem and give the best possible answers .
Is there a way to map everything using jQuery without having to iterate every row then every column and look for each input one by one?
Since you've configured all the <input/> with well-formed names, you can build a FormData directly.
var formdata = new FormData(someFormElement);
For example, add a submitDefectCode function to send an ajax request as below:
<script>
function submitDefectCode(element){ // the element is the `<button/>` that is clicked
var url = element.getAttribute("data-url");
var form = document.querySelector("form"); // get the `<form>` element
var formdata = new FormData(form);
$.ajax({
method:"post",
url: url,
contentType: false,
processData: false,
data: formdata,
success:function(resp){
console.log(resp); // now you get the response
}
});
}
</script>
And trigger this function when clicking the button:
<button type="button" data-url="#Url.Action("Index","AddDefectCode")"
onclick="event.preventDefault(); submitDefectCode(this);"
class="btn btn-success btn-sm">
<i class="fas fa-plus"></i> Adicionar
</button>
Finally, make your server side code to receive the form data with a [FromForm] attribute:
[HttpPost("/AddDefectCode")] // change this route as you need
public IActionResult TestPost([FromForm]DefectRecordViewModel model)
{
// ... now you get the model automatically
return Json(model);
}
Demo:
Here's a demo screenshot when posting data with multiple table rows:
In case you want to build a javascript array manually, you can use the FormData.entries():
var array= [];
for(var pair of formData.entries()) {
array.push({key: pair[0], value:pair[1]});
}
I used ajax unobtrusive to submit the form on the partial. The model is automatically binded on serverside, no need to do anything manually.
I'm learning Ember.js and now I'm stuck with a little contact form. I have two fields, one for email, another for the message, and I've set some validations for them. Unless the validations are satisfied, the 'Send' button must be disabled. Also, when everything is OK and user sends a message, the form must be substitued with a flash notice that message was sent. So, here's the code:
app/controllers/contact.js:
import Ember from 'ember';
export default Ember.Route.extend({
emailAddress: '',
message: '',
isValidEmail: Ember.computed.match('emailAddress', /^.+#.+\..+$/),
isValidMessage: Ember.computed.gte('message.length', 5),
isValid: Ember.computed.and('isValidEmail', 'isValidMessage'),
isInvalid: Ember.computed.not('isValid'),
actions: {
sendMessage() {
alert(`Sending message from: ${this.get('emailAddress')}`);
this.set('responseMessage', `Thank you! We've received message from: ${this.get('emailAddress')} . You will be responsed ASAP!`);
this.set('emailAddress', '');
}
}
});
and the template contact.hbs:
<h1>Contact us</h1>
<div class="well well-sm">
<p> If you have any questions, feel free to contact us!</p>
</div>
<div class="row">
<div class="col-md-6">
{{#if responseMessage}}
<br/>
<div class="alert alert-success">{{responseMessage}}</div>
{{else}}
<div class="form-group">
{{input type="email" value=emailAddress class="form-control" placeholder="Please type your e-mail address." autofocus="autofocus"}}
{{#if emailAddress.isValidEmail}}<span class="glyphicon glyphicon-ok form-control-feedback"></span>{{/if}}
</div>
<div class="form-group">
{{textarea class="form-control" placeholder="Your message. (At least 5 characters.)" rows="7" value=message}}
{{#if message.isValidMessage}}<span class="glyphicon glyphicon-ok form-control-feedback"></span>{{/if}}
</div>
{{/if}}
<button class="btn btn-primary btn-lg btn-block" disabled={{isInvalid}} {{action 'sendMessage'}}>Contact us!</button>
</div>
but, still:
The button remains active while the form is empty.
There's no glyphicons appearing while completing the form.
No notice appears when the message is sent.
What am I doing wrong?
You need to use the name of your form, as well as ng-disabled: DEMO
<form name="myForm">
<input name="myText" type="text" ng-model="mytext" required />
<button ng-disabled="myForm.$invalid">Save</button>
</form>
Please, feel free to vote me down, as I've shown a notorious lack of attention - the code which was pretended to be in app/controllers/contact.js was in app/routers/contact.js... So, naturally, when I removed it to the right place, everything worked. Special thanks to #Parag who made me enlighten. I am terribly sorry for disturbing and stealing time for this BS :(
I try to achieve the following functionality. Have editable form inputs in an angular application. For example a user can see his first name being fetched by the server and then clicking an edit button the form text input appears, edit button disappears and in its place the buttons save and cancel appear. I use the angular-bootstrap-show-errors component to show errors.
However when a validation rule is not fulfilled during editing and I click on cancel button the form tries to show the error before going back to the starting state. For example, I press edit and delete all the first name characters, then press cancel, so before disappearing it tries to validate. Below is my view.
<!--First name edits-->
<div class="row">
<form name="firstNameEditForm" role="form" novalidate>
<div class="col-xs-3">
<p class="text-right">First Name:</p>
</div>
<div class="col-xs-6" ng-if="model.beforeFirstNameEdit">
<p class="text-success">
{{accountData.firstname || "Loading..."}}
</p>
</div>
<div class="col-xs-6" ng-if="!model.beforeFirstNameEdit">
<div class="form-group" show-errors>
<input name="firstName" ng-model="accountData.firstname" class="form-control" placeholder="First Name" type="text" required minlength=2 auto-focus />
<small class="help-block" ng-if="firstNameEditForm.firstName.$error.required">At least 2 characters required</small>
<small class="help-block" ng-if="firstNameEditForm.firstName.$error.minlength">At least 2 characters required</small>
</div>
</div>
<div class="col-xs-3" ng-if="model.beforeFirstNameEdit">
<button type="button" class="btn btn-warning btn-xs" ng-click="editFirstName()">Edit</button>
</div>
<div class="col-xs-3" ng-if="!model.beforeFirstNameEdit">
<button type="button" class="btn btn-success btn-xs" ng-click="update(accountData.firstname)">Save</button>
<button type="button" class="btn btn-danger btn-xs" ng-click="cancelFirstNameEdit()">Cancel</button>
</div>
</form>
</div><!--First name edits-->
And the controller
$scope.preFirstNameEditModel = {};
$scope.editFirstName = function() {
// Copy preedited data locally
$scope.model.beforeFirstNameEdit = false;
$scope.preFirstNameEditModel = angular.copy($scope.accountData.firstname);
}
$scope.cancelFirstNameEdit = function(){
$scope.model.beforeFirstNameEdit = true;
$scope.accountData.firstname = angular.copy($scope.preFirstNameEditModel);
};
How can I completely avoid validation when I click on cancel button? I read some answers on similar questions suggesting to change the type of button to type = "button" but still doesn't solve my issue.
The validation of the fields is triggered on focus lost, whichis causing the validation message. You can prevent this behaviour by using ng-show="submitted && firstNameEditForm.firstName.$error.required" and ng-show="submitted && firstNameEditForm.firstName.$error.minlength". This causes the message showing up only when the form is submitted.
Furthermore you have to change the type of the update button to submit.
I have two questions that are related:
First: I have the following directive, who's purpose is to validate whether an input[type=file] is valid or not, however I have no idea how it does it least of all, what the actual code means, here it is:
angular.module('sccateringApp')
.directive('fileModel', ['$parse', function($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function(){
scope.$apply(function(){
modelSetter(scope, element[0].files[0]);
});
});
}
};
}]);
Like I said, I have no idea what the above code actually does, the explanation I got from the forum where I copied that was that it validated an input type file. Is this correct? (So far I haven't been able to verify if it works or not since it doesn't work with the code I'm using at the moment to validate my forms).
Second: Having the form below, using angular form validation it doesn't allow the submit button to be clicked until the actual inputs inside the form match the validation rules (enter a name for the category, and the description should have a max length of 144 characters). I included the directive into the file input, however the actual ng-model for the form ignores the required in the input type file and just verifies the rules are met for the first two inputs.
Here is my form:
<form method="post" role="form" name="newCategoryForm" ng-submit="submitForm()" enctype="multipart/form-data" novalidate>
<div class="row">
<div class="row">
<div class="col s12">
<div input-field>
<input type="text" name="cat-name" id="cat-name" ng-class="{ 'ng-invalid' : newCategoryForm.catname.$invalid && !newCategoryForm.catname.$pristine }"
ng-model="catname" required>
<label>Nombre</label>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div input-field>
<textarea class="materialize-textarea" name="cat-description" id="cat-description" length="144"
ng-model="catdescription" ng-maxlength="144" required></textarea>
<label>Descripción</label>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<h6>Imagen de Fondo</h6>
<div class="file-field input-field">
<div class="btn pink darken-2 waves-effect waves-light">
<span>Archivo</span>
<input type="file" name="cat-bgimg" id="cat-bgimg"
file-model="variable" required>
</div>
<div class="file-path-wrapper">
<input class="file-path" type="text">
</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-large pink darken-2 waves-effect waves-light center-button" ng-disabled="newCategoryForm.$invalid">Crear Categoría</button>
</form>
The first two inputs get validated correctly, the third one (file input) doesn't and I don't really know why since the directive got included on the input (I know natively, ngModel doesn't validate file inputs).
Any ideas or suggestions of how can I fix this? I'm really new to Angular, and all the tutorials are pretty much useless. I come from 5 years of experience working on jQuery, and the transition to Angular hasn't been easy at all.
The directive posted above is used to make the submit get the data found in the <input type="file"></input>.
Also, a variable should be initialized in the controller so that the values found inside the form are copied to said variable, then this variable needs to be sent as a parameter inside the ng-submit="submitForm().
Example:
angular.module('sccateringApp')
.controller('newSubcategoryController', function (httpcalls, $scope) {
...
$scope.subcategory = [];
...
$scope.submitForm = function(subcategory){
...
$scope.request.insertSubcategory(subcategory);
}
});
Each ng-model inside the form would be:
<input type="text" ng-model="category.name">
So that the category variable found in the controller acquires that value.
This works:
<h4>Radio & Uncheckable Radio</h4>
<pre>{{radioModel || 'null'}}</pre>
<div class="btn-group">
<label class="btn btn-primary" ng-model="radioModel" btn-radio="'Left'">Left</label>
<label class="btn btn-primary" ng-model="radioModel" btn-radio="'Middle'">Middle</label>
<label class="btn btn-primary" ng-model="radioModel" btn-radio="'Right'">Right</label>
</div>
This doesn't work
{{radioModel || 'null'}}
<div class="btn-group">
<label class="btn btn-primary" data-ng-repeat="store in global.user.store" ng-model="radioModel" btn-radio="{{store}}" uncheckable>{{store}}</label><br>
</div>
If you select one radio button, the other radiobuttons don't de-select. Instead of having one radio button checked at a time, all 3 can be checked! And the {{radioModel}} won't display any value. For the first example, {{radioModel}} would display 'Left,' 'Right,' or 'Middle' depending on the value of btn-radio.
It's like data-ng-repeat="store in global.user.store" breaks the button behavior!
Try setting the scope variable with a dot, like if it an object.
$scope.radio = {model: null}; //for example
And use always radio.model instead of radioModel.
This is because the way the scope inheritance works each ng-model of the ng-repeat will generate a new scope. With the 'dot' rule you want have this problem.
Here is more information https://github.com/angular/angular.js/wiki/Understanding-Scopes
Try removing the {{}} in the btn-radio attribute :
<label class="btn btn-primary" data-ng-repeat="store in global.user.store" ng-model="radioModel" btn-radio="store" uncheckable>{{store}}</label>