问题
I am writing a Jenkins Builder, and in the jelly script for its configuration in the build configuration page I have some Javascript that I want to run when the form is loaded, to do a server lookup and get some information to help the user out with their configuration, which will also be performed when the user changes the form's values.
Previously I have got references to the form elements by passing this
in to functions in onchange
or onkeyup
attributes. However, now I want to run some script even when the form hasn't changed.
I know I could set ID attributes on the form elements, however that's not going to work if the users add to a build two build steps both using this builder.
I've tried generating a random ID on my builder class, and then use that to construct IDs for the elements and write that into some Javascript in the jelly file so I can find those elements there, but that doesn't get initialised until the user saves, so it won't work if the user adds two instances of this builder without saving the job:
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="Entry 1">
<f:textbox field="field1" id="${instance.id}-field1" onchange="fieldChanged('${instance.id}-field1')"/>
</f:entry>
<script type="text/javascript">
function fieldChanged(elementId) {
...
}
fieldChanged('${instance.id}-field1');
</script>
</j:jelly>
Are there any conventions on how to do this sort of thing? Anything built in to Jenkins/jelly to support multiple instances of the same jelly file being able to refer to their own elements?
回答1:
There's a solution using j:set
which is simpler than my other answer.
com.example.MyBuilder.DescriptorImpl:
private int lastEditorId = 0;
...
@JavaScriptMethod
public synchronized String createEditorId() {
return String.valueOf(lastEditorId++);
}
com/example/MyBuilder/config.jelly:
...
<j:set var="editorId" value="${descriptor.createEditorId()}" />
<f:entry title="Field">
<f:textbox field="field" id="field-${editorId}"/>
<p id="message-${editorId}"></p>
</f:entry>
<script>
setTimeout(function(){
var field = document.getElementById('field-${editorId}');
var p = document.getElementById('message-${editorId}');
p.textContent = "Initial value: "+field.value;
}, 50);
</script>
(The call to setTimeout
is still due to the fact that when adding new build steps, the elements haven't been added to the DOM by the time that the script executes, so the script execution has to be deferred slightly).
回答2:
It looks like this solution below might work, but I haven't got very far with it yet.
In my builder class I've added an inner class called Editor:
com.example.MyBuilder(.Editor):
...
public static class Editor {
private final String id;
public Editor(final String id) {
this.id = id;
}
public String getId() {
return id;
}
}
...
Then in the descriptor Java class, provide a JavaScript function to create one of these with a unique ID:
com.example.MyBuilder.DescriptorImpl:
private int lastEditorId = 0;
@JavaScriptMethod
public synchronized Editor createEditor() {
return new Editor(String.valueOf(lastEditorId++));
}
Then in my jelly file I call that method and pass the returned object into st:include
, loading a new jelly file to render the fields:
com/example/MyBuilder/config.jelly:
<st:include page="editor.jelly" it="${descriptor.createEditor()}" />
(Although this appears to have to be inside an f:entry
element - or perhaps other elements, I haven't tried - otherwise it doesn't seem to get included when a new build step for this builder is added to the job config.)
And finally I create that new editor.jelly
file to render the fields (which has to be in a folder whose name reflects the Editor
class, as the it
object being passed into st:include
is of type Editor
):
com/example/MyBuilder/Editor/editor.jelly:
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:ajax>
<f:entry title="Field">
<f:textbox field="field" id="field-${it.id}"/>
<p id="message-${it.id}"></p>
</f:entry>
<script>
setTimeout(function(){
var field = document.getElementById('field-${it.id}');
var p = document.getElementById('message-${it.id}');
p.textContent = "Initial value: "+field.value;
}, 50);
</script>
</l:ajax>
</j:jelly>
(The call to setTimeout
is due to the fact that when adding new build steps, the elements haven't been added to the DOM by the time that the script executes, so the script execution has to be deferred slightly).
However, this breaks the link between the f:entry
elements and the equivalent fields in the builder class, and I'm not sure what to do about that. So this is an incomplete answer.
EDIT: I'm not sure if the f:entry
elements would have worked or not, as I had forgotten to add the field to the builder class when I was testing it, which was (at least one reason) why I did not see any data saved from this field when I tried this. However, I am now using the solution from my other answer, so I have not gone back to test whether it would have worked or not.
来源:https://stackoverflow.com/questions/28834365/how-to-get-a-reference-to-form-elements-in-a-jenkins-jelly-script-for-a-builder