Submitting forms containing multiple selects in Play Framework for Scala - forms

I have a multiple select item in a Form. According to Play Framework's documentation, I need 'repeated values' to make all the selected options fit into a List[String] property of my data structure.
case class MyFormData (
fq_cset: Option[List[String]]
)
val myForm: Form[MyFormData] = Form(
mapping(
"fq_cset" -> optional(list(text))
)
)(MyFormData.apply _)(MyFormData.unapply _)
I quote Play's documentation:
When you are using repeated data like this, the form values sent by the browser must be named emails[0], emails[1], emails[2], etc.
I cannot figure out how to name the values like mentioned above. I tried to create a select element like this one
<select name="fq_cset">
<option name="fq_cset[0]" value="A" selected="selected">Value A</option>
<option name="fq_cset[1]" value="B" >Value B</option>
<option name="fq_cset[2]" value="C" selected="selected">Value C</option>
<option name="fq_cset[3]" value="D" >Value D</option>
</select>
but after submitting the form I see in the URL
/path?fq_cset=A&fq_cset=C
instead of
/path?fq_cset[0]=A&fq_cset[2]=C
The absence of the indexes enclosed in square brackets, prevents the correct binding of the parameters in the List[String] property named fq_cset in MyFormData class.
How can I get this to work properly? Is this the right approach to get what I need or I misunderstood the documentation?

Related

Trying to choose any non-disabled option from a select element

I have a parent record with multiple child records, all shown together on the ViewParentWithChildren and EditParentWithChildren screens. I want to write a cypress test that adds a new child record to an existing parent record. Each child record is in a <tr> of course.
The problem is, the <select> element has many <option disabled> invalid options in it. I need to select a valid, enabled one, and I don't know ahead of time what the names/values in that option are going to be. I don't care what they are, I just need to select any non-disabled option.
I try a standard-ish:
cy.contains('button', /Add Another Child Record/i).click();
cy.get('[name=child_id_name][value=""]') // newly added has nothing in the required field
.parents('tr')
.within(tr => {
cy.get('input[name=child_id_name]').type(randomAlpha());
cy.get('input[name=description]').type(randomAlpha());
cy.get('select[name=type]').select(?????); // TODO
});
Cypress only allows selecting an <option> via name, value, or index. Attempting to .select a valid <option> directly doesn't work, by design.
Here's a couple of other ways you may or may not find easier
Example page for POC
<select>
<option value="1" disabled>three</option>
<option value="2">two</option>
<option value="3" disabled>three</option>
<option value="4">four</option>
</select>
Method 1: Expose the <select> first
cy.get('select')
.should('have.value', '2') // by default the selected value is the first enabled
.then($select => {
cy.wrap($select)
.find('option:enabled:last')
.then($lastEnabledOption => {
cy.wrap($select).select($lastEnabledOption.val())
})
})
.should('have.value', '4') // check new value
Method 2: Set the selected attribute with jQuery
cy.get('select')
.should('have.value', '2') // initial value
.find('option:enabled:last')
.invoke('attr', 'selected', true)
cy.get('select')
.should('have.value', '4') // new value
The solution was kind of inside-out. Get all non-disabled options from that select first, then go within one of them, and "escape hatch" back out again to the select, feeding the .text() to the .select
cy.contains('button', /Add Another Child Record/i).click();
cy.get('[name=child_id_name][value=""]') // newly added has nothing in the required field
.parents('tr')
.within(tr => {
cy.get('input[name=child_id_name]').type(randomAlpha());
cy.get('input[name=description]').type(randomAlpha());
cy.get('select[name=type] option:not([disabled])') // get all its non-disabled options
.last() // the first option is usually blank for un-selecting, so, .last
.within(option => {
cy.root().closest('select').select(option.text());
});
});

How do I get the value from a GWT SelectElement?

I am trying to get the value of a SelectElement using the getValue() method of that class. However, when I debug and watch what's happening, the value is always null. I am able to confirm that the SelectElement contains the expected HTML node when debugging and that one of the options contained within has the selected attribute.
Here is the code that finds the select element in the DOM and tries reading the value:
SelectElement e = (SelectElement) DOM.getElementById( "sel-" + transaction.getId().toString() ).cast();
Boolean isAcknowledged = Enums.TransactionType.ACKNOWLEDGED.equals( e.getValue() );
As I said above, calling the e.getValue() method does not return a value but when I watch what is contained in e, I see the expected HTML node with one of the options set as selected.
<select class="form-control" id="sel-88024">
<option value="CONSUMED" selected="">Used</option>
<option value="ACKNOWLEDGED">Received</option>
</select>
But there is never a value in getValue(). Any ideas would be appreciated.
I think the problem is not related to GWT. Could it be that Enums.TransactionType is a real Java-Enum and you have to use Enums.TransactionType.ACKNOWLEDGED.name().equals(e.getValue())?

Adding CSS class to form element in Moodle

I create forms in Moodle by extending the moodle moodleform class and calling the addElement() or createElement() methods. For example, I create a select element this way:
$mform->addElement('select', 'mySelect', 'Select title' , $list);
Where $mform is a reference to the form, and $list is an associative array like:
$list = array('1'=>'one', '2'=>'two', '3'=>'three').
The question is: Is there a way to add a CSS class associated with this or other form elements?
I other words, I would like to programmatically add a class myClass, so that the HTML code looks like this:
<select class="myClass">
<option value="1">one</option>
<option value="2">two</option>
<option value="3">three</option>
</select>
Use the 5th addElement parameter - https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#select
$mform->addElement('select', 'myselect', 'Select title', $list, ['class' => 'myclass']);
(And I assume your real code does this correctly, but make sure you use get_string() for param 3, so it can be translated).

How to bind multiple snippets to one input field in a form

I'am trying to have auto complete and onBlur functionality attached to the same input field using Liftweb framework.
I have them working independently.
What I'am trying to do is have an auto complete input field and on selecting the value from the suggestion, some business logic is to be performed and another input field needs to be updated.
But only the auto complete feature is working.
This is the form
<form class="lift:CapitalOnBlur">
Country : <input id="countryNameOnBlur" type="text" name="countryNameOnBlur"/><br />
Capital: <input id="capitalNameOnBlur" type="text" name="capital"/>
</form>
This is the snippet
object CapitalOnBlur {
val capitals: Map[String, String] = Map(
"india" -> "New Delhi",
"uganda" -> "Kampala",
"japan" -> "Tokyo")
def render = {
def callback(countryName: String): JsCmd = {
val capital = capitals.getOrElse(countryName.toLowerCase, "Not Found")
SetValById("capitalNameOnBlur", capital)
}
val default = ""
def suggest(value: String, limit: Int) = capitals.filter(_._1.startsWith(value.toLowerCase)).keys.toSeq
def submit(value: String) = Unit
"#countryNameOnBlur" #> AutoComplete(default, suggest, submit) &
"#countryNameOnBlur [onBlur]" #> SHtml.onEvent(callback)
}
}
This is what I actually want to do. I tried this and only onBlur event is triggered.
According to my needs, When I start typing the country name in the first input field, it should show me the suggestions and on selecting the suggestion i.e.; onBlur from that input field, the corresponding capital should be rendered in the next input field.
And also is there a way to trigger an action on selecting a suggestion using the inbuilt Auto complete feature of lift.
I am adding this as a separate answer since the edit is essentially a separate question. The AutoComplete widget from Lift does not modify an existing element on the page, but rather replaces it with the following NodeSeq, as per the source.
<span>
<head>
<link rel="stylesheet" href={"/" + LiftRules.resourceServerPath +"/autocomplete/jquery.autocomplete.css"} type="text/css" />
<script type="text/javascript" src={"/" + LiftRules.resourceServerPath +"/autocomplete/jquery.autocomplete.js"} />
{Script(onLoad)}
</head>
<input type="text" id={id} value={default.openOr("")} />
<input type="hidden" name={hidden} id={hidden} value={defaultNonce.openOr("")} />
</span>
Since that has now replaced the original HTML, the second line where you add an onBlur handler is not applied to anything useful. However, the AutoComplete constructor does take an optional parameter for attributes and you can probably use that to add an onBlur attribute to the input tag.
You can try something like this:
"#countryNameOnBlur" #> AutoComplete(default, suggest, submit,
("onBlur", SHtml.onEvent(callback).cmd.toJsCmd))
The above should pass in a tuple which specifies the attribute name, and the string representation of the Javascript you want executed. This should accomplish what you are looking for as long as the AutoComplete library doesn't also rely on the onBlur event. That case is doable too, but a bit more work.
One other thing to note is that onBlur is fired when the input loses focus, ie: the user moves the cursor to another field. If you want it to fire any time the text changes, regardless of cursor position, you may prefer the onChange event.
If you are looking to bind to different events on the same element, so that you end up with something like: <input onblur="getCapitalName" onchange="autoComplete">, you can try using SHtml.onEvent. Something like this in your snippet should do the trick:
object CapitalOnBlur {
def render =
"* [onblur]" #> SHtml.onEvent(e => CapitalOnBlur.getCapitalName(e)) &
"* [onchange]" #> SHtml.onEvent(e => CapitalOnBlur.autoComplete(e)) &
...
}
And then call the snippet from your input, like this:
<form>
Country : <input id="countryNameOnBlur" data-lift="CapitalOnBlur" type="text" name="countryNameOnBlur"/><br />
</form>
I am not sure what any of the arguments your code takes, so the above is mostly illustrative - but will hopefully get you on your way.

Filtering a scope with multiple select in AngularJS

Here is my fiddle: http://jsfiddle.net/mwrLc/12/
<div ng-controller="MyCtrl">
<select ng-model="searchCountries" ng-options="cc.country for cc in countriesList | orderBy:'country'">
<option value="">Country...</option>
</select>
<select ng-model="searchCities" ng-options="ci.city for ci in citiesList | filter:searchCountries | orderBy:'city'">
<option value="">City...</option>
</select>
<ul>
<li ng-repeat="item in list | filter:searchCountries | filter:searchCities">
<p>{{item.country}}</p>
<p>{{item.city}}</p>
<p>{{item.pop}}</p>
<br>
</li>
</ul>
The first select filters the second one but also the result list.
The second select filters only the result list.
It works great until a country and a city are chosen. Then, when you choose another country, second select got reseted but the scope seems to be stuck with the old value.
i cant find a way to have it works properly... Please help!
The best solution is to reset the city model when a change to country model is detected via $scope.$watch():
function MyCtrl($scope) {
// ...
$scope.$watch('searchCountry', function() {
$scope.searchCity = null;
});
}
Note that I changed your model names to singular form ("searchCountry" and "searchCity") which is more appropriate considering the value of those models is set to a single country or city.
Here is the full working example: http://jsfiddle.net/evictor/mwrLc/13/
Ezekiel Victors solution is excellent. However, I ran into a bit of a dependency injection issue while trying to get this to work when referencing the controller from an ng-include. Thanks to the help from Ben Tesser I was able to resolve the issue. Ive attached a jsfiddle that details the fix:
http://jsfiddle.net/uxtx/AXvaM/
the main difference is that you need to wrap the searchCountry/searchCity as an object and set the default value to null. Ditto for the watch.
$scope.test = {
searchCountry: null,
searchCity: null
};
/// And your watch statement ///
$scope.$watch('test.searchCountry', function () {
$scope.test.searchCity = null;
});
In your template you'll change all references from searchCountry/searchCity to test.searchCountry.
I hope this saves someone some time. It's trixy.