- Purpose
- Resources
- Anatomy of Gadgets
- Writing Your Gadget
- Setting up your Gadget
- Setting up your Project
- Creating Your Gadget Descriptor File
- Including Your Gadget in Your Plugin
- Including Your Gadget in the Gadget Directory
- Defining The Contents of Your Gadget
- Using HTML in Content
- Using Javascript and CSS in Content
- Using Views in Content
- Making Your Gadget Configurable
- User Preferences
- Extending The Functionality of Your Gadget
- Contact
Purpose
This guide describes the processes and architecture of developing Atlassian Gadgets, currently this is supported in JIRA 4.0 with plans to implementing these in Confluence 3.1 release as well (the required resources to enable gadgets in Confluence was included in the 3.1 milestone 2 release of Confluence. See: CONF-16414).
Resources
Atlassian has written a detailed guide on how to write Gadgets in their guide "Writing an Atlassian Gadget" which details a large amount of the steps one needs to write your own gadget, this guide is meant to be a supplement to Atlassian's guides to help clarify some points and help people get started on Atlassian Gadgets.
Anatomy of Gadgets
Declaration
Like all plugin modules, Gadgets are required to be declared in the "atlassian-plugin.xml" descriptor file for it to be picked up by the plugin system. There is no limit to the number of Gadgets which you are include within a single plugin.
The actual content of a Gadget is contained within a separate XML file (described here)

Data Flow
Gadgets has 2 main sources of information retrieval, this includes: User Preferences and obtaining them from external sources Using REST APIs.

For instructions on how to retrieve information from these sources, please refer to the sections User Preferences and Using REST APIs respectively.
Structure
Gadgets are standalone modules, they are not affected by the context in which they are used and as such, they are implemented on the JIRA dashboard using iframes.
Writing Your Gadget
Setting up your Gadget
Setting up your Project
| Gadgets are only available in the plugin 2.0 framework (which uses OSGi), legacy plugin will need to upgrade to the plugin 2.0 framework to support Gadgets.
The easiest way to check this is to check your atlassian-plugin.xml to see if it matches the following: <atlassian-plugin key="PLUGIN_KEY" name="PLUGIN_NAME" pluginsVersion="2"> |
Setting up a new Project
The fastest way to create a new project is to use the standard archetypes, as Gadgets are currently only released officially for JIRA 4.0, I will include the details for JIRA, for all others, please refer to the Atlassian documentation on plugin archetypes.
Run the following in your command line prompt:
For Linux/Unix:
mvn archetype:create \
-DarchetypeGroupId=com.atlassian.maven.archetypes \
-DarchetypeArtifactId=jira-plugin-archetype \
-DarchetypeVersion=16 \
-DremoteRepositories=https://maven.atlassian.com/repository/public/ \
-DgroupId=$MY_PACKAGE -DartifactId=$MY_PLUGIN
For Windows:
mvn archetype:create ^
-DarchetypeGroupId=com.atlassian.maven.archetypes ^
-DarchetypeArtifactId=jira-plugin-archetype ^
-DarchetypeVersion=16 ^
-DremoteRepositories=https://maven.atlassian.com/repository/public/ ^
-DgroupId=%MY_PACKAGE% -DartifactId=%MY_PLUGIN%
| Please note that the "DarchetypeVersion" specifies the version of the template to use, the latest version as of the time that this guide was written is version 16, please use the latest version as they are released, a list of all maven archetypes (for the various Atlassian products) can be found here: https://maven.atlassian.com/content/groups/public/com/atlassian/maven/archetypes |
Setting up an Existing Project
Please update your parent in your pom.xml, if you are not using a parent in your pom.xml, then please add it, below is the code to set up the parent:
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.atlassian.jira.plugins</groupId>
<artifactId>jira-plugin-base</artifactId>
<version>18</version>
</parent>
...
</project>
| The version specifies which version of the JIRA plugin base pom to use, please update this to the most recent, version 18 is the latest at the time that this guide was written, a list of all versions can be found here: https://maven.atlassian.com/content/groups/public/com/atlassian/jira/plugins/jira-plugin-base |
Creating Your Gadget Descriptor File
Create a simple XML file in your plugin resources directory (eg. /src/main/resources/net/customware/gadgets/example-gadget.xml) with the following standard format:
<?xml version="1.0" encoding="UTF-8" ?> <Module> <ModulePrefs title="__MSG_gadget.title__" title_url="__MSG_gadget.title.url__" directory_title="__MSG_gadget.title__" description="__MSG_gadget.description__" author="Bo Wang" author_email="bo.wang@customware.net" screenshot='#staticResourceUrl("net.customware.gadget:example-gadget", "screenshot.png")' thumbnail='#staticResourceUrl("net.customware.gadget:example-gadget", "thumbnail.png")' > <Optional feature="gadget-directory"> <Param name="categories"> JIRA </Param> </Optional> <Require feature="setprefs"/> </ModulePrefs> <UserPref name="isConfigured" datatype="hidden" default_value="false"/> <Content type="html" view="profile"> <![CDATA[ Hello World! ]]> </Content> </Module>
This sets up the basic layout of a Gadget, the above example is a very simple example of a Gadget, when run, it displays "Hello World!" within the Gadget frame.
Including Your Gadget in Your Plugin
To actually include the Gadget in your plugin, one extra step is required, and that is to include a reference to the descriptor file within your "atlassian-plugin.xml" so that the plugin system can pick it up.
<gadget key="example-gadget" name="Sonar Line Metrics Gadget" location="net/customware/gadgets/example-gadget.xml"> <description> Example gadget that displays Hello World! </description> </gadget>
Including Other Resources For Your Gadget
From the example shown in Creating Your Gadget Descriptor File, we actually used some references to external resources (namely the thumbnail and screenshot), these resources are typically included as part of the plugin and thus need to made available. To do this, we will need to set these up as resources, to do this, you will need to add something like the following in your "atlassian-plugin.xml".
<resource type="download" name="screenshot.png" location="net/customware/gadgets/example-gadget/images/screenshot.png"/> <resource type="download" name="thumbnail.png" location="net/customware/gadgets/example-gadget/images/thumbnail.png"/>
This will make the resources available for your Gadget.
Including Your Gadget in the Gadget Directory
To make your Gadget become available in the Gadget directory (currently only available in JIRA), you will need to include the "gadget-directory" feature in your Gadget, this feature is specific to the Atlassian implementation of Gadgets, so it is best to mark it as "Optional" as opposed to "Required".
In our example, we used the following:
<Optional feature="gadget-directory">
<Param name="categories">
JIRA
</Param>
</Optional>
Which makes the Gadget available in the Gadget directory under the JIRA section, there are currently a number of sections available, including:
- JIRA
- Confluence
- FishEye
- Crucible
- Crowd
- Clover
- Bamboo
- Admin
- Charts
- External Content
- Other
To make it appear in more than 1 section, just list them within the <Param name="categories"> element, with each one on a new line.
Defining The Contents of Your Gadget
| CDATA is required for almost all Gadgets, this is due to the some special characters which are commonly used that is not valid when used in an XML file (as they are reserved characters), these include namely the '<' and '>' characters which is treated as the start and end of an element tag.
Having CDATA around these forces everything inside it to be treated as plain text. To use CDATA, just put "<![CDATA[" at the very start and "]]>" at the very end of your "<Content>" element. |
The contents of your Gadget is specified by the "<Content>" element in your Gadget descriptor file, within our example, we used a very brief example using plain text:
<Content type="html" view="profile">
<![CDATA[
Hello World!
]]>
</Content>
There are several options available when declaring the contents of a Gadget, the main ways are described below:
Using HTML in Content
This is possible the most basic implementation of a Gadget, you can include static HTML in the "<Content>" element and by doing so, whenever the Gadget is rendered, the static HTML will be displayed, for example:
<Content type="html" view="profile">
<![CDATA[
<font color="red"><b>Hello World!</b></font>
]]>
</Content>
Will display Hello World! everytime the Gadget is rendered.
Using Javascript and CSS in Content
As with normal HTML content, you also have the ability to use both CSS and Javascript within your Gadget, and the way that it is done is no different from how it is done on traditional web pages, for example we can declare a Javascript method:
<script type="text/javascript" charset="utf-8"> function example() { alert("Hello World!"); } </script>
And then add a HTML link to call the Javascript:
<a href="#" onClick="example();">Click me</a>
Then add some CSS to the the top to style the link:
<style type="text/css">
a {
color:#ff00ff;
font-weight:bold;
}
</style>
Putting it all together:
<Content type="html" view="profile">
<![CDATA[
<style type="text/css">
a {
color:#ff00ff;
font-weight:bold;
}
</style>
<script type="text/javascript" charset="utf-8">
function example()
{
alert("Hello World!");
}
</script>
<a href="#" onClick="example();">Click me</a>
]]>
</Content>
Including External Javascript and CSS files
Normally, to include external Javascript and CSS files, you would use the standard notation:
// For Javascript <script type="text/javascript" src="..." ></script> // For CSS <link type="text/css" rel="stylesheet" href="..." media="all"/>
But within Gadgets, you can use the standard Atlassian notation for including these external resources, for those whom are familiar with Atlassian plugin development, this may seem familiar, and that is using the "#requireResource" function.
In order to use the #requireResource, 2 things needs to be completed, the first is to declare the resource in your atlassian-plugin.xml and the second thing is to update your Gadget descriptor file to reference the resource.
Declaring the Resources in Your Plugin
The first step is to put all of the resources in the resources directory of your plugin (eg. src/main/resources/net/customware/gadgets/example-gadget/css and src/main/resources/net/customware/gadgets/example-gadget/js), then adding references to these in your atlassian-plugin.xml (as web-resources)
<web-resource key="example-resources"> <resource type="download" name="example-gadget.js" location="net/customware/gadgets/example-gadget/js/example-gadget.js"> <property key="content-type" value="text/javascript"/> </resource> <resource type="download" name="example-gadget.css" location="net/customware/gadgets/example-gadget/css/example-gadget.css"> <property key="content-type" value="text/css"/> </resource> </web-resource>
This will make the resources available within your plugin.
Adding the Resource to Your Gadget
Once the resources are declared, you can make use of them in your Gadget, to do this you will need to first list all of your required resources, in this example, we will use the ones we declared in the previous step
#requireResource("net.customware.gadget:example-resources")
You may include as many or as few resources as you want in this section, once you have added all of your resources, you will need to put the following line to get these resources to be embedded into the Gadget:
#includeResources()
These should appear as the very first thing in your "<Content>" block of your descriptor.
...
<Content type="html" view="profile">
<![CDATA[
#requireResource("net.customware.gadget:example-resources")
... // More #requireResource() statements goes here
#includeResources()
...
]]>
</Content>
...
Using Views in Content
Enabling Views
Within your "<ModulePrefs>" element of your descriptor, you will need to add a new "<Require>" element to enable the "views" functionality as it is not enabled by default.
<ModulePrefs>
...
<Require feature="views"/>
...
</ModulePrefs>
Declaring Your View
The way to declare a view is to use the AJS.Gadget method, this method creates a Gadget object based upon the parameters passed in, the format that it accepts is in JSON.
Sample Gadget initialisation
AJS.Gadget({
baseUrl: ...,
useOauth: ...,
config: ...,
view:...
});
From the above list, only the "baseUrl" and "view" parameters are required, all of the rest is optional but recommended to have.
Gadget - baseUrl (required)
This sets the base URL for the Gadget, which will be used to append to relative Ajax requests as well as made available via the Gadget object (by using gadget.getBaseUrl()).
To get JIRA to inject this value, simply use the following:
baseUrl: "__ATLASSIAN_BASE_URL__"
Gadget - useOauth (optional)
OAuth is a standard used to allow secure API authorization that Gadgets relies on, there are 2 valid options for this parameter:
| Option | Description |
|---|---|
| always | Forces all requests to use OAuth, anonymous users will not be able to access this Gadget if this is used. |
| URL | Put the URL of the Oauth service you wish to use, JIRA has its own build it rest service for this located at "/rest/gadget/1.0/currentUser" it is recommended to use this one unless you have a good reason to use a different one. |
Gadget - config (optional)
| As this specifies a different view for the configurations, it is best to disable the default Edit screen by setting all of your "<UserPref>" elements to use "datatype="hidden"" (if all the fields are hidden, then the default Edit is not displayed, this helps avoid confusion as there will be 2 "Edit" links displayed if the fields are not set to hidden) |
Specifies the configuration view of the Gadget (what is displayed when the user chooses to Edit the Gadget).
This parameter accepts a JSON object containing a "descriptor" and "args".
Gadget - config - descriptor (required)
This describes a function which returns a JSON object that contains the details of all of the fields and options for the configuration display, and accepts a series of arguments specified by the args parameter.
config
{
descriptor: function(args)
{
...
},
...
}
The values that this should return is in the following format:
{
action: "validation url", /* OPTIONAL - A url to validate the form against */
theme: "", /* OPTIONAL - The layout of the form - "top-label" or "long-label" */
fields:[
... /* REQUIRED - Fields list goes here */
]
}
With the list of fields, please refer to the Atlassian documentation for the Field Definitions for a complete list of what is available.
Gadget - config - args (optional)
This allows you to specify which values are available in the descriptor function, this requires 2 parameters and relied heavily on the Ajax APIs, the parameters includes:
| Parameter | Description |
|---|---|
| key | The key to map the result of the Ajax call to, this can be accessed in the descriptor function using args.key |
| ajaxOptions | A set of options (JSON format) describing the Ajax call, for more information on this, please refer to using REST APIs |
Gadget - view (required)
This specifies the view of the Gadget, and is called every time the Gadget is rendered, please make sure that code used in this have a consistent behavior when called more than once (it might be a good idea to reset the view using "gadget.getView().empty()" at the start to get rid of everything from the previous calls before rendering the view.
Gadget - view - template (required)
This specifies the functional to call each time the Gadget is rendered, and accepts a series of arguments specified by the args parameter.
The functional should accept a parameter called args:
view
{
template: function(args)
{
...
},
...
}
Gadget - view - args (optional)
This allows you to specify which values are available in the descriptor function, this requires 2 parameters and relied heavily on the Ajax APIs, the parameters includes:
| Parameter | Description |
|---|---|
| key | The key to map the result of the Ajax call to, this can be accessed in the template function using args.key |
| ajaxOptions | A set of options (JSON format) describing the Ajax call, for more information on this, please refer to using REST APIs |
Making Your Gadget Configurable
User Preferences
User preferences is the simplest way to store user specific preferences for a Gadget, these are declared by adding "<UserPref>" elements in your descriptor file (usually right before the "<Content>" element), these describe possible user inputs which is stored into the Gadget and can be accessed within the "<Content>" using gadget.getPref("key").
The standard "<UserPref>" takes in a number of options, only one of which is required to be set explicitly (the others are not required but can be used to further customise the interface for the user).
The list of fields includes:
| Field | Description | Default Value |
|---|---|---|
| name | REQUIRED - specifies the name of the user preference, this is the "key" that the value will be stored, to access the value of a preference, simply use gadget.getPref("name") | N/A |
| display_name | This is the field name for display to the user, sometimes you may not want the actual field name to be displayed to the user but something more meaningful and descriptive, this also allows for internationalisation of the interface | Whatever the "name" is set to |
| urlparam | String to pass as the parameter name for content type="url" | N/A |
| datatype | The type of data this field describes, valid options include: string, bool, enum, hidden, or list | string |
| required | Marks this preference as required or not, if set to true, the user MUST specify a value for this. | false |
| default_value | Sets the default value for this, if no value is set by the user, then this default value will be used to render the Gadget | N/A |
| User preferences are set every time the Gadget loads, to prevent this, a special user preference is reserved for this, and that is the "isConfigured" UserPref.
To use this, you will need to do 2 things, the first is to add the following line to your descriptor file: <UserPref name="isConfigured" datatype="hidden" default_value="false" /> Then within your "config" descriptor, add the following as one of the elements in your fields:
descriptor: function(args) {
return {
fields: [
AJS.gadget.fields.nowConfigured(),
... /* More fields here */
]
};
}
|
Advanced User Preference Interfaces
An API has been set up as part of the Atlassian Gadget framework to display specific types of fields in User Preferences, these fields are extensions of the basic fields mentioned in the User Preferences section.
Currently this is only available in JIRA but there are plans to make some of the more generic ones standardised across the various Atlassian systems (some of these are related to JIRA specific components such as filters and project categories).
Required Arguments
The following is required to be added to your config arguments:
args.projects
config: {
descriptor: function(args) {
},
args : [
{
key: "projects",
ajaxOptions: "/rest/gadget/1.0/filtersAndProjects?showFilters=false"
}
]
}
This will allow the Advanced User Preference Templates to access the list of Projects in the JIRA instance.
args.categories
To be completed...
Advanced User Preference Templates
AJS.gadget.fields.filterPicker
Example
$.extend(true, {}, AJS.gadget.fields.filterPicker(gadget, "example-field"), { description: "example-description", label: "example-label" })
Display
Description
Displays a textbox which attempts to do autocomplete (searches for matching filter names as you type into the box) your input.
AJS.gadget.fields.projectPicker
Example
$.extend(true, {}, AJS.gadget.fields.projectPicker(gadget, "example-field" , [args.projects|Writing an Atlassian Gadget#args.projects]), { description: "example-description", label: "example-label" })
Display
Description
Displays a dropdown list of all of the projects that you have access to.
AJS.gadget.fields.projectsAndCategoriesPicker
Example
$.extend(true, {}, AJS.gadget.fields.projectsAndCategoriesPicker(gadget, "example-field" [args.categories|Writing an Atlassian Gadget#args.categories]), { description: "example-description", label: "example-label" })
Display
Description
AJS.gadget.fields.projectsOrCategoriesPicker
Example
$.extend(true, {}, AJS.gadget.fields.projectsOrCategoriesPicker(gadget, "example-field" , args.categories), { description: "example-description", label: "example-label" })
Display
Description
To be completed...
AJS.gadget.fields.projectOrFilterPicker
To be completed...
AJS.gadget.fields.period
Example
$.extend(true, {}, AJS.gadget.fields.period(gadget, "example-field"), { description: "example-description", label: "example-label" })
Display
Description
Displays a dropdown box a list of options related to time, the options generated includes:
- Hourly
- Daily
- Weekly
- Monthly
- Quarterly
- Yearly
AJS.gadget.fields.nowConfigured
Example
AJS.gadget.fields.nowConfigured()
Display
N/A
Description
This is a special hidden field just to set the "isConfigured" field to true, the purpose of this is to make it so that the configuration screen only appears once (when the Gadget if first configured).
Special - Refresh Interval
This is a special field that appears automatically when the "enableReload" parameter is set to "true" in your Gadget view and allows you to set how often the Gadget should refresh itself.
The options available are:
- Every 15 Minutes
- Every 30 Minutes
- Every 1 Hour
- Every 2 Hours
Injecting User Preferences
A very handy way of simplifying the accessing of user preferences is to basically specify it using the _UP_preferencename_ format, the Gadget API will actually replace these with the corresponding user preference value.
For example, if you have an user preference called "project_key", you can use this in your Gadget by putting: "_UP_project_key_".
Extending The Functionality of Your Gadget
There are a number of ways in which you can expand upon the functionalities of your Gadget beyond the standard set of features, the 2 main ways are described below:
Using REST APIs
This is one of the only ways in which your Gadget can communicate and gather information from various locations, by declaring REST APIs as resources in conjunction with User Preferences, a whole new set of possibilities is opened up, from the previous sections about the "args" parameter (in both "view" and "config"), we can include the results from REST APIs as input parameters to the various functions.
The way to declare one of these resources is described in the following example:
args: [
{
key: "data",
ajaxOptions: function ()
{
return
{
url: "rest/example-rest/1.0/services/getdata", /* URL to the REST service (relative or absolute)*/
data:
{
key : this.getPref("key")
}
}
}
}
]
Within this example, we declared one data source called "data" which accesses a REST API located at "rest/example-rest/1.0/services/getdata", passing in the parameter "key" (the value that is passed is gotten from the User Preference with the name "key"), the result is then mapped to a parameter called "data".
There are a number of options for Ajax calls from the jQuery library which is not included in this guide, please refer to the documentation for Ajax in jQuery for more details.
Once this has been declared, you can access the results from this as a standard JSON object via the args parameter.
Example:
The REST API returns:
{
title:"Result Title",
...
}
Then you can access the title by using "args.data.title".
Requiring Additional Features
There are a number of features which is not enabled by default in a Gadget, these features extends the standard functionality of the Gadget API, to enable these, you will need to explicitly declare these in "<Require>" tags within your "<ModulePrefs>" of your descriptor file.
For a complete listing of all of the available features, please visit the Gadget Feature Guide by Atlassian.
Contact
If you wish to contribute to this guide or have any questions or issues related to Atlassian Gadgets, please feel free to drop us a line in GetSatisfaction