| :linkattrs: |
| = Gerrit Code Review - PolyGerrit Plugin Development |
| |
| CAUTION: Work in progress. Hard hat area. Please |
| link:https://bugs.chromium.org/p/gerrit/issues/entry?template=PolyGerrit%20plugins[send |
| feedback,role=external,window=_blank] if something's not right. |
| |
| For migrating existing GWT UI plugins, please check out the |
| link:pg-plugin-migration.html#migration[migration guide]. |
| |
| [[loading]] |
| == Plugin loading and initialization |
| |
| link:js-api.html#_entry_point[Entry point] for the plugin and the loading method |
| is based on link:http://w3c.github.io/webcomponents/spec/imports/[HTML Imports,role=external,window=_blank] |
| spec. |
| |
| * The plugin provides pluginname.html, and can be a standalone file or a static |
| asset in a jar as a link:dev-plugins.html#deployment[Web UI plugin]. |
| * pluginname.html contains a `dom-module` tag with a script that uses |
| `Gerrit.install()`. There should only be single `Gerrit.install()` per file. |
| * PolyGerrit imports pluginname.html along with all required resources defined in it |
| (fonts, styles, etc). |
| * For standalone plugins, the entry point file is a `pluginname.html` file |
| located in `gerrit-site/plugins` folder, where `pluginname` is an alphanumeric |
| plugin name. |
| |
| Note: Code examples target modern browsers (Chrome, Firefox, Safari, Edge). |
| |
| Here's a recommended starter `myplugin.html`: |
| |
| ``` html |
| <dom-module id="my-plugin"> |
| <script> |
| Gerrit.install(plugin => { |
| 'use strict'; |
| |
| // Your code here. |
| }); |
| </script> |
| </dom-module> |
| ``` |
| |
| [[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]. |
| |
| PolyGerrit 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, PolyGerrit 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://www.polymer-project.org/2.0/docs/devguide/style-shadow-dom#style-modules[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 .html file. |
| |
| Note: TODO: Insert link to the full styling API. |
| |
| ``` html |
| <dom-module id="my-plugin"> |
| <script> |
| Gerrit.install(plugin => { |
| plugin.registerStyleModule('change-metadata', 'some-style-module'); |
| }); |
| </script> |
| </dom-module> |
| |
| <dom-module id="some-style-module"> |
| <style> |
| html { |
| --change-metadata-label-status: { |
| display: none; |
| } |
| --change-metadata-strategy: { |
| display: none; |
| } |
| } |
| </style> |
| </dom-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://www.polymer-project.org/1.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 `samples/bind-parameters.html` for examples on both Polymer data bindings |
| and `attibuteHelper` usage. |
| |
| === eventHelper |
| `plugin.eventHelper(element)` |
| |
| Note: TODO |
| |
| === hook |
| `plugin.hook(endpointName, opt_options)` |
| |
| See list of supported link:pg-plugin-endpoints.html[endpoints]. |
| |
| Note: TODO |
| |
| === registerCustomComponent |
| `plugin.registerCustomComponent(endpointName, opt_moduleName, opt_options)` |
| |
| See list of supported link:pg-plugin-endpoints.html[endpoints]. |
| |
| Note: TODO |
| |
| === registerDynamicCustomComponent |
| `plugin.registerDynamicCustomComponent(dynamicEndpointName, opt_moduleName, |
| opt_options)` |
| |
| See list of supported link:pg-plugin-endpoints.html[endpoints]. |
| |
| Note: TODO |
| |
| === registerStyleModule |
| `plugin.registerStyleModule(endpointName, moduleName)` |
| |
| Note: TODO |
| |
| [[high-level-api]] |
| == High-level API |
| |
| Plugin instance provides access to 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]. |
| |
| === changeReply |
| `plugin.changeReply()` |
| |
| Note: TODO |
| |
| === delete |
| `plugin.delete(url, opt_callback)` |
| |
| Note: TODO |
| |
| === get |
| `plugin.get(url, opt_callback)` |
| |
| Note: TODO |
| |
| === getPluginName |
| `plugin.getPluginName()` |
| |
| Note: TODO |
| |
| === getServerInfo |
| `plugin.getServerInfo()` |
| |
| Note: TODO |
| |
| === on |
| `plugin.on(eventName, callback)` |
| |
| Note: TODO |
| |
| === panel |
| `plugin.panel(extensionpoint, callback)` |
| |
| Deprecated. Use `plugin.registerCustomComponent()` instead. |
| |
| ``` js |
| Gerrit.install(function(self) { |
| self.panel('CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK', function(context) { |
| context.body.innerHTML = |
| 'Sample link: <a href="http://some.com/foo">Foo</a>'; |
| context.show(); |
| }); |
| }); |
| ``` |
| |
| Here's the recommended approach that uses Polymer for generating custom elements: |
| |
| ``` html |
| <dom-module id="some-plugin"> |
| <script> |
| Gerrit.install(plugin => { |
| plugin.registerCustomComponent( |
| 'change-view-integration', 'some-ci-module'); |
| }); |
| </script> |
| </dom-module> |
| |
| <dom-module id="some-ci-module"> |
| <template> |
| Sample link: <a href="http://some.com/foo">Foo</a> |
| </template> |
| <script> |
| Polymer({is: 'some-ci-module'}); |
| </script> |
| </dom-module> |
| ``` |
| |
| Here's a minimal example that uses low-level DOM Hooks API for the same purpose: |
| |
| ``` js |
| Gerrit.install(plugin => { |
| plugin.hook('change-view-integration', el => { |
| el.innerHTML = 'Sample link: <a href="http://some.com/foo">Foo</a>'; |
| }); |
| }); |
| ``` |
| |
| === popup |
| `plugin.popup(moduleName)` |
| |
| Note: TODO |
| |
| === post |
| `plugin.post(url, payload, opt_callback)` |
| |
| Note: TODO |
| |
| [[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[GrPluginRestApi]. |
| |
| [[plugin-repo]] |
| === repo |
| `plugin.repo()` |
| |
| .Params: |
| - none |
| |
| .Returns: |
| - Instance of link:pg-plugin-repo-api.html[GrRepoApi]. |
| |
| === put |
| `plugin.put(url, payload, opt_callback)` |
| |
| Note: TODO |
| |
| === screen |
| `plugin.screen(screenName, opt_moduleName)` |
| |
| .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 GrDomHook. |
| |
| === screenUrl |
| `plugin.url(opt_screenName)` |
| |
| .Params: |
| - `*string* screenName` (optional) URL path fragment of the screen, e.g. |
| `/x/pluginname/*screenname*` |
| |
| .Returns: |
| - Absolute URL for the screen, e.g. `http://localhost/base/x/pluginname/screenname` |
| |
| [[plugin-settings]] |
| === settings |
| `plugin.settings()` |
| |
| .Params: |
| - none |
| |
| .Returns: |
| - Instance of link:pg-plugin-settings-api.html[GrSettingsApi]. |
| |
| === settingsScreen |
| `plugin.settingsScreen(path, menu, callback)` |
| |
| Deprecated. Use link:#plugin-settings[`plugin.settings()`] instead. |
| |
| [[plugin-styles]] |
| === styles |
| `plugin.styles()` |
| |
| .Params: |
| - none |
| |
| .Returns: |
| - Instance of link:pg-plugin-styles-api.html[GrStylesApi] |
| |
| === changeMetadata |
| `plugin.changeMetadata()` |
| |
| .Params: |
| - none |
| |
| .Returns: |
| - Instance of link:pg-plugin-change-metadata-api.html[GrChangeMetadataApi]. |
| |
| === theme |
| `plugin.theme()` |
| |
| |
| Note: TODO |
| |
| === url |
| `plugin.url(opt_path)` |
| |
| Note: TODO |
| |
| [[deprecated-api]] |
| == Deprecated APIs |
| |
| Some of the deprecated APIs have limited implementation in PolyGerrit to serve |
| as a "stepping stone" to allow gradual migration. |
| |
| === install |
| `plugin.deprecated.install()` |
| |
| .Params: |
| - none |
| |
| Replaces plugin APIs with a deprecated version. This allows use of deprecated |
| APIs without changing JS code. For example, `onAction` is not available by |
| default, and after `plugin.deprecated.install()` it's accessible via |
| `self.onAction()`. |
| |
| === onAction |
| `plugin.deprecated.onAction(type, view_name, callback)` |
| |
| .Params: |
| - `*string* type` Action type. |
| - `*string* view_name` REST API action. |
| - `*function(actionContext)* callback` Callback invoked on action button click. |
| |
| Adds a button to the UI with a click callback. Exact button location depends on |
| parameters. Callback is triggered with an instance of |
| link:#deprecated-action-context[action context]. |
| |
| Support is limited: |
| |
| - type is either `change` or `revision`. |
| |
| See link:js-api.html#self_onAction[self.onAction] for more info. |
| |
| === panel |
| `plugin.deprecated.panel(extensionpoint, callback)` |
| |
| .Params: |
| - `*string* extensionpoint` |
| - `*function(screenContext)* callback` |
| |
| Adds a UI DOM element and triggers a callback with context to allow direct DOM |
| access. |
| |
| Support is limited: |
| |
| - extensionpoint is one of the following: |
| * CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK |
| * CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK |
| |
| See link:js-api.html#self_panel[self.panel] for more info. |
| |
| === settingsScreen |
| `plugin.deprecated.settingsScreent(path, menu, callback)` |
| |
| .Params: |
| - `*string* path` URL path fragment of the screen for direct link. |
| - `*string* menu` Menu item title. |
| - `*function(settingsScreenContext)* callback` |
| |
| Adds a settings menu item and a section in the settings screen that is provided |
| to plugin for setup. |
| |
| See link:js-api.html#self_settingsScreen[self.settingsScreen] for more info. |
| |
| [[deprecated-action-context]] |
| === Action Context (deprecated) |
| Instance of Action Context is passed to `onAction()` callback. |
| |
| Support is limited: |
| |
| - `popup()` |
| - `hide()` |
| - `refresh()` |
| - `textfield()` |
| - `br()` |
| - `msg()` |
| - `div()` |
| - `button()` |
| - `checkbox()` |
| - `label()` |
| - `prependLabel()` |
| - `call()` |
| |
| See link:js-api.html#ActionContext[Action Context] for more info. |