Dynamic Drop Down Selection in Spring using jQuery and no AJAX
In this post, I’m going to demonstrate one way to use jQuery to dynamically populate one drop down from another using values loaded to the page on the initial load. The example application is a small Spring Boot application, although the focus of this post will be on the user interface. In the example, the page is loaded with a JSON string enclosed in a hidden field. That JSON string represents a map that maps values in the first drop down, to a map of values with which to populate the second drop down.
[Edited 2021-07-15]: I got a comment that my example didn’t work. Since the original is 3 years old, that’s not terribly surprising. I’ve updated the dependencies, both here and in the example code in GitHub.
Prerequisites
- Java 1.8
- Java IDE (example uses eclipse)
- Basic understanding of Java and jQuery
- Browser
- Internet connection
Project Setup
The project is a simple Spring Boot application with just one controller and one page. The data used to populate the drop down files is stored in a YAML file called dropdownValues.yaml. The example uses categories of shapes. Here’s the contents of dropdownValues.yaml to illustrate the structure of the map.
polygon: polygon|triangle : triangle polygon|rectangle : rectangle polygon|square : square polygon|rhombus : rhombus polygon|trapezoid : trapezoid polygon|pentagon : pentagon polygon|hexagon : hexagon polygon|heptagon : heptagon polygon|octagon : octagon polygon|nonagon : nonagon polygon|decagon : decagon curved with circular arcs: curved with circular arcs|circle : circle curved with circular arcs|crescent : crescent curved with circular arcs|semicircle : semicircle curved with circular arcs|heart : heart curved without circular arcs: curved without circular arcs|ellipse : ellipse curved without circular arcs|oval : oval
Gradle Dependencies
The in addition to Spring Boot, Thymeleaf and jQuery, the application uses SnakeYaml to read the dropdownValues.yaml file and Gson to create the JSON string that’s sent to the browser.
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:2.5.2' implementation 'org.springframework.boot:spring-boot-starter-web:2.5.2' implementation 'org.springframework.boot:spring-boot-starter-validation:2.5.2' implementation('org.yaml:snakeyaml') implementation('com.google.code.gson:gson:2.8.7') implementation('org.webjars:jquery:3.6.0') implementation('org.webjars:jquery-ui:1.12.1') testImplementation 'org.springframework.boot:spring-boot-starter-test:2.5.2' testImplementation 'org.springframework.boot:spring-boot-test:2.5.2' testImplementation 'org.junit.vintage:junit-vintage-engine:5.7.2'
The Controller
The controller constructor gets a org.springframework.core.io.Resource
reference to the dropdownValues.yaml file and loads a map of the values using SnakeYaml.
private Map<String, LinkedHashMap> dropdownMap; public DropdownController(@Value("classpath:dropdownValues.yaml") Resource dropdownYaml) { InputStream input = null; try { input = dropdownYaml.getInputStream(); Yaml yaml = new Yaml(); dropdownMap = (Map<String, LinkedHashMap>) yaml.load(input); System.out.println("Dropdown Map: " + dropdownMap); } catch (IOException e) { e.printStackTrace(); } finally { if (input != null) { try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } }
The controller uses the org.springframework.web.bind.annotation.ModelAttribute
annotation to make the values used in the drop down available on the page.
For the Shape Types drop down, the getShapeTypes()
method prepares a Map with just the keys of the dropdownMap as both key and value. This value can be accessed on the Thymeleaf page like this ${shapeTypes}
.
@ModelAttribute("shapeTypes") public Map getShapeTypes() { Map shapeTypeMap = dropdownMap.keySet() .stream() .distinct() .sorted() .collect(Collectors.toMap(Function.identity(), Function.identity(), (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u));}, LinkedHashMap::new)); return shapeTypeMap; }
The getShapeSuggestionsJSON()
method uses Gson to create a JSON string out of the dropdownMap.
@ModelAttribute("shapeSuggestionsJSON") public String getShapeSuggestionsJSON() { String shapeSuggestions = ""; Gson gson = new Gson(); shapeSuggestions = gson.toJson(dropdownMap); System.out.println("Shape Suggestions: " + shapeSuggestions); return shapeSuggestions; }
The HTML
The first select contains the Shape Type and populates its values from the Map defined with @ModelAttribute("shapeTypes")
in the controller class. The second select has no options as it will be populated based on the selection made in the Shape Type dropdown. The “shapeSuggestVals” span contains the string defined as @ModelAttribute("shapeSuggestionsJSON")
.
<div> <select id="shapeType" th:field="*{shapeType}"> <option value="" selected="selected">N/A <option th:each="shapeOpt : ${shapeTypes.entrySet()}" th:value="${shapeOpt.key}" th:text="${shapeOpt.value}"></option> </select> <label for="shapeType">Shape Type</label> </div> <div> <select id="shape" th:field="*{shape}"></select> <label class="active" for="shape">Shape</label> <span id="shapeSuggestVals" class="suggestVals" th:text="${shapeSuggestionsJSON}"></span> </div>
In the jQuery/JavaScript, the JSON String is parsed into a variable using JavaScript’s built in function JSON.parse()
. The shapeSuggestions variable will then contain a map of maps, similar to the map created in the controller. The work is done in the onChange()
method of the Shape Type select. When the user changes the value of the Shape Type drop down, this method first saves the selected value in the variable called “type.” Using that value as the key gets the map of shapes from the shape suggestions map in this line var shapeMap = shapeSuggestions[type];
After acquiring the shapeMap, we loop through the map and build up a string of HTML to represent the options. Once we have our HTML built up, we call $("#shape").html(htmlOptions);
to replace any existing options on the Shape drop down. The final line $("#shape").trigger('contentChanged');
lets the page know that we’ve changed the content in the Shape drop down.
<script th:src="@{/webjars/jquery/3.2.0/jquery.js}"></script> <script th:src="@{/webjars/jquery-ui/1.12.1/jquery-ui.min.js}"></script> <script type="text/javascript"> $(function() { $(".suggestVals").hide(); var shapeSuggestions = JSON.parse($("#shapeSuggestVals").text()); console.log(shapeSuggestions); $("#shapeType").change(function(e) { var type = $("#shapeType").val(); console.log("ShapeType changed to " + type); var htmlOptions = ""; var shapeMap = shapeSuggestions[type]; console.log(shapeMap); $.each(shapeMap, function(shapeKey, shapeVal){ console.log("shape option: " + shapeKey + " " + shapeVal); htmlOptions = htmlOptions + "" + shapeVal +""; }); $("#shape").html(htmlOptions); $("#shape").trigger('contentChanged'); }); }); </script>
Conclusion
This is one way of handling dynamic drop down population in cases where you can’t or don’t want to use AJAX for one reason or another. This is how the finished example behaves.
The full example code is available on GitHub https://github.com/amdegregorio/DropdownPopulationExample
Nice example. The demo at the end is a nice touch
LikeLike
Thanks!
LikeLike
Doesn´t work
LikeLike
Hi Enzo – thanks for letting me know. I’ve updated the dependencies to use the latest of everything.
LikeLike