Forms testing Angular 4 - forms

I have done a small Web App in Angular 4 and I'm now trying to test it.
Here is what I would like to test :
<form class="navbar-form navbar-left" (ngSubmit)="onSubmit(f)" #f="ngForm">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" ngModel name="name" pattern=".{2,}" required #name="ngModel">
</div>
<button type="submit" class="btn btn-default" [disabled]="!f.valid">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
<span class="help-block help" *ngIf="!name.valid && name.touched ">Please enter at least two characters</span>
</form>
At first, I would just like to test if the form is really invalid (and then I cannot click on the submit button) if thye word entered in the input is less than 2 characters.
Here is the test code I have written
beforeEach(() => {
fixture = TestBed.createComponent(NavbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should disable submit if word<2',()=>{
const compiled = fixture.nativeElement;
const input = fixture.debugElement.query(By.css('input'));
input.nativeElement.value='b';
input.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
//console.log(compiled.querySelector('input').textContent);
//console.log(compiled.querySelector('.ng-valid'));
expect(compiled.querySelector('.ng-valid')).toBe(null);
});
The textContent of the input is modified properly but the form is still valid. However, when I use the webapplication, I can't submit the form is the word is less than two characters.
I feel like the word in the input doesn't really "reach" the form in the test. I've tried a lot of different things so any help would be great !

Related

How to conditionally require form inputs in angular 4?

I am using template driven forms for adding the task, and there are 2 input fields of type number for estimated mins to complete task,
one field is for estimated number of hrs and
another is for estimated minutes to complete the task
since the task estimate can be done either in hours like 1hrs , or in hours and minutes like 1Hrs 30Mins , so i want to set attribute required to inputs conditionally. So one of the 2 inputs must be set or form validation error will occur if both inputs are empty when submitting form.
so far i have done this but validation is not working
<form class="add-task" (ngSubmit)="onSubmit(newtask)" #newtask="ngForm">
<div class="estimate-container">
<input
type="number"
min="0"
max="10"
id="estimate_hrs"
ngModel
name="estimate_hrs"
mdInput
[required]="!estimateMins.valid"
placeholder="Estimated Hours"
#estimateHrs="ngModel"
>
<div class="error-msg" *ngIf="!estimateHrs.valid && !estimateMins.valid">
Please enter estimated hours
</div>
<input
type="number"
min="0"
max="60"
id="estimate_min"
ngModel
name="estimate_min"
mdInput
[required]="!estimateHrs.valid"
placeholder="Estimated Minutes"
#estimateMins="ngModel"
>
<div class="error-msg" *ngIf="!estimateMins.valid && !estimateHrs.valid">
Please enter estimated minutes
</div>
</div>
<button type='submit' [disabled]="!newtask.valid" >Submit</button>
</form>
Try using [attr.required] instead.
<input
type="number"
min="0"
max="10"
id="estimate_hrs"
ngModel
name="estimate_hrs"
mdInput
[attr.required]="!estimateMins.valid"
placeholder="Estimated Hours"
#estimateHrs="ngModel"
>
This is my working solution for Angular 5:
In component:
#Input()
required: boolean;
In template on the input (or select) element that:
[required]="required"
On the selector:
[required]="true"
Like #DeborahK double checked, no need for single quotes :-)
You need to put your !estimateMins.valid in single quotes like:
[required]="'!estimateMins.valid'" and [required]="'!estimateHrs.valid'"
See this plunkr
I spent some time trying this out because the basic syntax should have worked. I initially did a simply plunker just to test the syntax and the syntax does indeed work as defined.
After expanding the plunker to more closely match the OP's code: https://plnkr.co/edit/QAqeBYrg19dXcqbubVZ8?p=preview
<Links to plunker must be accompanied by code>
It became apparent that it is not a syntax error. Rather it is a logic error.
When the form first appears, both controls are valid so neither of them have the required attribute. So then neither are required and it appears that it does not work.
There are several possible ways to resolve this. One is to build a custom validator that does the cross field validation.
You can use [required]=boolean.
In angular2 or above you can use ngNativeValidate directive for template driven form. And also object to reserved and sending data(though it's your personal choice but i love this way) to api.
<form class="add-task" #newtask="ngForm" ngNativeValidate (ngSubmit)="onSubmit()">
<div class="estimate-container">
<input
type="number"
min="0"
max="10"
id="estimate_hrs"
name="estimate_hrs"
mdInput
[required]="!taskModel.estimateMins"
placeholder="Estimated Hours"
[(ngModel)]="taskModel.estimateHrs">
<div class="error-msg" *ngIf="!taskModel.estimateHrs && !taskModel.estimateMins">
Please enter estimated hours
</div>
<input
type="number"
min="0"
max="60"
id="estimate_min"
name="estimate_min"
mdInput
[required]="!taskModel.estimateHrs"
placeholder="Estimated Minutes"
[(ngModel)]="taskModel.estimateMins">
<div class="error-msg" *ngIf="!taskModel.estimateMins && !taskModel.estimateHrs">
Please enter estimated minutes
</div>
</div>
<button type='submit' [disabled]="!newtask.valid" [isFormValid]="!newtask.valid">Submit</button>
</form>
In .ts file
taskModel: any;
onSubmit(){
console.log(this.taskModel) // this object has all data.
}
You can also achieve the same with in AngularJS (version 1.x)
<form id="form" name="form" class="add-task" ng-init="taskModel={}">
<div class="estimate-container">
<input
type="number"
min="0"
max="10"
id="estimate_hrs"
name="estimate_hrs"
mdInput
ng-required="!taskModel.estimateMins"
placeholder="Estimated Hours"
ng-model="taskModel.estimateHrs">
<div class="error-msg" ng-if="!taskModel.estimateHrs && !taskModel.estimateMins">
Please enter estimated hours
</div>
<input
type="number"
min="0"
max="60"
id="estimate_min"
name="estimate_min"
mdInput
ng-required="!taskModel.estimateHrs"
placeholder="Estimated Minutes"
ng-model="taskModel.estimateMins">
<div class="error-msg" ng-if="!taskModel.estimateMins && !taskModel.estimateHrs">
Please enter estimated minutes
</div>
</div>
<button ng-disabled="form.$valid" ng-click="form.$valid && onSubmit()" >Submit</button>
</form>
Hope it's helpful to you!

Foundation 5 & Abide: a custom validator for a set of checkboxes?

I would like to create a validator for abide for a set of checkboxes.
Let's consider a set of 5 checkboxes. The user is asked to check 3 max, and at least 1.
So, here is my work-in-progress code:
<div data-abide-validator='checkboxes' data-abide-validator-values='1,3'>
<input type="checkbox"/>
<input type="checkbox"/>
<input type="checkbox"/>
<input type="checkbox"/>
<input type="checkbox"/>
</div>
<script>
$(document).foundation({
validators: {
checkboxes: function(el, required, parent) {
var countC = el.find(':checked').length;
alert(countC);
}
}
});
</script>
At this point, I just try to count the checked inputs. But it seems I can't even trigger the validator... I think I could manage to code my validation stuff if only I could figure out how to trigger it.
Indeed I didn't find many examples of the custom validator, and the official doc did not help me much.
Your HTML markup is not really "correct" for abide. You should be attaching the data-abide-validator attribute to the inputs, not the parent div. Additionally, you need some better markup so abide's default error display can work (and some better use of foundation's grid system to lay it out). I would point you toward the Abide Validation Page on Zurb's site for some examples of form markup.
I've taken the liberty of restructuring your markup to be something that is more becoming of a foundation layout:
<form action="/echo/html/" method="POST" data-abide>
<div class="row">
<div class="small-12 columns checkbox-group" data-abide-validator-limit="1,3">
<label>Check some boxes</label>
<small class="error">You have checked an invalid number of boxes.</small>
<ul class="small-block-grid-3">
<li>
<label>
<input type="checkbox" data-abide-validator="checkbox_limit" value="1" /> 1
</label>
</li>
<li>
<label>
<input type="checkbox" data-abide-validator="checkbox_limit" value="2" /> 2
</label>
</li>
<li>
<label>
<input type="checkbox" data-abide-validator="checkbox_limit" value="3" /> 3
</label>
</li>
<li>
<label>
<input type="checkbox" data-abide-validator="checkbox_limit" value="4" /> 4
</label>
</li>
<li>
<label>
<input type="checkbox" data-abide-validator="checkbox_limit" value="5" /> 5
</label>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="small-12 columns">
<button type="submit">Submit</button>
</div>
</div>
</form>
As to your JS code. It's not correct either. You need to address the abide -> validators namespace of the options, not just validators. I've rewritten your JS code to not only do that, but give the desired effect you wanted:
$(document).foundation({
abide: {
validators: {
checkbox_limit: function(el, required, parent) {
var group = parent.closest( '.checkbox-group' );
var limit = group.attr('data-abide-validator-limit').split(',');
var countC = group.find(':checked').length;
if( countC >= limit[0] && countC <= limit[1] ) {
group.find('small.error').hide();
//return true so abide can clear any invalid flags on this element
return true;
} else {
group.find('small.error').css({display:'block'});
//return false and let abide do its thing to make sure the form doesn't submit
return false;
}
}
}
}
});
In order to check adjacent elements when doing custom validation, you need to have something to target. The el variable in the validation function will be the DOM element of the input/field that is being validated. The required variable will tell you if the field is flagged as being required or not (boolean). The parent variable will be set to the "parent" of the field. I say "parent" because although the label tag is technically the parent of the input element, abide is smart enough to realize that the label is part of the field's element structure and skip over it to the li element instead.
From there, you need a way to identify a common parent. So I added the checkbox-group class to whatever element I decided to make the "parent" of all the checkboxes in the group. This is not a Foundation or Abide "magic" class, but rather something of my own creation for use in the validation function.
From there, you can easily trace the few lines of the validation function to see the workflow: Find the group container object, parse the limits off the container's data-abide-validator-limits attribute, count the number of checked inputs in the container, check if the number checked is between the limits, display/hide the error message and return true/false so abide knows if the field validated or not.
I've got a working Fiddle of it if you care to check it out yourself ;) Hopefully this was informative for you, and I wish you the best of luck playing with the awesome that is Foundation!

Text form that follows link

I have to create a form with a submit bottom following a link
<form action="http://domain/**(((MY TEXT INPUT VALUE)))**.htm">
<input type="text" name="verb">
<input type="submit" value="Conjugate">
</form>
something like this.
please note that every link should be different.
I also want that the new page be opened in a new tab/window
could you please help me, and also make changes to the form code if there is sth under newer standards. Thank you!
You need to use javascript to get the TEXTBOX value and then place it into the form action.
You can create the submit button with an onclickevent.
Or you can use jQuery
$('#btnSubmit').click(function(){
var sTextValue = $("#MyText").val();
$('#MyForm').attr('action', 'htttp://domain/' + sTextValue + '.htm');
$('#MyForm').submit();
});
And the HTML
<form id="MyForm" action="">
<input id="MyText" type="text" name="verb">
<input id="btnSubmit" type="button" value="Conjugate">
</form>
There are many ways to accomplish this. That's just one of them.
<form action="http://domain/**(((MY TEXT INPUT VALUE)))**.htm" id="btnForm">
<input type="text" name="verb" onchange='javascript:document.getElementById("btnForm").action = "http://domain/"+ this.value +".htm"'>
<input type="submit" value="Conjugate" >
</form
This would update as soon you type the text. It wouldn't require jquery. it makes use of onchange event handler of input type text
<form action="http://domain/**(((MY TEXT INPUT VALUE)))**.htm" id="btnForm">
<input type="text" name="verb" onchange='updateFormAction(this.value)'>
<input type="submit" value="Conjugate" >
</form>
<script type="text/javascript">
function updateFormAction(value){
var btnForm = document.getElementById("btnForm");
btnForm.action = "http://domain/"+ value +".htm";
}
</script>
This is more explanatory form. Its based on onchange event handler for text types.

How to edit data when combining angularjs and mongodb

I'm a beginner in the AngularJs and MongoDb world (i started learning today!!)
Actually i'm trying to do something very basic : Display a list of record, with an add button and a edit link with each record.
I'm using this lib https://github.com/pkozlowski-opensource/angularjs-mongolab to connect to mongoweb.
Actually my data is displayed, when i try to add a record it works, but the problem is when i try to display the edit form!
Here is my index.html file, in which i display the data with a form to add a record and with the edit links :
<div ng-controller="AppCtrl">
<ul>
<li ng-repeat="team in teams">
{{team.name}}
{{team.description}}
edit
</li>
</ul>
<form ng-submit="addTeam()">
<input type="text" ng-model="team.name" size="30" placeholder="add new team here">
<input type="text" ng-model="team.description" size="30" placeholder="add new team here">
<input class="btn-primary" type="submit" value="add">
</form>
</div>
And here is my edit.html code, which displays an edit form :
<div ng-controller="EditCtrl">
<form ng-submit="editTeam()">
<input type="text" name="name" ng-model="team.name" size="30" placeholder="edit team here">
<input type="text" name="description" ng-model="team.description" size="30" placeholder="edit team here">
<input class="btn-primary" type="submit" value="validate edit">
</form>
</div>
And finally my js code:
var app = angular.module('app', ['mongolabResource']);
app.constant('API_KEY', '____________________________');
app.constant('DB_NAME', 'groups');
app.factory('Teams', function ($mongolabResource) {
return $mongolabResource('teams');
});
app.controller('AppCtrl', function ($scope, Teams) {
$scope.teams = Teams.query();
$scope.addTeam = function() {
varteam = {
name: $scope.team.name,
description: $scope.team.description
};
$scope.teams.push(varteam);
Teams.save($scope.team);
$scope.team.name = '';
$scope.team.description = '';
};
});
app.controller('EditCtrl', function ($scope, Teams) {
//????????
});
My AppCtrl works perfecty, it displays the data w add records perfectly.
Now i want to add the js code for the edit, but i don't even know form where to start ? how do i a get the id parameter in the url ? how do i tell the view to fill out the form fields from the values from the database ? And finally how do i update the databse.
I know that i asked a lot of question but i'm really lost! thank you
There are of course many possible solutions.
One solution is to use angularjs routing. See http://docs.angularjs.org/tutorial/step_07 for a tutorial.
Basically replace your ul list with something like:
<ul>
<li ng-repeat="team in teams">
{{team.name}}
{{team.description}}
edit
</li>
</ul>
Then you can create a route that responde to your url:
yourApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/teams', {
templateUrl: 'partials/team-list.html',
controller: 'TeamListCtrl'
}).
when('/teams/:teamId', {
templateUrl: 'partials/team-detail.html',
controller: 'TeamDetailCtrl'
}).
otherwise({
redirectTo: '/teams'
});
}]);
In this way from the detail controller (that will replace your EditCtrl) you can access the id parameter using: $routeParams.teamId
Anyway I suggest to study well all the tutorials for a better overview.

One click to trigger several search forms?

I have 1 main search form with a submit button and several secondary search forms with submit buttons.
What I would like to do is when I enter text and click on the submit button of the main search form, the same text gets copied in all of the secondary search forms and all the submit buttons of the secondary search forms get automatically hit.
The HTML code for the mains earch form is shown below:
<form action="query.php" method="get">
Search: <input type="text" name="item" size="30">
<input type="submit" value="send">
</form>
One of the several secondary search forms is shown below:
<FORM action="http://www.dpbolvw.net/interactive" method="GET" target="_blank">
<div style="float: left; padding: 0 3px 0 0;">
<INPUT type="text" name="src" size="9"
value="<?php
$input = $_GET['item'];
echo $input;?>" style="width: 110px; height: 22px;margin:0; padding: 0; font-size:140%;">
</div>
<div style="float: left; padding: 0 3px 0 0;">
<input type="image" name="submit" value="GO" src="http://images.guitarcenter.com/Content/GC/banner/go.gif"
alt="Search" style="font-size:140%">
/div>
<input type="hidden" name="aid" value="1234"/>
<input type="hidden" name="pid" value="1234"/>
<input type="hidden" name="url" value="http://www.guitarcenter.com/Search/Default.aspx"/>
</form>
Notice the php code that I put in the "value" field of the secondary search form:
<?php
$input = $_GET['item'];
echo $input;?>
This automatically copies the text that I entered in the main search form into the secondary search form. I thus figured out how to do that.
The problem is to "simulate" an "Enter" keystroke or a click on the "GO" button with the mouse on the secondary search form when the user hits the Enter key or hits the "SEND" button with the mouse on the main search form.
Thank you for your insight!
I'm not sure what the point of that would be, It looks like all of these are search forms all pointing to different sites. Web browsers won't allow that. They can navigate to one page at a time. When you post a form to a page you are navigating to that page. Therefore, you are trying to navigate to several pages at once. It's like trying to be in Paris and London at the same time. I don't see how your plan will work the way you're describing it.
That said, You can use client-side javascript to call
document.forms[0].submit();
so if you can come up with a plan that does not involve trying to have the user see all the different search results in one window, you could try this on your first form...
<form action="query.php" method="get" onSubmit="document.forms(1).Submit();">
You should use AJAX (JQuery) as Brandon Suggested. Read http://docs.jquery.com/Events/submit
Example:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
$(document).ready(function(){
$("#submit").click(function() {
//Do you stuff here like triggering other submits
//Like:
$("input#submit2").click();
$("input#submit3").click();
return false;
});
});
</script>
</head>
<body>
<form action="javascript:alert('success!');">
<div>
<input type="text" />
<input type="submit" id="submit" />
</div>
</form>
<form >
<div>
<input type="text" />
<input type="submit" id="submit2" />
</div>
</form>
<form >
<div>
<input type="text" />
<input type="submit" id="submit3" />
</div>
</form>
</body>
</html>
Take a look at the submit() event in jQuery. That is going to be your key.
I am assuming that you are planning on submitting via ajax? Otherwise it is futile.
So you could do something like this-
Give all of your forms a certain class, let's call it 'ajax_search_forms'. So now you can actually hook into the submit event.
$('.ajax_search_forms').submit(function(){
var search_string = $('input[name=src]').val();
$('.ajax_search_forms').each(function(){
$.ajax({
url : $(this).attr('action'),
data : 'search_string=' + search_string,
success : function(html){
// Do something with the result
}
});
});
// Return false is VERY important so that the form submission does not continue
return false;
});