Having troubles getting values from dynamically added dropdown in thymeleaf.
This is my first
<select th:field="${offer.offerItemList[__${iterationStatus.index}__].mapa}" class="form-control input-sm ofa">
<option value="0" >---Choose option---</option>
<option th:each="attribute : ${offer.offerProductAttribute}" th:value="${attribute.id}" th:text="${attribute.name}"></option>
</select>
Based on selecton from this dropdown i am generating another dropdown with code similar to this :
var options = '<select th:field="offer.list" class="form-control input-sm"> <option th:value="0">--Choose--</option>';
$.each(value.offerProductAttributeValuesList, function (index, value) {
options += '<option th:value="' + value.id + '">' + value.value+ '</option>';
});
options+= '</select>';
of.closest('tr').find('td:last').html(options);
Dom elements generate fine. Everything is ok but values are never submitted with the rest of input fields.
I have done this many times but with previously rendered
<select>
dropdown on the server side, and i would just appendTo() options, but in this case i cannot do that since i potentially have more than 20 dropdowns, based on clients selection from previous dropdown.
I hope i am being clear enough about my issue.
I am gonna answer my own question, since it took me quite some time to get around this problem, and my solution will probably help someone in the future.
The problem was : i had list of objects, and each one of those objects had another list of objects.
The problem is much easier to solve if you just render entire view from the backend (i was using thymeleaf). That way you can use thymeleaf expressions to map everything correctly
Like this :
First you gonna need for each to iterate over top level list
<tr th:each="item,iterationStatus : ${offer.offerItemList}">
You need to use iterationStatus to iterate over nested List, like this :
<select th:field="${offer.offerItemList[__${iterationStatus.index}__].mapa}">
This little piece of code __${iterationStatus.index}__ will basically use iteration index and you will end up with number for each iteration and rendered view will look like this offer.offerItemList[0].mapa, and 1 and 2 and so on.
this way values will be mapped correctly, BUT, if you want to add fields dynamically things get a bit more complicated.
There is jquery issue. Since jquery pretty much binds selectors when page is rendering, even if you write add another element, say <div class="temp">
and write perfectly good jquery function something like this $('.temp').on('click',function(){ console.log("clicked")});
nothing will happen since jquery didnt bind your newly created element to any select/event listener. The solution is to use (document).
$(document).on("click",".temp",function(){console.log('clicked');})
ok we have fixed front end issue, now newly created items work, but how do i tell spring to bind them to each object within list, which is part of another list? well you will have to use iteration index again :
When rendering the view you will need to save iteration index value in each element(using hidden fields)
Get value for each input field to jquery var like this : var iteration = $(this).closest('tbody').find('td:first-child').find('input').attr('value'); ofc this is path to where i have placed hidden input field, you will have to tell jquery where to look according to your structure.
You will simulate array and index numbers like this
var options = '<select name="offerItemList['+iteration+'].mapaValues">';
And the very last thing you need to be careful about is this : Say you have Object which you would normally send from controller like this model.addAttribute("offer",offer); object Offer has attribute, list of Products, so you would access that list with simple offer.productsList, but each product has list of AttributeValues. So final setup looks like this
offer.products[0].attributes[0].name products is an arrayList of objects of class Product, attributes is an arrayList of objects of class AttributeValues and name is a String. In order to let spring create object of class AttributeValues with information spring is receiving from dynamically(and non-dinamically) created forms from the frond-end, you will need to teach him how. If your new form with has input type="text" you are sending back String, so you will need to create a Custom constructor for
your class AttributeValues which will receive a String and which will tell Spring how to "construct" instance of that class with String.
finally create two constructors, one default and one with String as a value :
public AttributeValues(){}; and another for String public AttributeValues(String n){this.name = n;};
The problem you're most likely seeing here is that you're generating thymeleaf markup on the client-side.
Thymeleaf is a server-side templating language, so the browser (and hence the Javascript) will only ever see plain HTML coming back.
Here's a few approaches to consider:
submit the form each time to get new data in, which means no javascript is required
Output every possible dropdown into your HTML, and show/hide them as needed when the user selects options. Some fairly simple Javascript required, but as you mention - the page size may be pretty big
Add a JSON endpoint to your Spring webapp (see the spring #ResponseBody annotation) that will return just the data you need, then pull that JSON data in when the user selects a dropdown using something like jQuery.get()
Related
I have a table with variable number of records (could be up to hundreds) where its body is build with one ng-repeat.
Within each record, there are two input fields of type select where their contents is also built using ng-repear and the number of options for each is about 100.
This is working right now, except that it takes a lot of time for the page to be built (several seconds; I guess due to the large number of html records that AngularJS is adding to the DOM).
Here is an example of one of the selects:
<select class="form-control" ng-model="One_Source.Measuring_Method_Code">
<option ng-selected="{{One_Method.Value == One_Source.Measuring_Method_Code}}"
ng-repeat="One_Method in All_Collections.Parameters_Test_Methods"
value="{{One_Method.Value}}"
title="{{One_Method.Test_Method_Name}} | {{One_Method.Method_Internal_Name}}">
{{One_Method.Value}}
</option>
</select>
Two questions:
Is there a simple way to speed up the page building process?
As shown in the example, each option in the list has a title clause displaying a detailed description of the option's meaning. How can I add a title to the select showing the description of the current value?
For the first question I was thinking about building the list of options for each select element only upon clicking on it, but I'm not sure if that would be the best way to do it.
Try using one time bindings so that Angular doesn't watch the value by prefixing it with ::. It can also be more efficient to use track by in your ng-repeat if each row has a unique value, like an ID.
<option
ng-selected="{{One_Method.Value == One_Source.Measuring_Method_Code}}"
ng-repeat="One_Method in All_Collections.Parameters_Test_Methods track by One_Method.id"
value="{{::One_Method.Value}}"
title="{{::One_Method.Test_Method_Name}} | {{::One_Method.Method_Internal_Name}}"
>
{{::One_Method.Value}}
</option>
If you still can't gain the performance you're expecting from #doublesharps's answer, you will have to implement one of the following:
You could build a custom list that has a 'load more' button which would destroy say the first '50' options and load the next 50.
A better option would be to turn this into an autocomplete, where the user searches for values.
Virtual repeat - Something angular material does really well, it constantly draw's and re-draws new elements based on the scroll position inside the element.
Other resources:
http://blog.scalyr.com/2013/10/angularjs-1200ms-to-35ms/
https://github.com/stackfull/angular-virtual-scroll
http://klajd.github.io/angular-virtual-repeat/#/Home
I found a PARTIAL SOLUTION that still needs to be polished but is quite promising.
During creation of the page, I do not make use of ng-repeat for the options but instead deploy a single option with the value received for the field from the database (if any, otherwise the select element is left blank).
When the user clicks on the select element a $scope function is invoked that checks the number of options within the select element and, if less or equal to 1, the inner HTML of this select element is re-generated and deployed.
Though clicking on all these select in the page will take (accumulative) a comparable time as when built upon load, this time is distributed over several events (the user clicking on all the select elements) and hence it is not perceived by the user.
Now, by polishing I mean that there is a strange behavior. In order to see the generated list of options, I need to click on the select twice. Will keep investigating this and post the solution (hoping I find one).
Just wondering, Imagine I have a checkbox like this:
<input type="checkbox" id="situationcontrol" name="situationcontrol">
I could check if this is checked or not by using this JavaScript code:
var situationcontrol = $("#situationcontrol").prop('checked');
Now I am wondering how this would work if you make a checkbox using #Html.EditorFor
Like this:
#Html.EditorFor(model =>Model.ServiceDeliveryMutableObjects.SituationControl)
I tried to change the same javascript code with the new generated ID
var situationcontrol = $("#ServiceDeliveryMutableObjects.SituationControl").prop('checked');
But that doesnt seems to work.
Any Idea how this would work?
Thanks
Edit: When I inspect element in browser when I use #Html.EditFor
EDIT
Didn't snap to that until you posted the rendered output. The . is not valid in HTML ids, so Razor uses underscores instead. So, the id you should be selecting is #ServiceDeliveryMutalObjects_SituationalControl, rather than #ServiceDeliveryMutalObjects.SituationalControl. Other than that, the rest of my original answer applies.
ORIGINAL
First, actually it's better to use:
$('#foo').is(':checked')
Now, as for using EditorFor, technically, this doesn't change anything. The id will obviously be based on the object graph, i.e. #ServiceDeliveryMutalObjects_SituationalControl, but nothing changes about the actual rendering of the HTML element. I emphasized "technically", here, because while that should be case, there's no default editor template that will actually render a checkbox input. The default is a text box, and a text box, obviously will not have a checked property. This can be corrected by either:
Use CheckBoxFor instead. That way, you're assured of getting an actual checkbox input.
Assuming this property is a boolean, you can create the view Views/Shared/EditorTemplates/Boolean.cshtml with something like:
#model bool?
#Html.CheckBox("", Model)
Then, EditorFor will use this template, and generate a checkbox input.
Finally, it may just be a typo in your question, but you want lowercase "model", not "Model", on the right side of your expression. In other words, it needs to match the left side of the lambda. I tend to avoid using model in these expressions, as not only is it more to type than needed, but you can easily get confused between "model" and "Model", especially with Intellisense's autocomplete. For example,
#Html.EditorFor(m => m.ServiceDeliveryMutableObjects.SituationControl)
You can change your code like this
Var situationcontrol = $("#ServiceDeliveryMutableObjects_SituationControl").prop('checked');
You need to remove .in Id of elements in mvc reazor view it's will convert '.' To '_' when we provide in elements name.
In my code I have a Struts2 select tag(<s:select>). It reads something like this :
<s:select id="s" list="list" listKey="id" listValue="displayValue">
On some selection made by the user, I want to change the value of list attibute to point to some other list.(possibly using javascript/jquery)
What have you tried ? *
You have at least two ways to do this:
1) Perform an AJAX operation to get the new data (in JSON for example, like a List of 'id' and 'description');
then alter the HTML with Javascript, removing the old Options from the Select,
building and adding the new Options to the Select,
and eventually changing some Select attributes;
2) Perform an AJAX operation to get a completely new Select, elaborated server side, returned as a HTML snippet (a JSP with only one <s:select> inside);
then replace it to the original one on the page, for example using its container (like a div) as target of the AJAX operation.
* #AshishGupta suggested me to remove the "What have you tried ?" part to keep the tone positive. Please read the article, it is a must to understand why this should be always the first (positive and legit) question to askers who doesn't post code (for their own good in first place).
Hope that helps, as the rest of my answer.
I'm trying to use jQuery plugin "Chosen"
(http://harvesthq.github.com/chosen/ and https://github.com/harvesthq/chosen)
in my project.
What I'm trying to achieve is update list basing on user selection (ajax call (tree based structure))
This is no bigger problem, because i can use .chosen().change(function()) and remove all unused select items and then .append new ones.
Then I can use .trigger("liszt:updated") to update list, but unfortunately all selections are deleted..
Does anyone know a way how to update chosen list without loosing selected data?
In theory I can manually remove all chosen generated elements and then populate with new ones, but then is a problem with getting SELECT "value" data.
This should be fairly simply if you save the items selected. For example:
<select data-placeholder="Choose a country..." style="width:350px;" multiple="true" class="chosen-select">
$(".chosen-select").chosen();
Now, before updating the chosen, make sure you save the items selected like this:
var chosenSelectedItems = $(".chosen-select").val(); // this gets you the select value data
// Update the select items
$('.chosen-select').trigger('liszt:updated');
$(".chosen-select").val(chosenSelectedItems);
This should be able to reset the original values before the change.
The new code now updates the list without losing the selections, and it sorts the selections based on the options order.
$('.chosen-select').trigger('chosen:updated');
Reference their project page.
This will reload the selection after xhr request (refresh list) and delete the selection if the new item list not contains the earlier selected item:
var chosenSelectedItems = $(".chosen-select").val();
$('select#GroupsStr').empty();
$.each(xhr.ReturnValue, function (index, item) {
var newOption = $('<option value="' + index + '">' + item + '</option>');
$('select#GroupsStr').append(newOption);
});
$("select#GroupsStr").val(chosenSelectedItems).trigger("chosen:updated");
I have created a few cascading or dependent dropdowns using chosen, but I have used them in addition to knockoutjs. KnockoutJS is used for binding data (in your case the select) to an object and a DOM element. Knockout also allows you to create custom bindings to handle things they may not have anticipated straight out of the box. With that being said I created a custom binding for knockout that utilized Chosen and it turned out well...
In our case we allow users to select a channel (using chosen) we then load in their locations (either by displaying or creating another select element) and trigger our custom binding which will update the data and trigger our custom binding that will tell chosen to run .trigger("liszt:updated") but keep the data in the background.
Our code is rather proprietary and I don't know that it would necessarily show you easily how to achieve this, but perhaps this will give you another way of looking at it.
I have a ViewModel of a form (Name, Address) etc and it's all bound to controls on my page (using Spark engine) - e.g.
!{ Html.DropDownList() }
That works fine. However, there is one DropDownList which is bound that has no values in it to begin with, the values are populated using Ajax (by selecting previous drop downs)
The problem lies when I submit the page and there's a validation error. The page loads and my select list has no values in it (as it hasn't been triggered to get them).
How can I set it up so that the ViewModel knows about values got dynamically so that it can populate the select list on page load?
You'll either need to:
Do this server side by using the Html.DropDownList() call that allows you to pass values in:
Html.DropDownList(string name, IEnumerable<SelectListItem> selectList, string optionLabel, object htmlAttributes)
Or the other way would be to just trigger the same ajax call when the page loads so you don't have duplicated logic.
I'd actually recommend the second way, as it'll keep you from doing the same thing multiple ways, but it does mean that there'
You can add a property of selectlist type in your viewmodel, populate it in constructor, and call it through Html.DropDownList function in your view page. Further since this selectlist is dependent on another list(i.e another property of you viewmodel). in your constructor you can write code like,
say if first listbox is connected to list1 property of type int and allowed values are >0
<code>
if (list1!=0)
{
write code to populate second list based on your first list parameter and also set
the selected value as if selected by the user at submit its stored in the other
property linked to the second list box.
}
</code>
if you are still not clear, write here the concerned code of your viewmodel, and server side database call of your second list, I may help you by editing your code.