Allow JavaScript plugins to manage their own action RPCs
This is the first step in building a public JavaScript API that
can be utilized by plugins to manage interaction with the web UI.
This commit makes the following plugin JavaScript possible:
Gerrit.install(function(self) {
function onCallMe(c) {
var f = c.textfield();
var t = c.checkbox();
var b = c.button('Phone', {onclick: function(){
c.call(
{message: f.value, dial: t.checked},
function(r) {
c.hide();
window.alert("Said " + r);
c.refresh();
});
}});
c.popup(c.div(
f, c.br(),
c.label(t, 'Dial'), c.br(),
b));
f.focus();
}
self.onAction('revision', 'callme', onCallMe);
});
JavaScript plugins are encouraged to protect the global namespace
by running Gerrit.install(f), passing an anonymous function Gerrit
can call to initialize the plugin.
Gerrit.install() performs some black magic to identify which plugin
the JavaScript is running from, exporting as Gerrit.getPluginName().
Obtaining the name is faster within the install() invocation as the
string is cached in a global variable.
A plugin's install function is given a reference to the plugin
JavaScript object. This object has many helper methods to support
communication with the server.
Gerrit.onAction() accepts the type of view and the RestView name
as defined in the plugin and a JavaScript function to execute when
the user has activated the action button.
An action callback is given a context object declaring a number of
useful properties:
change: The ChangeInfo object.
revision: The RevisionInfo object.
action: The ActionInfo object that invoked this callback.
popup(e): Display a popup containing element e.
hide(): Hide the popup that was created by popup.
call(in,cb): Executes the RPC using action.method.
get(cb): Executes the RPC using GET.
post(in,cb): Executes the RPC using POST.
put(in,cb): Executes the RPC using PUT.
delete(cb): Executes the RPC using DELETE.
HTML: br, hr, button, checkbox, div, label, span, textfield
These make it easier to construct a tiny UI for use in a
popup, such as gathering a small amount if input from the
user to supply to the RPC.
go(token): Navigate the web UI to the application URL.
refresh(): Redisplay the current web UI view.
Any result data from the server is parsed as JSON and passed as-is
to the callback function. A JSON string result is given as a string,
a JSON object or array is passed as-is.
A corresponding REST API view on revisions can be declared:
class Module extends RestApiModule {
@Override
protected void configure() {
post(RevisionResource.REVISION_KIND, "callme").to(CallMe.class);
}
}
class CallMe implements
RestModifyView<RevisionResource, CallMe.Input>,
UiAction<RevisionResource> {
static class Input {
String message;
}
@Override
public String apply(RevisionResource resource, Input input) {
return "You said: " + input.message;
}
@Override
public UiAction.Description getDescription(RevisionResource resource) {
return new UiAction.Description().setLabel("Call Me");
}
}
Currently the JavaScript code must be registered at the server side
to appear in the host page download:
class Web extends AbstractModule {
@Override
protected void configure() {
DynamicSet.bind(binder(), WebUiPlugin.class)
.toInstance(new JavaScriptPlugin("maybe.js"));
}
I find this binding method for JavaScript code is awkard and would
like to improve on it in the future.
Finally both modules must be registered in the plugin manifest:
gerrit_plugin(
name = 'actiondemo',
srcs = glob(['src/main/java/**/*.java']),
resources = glob(['src/main/resources/**/*']),
manifest_entries = [
"Gerrit-Module: com.googlesource.gerrit.plugins.actiondemo.Module",
"Gerrit-HttpModule: com.googlesource.gerrit.plugins.actiondemo.Web",
],
)
Change-Id: I2d572279ac978e644772b1cedbecc080746a2306
16 files changed