Skip to content

Commit b752f7a

Browse files
committed
Merge branch 'develop'
2 parents d0d5f48 + 69e3383 commit b752f7a

File tree

4 files changed

+138
-78
lines changed

4 files changed

+138
-78
lines changed

src/main/java/de/westemeyer/plugins/multiselect/MultiselectParameterDefinition.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public MultiselectParameterDefinition(String name, String description, @CheckFor
8484
* @param coordinates coordinates in tree, i.e. item indices from columns
8585
* @return array of parameter values for given coordinates
8686
*/
87-
@JavaScriptMethod
87+
@JavaScriptMethod(name = "getItemList")
8888
public String[] getItemList(Integer[] coordinates) {
8989
Queue<Integer> itemPath = createCoordinates(coordinates);
9090
List<String> returnList = new ArrayList<>();
@@ -100,6 +100,30 @@ public String[] getItemList(Integer[] coordinates) {
100100
return returnList.toArray(new String[0]);
101101
}
102102

103+
/**
104+
* Method used by JavaScript code to get all combo box ids that depend on content of the given combo box ID.
105+
* @param selectedId combo box ID
106+
* @return all combo box ids that depend on content of the given combo box ID
107+
*/
108+
@JavaScriptMethod(name = "getDependingVariableIds")
109+
public String[] getDependingVariableIds(String selectedId) {
110+
if (decisionTree == null) {
111+
return new String[0];
112+
}
113+
boolean found = false;
114+
List<String> result = new ArrayList<>();
115+
for (MultiselectVariableDescriptor variableDescription : decisionTree.getVariableDescriptions()) {
116+
if (found) {
117+
result.add(variableDescription.getUuid());
118+
}
119+
if (variableDescription.getUuid().equals(selectedId)) {
120+
found = true;
121+
}
122+
}
123+
124+
return result.toArray(new String[0]);
125+
}
126+
103127
/**
104128
* Create coordinates queue from integer array.
105129
* @param coordinates integer array
@@ -141,7 +165,7 @@ public MultiselectParameterValue createValue(Map<String, Object> jsonObject) {
141165
// convert json object to map of strings to integers (values from parameter form)
142166
jsonObject.forEach((key, value) -> {
143167
// exclude parameter name
144-
if (!key.equals(PARAMETER_NAME) && value instanceof String && ((String) value).length() > 0) {
168+
if (!key.equals(PARAMETER_NAME) && value instanceof String && !((String) value).isEmpty()) {
145169
try {
146170
// store new key combination in map
147171
selectedValues.put(key, Integer.valueOf((String) value));

src/main/resources/de/westemeyer/plugins/multiselect/MultiselectParameterDefinition/index.jelly

Lines changed: 12 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,28 @@
11
<?jelly escape-by-default='true'?>
22
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:f="/lib/form">
33

4-
<j:set var="decisionTree" value="${it.decisionTree}"/>
5-
6-
<script>
7-
// bind java variable
8-
var parameterDefinition${it.uuid} = <st:bind value="${it}"/>;
9-
10-
function fillInValues${it.uuid}(coordinates, selectBoxId) {
11-
// get select list with given id
12-
var select = document.getElementById(selectBoxId);
13-
14-
// get item list from java code, invoke anonymous function to fill received values into select list
15-
parameterDefinition${it.uuid}.getItemList(coordinates, function(t) {
16-
// get response values from "getItemList" call into javascript array
17-
var options = t.responseObject();
18-
19-
// clear select list
20-
select.options.length = 0;
21-
22-
// iterate all options
23-
for(var i = 0; i &lt; options.length; i++) {
24-
// create new HTML option element as child of select box HTML item
25-
var el = document.createElement("option");
26-
27-
// set option name as visible text
28-
el.textContent = options[i];
4+
<!-- include index.js file -->
5+
<st:adjunct includes="de.westemeyer.plugins.multiselect.MultiselectParameterDefinition.selects"/>
296

30-
// set option index as value
31-
el.value = i;
32-
33-
// append element
34-
select.appendChild(el);
35-
}
36-
37-
var functionName = selectBoxId.concat("Changed");
38-
if (select.options.length > 0) {
39-
window[functionName](select);
40-
}
41-
});
42-
}
43-
44-
<j:set var="variableDescriptions" value="${decisionTree.variableDescriptions}"/>
45-
46-
// list of successors, meaning: there is a variable for each of the selectboxes, containing the ID of its successor (box to the right)
47-
<j:set var="last" value=""/>
48-
<j:forEach var="variable" items="${variableDescriptions}" indexVar="i">
49-
<j:if test="${i gt 0}">
50-
var ${last} = "${variable.uuid}";
51-
</j:if>
52-
<j:set var="last" value="${variable.uuid}"/>
53-
54-
</j:forEach>
7+
<j:set var="decisionTree" value="${it.decisionTree}"/>
558

56-
<j:forEach var="variable" items="${variableDescriptions}" indexVar="i">
57-
function ${variable.uuid}Changed(select)
58-
{
59-
<j:if test="${i ne (variableDescriptions.size() - 1)}">
60-
var coordinatesBuffer = new Array();
61-
var e = null;
62-
<j:forEach var="listItem" items="${variableDescriptions}" indexVar="j">
63-
<j:if test="${j lt (i + 1)}">
64-
// check value of list box number ${j}
65-
e = document.getElementById("${listItem.uuid}");
66-
coordinatesBuffer.push(e.options[e.selectedIndex].value);
67-
<j:if test="${j eq i}">
68-
fillInValues${it.uuid}(coordinatesBuffer, ${listItem.uuid});
69-
</j:if>
70-
</j:if>
71-
</j:forEach>
72-
</j:if>
73-
}
74-
</j:forEach>
75-
</script>
9+
<!-- Bind a variable for each configured multiselect parameter. There could be more than one per job, that's why the UUID has to be part of the name. -->
10+
<st:bind var="multiselectParameterDefinition${it.uuid}" value="${it}"/>
7611

7712
<j:set var="escapeEntryTitleAndDescription" value="false"/>
7813
<f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
7914
<div name="parameter" description="${it.description}">
80-
<input type="hidden" name="name" value="${it.name}" />
81-
<ol id="${it.name} MultiLevelSelections">
82-
<li id="${it.name} dropdowns for MultiLevelMultiSelect 0"
83-
class="${it.name} select div" style="display:inline">
15+
<input type="hidden" name="name" value="${it.name}"/>
16+
17+
<ol id="MultiLevelSelections${it.uuid}">
18+
<li id="dropdownsForMultiLevelMultiSelect${it.uuid}"
19+
class="${it.name} select div" style="display:inline" data-select-form="${it.uuid}">
8420
<j:set var="i" value="0"/>
8521
<table>
8622
<tr>
8723
<j:forEach var="dropdown" items="${decisionTree.variableDescriptions}">
88-
<td>${dropdown.label}<br/>
89-
<select name="${dropdown.variableName}" onchange="${dropdown.uuid}Changed(this, '${it.name}')"
24+
<td><label for="${dropdown.uuid}">${dropdown.label}</label><br/>
25+
<select data-select="${it.uuid}${dropdown.uuid}" data-codeclass="multiselectParameterDefinition${it.uuid}" name="${dropdown.variableName}"
9026
id="${dropdown.uuid}" style="display:inline">
9127
<j:set var="j" value="0"/>
9228
<j:forEach var="choice" items="${dropdown.initialValues}">
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Get the java-class behind the code
2+
const getJavaBehindTheCode = (element) => {
3+
const javaClassNameInstance = element.dataset.codeclass;
4+
if (javaClassNameInstance !== undefined) return window[javaClassNameInstance];
5+
return null;
6+
}
7+
8+
const fillInValues = (name, coordinates, selectBoxId) => ((selectElement) =>
9+
// get item list from java code, invoke anonymous function to fill received values into select list
10+
getJavaBehindTheCode(selectElement).getItemList(coordinates, (response) => {
11+
// get response values from "getItemList" call into javascript array
12+
const options = response.responseObject();
13+
console.log("getItemList Response: %o", options);
14+
15+
// clear select list
16+
selectElement.options.length = 0;
17+
18+
// iterate all options
19+
options.forEach((option, index, collection) => {
20+
const newOption = document.createElement("option");
21+
newOption.textContent = option;
22+
newOption.value = index;
23+
selectElement.appendChild(newOption);
24+
});
25+
26+
if (selectElement.options.length > 0) {
27+
// invoke comboBoxValueChanged method, which will in turn call
28+
// fillInValues with the successor combo box
29+
comboBoxValueChanged(name, selectElement);
30+
}
31+
})
32+
)(document.getElementById(selectBoxId));
33+
34+
// Get all elements in the group until itself and the next element
35+
const getDependingVariableIds = (name, htmlElement) => {
36+
console.log("getDependingVariableIds for name %o", name);
37+
// get all elements that contain the data-select which starts with the name (this ensures that we only process the selects that are bound together)
38+
// receive element and the id of the element
39+
const selectsWithIds = [...document.querySelectorAll(`[data-select^='${name}']`)].map(e => ({element: e, id: e.id}));
40+
// the next element because we need it for the fillInValues
41+
const lastElementIndex = selectsWithIds.map(element => element.id).indexOf(htmlElement.id);
42+
// slice removes all elements after the wanted
43+
return {
44+
elements: selectsWithIds.slice(0, lastElementIndex + 1),
45+
nextId: selectsWithIds[lastElementIndex + 1]?.element
46+
};
47+
}
48+
49+
const comboBoxValueChanged = (name, element) => {
50+
console.log("Change detected for %o: %o", name, element);
51+
const information = getDependingVariableIds(name, element);
52+
// since we need only the selected values we collect them from the elements
53+
const coordinates = information.elements.map(item => item.element.options[item.element.selectedIndex].value);
54+
55+
// if we have the nextId we are not at the end
56+
if (information.nextId) {
57+
fillInValues(name, coordinates, information.nextId.id);
58+
}
59+
}
60+
61+
Behaviour.specify(".select.div", 'select', 0, (listElement) => {
62+
// if empty element (jenkins creates an entry with no content!?) do not add listener
63+
if (listElement.innerText === '') return;
64+
65+
// receive the name of the parameter-group
66+
const name = listElement.dataset.selectForm;
67+
68+
// in this list group add listener to all selects
69+
listElement.querySelectorAll('select').forEach(selectElement => {
70+
console.log("Add listener for all selects that contains name %o", name);
71+
selectElement.addEventListener('change', (event) => {
72+
comboBoxValueChanged(name, event.currentTarget);
73+
});
74+
});
75+
});

src/test/java/de/westemeyer/plugins/multiselect/MultiselectParameterDefinitionTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
import hudson.util.FormValidation;
77
import net.sf.json.JSONObject;
88
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.params.ParameterizedTest;
10+
import org.junit.jupiter.params.provider.CsvSource;
911
import org.kohsuke.stapler.StaplerRequest;
1012

1113
import java.io.IOException;
1214
import java.util.Collections;
15+
import java.util.List;
1316
import java.util.Queue;
17+
import java.util.stream.Collectors;
1418

1519
import static de.westemeyer.plugins.multiselect.MultiselectConfigurationFormat.CSV;
1620
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
@@ -80,6 +84,27 @@ void getItemList() {
8084
assertEquals(0, itemList.length);
8185
}
8286

87+
@ParameterizedTest(name = "Depending IDs for {0}")
88+
@CsvSource({"first item,0,3", "second item,1,2", "third item,2,1", "fourth item,3,0"})
89+
void getDependingVariableIds(String name, int index, int resultLength) {
90+
MultiselectParameterDefinition definition = new MultiselectParameterDefinition(NAME, DESCRIPTION, INPUT, CSV);
91+
MultiselectDecisionTree decisionTree = definition.getDecisionTree();
92+
assertNotNull(decisionTree);
93+
List<String> idList = decisionTree.getVariableDescriptions().stream().map(MultiselectVariableDescriptor::getUuid).collect(Collectors.toList());
94+
95+
String[] dependingVariableIds = definition.getDependingVariableIds(idList.get(index));
96+
assertEquals(resultLength, dependingVariableIds.length);
97+
}
98+
99+
@Test
100+
void getDependingVariableIdsWithoutDecisionTree() {
101+
MultiselectParameterDefinition definition = new MultiselectParameterDefinition(NAME, DESCRIPTION);
102+
MultiselectDecisionTree decisionTree = definition.getDecisionTree();
103+
assertNull(decisionTree);
104+
String[] dependingVariableIds = definition.getDependingVariableIds("anyString");
105+
assertEquals(0, dependingVariableIds.length);
106+
}
107+
83108
@Test
84109
void getDefaultParameterValue() {
85110
MultiselectParameterDefinition definition = new MultiselectParameterDefinition(NAME, DESCRIPTION, INPUT, CSV);

0 commit comments

Comments
 (0)