How to get a reference to form elements in a Jenkins jelly script for a Builder configuration when the form loads?

坚强是说给别人听的谎言 提交于 2021-01-29 02:11:32

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!