Thymeleaf - Checked attribute of checkbox is not set in th:each OR how to properly restore a list of checkboxes some of which were previously checked - spring

In my app I want to create a new Risk ( an instance of Risk object ) and when it is created I want to display 5 checkboxes and three radio buttons. Selected options are specific to each instance of Risk object.
Later I want to display a list of all added Risks with an Edit option button on each Risk. I want my app to restore the view specific to a selected Risk ( when an Edit button on a selected risk is clicked ) - with Risk name, all checkboxes and radio-buttons checked as selected previously. And I want to be able to edit these checkbox selections again so that all new changes were properly reflected in MySQL.
As a newbie in Thymeleaf I did the following:
<div th:each="top : ${topic}">
<input type="checkbox" th:field="*{topic}" th:checked="${top.checked}" th:value="${top.name}"/><label th:text="${top.name}">Something is wrong !</label>
</div>
I am sure that Controller and Hibernate/MySQL part works properly ( I checked using Logs ).
This works just fine - but only if I have selected only one checkbox ( initially when I added a risk ).
If I select more than one checkbox (when adding a risk) and later select this risk for editing no checkboxes are checked.
What is wrong ?

After some research I found the following text in Thymeleaf’s documentation:
“… th:field would have taken care of that and would have added a checked="checked" attribute to the corresponding input tags.”.
Also I found this guidance :
http://forum.thymeleaf.org/The-checked-attribute-of-the-checkbox-is-not-set-in-th-each-td3043675.html
Then I managed to develop a couple of small apps and I want to share what I found out and hope it will help someone.
( may be it is too detailed for experienced people, but I want it to be clear to all )
I do not want to repeat what is already in the above-mentioned Thymeleaf’s forum page ( see Administrator’s first response / explanation for detail - second in forum thread ) - just want to make a small summary and stress out few points:
you indeed do not need to add ‘checked’ when using th:each;
you must add th:field=“{…}” which should have the name of the field in your model class (referred by Thymeleaf as form-backing bean - th:object ) to which checkboxes are related. More on this: I stated above that my ‘form-backing bean’ was Risk.java. And for each Risk object instance the selected checkboxes represent topics(s) specific to this Risk instance. And selected topics are assigned to field ‘topic’ of Risk.java's instance (and hence in related table in MySQL when instance is saved). That field’s name should go inside th:field=“{…}” as th:field=“*{topic}” in my case. When you select checkboxes Thymeleaf will save selected values to Risk.java’s topic field using its setTopic method and when it needs to restore view Thymeleaf will use Risk.getTopic method to get info on earlier selected items.
all values of checkboxes (or radio-buttons ) should come from another source - it could be an Enum if you need a static set of checkboxes or if you need checkboxes to be dynamically generated you can use a class ( I needed static set of checkboxes for my app, but I decided to try to create a dynamic one as well - see links to my Github repo’s below to see the code I managed to develop ). So for my app I created an Enum Topics with all values for checkboxes and Enum Types with all values for radio-buttons. Then in your controller class you should add all values to Model’s attribute - I did this as I used an Enum:
model.addAttribute("topics", Topics.values());
model.addAttribute("types", Types.values());
(if you need dynamic ones do the following:
model.addAttribute("topics", topicsService.findAll());
model.addAttribute("types", typesService.findAll());
)
Then you should have something similar to:
<div>
<div th:each="top : ${topics}">
<input type="checkbox" th:field="*{topic}" th:value="${top.id}"/><label th:text=" | ${top.name}|">Something is wrong !</label>
</div>
</div>
<div>
<div th:each="typ : ${types}">
<input type="radio" th:field="*{type}" th:value="${typ.id}"/><label th:text="| ${typ.name} |">Something is wrong !</label>
</div>
</div>
where:
as mentioned, th:field=“{topic}" corresponds to form-backing Model class - Risk.java’s field. Same for th:field=“{type}" ;
topics in th:each="top : ${topics}” should match with attribute name you provided in controller.
And the MOST important part is that th:field=“*{topic}” should return an array.
th:field=“*{topic}” returning an array of selected items and th:each returning array of all options Thymeleaf should now be able to mark checkboxes / radio buttons as checked when values in first array match values in second array.
Since in case of radio buttons you can select only one option th:field=“*{type}” does not actually return an array - it returns only one item. But in case of checkboxes it should be an array - so the ‘topic’ field in Risk.java must return an array.
For this we need a converter - a class named e.g. StringListConverter that implements AttributeConverter….
( I learned how I can do it here. If not this answer in www.stackoverflow.com I would not be able to finalyze this app and would not be writing all this:
https://stackoverflow.com/a/34061723/6332774 )
Then in your form-backing model class - Risk.java in my case you need to do something like :
#Convert(converter = StringListConverter.class)
private List<String> topic = new ArrayList<>();
private String type;
Type can simply be a String.
That is it.
( I wanted to display checkboxes in table form indicating number of columns needed - I could do it, but I am not sure how clean it is or how safe it is. Related code is in riskformtable.html in example project linked below.
I posted a related question here - Thymeleaf - Displaying checkboxes in table form when using th:each - is what I am doing safe?
Also I wanted to use different color for all risk items with even sequential number in my risk’s list - it is in index.html
See full example code using links below )
Links to my GitHub repos:
example with static set of checkboxes : https://github.com/aripovula/Spring-Thymeleaf_checkboxes_and_radiobuttons
example with dynamically generated checkboxes : https://github.com/aripovula/Spring-Thymeleaf_dynamic_checkboxes_n_radio-buttons

Related

Add a watcher for form fields

I'm using Laravel Nova, and I want to make "Save" button(on the Update view) unavailable while all required fields are empty.
Here's the part of the code of the "Update.vue" file that creates my form fields:
<div v-for="field in fields">
<component
#file-deleted="updateLastRetrievedAtTimestamp"
:is="'form-' + field.component"
:errors="validationErrors"
:resource-id="resourceId"
:resource-name="resourceName"
:field="field"
/></div>
Fields array fills with the data from backend.
So I can see this structure in the Vue dev tool. And when I change the input value it changes value of <form-text-field>, but I don't see this changes anywhere in my Update component.
So my question is - How I can get changes of my input fields dynamically?
In Vue data flows only one way, so the change of data in your Update component will be reflected on form-text-field, but not the other way around. To create 2-way data binding you should use v-model.
I suppose form-text-field is one of your custom components, in that case you should create a custom v-model for it. See: Customizing Component v-model
If you can't get it work post your implementation of form-text-field here, so we can help.

Replace Orbeon Form with a new one asynchronously via AJAX

I am using Orbeon forms with Hybris. We have several pages linked together where a user needs to go through them in a sequence (checkout process).
The content of the Orbeon form is dynamically being determined based on actions from previous steps. E.g.
If user adds Product A to the cart on the step 1, only two fields will be visible on the form located on step 2, if he adds another (Product B) on step 1, one more field should be visible on the form.
I am using certain preprocessor class which prefills some of the hidden fields on the form and then the logic for dynamic display is on the Form itself, based on those hidden fields. This works in a simple scenario when moving back and forth, through the steps.
However, the problem is that I need to have a HTML Mini-cart displayed on the page as well (not part of Orbeon Form), which can also trigger adding/removing of the products asynchronously.
So while I am on step 2 where the form is displayed, the user can also remove/re-add some of the products -> therefore, this needs to trigger asynchronous re-rendering of the form and change the display of the form (with new fields added or removed).
I'm using AJAX for this async stuff and the problem I am facing is that a lot of Orbeon-specific Javascript files and variables is being generated when the page loads for the first time, and some random FormID is used. This FormID is different when I retrieve the new form from the back-end and when trying to replace the HTML content I'm getting various errors in the console, because old Form id is used all over the place.
Does anyone have any suggestion if this could be achieved and how to approach this problem?
Update: Example of "hidden" field glass-coverage-selected
<xf:instance id=""fr-form-instance"" xxf:exclude-result-prefixes=""#all"">
<form>
<glass-coverage-selected/>
<section-1>
<massive-exterior-walls/>
</section-1>
...
Later, a bind is created:
<xf:bind id=""section-40-bind"" ref=""section-40"" name=""section-40"" relevant=""instance('fr-form-instance')/glass-coverage-selected = 'yes'"">
<xf:bind id=""previous-glass-insurance-bind"" ref=""previous-glass-insurance"" name=""previous-glass-insurance"">
<xf:required id=""validation-156-validation"" value=""true()""/>
</xf:bind>
And that bind is used to control the visibility of certain section:
<fr:section id=""section-40-control"" bind=""section-40-bind"">
<xf:label ref=""$form-resources/section-40/label""/>
<fr:grid>
<xh:tr>
<xh:td>
<xf:select1 id=""previous-glass-insurance-control"" appearance=""full"" bind=""previous-glass-insurance-bind"" class=""previous-insurance"">
<xf:label ref=""$form-resources/previous-glass-insurance/label""/>
<xf:hint ref=""$form-resources/previous-glass-insurance/hint""/>
<xf:help ref=""$form-resources/previous-glass-insurance/help""/>
<xf:alert ref=""$form-resources/previous-glass-insurance/alert[1]"" validation=""validation-156-validation""/>
<xf:alert ref=""$form-resources/previous-glass-insurance/alert[2]""/>
<xf:itemset ref=""$form-resources/previous-glass-insurance/item"">
<xf:label ref=""label""/>
<xf:value ref=""value""/>
<xf:hint ref=""hint""/>
</xf:itemset>
</xf:select1>
</xh:td>
</xh:tr>
</fr:grid>
</fr:section>
You can manipulate the values of form fields in JavaScript, in the browser. If you want to set the value of "hidden fields", you make sure that those fields as not hidden by putting false() under Visibility for the field in Form Builder. If you do this, for security reasons, the value of the field isn't even sent to the browser by Orbeon Forms, and it can't be set from JavaScript. Instead, to be able to set the value from JavaScript, you need to hide the control with CSS. The simplest way to do this is to add the class xforms-disabled for that field in the Control Settings dialog.
Then, assuming the name of the control in Form Builder is my-control, in JavaScript you can write var control = ORBEON.jQuery('*[id $= "my-control-control"]'); ORBEON.xforms.Document.setValue(control.attr('id'), '42');. Note the -control added at the end of the name of the control. And to test this first, I recommend you don't put the CSS class, so you can more easily see if setting the value works.
For the documentation on the above setValue() and other JavaScript APIs, see the page Client-side JavaScript API.

Knockout - Disabling the default behavior of updating model when using binding value with form element

Knockout has the default behavior of updating the associated model if you change your focus (e.g. by clicking outside the input control) after editing the value inside an input control, populated by a binding of type Value.
Here is the link to the official documentation page explanation, section Parameters:
http://knockoutjs.com/documentation/value-binding.html
Do you know a way to disable this default behavior ?
The reason behind this : Im using Models that can tell if their last update requires persistent backup by comparing the previous value to the new one.
I would like to listen to the key up event on my form inputs but if I do that, knockout triggers twice event (the default one + the key up) to update the Model and the second one is basically telling my Model to replace the value by the same value because it is already updated, which it translates to "there is no need to do persistent backup since the value didnt change".
I would gladly appreciate any help or suggestion, since Im stuck and can't find a way around :)
EDIT
Couldnt reproduce the error with bare backbone code. It seems as "super cool" said that valueUpdate should override the default Blur event that triggers when you use form controls with binding Value.
It may be a bug introduced by the library Knockback that I use to create the ViewModel.
Despite all this, just replacing the binding Value by the binding textInput did the trick. Thank you all.
Don't listen to the event, subscribe to updates, which won't fire unless the value is changed. Using the textInput binding will register every change to the value, even those done using menu items like cut and paste, when they happen.
Equivalently, you can use the value binding along with valueUpdate: 'input'
vm = {
myvar: ko.observable(),
updates: ko.observableArray()
};
vm.myvar.subscribe(function(newValue) {
vm.updates.push(newValue);
});
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input data-bind="textInput: myvar" />
<div data-bind="foreach: updates">
<div data-bind="text: $data"></div>
</div>

How to force the Drupal form to clear previous results

Recently im working on a project and im trying to generate form elements with the help of ajax technology (implementing a form with codes). the situation is that the user should be able to select from a list of options and then due to his select another list of options should be appeared, then due to his/her select from the second sets of options he/she should see the third series of options. now the problem is that when the user tries to change the first option in the first set, the second option will be regenerate but the third one still sticks on the page. I was trying to use the form_sate['rebuild'] = TRUE
but it did not work and all form elements disappeared. can any one help me to see which code should be implemented and where it should be used?
Without any code it's almost impossible to help, except to say check out the examples modules, specifically the ajax_example module.
The basic principle is that you need a <div> container surrounding your 2nd and 3rd select elements, which will be replaced by the #ajax set on the first element. Then you need another container inside that one surrounding only the 3rd select element, which will be replaced by the #ajax set on the 2nd select element.
Hope that helps.
well.. the form page may contains previous values because of $_POST fields variables..
for example if I want to display clear "add" form on POST submit,
I do this tric to clear drupal previous form values via ajax:
<?php
// AJAX POST handler...
....
$my_form = drupal_render(drupal_get_form("the_form", ...));
$errors = form_get_errors();
if (!$errors) {
// re-render clean form, unset your POST fields....
unset($_POST['link_path']);
unset($_POST['link_title']);
unset($_POST['parent']);
unset($_POST['weight']);
$my_form = drupal_render(drupal_get_form("the_form", ...));
}
?>

HTML.SelectListFor selectedValue never gets set

I have the following partial view which renders a drop down list:
#model MartinDog.Core.Models.Section
#{
#Html.DropDownListFor(x=>x.Name
, new SelectList(Model.Dock.DockTemplate.Columns,
"Id", "FriendlyName",
Model.DockTemplateColumn.Id.ToString())
, new { #id = "ddlb_dockTemplateColumns" +
Model.Id.ToString()})
}
I render it on my page like so:
#{Html.RenderPartial("_Admin_Page_DockTemplateColumnDropDown", Model);}
The partial view is rendered once for every Section object. A Section object one I've created and is editable in a jquery dialog box (change the name, display order, dock template column, etc.)
On the test page I am using, this Section dialog box is rendered four times (as there are four of them in my parent object).
The problem:
*The SelectedValue in the SelectList for the drop down never gets set* - that is to say, the correct item in the drop down list is never selected when the dialog is displayed and I can't quite work out why.
I thought it might be because the drop down is rendered four times, so I tried rendering it for just one of the 'Sections' but still the same problem.
Anyone know what I can do?
***edit
Not sure if I'm doing it in a sucky way. I had thought of building the dialog just once with jquery and json but I'd prefer to do it this way as it just feels cleaner.
I do this:
In controller action (Edit for example):
ViewData["ProvinceID"] = new SelectList(dc.Provinces.OrderBy(p => p.NameAr), "ID", "NameAr", factory.ProvinceID);
and Markup:
<%: Html.DropDownList("ProvinceID") %>
See? so it is a list of factories and it has a Province field and what I want you to notice is the 4th parameter in the SelectList constructor, I passed factory.ProvinceID so the DropDownList knows which option to be set on. Otherwise the DropDownList will show the default value (the first one).
P.S: It is your job to change to Razor syntax; I don't use it.
Hope that helps.
Doh... fixed - totally my own fault.
I had set up my html.dropdownlistfor like so
#Html.DropDownListFor(x=>x.Name,
When it should've been like so:
#Html.DropDownListFor(x=>x.DockTemplateColumn.Id,
Setting the first argument to x=>x.DockTemplateColumn.Id (which uniquely identifies the items in my list) instead of x.Name fixed the issue straight away.
Just thought I'd post it here in case someone else makes the same mistake I did.
edit
Found the answer here:
C# mvc 3 using selectlist with selected value in view

Resources