JavaScript API
Bitbucket Server exposes a JavaScript API for use in building your plugin's UI and behavior in the browser.
JSDoc
- JavaScript API reference documentation is available.
Compatibility Policy
The JS API's compatibility policy matches that of Bitbucket's other APIs. JS APIs will not contain breaking changes between minor versions (e.g. 4.0 and 4.1). Instead, any removed or modified methods and properties will log warnings in the browser console when used, and the legacy behavior will cease to work in the next major release (e.g. Bitbucket Server 5.0).
"Breaking changes" describes incompatible changes in behavior when valid inputs are given to any API functions, and incompatible type changes in data properties. Behavior for invalid inputs may change at any release, and mutation of API modules is not supported. See API Scope for details.
Loading the APIs
JavaScript APIs are not guaranteed to be available on the page. Before using an API you MUST depend on the matching web-resource module for that API. Each JS module will list its web resource key, which should be listed as a <dependency>{key}</dependency>
in your <web-resource/>
module. See the Web Resource Plugin Module documentation for details.
Asynchronous Module Definition (AMD)
require()
Our JavaScript APIs are delivered using Asynchronus Module Definitions (AMD) patterns. This means they can referenced with the global function require
. Given an array of string identifiers, require
will call your callback with the modules referenced.
AخA1require(['bitbucket/feature/files/file-handlers', 'some/other/module'], function(fileHandlers, someOtherModule) {
2// do things with the file-handlers module and some other module.
3});
Asynchronous execution
It is important to note that your callback to the require function MAY be executed asynchronously. You should not depend on your callback being called by any certain point in time. For example, you should not expect that your code is executed before DOMContentLoaded
(also known as $(document).ready()
)
Synchronous modules
For some but not all modules we also support a synchronous version of require
, usable like var module = require("bitbucket/x")
. This is to support use cases like registering a File Handler. These modules will explicitly note their synchronous require
support in documentation, and synchronous support should not be otherwise assumed, even if it happens to work when attempted.
See the "Events and AMD" section below for an example of combining synchronous and asynchronous dependencies.
define()
You can also define your own modules using define
. Note that you SHOULD prefix any modules you define with a unique key (e.g. "mycompany/"
) to avoid conflicts with Bitbucket or other plugins. You MUST NOT begin your module names with "bitbucket/"
, "atlassian/"
, or "internal/"
. These prefixes are explicitly reserved for use by Atlassian Bitbucket core.
The format of a define call is define(module name string, array of dependency ids, callback that returns the module)
41define('mycompany/things/helper', ['any', 'module', 'dependencies'], function(any, module, dependencies) {
2var helper = { };
3return helper;
4});
It should be noted that, like require
, define
MAY be called asynchronously, and you should NOT depend on your callback being called by any particular point in time.
Good module names
- mycompany/my-module
- mycompany/my-other-module
- mycompany/my-topic/my-third-module
Bad module names
- my-module
- bitbucket/my-module
- atlassian/my-module
- internal/my-module
API Scope
The scope of the JS API is limited to the require
and define
global functions, and within that, the "aui"
module and any modules prefixed with "bitbucket/"
except those prefixed with "bitbucket/internal"
. Use of any modules with the "bitbucket/internal"
prefix is not covered by this API and SHOULD NOT be used. Similarly, use of any global properties or functions is not covered by this API and SHOULD NOT be used.
Private naming (_
or internal.
prefix)
Any property whose name begins with an underscore is considered private and SHOULD NOT be used. E.g. publicObject._privateProperty
may not be stable between versions of Bitbucket, but publicObject.publicProperty
will be.
Similarly, events named starting with internal.
are for internal use and will not be stable between releases of Bitbucket.
Data properties
Public data properties will guarantee the following between minor versions:
- If a value is a primitive or object
Number
,Boolean
,String
,Array
,Date
,Function
or otherObject
, it will continue to betypeof
orinstanceof
that type. - If a value is
instanceof Object
(including Arrays, Dates, and Functions), these stability guarantees apply recursively to all non-private properties on that object.
Functions
When public functions are called, we guarantee the following between minor versions:
- Any arguments that are valid input to the function as specified in the JS API Reference will remain valid between minor versions.
- When called with valid arguments, the return value will remain valid and will be of the return type(s) specified in the reference docs.
Browser Support
The API's behavior is guaranteed stable only within the browsers supported by Bitbucket for any given release. Since our browser support is not guaranteed stable between minor versions, this means the supported browsers for the API may change between minor versions. For example, if support for IE11 is removed in Bitbucket Server 4.x, the behavior of API methods is no longer guaranteed to work in IE11 in Bitbucket Server 4.x+.
Explicitly out of scope
The following uses are explicitly out of scope for the API. Their behavior is undefined and we make no guarantees about their stability.
- Calling a method property of an object (e.g.
foo.doSomething()
) with anythis
value that isn't that exact object (e.g.foo.doSomething.call(bar)
). - Calling any function with a non-
Object
this
value (e.g.,false
,null
,"string"
). We'use strict'
which means these may behave differently in different browsers.
And note that the following MAY change between minor releases:
Behavior when calling a function with more arguments than are specified in the reference documentation. We may add extra arguments to functions in minor releases.
- UNSUPPORTED:
threeArgFunc(one, two, three, four)
- UNSUPPORTED:
Behavior when calling a function with object arguments that contain extra properties. These properties may gain meaning in a minor release (e.g. new options are supported).
- UNSUPPORTED:
func({ validOption: 'a', somethingExtra : 'invalid' })
- UNSUPPORTED:
When omitting an optional argument or argument property in a function call, the default behavior of that function may change (but will remain valid).
- Bitbucket x.y:
getPrimaryColor() === 'blue'
- Bitbucket x.y+1:
getPrimaryColor() === 'yellow'
- Bitbucket x.y:
Properties on an object's
prototype
may become enumerable properties on the object itself, or visa versa.- The configuration of a property may change: (e.g., a
value
may be replaced byget
/set
pairs, may changeenumerable
orwritable
values, etc).
Useful JS patterns
There are a few useful patterns when working against the Bitbucket JS API. Some are also useful in JS at large.
Immediately-invoked function expressions (IIFE)
When using an AMD module, your code is within a closure, and thus is protected and (somewhat) sandboxed from the global environment. E.g.,
31require(['some', 'modules'], function() {
2// code in here can be isolated
3});
When not using AMD, you should isolate your code using an IIFE, so it is less likely to affect or be affected by the global scope:
31(function() {
2// this code has the same isolation benefits.
3})();
This ensures that you won't inadvertently create any global variables that have naming conflicts with another plugin.
Promises
We use a lot of Promises in Bitbucket. Specifically we use the jQuery implementation of Promises and Deferreds. Promises allow you to pass around a result before that result is actually calculated and available. They are similar to Futures. For any function, rather than passing in callbacks that will be called when a result is ready, we generally pass out a Promise you can observe for success or failure.
E.g. where we might have had a function like this:
x1function cookBacon(cookTime, tastyBaconCallback, ruinedBaconCallback) {
2var burner = getStove().getBurner();
3var pan = getPan();
4var bacon = getBacon();
5
6pan.add(bacon);
7burner.put(pan);
8burner.light('medium');
9
10setTimeout(function() {
11if (!bacon.isCrispy()) {
12ruinedBaconCallback('Bit longer next time.');
13} else if (bacon.isBurnt()) {
14ruinedBaconCallback('Bit less next time.');
15} else {
16tastyBaconCallback(bacon);
17}
18}, cookTime);
19}
We do something like this:
231function cookBacon(cookTime) {
2var deferred = new $.Deferred();
3
4var burner = getStove().getBurner();
5var pan = getPan();
6var bacon = getBacon();
7
8pan.add(bacon);
9burner.put(pan);
10burner.light('medium');
11
12setTimeout(function() {
13if (!bacon.isCrispy()) {
14deferred.reject('Bit longer next time.');
15} else if (bacon.isBurnt()) {
16deferred.reject('Bit less next time.');
17} else {
18deferred.resolve(bacon);
19}
20}, cookTime);
21
22return deferred.promise();
23}
Note that the interface becomes simpler - there are no more callbacks required. If you want to wait for the bacon, you write code like:
51cookBacon(5 * 60 * 1000).then(function success(bacon) {
2eat(bacon);
3}, function failure(suggestion) {
4console.log(suggestion);
5})
The biggest advantages to Promises are seen when you begin to chain them together, or wait on multiple of them. Check out JQuery's Promise documentation for more information.
Events and AMD
Because AMD callbacks may not be called synchronously, it's recommended that you listen for events outside of a require call when you expect those events to occur before or soon after the page is loaded. The events module is one of a few that can be loaded synchronously.
E.g.
251(function() {
2var registry;
3var things;
4
5function tryInit() {
6if (registry && things) {
7things.foo();
8registry.enableContext('my-shortcut-context');
9}
10}
11
12// Synchronously get a reference to the `events` module.
13var events = require('bitbucket/util/events');
14// Listen for our event
15events.on('stash.widget.keyboard-shortcuts.register-contexts', function(r) {
16registry = r;
17tryInit();
18});
19
20// Require any dependencies we need separately from attaching our event listeners.
21require(['things'], function(t) {
22things = t;
23tryInit();
24});
25})();
This example could be simplified with the use of Promises as well:
161(function() {
2var gotThings = new $.Deferred();
3
4// Require any dependencies we need separately from attaching our event listeners.
5require(['things'], gotThings.resolve.bind(gotThings));
6
7// Synchronously get a reference to the `events` module.
8var events = require('bitbucket/util/events');
9// Listen for our event
10events.on('stash.widget.keyboard-shortcuts.register-contexts', function(registry) {
11gotThings.then(function(things) {
12things.foo();
13registry.enableContext('my-shortcut-context');
14});
15});
16})();