How to use optionalBlock in build step's config.jelly
hudson, java, jelly, jenkins
Solution
The hint comes from the JSON data:
{
"enableText":{"text":"xx"},
"kind":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder",
"stapler-class":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder"
}
You can see here that `enableText` contains a child property, `text`. That means that the `f:optionalBlock` is actually expecting an encapsulation of all the fields contained within the block -- when the block is checked, you will receive an instance of the encapsulation field class; when it is unchecked, that field will be `null`. To use the `optionalBlock` properly, you would need the `@DataBoundConstructor` to take in a single nullable class instance that encapsulates the entire `optionalBlock`. For example:
private String text;
@DataBoundConstructor
public MyClass(EnableTextBlock enableText)
{
if (enableText != null)
{
this.text = enableText.text;
}
}
public static class EnableTextBlock
{
private String text;
@DataBoundConstructor
public EnableTextBlock(String text)
{
this.text = text;
}
}
Notice that the `enableText` field in this case is actually an instance of `EnableTextBlock` class, which contains a child property, `text`. That will satisfy the JSON object that is being sent in the form.
Instead, if all you need is a single field that has a checkbox to enable entry of that field, you might want to consider instead using the `f:optionalProperty` tag, which will take care of that single-field encapsulation for you. However, in many cases, the `optionalBlock` is actually needed to configure multiple fields, in which case the encapsulation class--as exampled above--is usually the correct way to go.
The encapsulation class does not have to be a static inner class; it could be a separate class within your package, but the important part is that the `DataBoundConstructor` should take in an argument that matches the JSON structure being passed from the form.
Problem
I have problem with creating constructor, which Jenkins can call for some JSON data originating from a Jelly form,. For testing, I created a minimal Jenkins plugin with `mvn hpi:create` and following two custom files: src/main/resources/foo/hyde/jenkins/plugins/OptionalBlockSampleBuilder/config.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"> <f:block> <table> <f:optionalBlock name="enableText" title="Enable optional text" checked="${instance.enableText}"> <f:entry title="Optional text" field="text"> <f:textbox /> </f:entry> </f:optionalBlock> </table> </f:block> ``` src/main/java/foo/hyde/jenkins/plugins/OptionalBlockSampleBuilder.java ``` package foo.hyde.jenkins.plugins; public class OptionalBlockSampleBuilder extends hudson.tasks.Builder { public final String text; public final boolean enableText; @org.kohsuke.stapler.DataBoundConstructor public OptionalBlockSampleBuilder(String text, Boolean enableText) { this.text = text; this.enableText = (enableText != null) && enableText; } @Override public boolean perform(hudson.model.AbstractBuild build, hudson.Launcher launcher, hudson.model.BuildListener listener) { listener.getLogger().println("OptionalBlockSampleBuilder " + enableText + "/" + text); return true; } @hudson.Extension public static final class DescriptorImpl extends hudson.tasks.BuildStepDescriptor<hudson.tasks.Builder> { public boolean isApplicable(Class<? extends hudson.model.AbstractProject> aClass) { return true; } public String getDisplayName() { return "Optional Block Sample"; } } } ``` I'm building against pom.xml parent `<groupId>org.jenkins-ci.plugins</groupId><artifactId>plugin</artifactId><version>1.454</version>`, and everything builds, Netbeans 6.9.1 launches Debug Jenkins and I get to create a job with this build step. Everything works if I don't check that checkbox, and I get expected `OptionalBlockSampleBuilder false/null` to job's console output. But if I do check the checkbox and add text, then saving/applying the job config gives this exception from the depths of Jenkins code, when it tries to call my constructor: ``` java.lang.RuntimeException: Failed to instantiate class foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder from { "enableText":{"text":"xx"}, "kind":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder", "stapler-class":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder" } ``` There has to be a simple fix. I have tried many different changes, and also tried to see how other plugins use it, and finally created this minimal test plugin. How to fix it to make optionalBlock work?