List mapping in Play2's form - scala

I'm trying to create a form with multiple textarea, each goes with a corresponding checkbox. Basically, the application works as "If yes (the checkbox is checked), leave the textarea blank. Otherwise, fill in your explanation on why do you think it's wrong".
In models, I have
case class AnswerMapping(id: Long, status: Option[String], result: Option[String]
val form = Form[Seq[Answers](
mapping(
"details" ->
list(mapping(
"id" -> longNumber,
"status" -> optional(text),
"result" -> optional(text)
)(AnswerMapping.apply)(AnswerMapping.unapply)
))(apply)(unapply)
)
In views, I have
#helper.form(action = controllers.conflict.routes.Answer.updateAnswer(ans.id()) {
<div class="row-fluid">
<div class="span12">
#ans.details.get.zipWithIndex.map { case(detail, index) =>
#helper.textarea(
form(("details[" + index + "].result")),
'class -> "input-xlarge resizable",
'id -> ("box" + index),
'_label -> "")
}
</div>
<div class="controls">
<input value="Submit" type="submit" class="btn btn-primary">
</div>
</div>
}
The rendered HTML looks like <textarea id="box0" name="details[0].result" class="input-xlarge resizable" id="box0"></textarea>
However, when I submitted the form, I was redirected back to the same page. This is probably because I have this in my controllers, which means there's error in my form
Ans.form.bindFromRequest.fold(
formWithErrors => Ok(views.html.answer.edit(answer, formWithErrors)),
ans => { // save the answer }
My question:
Is the above, details[0].result the right syntax to access an element in a form's list
I don't quite understand why my form has errors. The two fields that needs to be filled in are marked as optional, simply because sometimes a checkbox is not checked, and sometimes the answer box is left blank. Is this perhaps because of the id field? I already set it in my apply/unapply method, so I'm not sure what I'm missing.
Thanks for all the input.

see document:Repeated values
#helper.repeat(myForm("emails"), min = 1) { emailField =>
#helper.inputText(emailField)
}

Related

KnockoutJS: Select object as return value

I am trying to convert an existing template from button clicks to a select with options. The original template looks like this:
<!-- ko foreach: {data: amounts, as: 'amount'} -->
<button type="button" data-bind="click: $parent.changeAmount.bind($parent)">
<span data-bind="text: amount.amountFormatted"></span>
</button>
<!-- /ko -->
The content of the array amounts is this:
[
{
"baseValue":"15",
"value":15,
"amountFormatted":"15,00 €",
"price":"15,00 €"
},
{
"baseValue":"25",
"value":25,
"amountFormatted":"25,00 €",
"price":"25,00 €"
},
{
"baseValue":"50",
"value":50,
"amountFormatted":"50,00 €",
"price":"50,00 €"
}
]
And this is the handler function:
changeAmount: function (amount) {
console.log(amount);
this.activeAmount(amount);
}
The console.log shows me how the return value of the button click looks like:
{
"baseValue":"50",
"value":50,
"amountFormatted":"50,00 €",
"price":"50,00 €"
}
Now I want this whole thing displayed as a select instead of buttons:
<select data-bind="options: amounts,
optionsText: 'amountFormatted',
optionsValue: 'value',
value: activeAmount,
event:{ change: changeAmount}"></select>
But with this template the select returns the entire view object instead of what a button click returns. How can I get the select to return the same object/array?
You should only be needing this as the minimal working sample:
The amounts observableArray with the options
The binding below:
<select data-bind="options: amounts,
optionsText: 'amountFormatted',
value: activeAmount"></select>
You are also using the event binding which is probably not needed on your context at all, you are binding it against the parent but instead of working with this you are also expecting the amount to be passed in, which does not work well with your click handler definition ( where you bind the function, ok, but no params are given if you go through that route ).
Rework your context and the click handler, so that it accesses activeAmount such that you dont expect a parameter. Since you are bound on the parent, just use this.activeAmount internally.
Note: you dont need to do anything to update the activeAmount if the select bind works properly. it will contain the seleced option's formattedAmount text already

Play framework + scala: Process checkbox list and selected items

I have a list of items I want to show as checkboxes, and I have a list of the items the user actually has selected previously and that I have stored in the database. So what I need is to show all the items but only mark as checked the selected items:
[ ]item1
[ ]item2
[x]item3
[x]item4
Here is my mapping:
def formMapping: Mapping[Data] = {
mapping(
"selectedItems" -> play.api.data.Forms.list(String)
)(Data.apply)(Data.unapply)
}
And my html:
#(theForm: Form[DataForView])
#for((itemValue, itemName) <- allItems) {
<input type="checkbox" name="selectedItems" value="#itemValue"/>#itemName<br/>
}
I'm using the name "selectedItems" so it maps to the form field. I though about using the #checkbox helper but then I would only see the selectedItems, not all items.
I've considered other options, for instance, eliminating "selectedItems" and having a list of all items with a Boolean that indicates if it is selected or not. But that means that I would have to cross the items with the selectedItems and I was hoping there would be a better way.
Any ideas?
UPDATE
I've tried this approach:
#for(item <- allItems) {
#if(theForm("selected").contains(#item.value)) {
<input type="checkbox" name="selected" value="#item.value" CHECKED/>#item.name<br/>
} else {
<input type="checkbox" name="selected" value="#item.value"/>#item.name<br/>
}
}
But theForm("selected") is something like selected[0], selected[1], ... I can't match it to the form field, which is a list of Strings.
of the top of my head you could do something like this:
#(theForm: Form[DataForView], selectedItems: Set[String])
#for((itemValue, itemName) <- allItems) {
#if(selectedItems.contains(#itemValue)) {
<input type="checkbox" name="selectedItems" value="#itemValue" CHECKED/>#itemName<br/>
} else {
<input type="checkbox" name="selectedItems" value="#itemValue"/>#itemName<br/>
}
}
Where the selectedItems is the set of selected elements.

Angular 2: Form valid returns false until all fields are populated

I am using reactive forms.
I have a submit button that should be disabled until the form is valid:
<button type="submit" [disabled]="!_searchForm.valid && !_searchForm.pristine">Submit</button>
My fields can be toggled on/off using bools:
showName: boolean = true;
showPhone: boolean = true;
showCellphone: boolean = true;
And this is my validation rules:
this._searchForm = this._formBuilder.group({
name: [{value: '', disabled: !this.showName}, Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[a-zA-Z]+$')])],
phone: [{value: '', disabled: !this.showPhone}, Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[0-9-]+$')])],
cellphone: [{value: '', disabled: !this.showCellphone}, Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[0-9]+$')])]
});
And finally, this is how each field is displayed in the HTML:
<div class="form-group" [ngClass]="{'has-danger': _searchForm.controls.name.errors && !_searchForm.controls.name.pristine}">
<label for="name">Name:</label>
<div class="input-group">
<span class="input-group-addon">
<div class="onofwrapper">
<div class="onoffswitch">
<input id="toggleName" type="checkbox" class="onoffswitch-checkbox" (click)='toggleName()' [checked]="showName">
<label class="onoffswitch-label" for="toggleName"></label>
</div>
</div>
</span>
<input type="text" formControlName="name" class="form-control" [ngClass]="{'form-control-danger': _searchForm.controls.name.errors && !_searchForm.controls.name.pristine}" autocomplete="off" spellcheck="false">
</div>
<div *ngIf="_searchForm.controls.name.errors && !_searchForm.controls.name.pristine" class="form-control-feedback">Error message</div>
If I don't touch the form I can submit, so pristine seems to be working.
The problem is, I can't enter text in just a single field and submit. If I enter text in one field, I have to enter text in all of them to be able to submit, or else _searchForm.valid won't return true even though I am not using Validators.required on all fields.
I have verified that each input "ships its own value", by removing the [disabled="!_searchForm.valid" line, and then just dumping out the values in my submit function like this:
console.log('Name: ' + this._searchForm.value.name);
console.log('Phone: ' + this._searchForm.value.phone);
console.log('Cellphone: ' + this._searchForm.value.cellphone);
What am I doing wrong?
Why does .valid require all fields in the form?
If you disable or enable an input, then, you need a function:
enableDisableInput(inputName: string): void {
if(!this._searchForm.controls[inputName].disabled) {
this._searchForm.controls[inputName].clearValidators();
this._searchForm.controls[inputName].disable();
} else {
this._searchForm.controls[inputName].setValidators(Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[0-9]+$')])]);
}
this._searchForm.controls[inputName].updateValueAndValidity();
}
Invoked <input id="toggleName" type="checkbox" class="onoffswitch-checkbox" (click)='enableDisableInput("name")' [checked]="showName">
The manner in which the form building and validation is done with Reactive Forms means that you must manually clear and add validation even on disabled items (there may be plans to change this as it is a not uncommon complaint on the angular github). This is a code oriented and driven manner of forms and needs to be treated as such for the time being.
Whether or not the inputs are required is moot if they have an unmet minimum length. Reference https://github.com/angular/angular/pull/11450 which was recently included in Angular 2.0.2 https://github.com/angular/angular/blob/master/CHANGELOG.md
For forms driven it looks like they have a correction in 2.1.0 where those fields are optional with pattern and minlength but I don't know if that's also in Reactive Forms or not.
It turns out that this was a problem with input type="number". The fields containing phone and cellphone values was only supposed to contain numbers, so in addition to the Validators.pattern('^[0-9-]+$') validation I also gave them the input type of number.
As soon as that was changed into text, everything worked like expected.
I had the same issue with the Angular reactive forms. I was disabling and enabling my form controls based on some logic. It turns out I was disabling, but not enabling back the form controls, so I was getting a form.valid as false. Apparently in Angular's reactive forms a form with disabled fields is invalid and the docs are not mentioning this default behavior.

How do I use React and forms to get an array of checked checkbox values?

I am trying to build a filter for my portfolio website. Checkboxes that let you pick a technology (react, redux, jquery etc.) to display a piece of work(s) that contain(s) that/those technologies. So every time the user clicks on a box, I want to add the value (JavaScript, Redux, React etc.) to an array that I use in another function to check against my portfolio pieces and filter out what isn't there.
I am finding this very difficult and I think it should be quite simple. Can someone point me in the right direction? Is there a way to simply have a function trigger (onChange callback?) that reads the checked/unchecked status of my form input elements and then updates my state array accordingly? Can I get the status of all the checkboxes simply in React? Do I need to have individual state of checked/unchecked for my checkboxes?
It seems that jQuery makes it pretty possible with selectors with:
$('input[type="checkbox"]:checked').each(function () {}
If you don't care about the order and you just want to append the items to the array as they appear we could definitely do exactly what you suggest in your question. On the change event of the checkbox check if the box is checked or or unchecked (event.target.checked returns true if checked or false if unchecked) and handle the array logic accordingly. this is a simple representation of how that could work:
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Portfolio extends Component {
constructor() {
super()
// initialize your options array on your state
this.state = {
options: []
}
}
onChange(e) {
// current array of options
const options = this.state.options
let index
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(+e.target.value)
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(+e.target.value)
options.splice(index, 1)
}
// update the state with the new array of options
this.setState({ options: options })
}
render() {
return (
<main className='portfolio'>
<form>
<div className="input-group">
<label>cb1</label>
<input type="checkbox" value={1} onChange={this.onChange.bind(this)} />
</div>
<div className="input-group">
<label>cb2</label>
<input type="checkbox" value={2} onChange={this.onChange.bind(this)} />
</div>
<div className="input-group">
<label>cb3</label>
<input type="checkbox" value={3} onChange={this.onChange.bind(this)} />
</div>
</form>
<div className="selected-items">
{this.state.options.map(number =>
<p key={number}>item: {number}</p>
)}
</div>
</main>
)
}
}
if you DO care about order, if you can append numerical values to the array like I did in this example you could easily give your checkboxes sorted numerical values and you could sort the array before updating your state so it's always in a certain order regardless of the order they are checked.
onChange(e) {
// current array of options
const options = this.state.options
let index
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(+e.target.value)
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(+e.target.value)
options.splice(index, 1)
}
// sort the array
options.sort()
// update the state with the new array of options
this.setState({ options: options })
}
Here's how I'm doing it:
// util.js
import getPath from 'lodash/get';
import setIn from 'lodash/fp/set';
export function linkMultiCheck(name, value) {
return {
checked: getPath(this.state, name, []).includes(value),
onChange: ev => {
let values = getPath(this.state, name, []);
if(ev.target.checked) {
values = [...values, value];
} else {
values = values.filter(v => v !== value);
}
this.setState(setIn(name, values));
},
}
}
// form.js
<ul>
{options.branches.map(branch => (
<li key={branch.id} className="checkbox">
<label>
<input type="checkbox" name={this.id} {...this::linkMultiCheck('formData.branchIds',branch.id)}/>
{branch.id}
</label>
</li>
))}
</ul>
i.e., if a checkbox is checked, append it to the current array of values. If it's unchecked, filter it out.
I'm using lodash here so that we can set deeply nested state values using dot notation.

Angularjs check if section in the form is valid

I want to check my angular form validity with a little tweak,
I have a form builded dynamically with directives involved, Now the form has more than one page to it, so i play with ng-hide/ng-show when i move from page to page...
All i want to do is to check the validity of the first chunk of form inputs, for example:
I have 3 pages, 3 questions in every 'page', before the user can go to the next page, it should check for validation on the three inputs, and only than! he can move to the next one...
on my form tag i have 'novalidate' so i must do all the validations myself...
What you're after is ng-form
You can't nest HTML <form></form> tags but you can with ng-form to split your form into sections.
i.e.
<form name="myForm">
<ng-form name="subForm1">
<input name="txtField1" type="text" ng-model="Field1" ng-maxlength="50" required>
</ng-form>
<ng-form name="subForm2">
<input name="txtField2" type="text" ng-model="Field2" ng-maxlength="10" required>
</ng-form>
<button type="button1" ng-disabled="subForm1.$invalid" ng-click="doSomething() ">Button 1</button>
<button type="button1" ng-disabled="subForm2.$invalid" ng-click="doSomething()" >Button 2</button>
<button type="button3" ng-disabled="myForm.$invalid" ng-click="doSomething()" >Button 3</button>
</form>
In this instance button1 and button2 are disabled on parts of the form where as button3 is disabled based on the whole forms input
Source: https://docs.angularjs.org/api/ng/directive/ngForm
You can use the Angular's form element property $dirty, or you could check if the element you want to validate has the class ng-dirty.
If you'd like, read more here, it explains how to use and check this.
Angular JS has some pretty features which you can take advantage of especially the class .ng-valid and .ng-invalid. As the user fills your form, angular dose a real time update on the state of form fields by changing the classList to correspond to the current state of the form.
Any for field that is has been altered and does not pass the Angular validation will have a class .ng-invalid well all classes that passed the validation will have .ng-valid. While ng-pristine indicates that the form have not been modified ng-dirty tells you that the form has been modified. Not that ng-pristine and ng-dirty cannot be used to ascertain the validity of the field.
Meanwhile for your case I have created a CodePen
angular.module("paged", [])
.controller("FormCtrl", function($scope) {
$scope.form = {page: 1};
$scope.canShow = function(i) {
return (i === $scope.form.page);
};
$scope.submit = function(form) {
alert("Form Submitted", form);
};
$scope.gotoPage = function(pageTo) {
var show = false;
var els = document.getElementsByTagName("input"); //Just with input only to keep it simple
for (var i = 0; i < els.length; i++) {
if (els[i].hasAttribute("data-page") && els[i].getAttribute("data-page") == pageTo - 1) {
if (els[i].classList.contains("ng-invalid")) {
show = false;
break;
}
}
show = true;
}
if (show) {
$scope.form.page = pageTo;
if (pageTo == 4) {
$scope.submit($scope.form);
}
}
}
});
to show you how this can done. As someone will rightfully say, there may ways to kill a rat. I think this is one of them