= 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.
