|  | = 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] 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] | 
|  | 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] 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] 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 | 
|  |  | 
|  | === 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 | 
|  |  | 
|  | === changeView | 
|  | `plugin.changeView()` | 
|  |  | 
|  | 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. | 
|  |  | 
|  | === 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. |