blob: dc7986f9b90c4303f0ca208f75cb061b8c8371ca [file] [log] [blame]
Marian Harbachebeb1542019-12-13 10:42:46 +01001:linkattrs:
Ben Rohlfsda0a62b2021-04-26 17:02:19 +02002= Gerrit Code Review - JavaScript Plugin Development and API
Viktar Donich2b2fdb72017-03-13 16:21:44 -07003
Ben Rohlfsda0a62b2021-04-26 17:02:19 +02004Gerrit Code Review supports an API for JavaScript plugins to interact
5with the web UI and the server process.
Viktar Donich15ef24c2017-12-07 16:04:38 -08006
Viktar Donich9bd0ce62017-02-07 08:56:33 -08007[[loading]]
Viktar Donich2b2fdb72017-03-13 16:21:44 -07008== Plugin loading and initialization
9
Ben Rohlfsda0a62b2021-04-26 17:02:19 +020010JavaScript is loaded using a standard `<script src='...'>` HTML tag.
11Plugins should protect the global namespace by defining their code
12within an anonymous function passed to `Gerrit.install()`. The plugin
13will be passed an object describing its registration with Gerrit.
Viktar Donich2b2fdb72017-03-13 16:21:44 -070014
Tao Zhoud2a8ded2020-06-05 16:08:54 +020015* The plugin provides pluginname.js, and can be a standalone file or a static
Viktar Donich5055e8d2017-11-09 13:02:42 -080016 asset in a jar as a link:dev-plugins.html#deployment[Web UI plugin].
Tao Zhoud2a8ded2020-06-05 16:08:54 +020017* pluginname.js contains a call to `Gerrit.install()`. There should
Ben Rohlfsda0a62b2021-04-26 17:02:19 +020018 only be a single `Gerrit.install()` call per file.
19* The Gerrit web app imports pluginname.js.
Tao Zhoud2a8ded2020-06-05 16:08:54 +020020* For standalone plugins, the entry point file is a `pluginname.js` file
Viktar Donichcbc25672017-07-24 13:23:18 -070021 located in `gerrit-site/plugins` folder, where `pluginname` is an alphanumeric
22 plugin name.
Viktar Donich2b2fdb72017-03-13 16:21:44 -070023
Ben Rohlfs233d86e2021-04-29 17:08:23 +020024=== Examples
Tao Zhoud2a8ded2020-06-05 16:08:54 +020025Here's a recommended starter `myplugin.js`:
Viktar Donich2b2fdb72017-03-13 16:21:44 -070026
Tao Zhoud2a8ded2020-06-05 16:08:54 +020027``` js
28Gerrit.install(plugin => {
Tao Zhoud2a8ded2020-06-05 16:08:54 +020029 // Your code here.
30});
Viktar Donich2b2fdb72017-03-13 16:21:44 -070031```
Viktar Donichcbc25672017-07-24 13:23:18 -070032
Ben Rohlfs233d86e2021-04-29 17:08:23 +020033You can find more elaborate examples in the
34link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/samples/[polygerrit-ui/app/samples/]
35directory of the source tree.
36
Viktar Donich7610f782017-10-02 11:51:41 +010037[[low-level-api-concepts]]
38== Low-level DOM API concepts
Viktar Donichcbc25672017-07-24 13:23:18 -070039
40Basically, the DOM is the API surface. Low-level API provides methods for
41decorating, replacing, and styling DOM elements exposed through a set of
Viktar Donich6d10eca2017-11-13 17:57:43 -080042link:pg-plugin-endpoints.html[endpoints].
Viktar Donichcbc25672017-07-24 13:23:18 -070043
Ben Rohlfsda0a62b2021-04-26 17:02:19 +020044Gerrit provides a simple way for accessing the DOM via DOM hooks API. A DOM
Viktar Donichcbc25672017-07-24 13:23:18 -070045hook is a custom element that is instantiated for the plugin endpoint. In the
46decoration case, a hook is set with a `content` attribute that points to the DOM
47element.
48
Viktar Donichab491f772017-08-15 08:02:58 -0700491. Get the DOM hook API instance via `plugin.hook(endpointName)`
Viktar Donichcbc25672017-07-24 13:23:18 -0700502. Set up an `onAttached` callback
513. Callback is called when the hook element is created and inserted into DOM
524. Use element.content to get UI element
53
54``` js
Viktar Donich5055e8d2017-11-09 13:02:42 -080055Gerrit.install(plugin => {
Viktar Donichab491f772017-08-15 08:02:58 -070056 const domHook = plugin.hook('reply-text');
Viktar Donichcbc25672017-07-24 13:23:18 -070057 domHook.onAttached(element => {
58 if (!element.content) { return; }
59 // element.content is a reply dialog text area.
60 });
61});
62```
63
64[[low-level-decorating]]
65=== Decorating DOM Elements
66
Ben Rohlfsda0a62b2021-04-26 17:02:19 +020067For each endpoint, Gerrit provides a list of DOM properties (such as
Viktar Donichcbc25672017-07-24 13:23:18 -070068attributes and events) that are supported in the long-term.
69
Viktar Donichcbc25672017-07-24 13:23:18 -070070``` js
Viktar Donich5055e8d2017-11-09 13:02:42 -080071Gerrit.install(plugin => {
Viktar Donichab491f772017-08-15 08:02:58 -070072 const domHook = plugin.hook('reply-text');
Viktar Donichcbc25672017-07-24 13:23:18 -070073 domHook.onAttached(element => {
74 if (!element.content) { return; }
75 element.content.style.border = '1px red dashed';
76 });
77});
78```
79
80[[low-level-replacing]]
81=== Replacing DOM Elements
82
83An endpoint's contents can be replaced by passing the replace attribute as an
84option.
85
86``` js
Viktar Donich5055e8d2017-11-09 13:02:42 -080087Gerrit.install(plugin => {
Viktar Donichab491f772017-08-15 08:02:58 -070088 const domHook = plugin.hook('header-title', {replace: true});
Viktar Donichcbc25672017-07-24 13:23:18 -070089 domHook.onAttached(element => {
90 element.appendChild(document.createElement('my-site-header'));
91 });
92});
93```
94
95[[low-level-style]]
96=== Styling DOM Elements
97
98A plugin may provide Polymer's
Tao Zhoud2a8ded2020-06-05 16:08:54 +020099https://polymer-library.polymer-project.org/3.0/docs/devguide/style-shadow-dom[style
Marian Harbach34253372019-12-10 18:01:31 +0100100modules,role=external,window=_blank] to style individual endpoints using
Viktar Donichcbc25672017-07-24 13:23:18 -0700101`plugin.registerStyleModule(endpointName, moduleName)`. A style must be defined
Tao Zhoud2a8ded2020-06-05 16:08:54 +0200102as a standalone `<dom-module>` defined in the same .js file.
103
Ben Rohlfs233d86e2021-04-29 17:08:23 +0200104See
105link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/samples/theme-plugin.js[samples/theme-plugin.js]
106for an example.
Viktar Donichcbc25672017-07-24 13:23:18 -0700107
Tao Zhoud2a8ded2020-06-05 16:08:54 +0200108``` js
109const styleElement = document.createElement('dom-module');
110styleElement.innerHTML =
111 `<template>
112 <style>
Viktar Donichcbc25672017-07-24 13:23:18 -0700113 html {
Tao Zhoud2a8ded2020-06-05 16:08:54 +0200114 --primary-text-color: red;
Viktar Donichcbc25672017-07-24 13:23:18 -0700115 }
Tao Zhoud2a8ded2020-06-05 16:08:54 +0200116 </style>
117 </template>`;
118
119styleElement.register('some-style-module');
120
121Gerrit.install(plugin => {
122 plugin.registerStyleModule('change-metadata', 'some-style-module');
123});
Viktar Donichcbc25672017-07-24 13:23:18 -0700124```
Viktar Donich7610f782017-10-02 11:51:41 +0100125
126[[high-level-api-concepts]]
127== High-level DOM API concepts
128
Quinten Yearsley888e6382017-12-05 11:11:09 -0800129High level API is based on low-level DOM API and is essentially a standardized
Viktar Donich7610f782017-10-02 11:51:41 +0100130way for doing common tasks. It's less flexible, but will be a bit more stable.
131
Quinten Yearsley888e6382017-12-05 11:11:09 -0800132The common way to access high-level API is through `plugin` instance passed
133into setup callback parameter of `Gerrit.install()`, also sometimes referred to
134as `self`.
Viktar Donich7610f782017-10-02 11:51:41 +0100135
136[[low-level-api]]
137== Low-level DOM API
138
Quinten Yearsley888e6382017-12-05 11:11:09 -0800139The low-level DOM API methods are the base of all UI customization.
Viktar Donich7610f782017-10-02 11:51:41 +0100140
141=== attributeHelper
142`plugin.attributeHelper(element)`
143
Viktar Donich6e1e69b2018-01-22 14:08:21 -0800144Alternative for
Tao Zhoud2a8ded2020-06-05 16:08:54 +0200145link:https://polymer-library.polymer-project.org/3.0/docs/devguide/data-binding[Polymer data
Marian Harbach34253372019-12-10 18:01:31 +0100146binding,role=external,window=_blank] for plugins that don't use Polymer. Can be used to bind element
Viktar Donich6e1e69b2018-01-22 14:08:21 -0800147attribute changes to callbacks.
148
Ben Rohlfs233d86e2021-04-29 17:08:23 +0200149See
150link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/samples/bind-parameters.js[samples/bind-parameters.js]
151for an example.
Viktar Donich7610f782017-10-02 11:51:41 +0100152
Viktar Donich7610f782017-10-02 11:51:41 +0100153=== hook
154`plugin.hook(endpointName, opt_options)`
155
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200156See link:pg-plugin-endpoints.html[endpoints].
Viktar Donich7610f782017-10-02 11:51:41 +0100157
158=== registerCustomComponent
159`plugin.registerCustomComponent(endpointName, opt_moduleName, opt_options)`
160
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200161See link:pg-plugin-endpoints.html[endpoints].
Viktar Donich7610f782017-10-02 11:51:41 +0100162
Thomas Shafer03275862019-02-26 15:39:16 -0800163=== registerDynamicCustomComponent
164`plugin.registerDynamicCustomComponent(dynamicEndpointName, opt_moduleName,
165opt_options)`
166
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200167See link:pg-plugin-endpoints.html[endpoints].
Thomas Shafer03275862019-02-26 15:39:16 -0800168
Viktar Donich7610f782017-10-02 11:51:41 +0100169=== registerStyleModule
170`plugin.registerStyleModule(endpointName, moduleName)`
171
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200172See link:#low-level-style[above].
173
174=== on
175Register a JavaScript callback to be invoked when events occur within
176the web interface. Signature
177
178``` js
179self.on(event, callback);
180```
181
182Parameters
183
184* event: A supported event type. See below for description.
185
186* callback: JavaScript function to be invoked when event happens.
187 Arguments may be passed to this function, depending on the event.
188
189Supported events:
190
191* `history`: Invoked when the view is changed to a new screen within
192 the Gerrit web application. The token after "#" is passed as the
193 argument to the callback function, for example "/c/42/" while
194 showing change 42.
195
196* `showchange`: Invoked when a change is made visible. A
197 link:rest-api-changes.html#change-info[ChangeInfo] and
198 link:rest-api-changes.html#revision-info[RevisionInfo]
199 are passed as arguments. Gerrit provides a third parameter which
200 is an object with a `mergeable` boolean.
201
202* `submitchange`: Invoked when the submit button is clicked
203 on a change. A link:rest-api-changes.html#change-info[ChangeInfo]
204 and link:rest-api-changes.html#revision-info[RevisionInfo] are
205 passed as arguments. Similar to a form submit validation, the
206 function must return true to allow the operation to continue, or
207 false to prevent it. The function may be called multiple times, for
208 example, if submitting a change shows a confirmation dialog, this
209 event may be called to validate that the check whether dialog can be
210 shown, and called again when the submit is confirmed to check whether
211 the actual submission action can proceed.
212
213* `comment`: Invoked when a DOM element that represents a comment is
214 created. This DOM element is passed as argument. This DOM element
215 contains nested elements that Gerrit uses to format the comment. The
216 DOM structure may differ between comment types such as inline
217 comments, file-level comments and summary comments, and it may change
218 with new Gerrit versions.
219
220* `highlightjs-loaded`: Invoked when the highlight.js library has
221 finished loading. The global `hljs` object (also now accessible via
222 `window.hljs`) is passed as an argument to the callback function.
223 This event can be used to register a new language highlighter with
224 the highlight.js library before syntax highlighting begins.
Viktar Donich7610f782017-10-02 11:51:41 +0100225
226[[high-level-api]]
227== High-level API
228
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200229Plugin instance provides access to a number of more specific APIs and methods
Viktar Donich7610f782017-10-02 11:51:41 +0100230to be used by plugin authors.
231
Wyatt Allenc1485932018-03-30 10:53:36 -0700232=== admin
233`plugin.admin()`
234
235.Params:
236- none
237
238.Returns:
239- Instance of link:pg-plugin-admin-api.html[GrAdminApi].
240
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200241=== changeActions
242`self.changeActions()`
243
244Returns an instance of the
245link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/api/change-actions.ts[ChangeActionsPluginApi].
246
247==== changeActions.add()
248Adds a new action to the change actions section. Returns the key of the newly
249added action.
250
251``` js
252changeActions.add(type, label)
253```
254
255* type: The type of the action, either `change` or `revision`.
256
257* label: The label to be used in UI for this action.
258
259
260==== changeActions.remove()
261Removes an action from the change actions section.
262
263``` js
264changeActions.remove(key)
265```
266
267* key: The key of the action.
268
269
270==== changeActions.addTapListener()
271Adds a tap listener to an action that will be invoked when the action
272is tapped.
273
274``` js
275changeActions.addTapListener(key, callback)
276```
277
278* key: The key of the action.
279
280* callback: JavaScript function to be invoked when action tapped.
281
282
283==== changeActions.removeTapListener()
284Removes an existing tap listener on an action.
285
286``` js
287changeActions.removeTapListener(key, callback)
288```
289
290* key: The key of the action.
291
292* callback: JavaScript function to be removed.
293
294
295==== changeActions.setLabel()
296Sets the label for an action.
297
298``` js
299changeActions.setLabel(key, label)
300```
301
302* key: The key of the action.
303
304* label: The label of the action.
305
306
307==== changeActions.setTitle()
308Sets the title for an action.
309
310``` js
311changeActions.setTitle(key, title)
312```
313
314* key: The key of the action.
315
316* title: The title of the action.
317
318
319==== changeActions.setIcon()
320Sets an icon for an action.
321
322``` js
323changeActions.setIcon(key, icon)
324```
325
326* key: The key of the action.
327
328* icon: The name of the icon.
329
330
331==== changeActions.setEnabled()
332Sets an action to enabled or disabled.
333
334``` js
335changeActions.setEnabled(key, enabled)
336```
337
338* key: The key of the action.
339
340* enabled: The status of the action, true to enable.
341
342
343==== changeActions.setActionHidden()
344Sets an action to be hidden.
345
346``` js
347changeActions.setActionHidden(type, key, hidden)
348```
349
350* type: The type of the action.
351
352* key: The key of the action.
353
354* hidden: True to hide the action, false to show the action.
355
356
357==== changeActions.setActionOverflow()
358Sets an action to show in overflow menu.
359
360``` js
361changeActions.setActionOverflow(type, key, overflow)
362```
363
364* type: The type of the action.
365
366* key: The key of the action.
367
368* overflow: True to move the action to overflow menu, false to move
369 the action out of the overflow menu.
370
371
Viktar Donich7610f782017-10-02 11:51:41 +0100372=== changeReply
373`plugin.changeReply()`
374
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200375Returns an instance of the
376link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/api/change-reply.ts[ChangeReplyPluginApi].
Viktar Donich7610f782017-10-02 11:51:41 +0100377
Ben Rohlfsab7fbbf2021-04-29 10:27:03 +0200378[[checks]]
379=== checks
380`plugin.checks()`
381
382Returns an instance of the link:pg-plugin-checks-api.html[ChecksApi].
383
Viktar Donich7610f782017-10-02 11:51:41 +0100384=== getPluginName
385`plugin.getPluginName()`
386
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200387Returns the name this plugin was installed as by the server
388administrator. The plugin name is required to access REST API
389views installed by the plugin, or to access resources.
Viktar Donich7610f782017-10-02 11:51:41 +0100390
391=== getServerInfo
392`plugin.getServerInfo()`
393
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200394Returns the host config as a link:rest-api-config.html#server-info[ServerInfo]
395object.
Viktar Donich9c7164a2017-12-19 15:03:58 -0800396
Viktar Donich7610f782017-10-02 11:51:41 +0100397=== popup
398`plugin.popup(moduleName)`
399
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200400Creates a popup that contains the given web components. Can be controlled with
401calling `open()` and `close()` on the return value.
Viktar Donich7610f782017-10-02 11:51:41 +0100402
Viktar Donichbc8088e2018-04-23 15:29:57 -0700403[[plugin-rest-api]]
404=== restApi
405`plugin.restApi(opt_prefix)`
406
407.Params:
408- (optional) URL prefix, for easy switching into plugin URL space,
409 e.g. `changes/1/revisions/1/cookbook~say-hello`
410
411.Returns:
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200412- Instance of link:pg-plugin-rest-api.html[RestPluginApi].
Viktar Donichbc8088e2018-04-23 15:29:57 -0700413
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200414[[plugin-screen]]
Viktar Donich6ea8c8a2017-12-12 14:41:47 -0800415=== screen
416`plugin.screen(screenName, opt_moduleName)`
417
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200418Registers a web component as a dedicated top-level page that the router
419understands and that has a URL (/x/pluginname/screenname) that can be navigated
420to. Extension screens are usually linked from the
421link:dev-plugins.html#top-menu-extensions[top menu].
422
Viktar Donich6ea8c8a2017-12-12 14:41:47 -0800423.Params:
424- `*string* screenName` URL path fragment of the screen, e.g.
425`/x/pluginname/*screenname*`
426- `*string* opt_moduleName` (Optional) Web component to be instantiated for this
427screen.
428
429.Returns:
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200430- Instance of HookApi.
Viktar Donich7610f782017-10-02 11:51:41 +0100431
432=== url
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200433`plugin.url(opt_path)`
434
435Returns a URL within the plugin's URL space. If invoked with no
436parameter the URL of the plugin is returned. If passed a string
437the argument is appended to the plugin URL.
438
439A plugin's URL is where this plugin is loaded, it doesn't
440necessary to be the same as the Gerrit host. Use `window.location`
441if you need to access the Gerrit host info.
442
443``` js
444self.url(); // "https://gerrit-review.googlesource.com/plugins/demo/"
445self.url('/static/icon.png'); // "https://gerrit-review.googlesource.com/plugins/demo/static/icon.png"
446```