A web-item is a button or link that will appear at your chosen location.

What happens when the link is clicked is up to you - some examples might be:

Let’s start with a simple example: We would like to add a link to Google on the top navigation bar. Fill out the form as follows:

link to google

Click Update to register the fragment. Go the Dashboard in a new tab, it should look like:

top item
You can understand what the values of the section attribute mean by reading the Atlassian documentation on Web UI modules , or you use the section finder tool.

Clicking the link should take you to Google. Experiment with the Weight field - try setting it to 1. When you refresh the page you should find that your link is now the left-most one.

So far, so easy, but not very useful.

Next we’ll try to add a link to Confluence wiki pages, which will do a Google search on the title of the current page.

Create a new web item or change the values to the following:

page link

You should now be able to select this item from the ellipsis menu of any wiki page:

tools menu link

Note that the URL is processed with velocity before rendering. The variables you can use depend on the context of the Web Item.

  • page - a Page object

  • space - a Space object

If you don’t change the key, the module from the first part of the tutorial will be overwritten.
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonOutput
import groovy.transform.BaseScript

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

approve(httpMethod: "GET") { MultivaluedMap queryParams ->

    // the details of adding properties or labels to the page, to indicate approval, are ommitted for brevity
    def pageId = queryParams.getFirst("pageId") as Long // use the pageId to retrieve this page

    def flag = [
        type : 'success',
        title: "Page approved",
        close: 'auto',
        body : "You have signified your approval of this page"
    ]

    Response.ok(JsonOutput.toJson(flag)).build()
}

Verify that this works by browsing to: http://<confluence-url>/rest/scriptrunner/latest/custom/approve?pageId=12345. It should simply respond with some JSON. If you are doing something with the page be sure to enter a valid page ID. The problem we have now is that will probably want to limit approving a page to just those in a certain group, or with a certain space permission. Also, we don’t want to show the menu item if the page has already been approved. Which brings us to Conditions.

Conditions

The condition is used to define whether the link should be shown or not, and it can use anything available in its context, plus details of the current user. If the item is on a wiki page, you will get the page and space etc. If it is on a space admin section then you may only get the space.

In Confluence the context variable is an instance of WebInterfaceContext, you can use its methods to retrieve the current page etc.

Let’s say that we want to only show the Approve menu item where the current user is a space admin, and the current page does not already have the approved label.

Clicking the Expand Examples reveals that there are already two samples that are similar to the two parts to this condition, combining them gives us:

import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.security.Permission
import com.atlassian.confluence.security.PermissionManager
import com.atlassian.confluence.user.ConfluenceUser
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.spaces.Space

def user = context.user as ConfluenceUser
def space = context.space as Space
def page = context.page as Page

if (user && space) {
    def permissionManager = ComponentLocator.getComponent(PermissionManager)
    return ! page.labels*.name.contains("approved") &&
        permissionManager.hasPermission(user, Permission.ADMINISTER, space)
}
else {
    return false
}

Enter this in as the Condition. It can either be entered inline or written to a file underneath a script root.

You should be able to see now that it doesn’t appear for users that are not space admins, or when the page is already labelled approved.

Conditions must be written defensively. What is available in the context or jiraHelper map depends on the particular web section, and the page that it’s viewed on.

For instance, a link in the top section (system.header/left) will not always have the same context variables. When viewed on the dashboard, it may contain just the current user, when you are looking at a page in a space, you will have a reference to the current Page object. Therefore, check that context variables are not null before calling methods or properties on them.

If you are using a section like system.content.action, you can assume you will always have the page context variable defined.

As well as using a script, you can use a concrete class that implements Condition. Why would you want to do this? Because you may wish to extend your own existing Condition, or one provided by the application.

Dialogs (Advanced)

There is some support for showing dialogs, although if you require complex interactions you will need to write some javascript.

To display a dialog when the user clicks a web-item, select the Run code and display a dialog option. The endpoint needs to return HTML for an AUI dialog2. The following REST endpoint code displays a dialog when clicked:

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript

import javax.ws.rs.core.MediaType
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

showDialog() { MultivaluedMap queryParams ->

    // get a reference to the current page...
    // def page = getPage(queryParams)

    def dialog =
        """<section role="dialog" id="sr-dialog" class="aui-layer aui-dialog2 aui-dialog2-medium" aria-hidden="true" data-aui-remove-on-hide="true">
            <header class="aui-dialog2-header">
                <h2 class="aui-dialog2-header-main">Some dialog</h2>
                <a class="aui-dialog2-header-close">
                    <span class="aui-icon aui-icon-small aui-iconfont-close-dialog">Close</span>
                </a>
            </header>
            <div class="aui-dialog2-content">
                <p>This is a dialog...</p>
            </div>
            <footer class="aui-dialog2-footer">
                <div class="aui-dialog2-footer-actions">
                    <button id="dialog-close-button" class="aui-button aui-button-link">Close</button>
                </div>
                <div class="aui-dialog2-footer-hint">Some hint here if you like</div>
            </footer>
        </section>
        """

    Response.ok().type(MediaType.TEXT_HTML).entity(dialog.toString()).build()
}

The button with the ID dialog-close-button will be automatically wired to close when clicked if you use a dialog ID of sr-dialog. If you require more complex interactions you should require some javascript and wire the elements, for example:

(function ($) {
    $(function () {
		AJS.dialog2.on("show", function (e) {
			var targetId = e.target.id;
			if (targetId == "my-own-dialog") {
				var someDialog = AJS.dialog2(e.target);
				$(e.target).find("#dialog-close-button").click(function (e) {
					e.preventDefault();
					someDialog.hide();
					someDialog.remove();
				});
				// etc
			}
		});
    });
})(AJS.$);
Removing the dialog from the DOM when the dialog is hidden (for example when pressing the ESC key) is controlled by the data-aui-remove-on-hide attribute. Removing this attribute will hide the dialog rather than remove it when the dialog is hidden. In this case you will need to handle re-showing the dialog in your JavaScript code, the web item trigger will re-render a completely new dialog for you.

You can include your additional dialog with a web resource fragment.

On clicking the link the dialog will display:

show dialog

Redirects

Sometimes you want to run some code and then redirect to another page, or even the same page after updating it. In that case, just return a redirect. The following example adds a label to the current page/issue and then refreshes it, by redirecting to the same page:

import com.atlassian.confluence.labels.Label
import com.atlassian.confluence.labels.LabelManager
import com.atlassian.confluence.labels.Namespace
import com.atlassian.confluence.pages.PageManager
import com.atlassian.sal.api.ApplicationProperties
import com.atlassian.sal.api.component.ComponentLocator
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

def labelManager = ComponentLocator.getComponent(LabelManager)
def pageManager = ComponentLocator.getComponent(PageManager)
def applicationProperties = ComponentLocator.getComponent(ApplicationProperties)

labelPage(httpMethod: "GET") { MultivaluedMap queryParams ->

    def pageId = queryParams.getFirst("pageId") as Long
    def page = pageManager.getPage(pageId)

    def label = labelManager.getLabel("approved")
    if (! label) {
        label = labelManager.createLabel(new Label("approved", Namespace.GLOBAL))
    }
    labelManager.addLabel(page, label)

    Response.temporaryRedirect(URI.create("${applicationProperties.baseUrl}/pages/viewpage.action?pageId=${pageId}")).build()
}
Make sure to select Navigate to a link as the intention setting.

Further examples

For how-to questions please ask on Atlassian Answers where there is a very active community. Adaptavist staff are also likely to respond there.

Ask a question about ScriptRunner for JIRA, for for Bitbucket Server, or for Confluence.