: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 ``` [[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 `` defined in the same .html file. Note: TODO: Insert link to the full styling API. ``` html ``` [[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 === 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: Foo'; context.show(); }); }); ``` Here's the recommended approach that uses Polymer for generating custom elements: ``` html ``` 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: Foo'; }); }); ``` === 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.