# Client Web Panel Plugin Module

## Introduction

Client Web Panels are part of Bitbucket Server's [Client Web Fragment](client-web-fragment.html) family of modules. They parallel the functionality of
[Web Panels](https://developer.atlassian.com/display/DOCS/Web+Panel+Plugin+Module), but are rendered dynamically in the browser.

Please see the guide on [adding to the pull request overview](../../tutorials-and-examples/pull-request-overview.html) for an example
of how they can be used.

## Configuration

The root element for the Client Web Panel plugin module is `client-web-panel`. It allows the following attributes and child elements for
configuration:

### Attributes

<table>
    <tr>
        <th>Name</th>
        <th>Required</th>
        <th>Description</th>
        <th>Default</th>
    </tr>
    <tr>
        <td>key</td>
        <td><span class="tick"/></td>
        <td>The identifier of the plugin module. This key must be unique within the plugin where it is defined.</td>
        <td>N/A</td>
    </tr>
    <tr>
        <td>name</td>
        <td></td>
        <td>The human-readable name of the plugin module. I.e. the human-readable name of the web panel.</td>
        <td>N/A</td>
    </tr>
    <tr>
        <td>location</td>
        <td><span class="tick"/></td>
        <td>Location into which this web panel should be placed. For non-sectioned
            locations, this is just the location key. For sectioned locations it is the
            location key, followed by a slash ('/'), and the name of the web section in
            which it should appear.</td>
        <td>N/A</td>
    </tr>
    <tr>
        <td>weight</td>
        <td></td>
        <td>Determines the order in which web panels appear. Panels are displayed in order of ascending weight.
            The 'lightest' weight is displayed first, the 'heaviest' weight sink to the bottom.</td>
        <td>1000</td>
    </tr>
</table>

### Elements

<table>
    <tr>
        <th>Name</th>
        <th>Required</th>
        <th>Description</th>
        <th>Default</th>
        <th>Default type</th>
    </tr>
    <tr>
        <td>condition</td>
        <td></td>
        <td>
            Defines a condition (evaluated on the server during page render) that must be satisfied  for the web panel to be displayed.
            For details on using conditions, see <a href="../web-fragments.html#Conditions">Web Fragments - Conditions</a>.
        </td>
        <td>N/A</td>
        <td>N/A</td>
    </tr>
    <tr>
        <td>conditions</td>
        <td></td>
        <td>
            Defines the logical operator type to evaluate its condition elements (evaluated on the server during page render). By
            default 'AND' will be used.
        </td>
        <td>N/A</td>
        <td>N/A</td>
    </tr>
    <tr>
        <td>context-provider</td>
        <td></td>
        <td>
            Defines the data that will be passed to your web-panel's "js" type fields. This acts similarly to how it would on a normal web-panel
            (see <a href="https://developer.atlassian.com/display/DOCS/Web+Panel+Plugin+Module#WebPanelPluginModule-Context-providerElement">Web Panel - Context provider</a>),
            but there will be no incoming context object passed to the ContextProvider.
        </td>
        <td>N/A</td>
        <td>N/A</td>
    </tr>
    <tr>
        <td>client-condition</td>
        <td></td>
        <td>
            <p>
                Defines a JS function that is evaluated before each display of the client-web-panel. if the function returns true,
                the client-web-panel will be displayed.
            </p><p>
                <b>NOTE:</b> client-conditions should not be used for hiding sensitive data. See the
                <a href="client-web-fragment.html#Security_Note">Security Note</a> on client web fragments for details.
            </p>
        </td>
        <td>N/A</td>
        <td>js</td>
    </tr>
    <tr>
        <td>client-context-provider</td>
        <td></td>
        <td>
            Defines a JS function for transforming the data that will be passed to your client-web-panel's "js" type fields.
        </td>
        <td>N/A</td>
        <td>js</td>
    </tr>
    <tr>
        <td>dependency</td>
        <td></td>
        <td>Defines a Web Resource that this client-web-panel depends on.</td>
        <td>N/A</td>
        <td>N/A</td>
    </tr>
    <tr>
        <td>description</td>
        <td></td>
        <td>The description of the plugin module. The 'key' attribute can be specified to declare a localisation key
        for the value instead of text in the element body. I.e. the description of the web panel.</td>
        <td>N/A</td>
        <td>N/A</td>
    </tr>
    <tr>
        <td>resource</td>
        <td><span class="tick"></span></td>
        <td>Resources in a client-web-panel are used to specify a template that should be rendered. Currently Soy templates are supported. Either a resource with name "view" or a view element is required.</td>
        <td>N/A</td>
        <td>N/A</td>
    </tr>
    <tr>
        <td>view</td>
        <td></td>
        <td>A JavaScript function that returns an HTML string to display. Either a resource with name "view" or a view element is required.</td>
        <td>N/A</td>
        <td>js</td>
    </tr>
    <tr>
        <td>param</td>
        <td></td>
        <td>Parameters for the plugin module. Use the 'key' attribute to declare the parameter key, then specify the value
        in either the 'value' attribute or the element body. This element may be repeated. These will be merged into the context.</td>
        <td>N/A</td>
        <td>text</td>
    </tr>
    <tr>
        <td>tooltip</td>
        <td></td>
        <td>Is the text representation used for mouse-over text for the link. Note that this value may be ignored
        in some situations.</td>
        <td>N/A</td>
        <td>text</td>
    </tr>
</table>

## Example

Here is an example `atlassian-plugin.xml` file containing a client web panel and its dependencies:

```xml
<atlassian-plugin name="My Plugin" key="example.plugin" plugins-version="2">
    <plugin-info>
        <description>A basic plugin</description>
        <vendor name="My Company" url="http://www.mycompany.com"/>
        <version>1.0</version>
    </plugin-info>

    <client-web-panel key="myWebPanel" name="A Web Panel" location="web-panel-location" weight="100">
        <context-provider class="com.mycompany.example.plugin.PrivateWidgetsContextProvider" />
        <client-condition>WidgetManager.hasWidgets</client-condition>
        <client-context-provider>WidgetManager.getWidgetNames</client-context-provider>
        <resource type="soy" name="view" location="example.plugin:soy-templates/example.plugin.widgetList" />
        <dependency>example.plugin:widget-manager-js</dependency>
    </client-web-panel>

    <web-resource key="widget-manager-js" name="JS for managing widgets">
        <resource type="download" name="widget-manager.js" location="js/widget-manager.js" />
    </web-resource>
</atlassian-plugin>
```

And the corresponding com.mycompany.example.plugin.PrivateWidgetsContextProvider:

```java
package com.mycompany.example.plugin;

import com.atlassian.plugin.web.ContextProvider;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.google.common.collect.ImmutableMap;

public class PrivateWidgetsContextProvider implements ContextProvider {

    private WidgetService widgetService;

    public PrivateWidgetsContextProvider(WidgetService widgetService) {
        this.widgetService = widgetService;
    }

    @Override
    public void init(Map<String, String> params) throws PluginParseException {
    }

    @Override
    public Map<String, Object> getContextMap(Map<String, Object> context) {
        return ImmutableMap.<String, Object>builder()
                .put("widgets", widgetService.getWidgetsForUser((ApplicationUser) context.get('currentUser')))
                .build();
    }
}
```

And the corresponding widget-manager.js:

```javascript
var WidgetManager = {
    hasWidgets : function(context) {
        var widgets = context.widgets;
        return widgets && widgets.length > 0;
    },
    getWidgetNames : function(context) {
        var widgets = context.widgets;
        var names = [];
        for (var i = 0, len = widgets.length; i < len; i++) {
            names.push(widgets[i].name);
        }
        return names;
    }
};
```
