Bitbucket Data Center exposes a JavaScript API for use in building your plugin's UI and behavior in the browser.
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.
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.
Our JavaScript APIs are delivered using Asynchronous 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.
1 2require(['bitbucket/feature/files/file-handlers', 'some/other/module'], function(fileHandlers, someOtherModule) { // do things with the file-handlers module and some other module. });
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()
)
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.
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)
1 2define('mycompany/things/helper', ['any', 'module', 'dependencies'], function(any, module, dependencies) { var helper = { ... }; return helper; });
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.
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.
_
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.
Public data properties will guarantee the following between minor versions:
Number
, Boolean
, String
, Array
, Date
, Function
or other Object
, it will continue to be typeof
or instanceof
that type.instanceof Object
(including Arrays, Dates, and Functions), these stability guarantees apply recursively to all non-private properties on that object.When public functions are called, we guarantee the following between minor versions:
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+.
The following uses are explicitly out of scope for the API. Their behavior is undefined and we make no guarantees about their stability.
foo.doSomething()
) with any this
value that isn't that exact object (e.g. foo.doSomething.call(bar)
).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.
threeArgFunc(one, two, three, four)
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).
func({ validOption: 'a', somethingExtra : 'invalid' })
When omitting an optional argument or argument property in a function call, the default behavior of that function may change (but will remain valid).
getPrimaryColor() === 'blue'
getPrimaryColor() === 'yellow'
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 by get
/set
pairs, may change enumerable
or writable
values, etc).
There are a few useful patterns when working against the Bitbucket JS API. Some are also useful in JS at large.
When using an AMD module, your code is within a closure, and thus is protected and (somewhat) sandboxed from the global environment. E.g.,
1 2require(['some', 'modules'], function() { // code in here can be isolated });
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:
1 2(function() { // this code has the same isolation benefits. })();
This ensures that you won't inadvertently create any global variables that have naming conflicts with another plugin.
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:
1 2function cookBacon(cookTime, tastyBaconCallback, ruinedBaconCallback) { var burner = getStove().getBurner(); var pan = getPan(); var bacon = getBacon(); pan.add(bacon); burner.put(pan); burner.light('medium'); setTimeout(function() { if (!bacon.isCrispy()) { ruinedBaconCallback('Bit longer next time.'); } else if (bacon.isBurnt()) { ruinedBaconCallback('Bit less next time.'); } else { tastyBaconCallback(bacon); } }, cookTime); }
We do something like this:
1 2function cookBacon(cookTime) { var deferred = new $.Deferred(); var burner = getStove().getBurner(); var pan = getPan(); var bacon = getBacon(); pan.add(bacon); burner.put(pan); burner.light('medium'); setTimeout(function() { if (!bacon.isCrispy()) { deferred.reject('Bit longer next time.'); } else if (bacon.isBurnt()) { deferred.reject('Bit less next time.'); } else { deferred.resolve(bacon); } }, cookTime); return deferred.promise(); }
Note that the interface becomes simpler - there are no more callbacks required. If you want to wait for the bacon, you write code like:
1 2cookBacon(5 * 60 * 1000).then(function success(bacon) { eat(bacon); }, function failure(suggestion) { console.log(suggestion); })
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.
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.
1 2(function() { var registry; var things; function tryInit() { if (registry && things) { things.foo(); registry.enableContext('my-shortcut-context'); } } // Synchronously get a reference to the `events` module. var events = require('bitbucket/util/events'); // Listen for our event events.on('stash.widget.keyboard-shortcuts.register-contexts', function(r) { registry = r; tryInit(); }); // Require any dependencies we need separately from attaching our event listeners. require(['things'], function(t) { things = t; tryInit(); }); })();
This example could be simplified with the use of Promises as well:
1 2(function() { var gotThings = new $.Deferred(); // Require any dependencies we need separately from attaching our event listeners. require(['things'], gotThings.resolve.bind(gotThings)); // Synchronously get a reference to the `events` module. var events = require('bitbucket/util/events'); // Listen for our event events.on('stash.widget.keyboard-shortcuts.register-contexts', function(registry) { gotThings.then(function(things) { things.foo(); registry.enableContext('my-shortcut-context'); }); }); })();
Rate this page: