Skip to end of metadata
Go to start of metadata

This applies to Jenkins 1.402 and later

Introduction

In Jenkins, you can export arbitrary server-side objects to JavaScript via proxy. From inside JavaScript, you can invoke methods on the proxy object, which sends an HTTP request that eventually gets translated to the method call of the exported object. The return value from this Java method is sent back as an HTTP response, and JavaScript can receive this value through a callback method.

As such, this is an useful building block for AJAX.

How-To

To expose a method of a Java class to JavaScript proxy, annotate the method with @JavaScriptMethod. For security reasons, only public methods on public classes annotated with this annotation are invokable from browsers:

import org.kohsuke.stapler.bind.JavaScriptMethod;

public class Foo {
    @JavaScriptMethod
    public int add(int x, int y) {
        return x+y;
    }
}

Then from Jelly scripts, use <st:bind> tag to export a Java object into a proxy. The "value" attribute evaluates to a server-side Java object to be exported, and the tag produces a JavaScript expression that creates a proxy.

In the example below, we are pretending that the JEXL expression evaluates to some instance of Foo.

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
  ...

  <div id="msg" />
  <script>
    var foo = <st:bind value="${some.expression.thatEvaluatesTo().fooInstance}"/>

    foo.add(1,5, function(t) {
      document.getElementById('msg').innerHTML = t.responseObject();
    })
  </script>
  ...
</j:jelly>

Invoking method

As you can see above, one can invoke methods on the proxy created by the <bind> tag. The JavaScript method takes the arguments that the Java method takes, then it can optionally take a function as an additional parameter, which is used as a callback method when the return value is available. The callback method receives an Ajax.Response object.

If the Java method returns an object value (such as int, String, Collection, Object[], JSONObject, etc.), you can use the responseObject() method to evaluate the response into a JavaScript object and use it. If the Java method renders more complex HTTP response (for example by writing directly to StaplerResponse or returning an HttpResponse), JavaScript can use other Ajax.Response methods to access the full HTTP response.

The method call uses XmlHttpRequest underneath, and it gets eventually routed to the corresponding method call on the exact instance that was exported.

Tips

Parameters of the server Java method

The Java method can define arbitrary number of parameters for JavaScript. Each parameter is converted from JSON to Java via StaplerRequest.bindJSON, so aside from primitive Java data types and typeless JSONObject/JSONArray, you can use Stapler databinding to accept typed structured data.

After defining the parameters from JavaScript, you can additionally define parameters that are injectable by Stapler, such as StaplerRequest/StaplerResponse.

Exporting null

If the value attribute of a <bind> tag evaluates to null, then the corresponding JavaScript proxy will be null.

  • No labels

5 Comments

  1. Unknown User (hultee)

    Just sharing some hard-earned rookie knowledge =)

    >> var foo = <st:bind value="${some.expression.thatEvaluatesTo().fooInstance}"/>

    • ${it} refers to component where the jelly-script is defined
    • ${app.getPlugin('short-plugin-name')} is a reference to a plugin-instance
  2. Unknown User (icesaber)

    To get this working I used :

    var foo = <st:bind value="${instance}"/>
    

    I did not see the use of ${instance} in any of the tutorials.

  3. Unknown User (jglick)

    Works as advertised. One thing to watch out for: if the annotated Java method throws a RuntimeException, no stack trace will appear in the Jenkins logs; instead the responseObject will be a special object containing the stack trace. This can cause perplexing JavaScript errors when your script attempts to access normally defined fields of the response and they come up undefined. If in doubt, alert(t.responseObject()); and/or wrap the body of the Java method in try {...} catch (RuntimeException x) {Logger.getLogger(ThisClass.class.getName()).log(Level.WARNING, null, x);}.

  4. Unknown User (jglick)

    Beware that this does not work in some cases inside plugin-supplied page sections (those which are inserted via AJAX rather than during initial page load): https://issues.jenkins-ci.org/browse/JENKINS-15617

  5. Unknown User (mig82)

    We're getting issues attempting to use this feature because of CSRF protection. Getting

     403 No valid crumb was included in the request

    When we disable CSRF it works fine, but of course that is not an option. Any pointers on how to get around this? is there a way to include a crumb from the client-side?