| :linkattrs: |
| = Gerrit Code Review - JavaScript Plugin Development and API |
| |
| Gerrit Code Review supports an API for JavaScript plugins to interact |
| with the web UI and the server process. |
| |
| [[loading]] |
| == Plugin loading and initialization |
| |
| JavaScript is loaded using a standard `<script src='...'>` HTML tag. |
| Plugins should protect the global namespace by defining their code |
| within an anonymous function passed to `Gerrit.install()`. The plugin |
| will be passed an object describing its registration with Gerrit. |
| |
| * The plugin provides pluginname.js, and can be a standalone file or a static |
| asset in a jar as a link:dev-plugins.html#deployment[Web UI plugin]. |
| * pluginname.js contains a call to `Gerrit.install()`. There should |
| only be a single `Gerrit.install()` call per file. |
| * The Gerrit web app imports pluginname.js. |
| * For standalone plugins, the entry point file is a `pluginname.js` file |
| located in `gerrit-site/plugins` folder, where `pluginname` is an alphanumeric |
| plugin name. |
| |
| === Examples |
| Here's a recommended starter `myplugin.js`: |
| |
| ``` js |
| Gerrit.install(plugin => { |
| // Your code here. |
| }); |
| ``` |
| |
| You can find more elaborate examples in the |
| link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/samples/[polygerrit-ui/app/samples/] |
| directory of the source tree. |
| |
| == TypeScript API == |
| |
| Gerrit provides a TypeScript plugin API. |
| |
| For a plugin built inline, its `tsconfig.json` can extends Gerrit plugin |
| TypeScript configuration: |
| |
| `tsconfig.json`: |
| ``` json |
| { |
| "extends": "../tsconfig-plugins-base.json" |
| } |
| ``` |
| |
| For standalone plugins (outside of a Gerrit tree), a TypeScript plugin API is |
| published: |
| link:https://www.npmjs.com/package/@gerritcodereview/typescript-api[@gerritcodereview/typescript-api]. |
| It provides a TypeScript configuration `tsconfig-plugins-base.json` which can |
| be used in your plugin `tsconfig.json`: |
| |
| ``` json |
| { |
| "extends": "node_modules/@gerritcodereview/typescript-api/tsconfig-plugins-base.json", |
| // your custom configuration and overrides |
| } |
| ``` |
| |
| [[low-level-api-concepts]] |
| == Low-level DOM API concepts |
| |
| Basically, the DOM is the API surface. Low-level API provides methods for |
| decorating, replacing, and styling DOM elements exposed through a set of |
| link:pg-plugin-endpoints.html[endpoints]. |
| |
| Gerrit provides a simple way for accessing the DOM via DOM hooks API. A DOM |
| hook is a custom element that is instantiated for the plugin endpoint. In the |
| decoration case, a hook is set with a `content` attribute that points to the DOM |
| element. |
| |
| 1. Get the DOM hook API instance via `plugin.hook(endpointName)` |
| 2. Set up an `onAttached` callback |
| 3. Callback is called when the hook element is created and inserted into DOM |
| 4. Use element.content to get UI element |
| |
| ``` js |
| Gerrit.install(plugin => { |
| const domHook = plugin.hook('reply-text'); |
| domHook.onAttached(element => { |
| if (!element.content) { return; } |
| // element.content is a reply dialog text area. |
| }); |
| }); |
| ``` |
| |
| [[low-level-decorating]] |
| === Decorating DOM Elements |
| |
| For each endpoint, Gerrit provides a list of DOM properties (such as |
| attributes and events) that are supported in the long-term. |
| |
| ``` js |
| Gerrit.install(plugin => { |
| const domHook = plugin.hook('reply-text'); |
| domHook.onAttached(element => { |
| if (!element.content) { return; } |
| element.content.style.border = '1px red dashed'; |
| }); |
| }); |
| ``` |
| |
| [[low-level-replacing]] |
| === Replacing DOM Elements |
| |
| An endpoint's contents can be replaced by passing the replace attribute as an |
| option. |
| |
| ``` js |
| Gerrit.install(plugin => { |
| const domHook = plugin.hook('header-title', {replace: true}); |
| domHook.onAttached(element => { |
| element.appendChild(document.createElement('my-site-header')); |
| }); |
| }); |
| ``` |
| |
| [[low-level-style]] |
| === Styling DOM Elements |
| |
| A plugin may provide Polymer's |
| https://polymer-library.polymer-project.org/3.0/docs/devguide/style-shadow-dom[style |
| modules,role=external,window=_blank] to style individual endpoints using |
| `plugin.registerStyleModule(endpointName, moduleName)`. A style must be defined |
| as a standalone `<dom-module>` defined in the same .js file. |
| |
| See |
| link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/samples/theme-plugin.js[samples/theme-plugin.js] |
| for an example. |
| |
| ``` js |
| const styleElement = document.createElement('dom-module'); |
| styleElement.innerHTML = |
| `<template> |
| <style> |
| html { |
| --primary-text-color: red; |
| } |
| </style> |
| </template>`; |
| |
| styleElement.register('some-style-module'); |
| |
| Gerrit.install(plugin => { |
| plugin.registerStyleModule('change-metadata', 'some-style-module'); |
| }); |
| ``` |
| |
| [[high-level-api-concepts]] |
| == High-level DOM API concepts |
| |
| High level API is based on low-level DOM API and is essentially a standardized |
| way for doing common tasks. It's less flexible, but will be a bit more stable. |
| |
| The common way to access high-level API is through `plugin` instance passed |
| into setup callback parameter of `Gerrit.install()`, also sometimes referred to |
| as `self`. |
| |
| [[low-level-api]] |
| == Low-level DOM API |
| |
| The low-level DOM API methods are the base of all UI customization. |
| |
| === attributeHelper |
| `plugin.attributeHelper(element)` |
| |
| Alternative for |
| link:https://polymer-library.polymer-project.org/3.0/docs/devguide/data-binding[Polymer data |
| binding,role=external,window=_blank] for plugins that don't use Polymer. Can be used to bind element |
| attribute changes to callbacks. |
| |
| See |
| link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/samples/bind-parameters.js[samples/bind-parameters.js] |
| for an example. |
| |
| === hook |
| `plugin.hook(endpointName, opt_options)` |
| |
| See link:pg-plugin-endpoints.html[endpoints]. |
| |
| === registerCustomComponent |
| `plugin.registerCustomComponent(endpointName, opt_moduleName, opt_options)` |
| |
| See link:pg-plugin-endpoints.html[endpoints]. |
| |
| === registerDynamicCustomComponent |
| `plugin.registerDynamicCustomComponent(dynamicEndpointName, opt_moduleName, |
| opt_options)` |
| |
| See link:pg-plugin-endpoints.html[endpoints]. |
| |
| === registerStyleModule |
| `plugin.registerStyleModule(endpointName, moduleName)` |
| |
| See link:#low-level-style[above]. |
| |
| === on |
| Register a JavaScript callback to be invoked when events occur within |
| the web interface. Signature |
| |
| ``` js |
| self.on(event, callback); |
| ``` |
| |
| Parameters |
| |
| * event: A supported event type. See below for description. |
| |
| * callback: JavaScript function to be invoked when event happens. |
| Arguments may be passed to this function, depending on the event. |
| |
| Supported events: |
| |
| * `history`: Invoked when the view is changed to a new screen within |
| the Gerrit web application. The token after "#" is passed as the |
| argument to the callback function, for example "/c/42/" while |
| showing change 42. |
| |
| * `showchange`: Invoked when a change is made visible. A |
| link:rest-api-changes.html#change-info[ChangeInfo] and |
| link:rest-api-changes.html#revision-info[RevisionInfo] |
| are passed as arguments. Gerrit provides a third parameter which |
| is an object with a `mergeable` boolean. |
| |
| * `submitchange`: Invoked when the submit button is clicked |
| on a change. A link:rest-api-changes.html#change-info[ChangeInfo] |
| and link:rest-api-changes.html#revision-info[RevisionInfo] are |
| passed as arguments. Similar to a form submit validation, the |
| function must return true to allow the operation to continue, or |
| false to prevent it. The function may be called multiple times, for |
| example, if submitting a change shows a confirmation dialog, this |
| event may be called to validate that the check whether dialog can be |
| shown, and called again when the submit is confirmed to check whether |
| the actual submission action can proceed. |
| |
| * `comment`: Invoked when a DOM element that represents a comment is |
| created. This DOM element is passed as argument. This DOM element |
| contains nested elements that Gerrit uses to format the comment. The |
| DOM structure may differ between comment types such as inline |
| comments, file-level comments and summary comments, and it may change |
| with new Gerrit versions. |
| |
| * `highlightjs-loaded`: Invoked when the highlight.js library has |
| finished loading. The global `hljs` object (also now accessible via |
| `window.hljs`) is passed as an argument to the callback function. |
| This event can be used to register a new language highlighter with |
| the highlight.js library before syntax highlighting begins. |
| |
| [[high-level-api]] |
| == High-level API |
| |
| Plugin instance provides access to a number of more specific APIs and methods |
| to be used by plugin authors. |
| |
| === admin |
| `plugin.admin()` |
| |
| .Params: |
| - none |
| |
| .Returns: |
| - Instance of link:pg-plugin-admin-api.html[GrAdminApi]. |
| |
| === changeActions |
| `self.changeActions()` |
| |
| Returns an instance of the |
| link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/api/change-actions.ts[ChangeActionsPluginApi]. |
| |
| ==== changeActions.add() |
| Adds a new action to the change actions section. Returns the key of the newly |
| added action. |
| |
| ``` js |
| changeActions.add(type, label) |
| ``` |
| |
| * type: The type of the action, either `change` or `revision`. |
| |
| * label: The label to be used in UI for this action. |
| |
| |
| ==== changeActions.remove() |
| Removes an action from the change actions section. |
| |
| ``` js |
| changeActions.remove(key) |
| ``` |
| |
| * key: The key of the action. |
| |
| |
| ==== changeActions.addTapListener() |
| Adds a tap listener to an action that will be invoked when the action |
| is tapped. |
| |
| ``` js |
| changeActions.addTapListener(key, callback) |
| ``` |
| |
| * key: The key of the action. |
| |
| * callback: JavaScript function to be invoked when action tapped. |
| |
| |
| ==== changeActions.removeTapListener() |
| Removes an existing tap listener on an action. |
| |
| ``` js |
| changeActions.removeTapListener(key, callback) |
| ``` |
| |
| * key: The key of the action. |
| |
| * callback: JavaScript function to be removed. |
| |
| |
| ==== changeActions.setLabel() |
| Sets the label for an action. |
| |
| ``` js |
| changeActions.setLabel(key, label) |
| ``` |
| |
| * key: The key of the action. |
| |
| * label: The label of the action. |
| |
| |
| ==== changeActions.setTitle() |
| Sets the title for an action. |
| |
| ``` js |
| changeActions.setTitle(key, title) |
| ``` |
| |
| * key: The key of the action. |
| |
| * title: The title of the action. |
| |
| |
| ==== changeActions.setIcon() |
| Sets an icon for an action. |
| |
| ``` js |
| changeActions.setIcon(key, icon) |
| ``` |
| |
| * key: The key of the action. |
| |
| * icon: The name of the icon. |
| |
| |
| ==== changeActions.setEnabled() |
| Sets an action to enabled or disabled. |
| |
| ``` js |
| changeActions.setEnabled(key, enabled) |
| ``` |
| |
| * key: The key of the action. |
| |
| * enabled: The status of the action, true to enable. |
| |
| |
| ==== changeActions.setActionHidden() |
| Sets an action to be hidden. |
| |
| ``` js |
| changeActions.setActionHidden(type, key, hidden) |
| ``` |
| |
| * type: The type of the action. |
| |
| * key: The key of the action. |
| |
| * hidden: True to hide the action, false to show the action. |
| |
| |
| ==== changeActions.setActionOverflow() |
| Sets an action to show in overflow menu. |
| |
| ``` js |
| changeActions.setActionOverflow(type, key, overflow) |
| ``` |
| |
| * type: The type of the action. |
| |
| * key: The key of the action. |
| |
| * overflow: True to move the action to overflow menu, false to move |
| the action out of the overflow menu. |
| |
| |
| === changeReply |
| `plugin.changeReply()` |
| |
| Returns an instance of the |
| link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/api/change-reply.ts[ChangeReplyPluginApi]. |
| |
| [[checks]] |
| === checks |
| `plugin.checks()` |
| |
| Returns an instance of the link:pg-plugin-checks-api.html[ChecksApi]. |
| |
| === getPluginName |
| `plugin.getPluginName()` |
| |
| Returns the name this plugin was installed as by the server |
| administrator. The plugin name is required to access REST API |
| views installed by the plugin, or to access resources. |
| |
| === getServerInfo |
| `plugin.getServerInfo()` |
| |
| Returns the host config as a link:rest-api-config.html#server-info[ServerInfo] |
| object. |
| |
| === popup |
| `plugin.popup(moduleName)` |
| |
| Creates a popup that contains the given web components. Can be controlled with |
| calling `open()` and `close()` on the return value. |
| |
| [[plugin-rest-api]] |
| === restApi |
| `plugin.restApi(opt_prefix)` |
| |
| .Params: |
| - (optional) URL prefix, for easy switching into plugin URL space, |
| e.g. `changes/1/revisions/1/cookbook~say-hello` |
| |
| .Returns: |
| - Instance of link:pg-plugin-rest-api.html[RestPluginApi]. |
| |
| [[plugin-screen]] |
| === screen |
| `plugin.screen(screenName, opt_moduleName)` |
| |
| Registers a web component as a dedicated top-level page that the router |
| understands and that has a URL (/x/pluginname/screenname) that can be navigated |
| to. Extension screens are usually linked from the |
| link:dev-plugins.html#top-menu-extensions[top menu]. |
| |
| .Params: |
| - `*string* screenName` URL path fragment of the screen, e.g. |
| `/x/pluginname/*screenname*` |
| - `*string* opt_moduleName` (Optional) Web component to be instantiated for this |
| screen. |
| |
| .Returns: |
| - Instance of HookApi. |
| |
| === url |
| `plugin.url(opt_path)` |
| |
| Returns a URL within the plugin's URL space. If invoked with no |
| parameter the URL of the plugin is returned. If passed a string |
| the argument is appended to the plugin URL. |
| |
| A plugin's URL is where this plugin is loaded, it doesn't |
| necessary to be the same as the Gerrit host. Use `window.location` |
| if you need to access the Gerrit host info. |
| |
| ``` js |
| self.url(); // "https://gerrit-review.googlesource.com/plugins/demo/" |
| self.url('/static/icon.png'); // "https://gerrit-review.googlesource.com/plugins/demo/static/icon.png" |
| ``` |