blob: 3f7d90d1cb0ec252a1c79667fd1216b2faed5369 [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
Viktar Donich7610f782017-10-02 11:51:41 +010033[[low-level-api-concepts]]
34== Low-level DOM API concepts
Viktar Donichcbc25672017-07-24 13:23:18 -070035
36Basically, the DOM is the API surface. Low-level API provides methods for
37decorating, replacing, and styling DOM elements exposed through a set of
Viktar Donich6d10eca2017-11-13 17:57:43 -080038link:pg-plugin-endpoints.html[endpoints].
Viktar Donichcbc25672017-07-24 13:23:18 -070039
Ben Rohlfsda0a62b2021-04-26 17:02:19 +020040Gerrit provides a simple way for accessing the DOM via DOM hooks API. A DOM
Viktar Donichcbc25672017-07-24 13:23:18 -070041hook is a custom element that is instantiated for the plugin endpoint. In the
42decoration case, a hook is set with a `content` attribute that points to the DOM
43element.
44
Viktar Donichab491f772017-08-15 08:02:58 -0700451. Get the DOM hook API instance via `plugin.hook(endpointName)`
Viktar Donichcbc25672017-07-24 13:23:18 -0700462. Set up an `onAttached` callback
473. Callback is called when the hook element is created and inserted into DOM
484. Use element.content to get UI element
49
50``` js
Viktar Donich5055e8d2017-11-09 13:02:42 -080051Gerrit.install(plugin => {
Viktar Donichab491f772017-08-15 08:02:58 -070052 const domHook = plugin.hook('reply-text');
Viktar Donichcbc25672017-07-24 13:23:18 -070053 domHook.onAttached(element => {
54 if (!element.content) { return; }
55 // element.content is a reply dialog text area.
56 });
57});
58```
59
60[[low-level-decorating]]
61=== Decorating DOM Elements
62
Ben Rohlfsda0a62b2021-04-26 17:02:19 +020063For each endpoint, Gerrit provides a list of DOM properties (such as
Viktar Donichcbc25672017-07-24 13:23:18 -070064attributes and events) that are supported in the long-term.
65
Viktar Donichcbc25672017-07-24 13:23:18 -070066``` js
Viktar Donich5055e8d2017-11-09 13:02:42 -080067Gerrit.install(plugin => {
Viktar Donichab491f772017-08-15 08:02:58 -070068 const domHook = plugin.hook('reply-text');
Viktar Donichcbc25672017-07-24 13:23:18 -070069 domHook.onAttached(element => {
70 if (!element.content) { return; }
71 element.content.style.border = '1px red dashed';
72 });
73});
74```
75
76[[low-level-replacing]]
77=== Replacing DOM Elements
78
79An endpoint's contents can be replaced by passing the replace attribute as an
80option.
81
82``` js
Viktar Donich5055e8d2017-11-09 13:02:42 -080083Gerrit.install(plugin => {
Viktar Donichab491f772017-08-15 08:02:58 -070084 const domHook = plugin.hook('header-title', {replace: true});
Viktar Donichcbc25672017-07-24 13:23:18 -070085 domHook.onAttached(element => {
86 element.appendChild(document.createElement('my-site-header'));
87 });
88});
89```
90
91[[low-level-style]]
92=== Styling DOM Elements
93
Ben Rohlfs0dabd35f2022-02-25 10:07:44 +010094Gerrit only offers customized CSS styling by setting
95link:https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_[custom_properties]
96(aka css variables).
Tao Zhoud2a8ded2020-06-05 16:08:54 +020097
Ben Rohlfs0dabd35f2022-02-25 10:07:44 +010098See link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/styles/themes/[app-theme.ts]
99for the list of available variables.
100
101Just add code like this to your JavaScript plugin:
Viktar Donichcbc25672017-07-24 13:23:18 -0700102
Tao Zhoud2a8ded2020-06-05 16:08:54 +0200103``` js
David Ã…kerman4f1e9f62022-06-27 10:10:31 +0200104Gerrit.install(plugin => {
105 const styleEl = document.createElement('style');
106 styleEl.innerHTML = `
107 html {
108 --header-background-color: #c3d9ff;
109 }
110 html.darkTheme {
111 --header-background-color: #c3d9ff90;
112 }
113 `;
114 document.head.appendChild(styleEl);
115});
Viktar Donichcbc25672017-07-24 13:23:18 -0700116```
Viktar Donich7610f782017-10-02 11:51:41 +0100117
118[[high-level-api-concepts]]
119== High-level DOM API concepts
120
Quinten Yearsley888e6382017-12-05 11:11:09 -0800121High level API is based on low-level DOM API and is essentially a standardized
Viktar Donich7610f782017-10-02 11:51:41 +0100122way for doing common tasks. It's less flexible, but will be a bit more stable.
123
Quinten Yearsley888e6382017-12-05 11:11:09 -0800124The common way to access high-level API is through `plugin` instance passed
125into setup callback parameter of `Gerrit.install()`, also sometimes referred to
126as `self`.
Viktar Donich7610f782017-10-02 11:51:41 +0100127
128[[low-level-api]]
129== Low-level DOM API
130
Quinten Yearsley888e6382017-12-05 11:11:09 -0800131The low-level DOM API methods are the base of all UI customization.
Viktar Donich7610f782017-10-02 11:51:41 +0100132
133=== attributeHelper
134`plugin.attributeHelper(element)`
135
Viktar Donich6e1e69b2018-01-22 14:08:21 -0800136Alternative for
Tao Zhoud2a8ded2020-06-05 16:08:54 +0200137link:https://polymer-library.polymer-project.org/3.0/docs/devguide/data-binding[Polymer data
Marian Harbach34253372019-12-10 18:01:31 +0100138binding,role=external,window=_blank] for plugins that don't use Polymer. Can be used to bind element
Viktar Donich6e1e69b2018-01-22 14:08:21 -0800139attribute changes to callbacks.
140
Viktar Donich7610f782017-10-02 11:51:41 +0100141=== hook
142`plugin.hook(endpointName, opt_options)`
143
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200144See link:pg-plugin-endpoints.html[endpoints].
Viktar Donich7610f782017-10-02 11:51:41 +0100145
146=== registerCustomComponent
147`plugin.registerCustomComponent(endpointName, opt_moduleName, opt_options)`
148
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200149See link:pg-plugin-endpoints.html[endpoints].
Viktar Donich7610f782017-10-02 11:51:41 +0100150
Thomas Shafer03275862019-02-26 15:39:16 -0800151=== registerDynamicCustomComponent
152`plugin.registerDynamicCustomComponent(dynamicEndpointName, opt_moduleName,
153opt_options)`
154
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200155See link:pg-plugin-endpoints.html[endpoints].
Thomas Shafer03275862019-02-26 15:39:16 -0800156
Viktar Donich7610f782017-10-02 11:51:41 +0100157=== registerStyleModule
158`plugin.registerStyleModule(endpointName, moduleName)`
159
Ben Rohlfs0dabd35f2022-02-25 10:07:44 +0100160This API is deprecated and will be removed either in version 3.6 or 3.7,
161see link:#low-level-style[above] for an alternative.
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200162
163=== on
164Register a JavaScript callback to be invoked when events occur within
165the web interface. Signature
166
167``` js
168self.on(event, callback);
169```
170
171Parameters
172
173* event: A supported event type. See below for description.
174
175* callback: JavaScript function to be invoked when event happens.
176 Arguments may be passed to this function, depending on the event.
177
178Supported events:
179
180* `history`: Invoked when the view is changed to a new screen within
181 the Gerrit web application. The token after "#" is passed as the
182 argument to the callback function, for example "/c/42/" while
183 showing change 42.
184
185* `showchange`: Invoked when a change is made visible. A
186 link:rest-api-changes.html#change-info[ChangeInfo] and
187 link:rest-api-changes.html#revision-info[RevisionInfo]
188 are passed as arguments. Gerrit provides a third parameter which
189 is an object with a `mergeable` boolean.
190
191* `submitchange`: Invoked when the submit button is clicked
192 on a change. A link:rest-api-changes.html#change-info[ChangeInfo]
193 and link:rest-api-changes.html#revision-info[RevisionInfo] are
194 passed as arguments. Similar to a form submit validation, the
195 function must return true to allow the operation to continue, or
196 false to prevent it. The function may be called multiple times, for
197 example, if submitting a change shows a confirmation dialog, this
198 event may be called to validate that the check whether dialog can be
199 shown, and called again when the submit is confirmed to check whether
200 the actual submission action can proceed.
201
202* `comment`: Invoked when a DOM element that represents a comment is
203 created. This DOM element is passed as argument. This DOM element
204 contains nested elements that Gerrit uses to format the comment. The
205 DOM structure may differ between comment types such as inline
206 comments, file-level comments and summary comments, and it may change
207 with new Gerrit versions.
208
209* `highlightjs-loaded`: Invoked when the highlight.js library has
210 finished loading. The global `hljs` object (also now accessible via
211 `window.hljs`) is passed as an argument to the callback function.
212 This event can be used to register a new language highlighter with
213 the highlight.js library before syntax highlighting begins.
Viktar Donich7610f782017-10-02 11:51:41 +0100214
215[[high-level-api]]
216== High-level API
217
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200218Plugin instance provides access to a number of more specific APIs and methods
Viktar Donich7610f782017-10-02 11:51:41 +0100219to be used by plugin authors.
220
Wyatt Allenc1485932018-03-30 10:53:36 -0700221=== admin
222`plugin.admin()`
223
224.Params:
225- none
226
227.Returns:
228- Instance of link:pg-plugin-admin-api.html[GrAdminApi].
229
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200230=== changeActions
231`self.changeActions()`
232
233Returns an instance of the
234link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/api/change-actions.ts[ChangeActionsPluginApi].
235
236==== changeActions.add()
237Adds a new action to the change actions section. Returns the key of the newly
238added action.
239
240``` js
241changeActions.add(type, label)
242```
243
244* type: The type of the action, either `change` or `revision`.
245
246* label: The label to be used in UI for this action.
247
248
249==== changeActions.remove()
250Removes an action from the change actions section.
251
252``` js
253changeActions.remove(key)
254```
255
256* key: The key of the action.
257
258
259==== changeActions.addTapListener()
260Adds a tap listener to an action that will be invoked when the action
261is tapped.
262
263``` js
264changeActions.addTapListener(key, callback)
265```
266
267* key: The key of the action.
268
269* callback: JavaScript function to be invoked when action tapped.
270
271
272==== changeActions.removeTapListener()
273Removes an existing tap listener on an action.
274
275``` js
276changeActions.removeTapListener(key, callback)
277```
278
279* key: The key of the action.
280
281* callback: JavaScript function to be removed.
282
283
284==== changeActions.setLabel()
285Sets the label for an action.
286
287``` js
288changeActions.setLabel(key, label)
289```
290
291* key: The key of the action.
292
293* label: The label of the action.
294
295
296==== changeActions.setTitle()
297Sets the title for an action.
298
299``` js
300changeActions.setTitle(key, title)
301```
302
303* key: The key of the action.
304
305* title: The title of the action.
306
307
308==== changeActions.setIcon()
309Sets an icon for an action.
310
311``` js
312changeActions.setIcon(key, icon)
313```
314
315* key: The key of the action.
316
317* icon: The name of the icon.
318
319
320==== changeActions.setEnabled()
321Sets an action to enabled or disabled.
322
323``` js
324changeActions.setEnabled(key, enabled)
325```
326
327* key: The key of the action.
328
329* enabled: The status of the action, true to enable.
330
331
332==== changeActions.setActionHidden()
333Sets an action to be hidden.
334
335``` js
336changeActions.setActionHidden(type, key, hidden)
337```
338
339* type: The type of the action.
340
341* key: The key of the action.
342
343* hidden: True to hide the action, false to show the action.
344
345
346==== changeActions.setActionOverflow()
347Sets an action to show in overflow menu.
348
349``` js
350changeActions.setActionOverflow(type, key, overflow)
351```
352
353* type: The type of the action.
354
355* key: The key of the action.
356
357* overflow: True to move the action to overflow menu, false to move
358 the action out of the overflow menu.
359
360
Viktar Donich7610f782017-10-02 11:51:41 +0100361=== changeReply
362`plugin.changeReply()`
363
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200364Returns an instance of the
365link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/api/change-reply.ts[ChangeReplyPluginApi].
Viktar Donich7610f782017-10-02 11:51:41 +0100366
Ben Rohlfsab7fbbf2021-04-29 10:27:03 +0200367[[checks]]
368=== checks
369`plugin.checks()`
370
371Returns an instance of the link:pg-plugin-checks-api.html[ChecksApi].
372
Viktar Donich7610f782017-10-02 11:51:41 +0100373=== getPluginName
374`plugin.getPluginName()`
375
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200376Returns the name this plugin was installed as by the server
377administrator. The plugin name is required to access REST API
378views installed by the plugin, or to access resources.
Viktar Donich7610f782017-10-02 11:51:41 +0100379
380=== getServerInfo
381`plugin.getServerInfo()`
382
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200383Returns the host config as a link:rest-api-config.html#server-info[ServerInfo]
384object.
Viktar Donich9c7164a2017-12-19 15:03:58 -0800385
Viktar Donich7610f782017-10-02 11:51:41 +0100386=== popup
387`plugin.popup(moduleName)`
388
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200389Creates a popup that contains the given web components. Can be controlled with
390calling `open()` and `close()` on the return value.
Viktar Donich7610f782017-10-02 11:51:41 +0100391
Viktar Donichbc8088e2018-04-23 15:29:57 -0700392[[plugin-rest-api]]
393=== restApi
394`plugin.restApi(opt_prefix)`
395
396.Params:
397- (optional) URL prefix, for easy switching into plugin URL space,
398 e.g. `changes/1/revisions/1/cookbook~say-hello`
399
400.Returns:
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200401- Instance of link:pg-plugin-rest-api.html[RestPluginApi].
Viktar Donichbc8088e2018-04-23 15:29:57 -0700402
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200403[[plugin-screen]]
Viktar Donich6ea8c8a2017-12-12 14:41:47 -0800404=== screen
405`plugin.screen(screenName, opt_moduleName)`
406
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200407Registers a web component as a dedicated top-level page that the router
408understands and that has a URL (/x/pluginname/screenname) that can be navigated
409to. Extension screens are usually linked from the
410link:dev-plugins.html#top-menu-extensions[top menu].
411
Viktar Donich6ea8c8a2017-12-12 14:41:47 -0800412.Params:
413- `*string* screenName` URL path fragment of the screen, e.g.
414`/x/pluginname/*screenname*`
415- `*string* opt_moduleName` (Optional) Web component to be instantiated for this
416screen.
417
418.Returns:
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200419- Instance of HookApi.
Viktar Donich7610f782017-10-02 11:51:41 +0100420
421=== url
Ben Rohlfsda0a62b2021-04-26 17:02:19 +0200422`plugin.url(opt_path)`
423
424Returns a URL within the plugin's URL space. If invoked with no
425parameter the URL of the plugin is returned. If passed a string
426the argument is appended to the plugin URL.
427
428A plugin's URL is where this plugin is loaded, it doesn't
429necessary to be the same as the Gerrit host. Use `window.location`
430if you need to access the Gerrit host info.
431
432``` js
433self.url(); // "https://gerrit-review.googlesource.com/plugins/demo/"
434self.url('/static/icon.png'); // "https://gerrit-review.googlesource.com/plugins/demo/static/icon.png"
435```