Form validation in Action? doCheck of Descriptor is not called before form submission.

23 views
Skip to first unread message

Tamás Mucs

unread,
Mar 23, 2023, 12:25:11 PM3/23/23
to Jenkins Developers
Hi All,

Looking for the solution. Given class, implements RunAction2 and has a Descriptor.
In its index.jelly I  have a form which I want to validate. It's pretty much the same as this example except in my case the form is in an Action, not in a Builder.

index.jelly:
<?jelly escape-by-default='true'?>
<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:layout title="Mark this build as release">
        <l:main-panel>
            <st:contentType value="text/html;charset=UTF-8"/>
            <st:bind var="backingClass" value="${it}"/>
                <f:form action="submit" method="get" name="release-build-form">
                    <f:entry title="Comments" field="comments">
                        <f:textbox/>
                    </f:entry>
                    <f:entry>
                        <f:checkbox name="release" value="${it.isBuildMarkedAsReleaseBuild}" title="Is this build release?"/>
                    </f:entry>
                    <f:submit/>
                </f:form>
        </l:main-panel>
    </l:layout>
</j:jelly>

My problem is, doSubmit() is called before any validation would happen. I tried to add the following method to my descriptor but it is not called.

public FormValidation doCheckComments(@QueryParameter String comments) {
            if (comments == null) {
                return FormValidation.error("Comments is missing");
            }
            if (comments.isBlank()) {
                return FormValidation.error("Supply a non empty comment.");
            }
            return FormValidation.ok();
}

Any ideas?

Tamas

Jesse Glick

unread,
Mar 23, 2023, 4:33:36 PM3/23/23
to jenkin...@googlegroups.com
On Thu, Mar 23, 2023 at 12:25 PM Tamás Mucs <tamas....@gmail.com> wrote:
doSubmit() is called before any validation would happen. I tried to add the following method to my descriptor but it is not called

Form validation is advisory; it can display a warning message but does not block save. https://issues.jenkins.io/browse/JENKINS-19584

Tamás Mucs

unread,
Mar 24, 2023, 9:17:04 AM3/24/23
to Jenkins Developers
Ah, good point.

Anyhow, for reference, and for stumblers across this post, here is the solution that I came up with. Not sure if it has flaws, any comments are welcome.
Key points:
  • I partially utilized HTML5 client side validation.
  • I replaced the <f:input> with vanilla <input type="submit">, and the input form to be validated (comments) with plain <input> (important that I added the name="comments" which matches the field name and class="jenkins-input" so that it looks nice. Without the input type replacements the html5 validator does not kick in, therefore it is important.
  • In addition to the above, I added a "submit" event listener to the form (Vanilla JS). With the preventDefault() method call I prevented the event "bubbling up" and submitting the form. In the handler method I ran my own custom (client side) validator.
  • In addition, I run an additional server-side validator by utilizing the tooling called JavaScript proxy. (payload are the form fields. Parsing the JSON String is up to the server, use Jackson or whatever you like).
  • If the server side validator returns anything else than "OK", it is displayed as an error message, else the form is actually submitted.

<?jelly escape-by-default='true'?>
<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:layout title="${%title}">

        <l:main-panel>
        <st:contentType value="text/html;charset=UTF-8"/>
        <st:bind var="backingClass" value="${it}"/>
                <f:form action="submit" method="post" name="release-build-form" id="mark-as-release-form">
                    <f:entry field="release">
                        <f:checkbox checked="${it.releaseBuildMarkForRun != null}" title="${%chkIsReleaseBuild}"/>
                    </f:entry>
                    <f:entry title="${%inputComments}" field="comments">
                        <input class="jenkins-input" name="comments" minlength="${it.commentsLengthMin}" maxlength="${it.commentsLengthMax}" required="true" />
                    </f:entry>
                    <input type="submit" value="${%btnSubmit}"/>
                </f:form>
            <script>
                const handleFormSubmit = (event) => {
                    event.preventDefault();
                    const form = event.target;

                    console.log("Form: " + form);

                    const payload = {};
                    const formData = new FormData(form);
                    formData.forEach(function(value, key){
                        payload[key] = value;
                    });

                    backingClass.validateFormDataJS(JSON.stringify(payload), (t) => {
                        const responseMessage = t.responseObject();
                        if (responseMessage == "${it.JS_VALIDATOR_RESPONSE_OK}") {
                            form.submit();
                        } else {
                            notificationBar.show(responseMessage, notificationBar.ERROR);
                        }
                    });
                }

                const form = document.getElementById('mark-as-release-form');
                form.addEventListener('submit', handleFormSubmit);
            </script>

        </l:main-panel>
    </l:layout>
</j:jelly>

And in the Action:
@JavaScriptMethod
public String validateFormDataJS(String json) {
        // Parse json, return error message if that's the case.
        return JS_VALIDATOR_RESPONSE_OK;
}

Tamas
Reply all
Reply to author
Forward
0 new messages