Play framework + scala: Process checkbox list and selected items - scala

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.

Related

Angular 2: Is there an easy way using FormBuilder with FormArray in ng2?

Situation
I'm working on a form where I want to list some mails having a checkbox besides the subject and a "check all" checkbox in the same column as the other checkboxes.
The form looks simply like this:
[ ] Check all
------------------------------------------
[ ] This is email subject #1
[ ] This is email subject #2
[ ] ...
When I select Check all all the below checkboxes should be selected and when I click again, all mails should be unselected.
The mails are coming dynamically into the component via an #Input and the list can change at any point of time.
So far so easy, nothing special. BUT it seems not so easy when using the FormBuilder in ng2 for that. Side note: I want to use the FormBuilder to test my code less end-to-end but more with unit tests.
Current code
Template
<form [formGroup]="form">
<div><input formControlName="toggleAll" type="checkbox"></div>
<div>
<ul formArrayName="checkMailList">
<li *ngFor="let mail of mails; let i=index">
<input type="checkbox" [formControlName]="i">
<div>mail.subject</div>
</li>
</ul>
</div>
</form>
Component
#Component({ ... })
export class MailListComponent implements OnChanges {
#Input() mails: Mail[];
private get checkMailList(): FormArray { return this.form.get('checkMailList') as FormArray; }
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
checkMailList: this.fb.array([]);
});
}
ngOnChanges(changes: SimpleChanges) {
if (!changes['mails'] || !changes['mails'].currentValue) {
return;
}
// remove array first from form, as we will get
// mails again when anything updates
if (this.checkMailList.length > 0) {
this.form.removeControl('checkMailList');
this.form.addControl('checkMailList', this.fb.array([]));
}
this.mails.forEach(m => {
this.checkMailList.push(this.fb.control());
});
this.form
.valueChanges
.pluck('toggleAll')
.distinctUntilChanged()
.subscribe(selectAll => {
if (selectAll) {
this.checkMailList.setValue(this.mails.map(_ => true));
} else {
this.checkMailList.reset();
}
});
}
}
Problem
I think that there could occur a race condition/timing issue: I iterate over the mails array provided by #Input but I wire the checkMailList manually in the template to the corresponding index. I iterate over all mails whenever the #Input changes. I don't know if Angular first iterates over all mails in the template and then runs the ngOnChange method or vice versa. Can anyone can give me a profounded answer here?
Forms are the fundamental part of every WebApp. Am I doing it right? Any help would be appreciated.

Checkbox groups in #angular/forms:^0.2.0

I need two different groups of checkboxes. I've written the TS below which seems to work. But I can't seem to understand how the HTML for the form would look like to properly bind it to the form. Any suggestions?
buildForm():void {
let countryGroup = {};
let shipperGroup = {};
this.countryList.forEach(country => {
countryGroup[country.key] = new FormControl(false);
});
this.shipperList.forEach(shipper => {
shipperGroup[shipper.key] = new FormControl(false);
});
this.form = new FormGroup({
countries: new FormGroup(countryGroup),
shippers: new FormGroup(shipperGroup),
});
}
Edit:
Ok. So I don't even think this is the correct approach. I can't seem to find any example using checkboxes and/or multiselect.
What I need is basically a way to map the below. I need an array of the checked values (just as a normal POST would generate).
<label>
<input type="checkbox" name="foobar[]" value="foo">
Foo
</label>
<label>
<input type="checkbox" name="foobar[]" value="bar">
Bar
</label>

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

List mapping in Play2's form

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)
}