@atlassian/aui
Version:
Atlassian User Interface Framework
733 lines (642 loc) • 29.1 kB
JavaScript
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(['module', 'exports', './jquery', './internal/deprecation', './internal/log', './internal/globalize', '../../js-vendor/jquery/plugins/jquery.aop'], factory);
} else if (typeof exports !== "undefined") {
factory(module, exports, require('./jquery'), require('./internal/deprecation'), require('./internal/log'), require('./internal/globalize'), require('../../js-vendor/jquery/plugins/jquery.aop'));
} else {
var mod = {
exports: {}
};
factory(mod, mod.exports, global.jquery, global.deprecation, global.log, global.globalize, global.jquery);
global.dropDown = mod.exports;
}
})(this, function (module, exports, _jquery, _deprecation, _log, _globalize) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _jquery2 = _interopRequireDefault(_jquery);
var deprecate = _interopRequireWildcard(_deprecation);
var logger = _interopRequireWildcard(_log);
var _globalize2 = _interopRequireDefault(_globalize);
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
} else {
var newObj = {};
if (obj != null) {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
}
}
newObj.default = obj;
return newObj;
}
}
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
/**
* Displays a drop down, typically used for menus.
*
* @param obj {jQuery Object|String|Array} object to populate the drop down from.
* @param usroptions optional dropdown configuration. Supported properties are:
* <li>alignment - "left" or "right" alignment of the drop down</li>
* <li>escapeHandler - function to handle on escape key presses</li>
* <li>activeClass - class name to be added to drop down items when 'active' ie. hover over</li>
* <li>selectionHandler - function to handle when drop down items are selected on</li>
* <li>hideHandler - function to handle when the drop down is hidden</li>
* When an object of type Array is passed in, you can also configure:
* <li>isHiddenByDefault - set to true if you would like to hide the drop down on initialisation</li>
* <li>displayHandler - function to display text in the drop down</li>
* <li>useDisabled - If set to true, the dropdown will not appear if a class of disabled is added to aui-dd-parent</li>
*
* @returns {Array} an array of jQuery objects, referring to the drop down container elements
*/
function dropDown(obj, usroptions) {
var dd = null;
var result = [];
var moving = false;
var $doc = (0, _jquery2.default)(document);
var options = {
item: 'li:has(a)',
activeClass: 'active',
alignment: 'right',
displayHandler: function displayHandler(obj) {
return obj.name;
},
escapeHandler: function escapeHandler() {
this.hide('escape');
return false;
},
hideHandler: function hideHandler() {},
moveHandler: function moveHandler() {},
useDisabled: false
};
_jquery2.default.extend(options, usroptions);
options.alignment = { left: 'left', right: 'right' }[options.alignment.toLowerCase()] || 'left';
if (obj && obj.jquery) {
// if $
dd = obj;
} else if (typeof obj === 'string') {
// if $ selector
dd = (0, _jquery2.default)(obj);
} else if (obj && obj.constructor === Array) {
// if JSON
dd = (0, _jquery2.default)('<div></div>').addClass('aui-dropdown').toggleClass('hidden', !!options.isHiddenByDefault);
for (var i = 0, ii = obj.length; i < ii; i++) {
var ol = (0, _jquery2.default)('<ol></ol>');
for (var j = 0, jj = obj[i].length; j < jj; j++) {
var li = (0, _jquery2.default)('<li></li>');
var properties = obj[i][j];
if (properties.href) {
li.append((0, _jquery2.default)('<a></a>').html('<span>' + options.displayHandler(properties) + '</span>').attr({ href: properties.href }).addClass(properties.className));
// deprecated - use the properties on the li, not the span
_jquery2.default.data((0, _jquery2.default)('a > span', li)[0], 'properties', properties);
} else {
li.html(properties.html).addClass(properties.className);
}
if (properties.icon) {
li.prepend((0, _jquery2.default)('<img />').attr('src', properties.icon));
}
if (properties.insideSpanIcon) {
li.children('a').prepend((0, _jquery2.default)('<span></span>').attr('class', 'icon'));
}
if (properties.iconFontClass) {
li.children('a').prepend((0, _jquery2.default)('<span></span>').addClass('aui-icon aui-icon-small aui-iconfont-' + properties.iconFontClass));
}
_jquery2.default.data(li[0], 'properties', properties);
ol.append(li);
}
if (i === ii - 1) {
ol.addClass('last');
}
dd.append(ol);
}
(0, _jquery2.default)('body').append(dd);
} else {
throw new Error('dropDown function was called with illegal parameter. Should be $ object, $ selector or array.');
}
var moveDown = function moveDown() {
move(+1);
};
var moveUp = function moveUp() {
move(-1);
};
var move = function move(dir) {
var trigger = !moving;
var cdd = dropDown.current.$[0];
var links = dropDown.current.links;
var oldFocus = cdd.focused;
moving = true;
if (links.length === 0) {
// Nothing to move focus to. Abort.
return;
}
cdd.focused = typeof oldFocus === 'number' ? oldFocus : -1;
if (!dropDown.current) {
logger.log('move - not current, aborting');
return true;
}
cdd.focused += dir;
// Resolve out of bounds values:
if (cdd.focused < 0) {
cdd.focused = links.length - 1;
} else if (cdd.focused >= links.length) {
cdd.focused = 0;
}
options.moveHandler((0, _jquery2.default)(links[cdd.focused]), dir < 0 ? 'up' : 'down');
if (trigger && links.length) {
(0, _jquery2.default)(links[cdd.focused]).addClass(options.activeClass);
moving = false;
} else if (!links.length) {
moving = false;
}
};
var moveFocus = function moveFocus(e) {
if (!dropDown.current) {
return true;
}
var c = e.which;
var cdd = dropDown.current.$[0];
var links = dropDown.current.links;
dropDown.current.cleanActive();
switch (c) {
case 40:
{
moveDown();
break;
}
case 38:
{
moveUp();
break;
}
case 27:
{
return options.escapeHandler.call(dropDown.current, e);
}
case 13:
{
if (cdd.focused >= 0) {
if (!options.selectionHandler) {
if ((0, _jquery2.default)(links[cdd.focused]).attr('nodeName') != 'a') {
return (0, _jquery2.default)('a', links[cdd.focused]).trigger('focus'); //focus on the "a" within the parent item elements
} else {
return (0, _jquery2.default)(links[cdd.focused]).trigger('focus'); //focus on the "a"
}
} else {
return options.selectionHandler.call(dropDown.current, e, (0, _jquery2.default)(links[cdd.focused])); //call the selection handler
}
}
return true;
}
default:
{
if (links.length) {
(0, _jquery2.default)(links[cdd.focused]).addClass(options.activeClass);
}
return true;
}
}
e.stopPropagation();
e.preventDefault();
return false;
};
var hider = function hider(e) {
if (!(e && e.which && e.which == 3 || e && e.button && e.button == 2 || false)) {
// right click check
if (dropDown.current) {
dropDown.current.hide('click');
}
}
};
var active = function active(i) {
return function () {
if (!dropDown.current) {
return;
}
dropDown.current.cleanFocus();
this.originalClass = this.className;
(0, _jquery2.default)(this).addClass(options.activeClass);
dropDown.current.$[0].focused = i;
};
};
var handleClickSelection = function handleClickSelection(e) {
if (e.button || e.metaKey || e.ctrlKey || e.shiftKey) {
return true;
}
if (dropDown.current && options.selectionHandler) {
options.selectionHandler.call(dropDown.current, e, (0, _jquery2.default)(this));
}
};
var isEventsBound = function isEventsBound(el) {
var bound = false;
if (el.data('events')) {
_jquery2.default.each(el.data('events'), function (i, handler) {
_jquery2.default.each(handler, function (type, handler) {
if (handleClickSelection === handler) {
bound = true;
return false;
}
});
});
}
return bound;
};
dd.each(function () {
var cdd = this;
var $cdd = (0, _jquery2.default)(this);
var res = {};
var methods = {
reset: function reset() {
res = _jquery2.default.extend(res, {
$: $cdd,
links: (0, _jquery2.default)(options.item || 'li:has(a)', cdd),
cleanActive: function cleanActive() {
if (cdd.focused + 1 && res.links.length) {
(0, _jquery2.default)(res.links[cdd.focused]).removeClass(options.activeClass);
}
},
cleanFocus: function cleanFocus() {
res.cleanActive();
cdd.focused = -1;
},
moveDown: moveDown,
moveUp: moveUp,
moveFocus: moveFocus,
getFocusIndex: function getFocusIndex() {
return typeof cdd.focused === 'number' ? cdd.focused : -1;
}
});
res.links.each(function (i) {
var $this = (0, _jquery2.default)(this);
if (!isEventsBound($this)) {
$this.hover(active(i), res.cleanFocus);
$this.click(handleClickSelection);
}
});
},
appear: function appear(dir) {
if (dir) {
$cdd.removeClass('hidden');
//handle left or right alignment
$cdd.addClass('aui-dropdown-' + options.alignment);
} else {
$cdd.addClass('hidden');
}
},
fade: function fade(dir) {
if (dir) {
$cdd.fadeIn('fast');
} else {
$cdd.fadeOut('fast');
}
},
scroll: function scroll(dir) {
if (dir) {
$cdd.slideDown('fast');
} else {
$cdd.slideUp('fast');
}
}
};
res.reset = methods.reset;
res.reset();
/**
* Uses Aspect Oriented Programming (AOP) to wrap a method around another method
* Allows control of the execution of the wrapped method.
* specified method has returned @see $.aop
* @method addControlProcess
* @param {String} methodName - Name of a public method
* @param {Function} callback - Function to be executed
* @return {Array} weaved aspect
*/
res.addControlProcess = function (method, process) {
_jquery2.default.aop.around({ target: this, method: method }, process);
};
/**
* Uses Aspect Oriented Programming (AOP) to insert callback <em>after</em> the
* specified method has returned @see $.aop
* @method addCallback
* @param {String} methodName - Name of a public method
* @param {Function} callback - Function to be executed
* @return {Array} weaved aspect
*/
res.addCallback = function (method, callback) {
return _jquery2.default.aop.after({ target: this, method: method }, callback);
};
res.show = function (method) {
if (options.useDisabled && this.$.closest('.aui-dd-parent').hasClass('disabled')) {
return;
}
this.alignment = options.alignment;
hider();
dropDown.current = this;
this.method = method || this.method || 'appear';
this.timer = setTimeout(function () {
$doc.click(hider);
}, 0);
$doc.keydown(moveFocus);
if (options.firstSelected && this.links[0]) {
active(0).call(this.links[0]);
}
(0, _jquery2.default)(cdd.offsetParent).css({ zIndex: 2000 });
methods[this.method](true);
(0, _jquery2.default)(document).trigger('showLayer', ['dropdown', dropDown.current]);
};
res.hide = function (causer) {
this.method = this.method || 'appear';
(0, _jquery2.default)($cdd.get(0).offsetParent).css({ zIndex: '' });
this.cleanFocus();
methods[this.method](false);
$doc.unbind('click', hider).unbind('keydown', moveFocus);
(0, _jquery2.default)(document).trigger('hideLayer', ['dropdown', dropDown.current]);
dropDown.current = null;
return causer;
};
res.addCallback('reset', function () {
if (options.firstSelected && this.links[0]) {
active(0).call(this.links[0]);
}
});
if (!dropDown.iframes) {
dropDown.iframes = [];
}
dropDown.createShims = function createShims() {
(0, _jquery2.default)('iframe').each(function (idx) {
var iframe = this;
if (!iframe.shim) {
iframe.shim = (0, _jquery2.default)('<div />').addClass('shim hidden').appendTo('body');
dropDown.iframes.push(iframe);
}
});
return createShims;
}();
res.addCallback('show', function () {
(0, _jquery2.default)(dropDown.iframes).each(function () {
var $this = (0, _jquery2.default)(this);
if ($this.is(':visible')) {
var offset = $this.offset();
offset.height = $this.height();
offset.width = $this.width();
this.shim.css({
left: offset.left + 'px',
top: offset.top + 'px',
height: offset.height + 'px',
width: offset.width + 'px'
}).removeClass('hidden');
}
});
});
res.addCallback('hide', function () {
(0, _jquery2.default)(dropDown.iframes).each(function () {
this.shim.addClass('hidden');
});
options.hideHandler();
});
result.push(res);
});
return result;
};
/**
* For the given item in the drop down get the value of the named additional property. If there is no
* property with the specified name then null will be returned.
*
* @method getAdditionalPropertyValue
* @namespace dropDown
* @param item {Object} jQuery Object of the drop down item. An LI element is expected.
* @param name {String} name of the property to retrieve
*/
dropDown.getAdditionalPropertyValue = function (item, name) {
var el = item[0];
if (!el || typeof el.tagName !== 'string' || el.tagName.toLowerCase() !== 'li') {
// we are moving the location of the properties and want to deprecate the attachment to the span
// but are unsure where and how its being called so for now we just log
logger.log('dropDown.getAdditionalPropertyValue : item passed in should be an LI element wrapped by jQuery');
}
var properties = _jquery2.default.data(el, 'properties');
return properties ? properties[name] : null;
};
/**
* Only here for backwards compatibility
* @method removeAllAdditionalProperties
* @namespace dropDown
* @deprecated Since 3.0
*/
dropDown.removeAllAdditionalProperties = function (item) {};
/**
* Base dropdown control. Enables you to identify triggers that when clicked, display dropdown.
*
* @class Standard
* @constructor
* @namespace dropDown
* @param {Object} usroptions
* @return {Object
*/
dropDown.Standard = function (usroptions) {
var res = [];
var options = {
selector: '.aui-dd-parent',
dropDown: '.aui-dropdown',
trigger: '.aui-dd-trigger'
};
var dropdownParents;
// extend defaults with user options
_jquery2.default.extend(options, usroptions);
var hookUpDropDown = function hookUpDropDown($trigger, $parent, $dropdown, ddcontrol) {
// extend to control to have any additional properties/methods
_jquery2.default.extend(ddcontrol, { trigger: $trigger });
// flag it to prevent additional dd controls being applied
$parent.addClass('dd-allocated');
//hide dropdown if not already hidden
$dropdown.addClass('hidden');
//show the dropdown if isHiddenByDefault is set to false
if (options.isHiddenByDefault == false) {
ddcontrol.show();
}
ddcontrol.addCallback('show', function () {
$parent.addClass('active');
});
ddcontrol.addCallback('hide', function () {
$parent.removeClass('active');
});
};
var handleEvent = function handleEvent(event, $trigger, $dropdown, ddcontrol) {
if (ddcontrol != dropDown.current) {
$dropdown.css({ top: $trigger.outerHeight() });
ddcontrol.show();
event.stopImmediatePropagation();
}
event.preventDefault();
};
if (options.useLiveEvents) {
// cache arrays so that we don't have to recalculate the dropdowns. Since we can't store objects as keys in a map,
// we have two arrays: keysCache stores keys of dropdown triggers; valuesCache stores a map of internally used objects
var keysCache = [];
var valuesCache = [];
(0, _jquery2.default)(options.trigger).live('click', function (event) {
var $trigger = (0, _jquery2.default)(this);
var $parent;
var $dropdown;
var ddcontrol;
// if we're cached, don't recalculate the dropdown and do all that funny shite.
var index;
if ((index = _jquery2.default.inArray(this, keysCache)) >= 0) {
var val = valuesCache[index];
$parent = val.parent;
$dropdown = val.dropdown;
ddcontrol = val.ddcontrol;
} else {
$parent = $trigger.closest(options.selector);
$dropdown = $parent.find(options.dropDown);
// Sanity checking
if ($dropdown.length === 0) {
return;
}
ddcontrol = dropDown($dropdown, options)[0];
// Sanity checking
if (!ddcontrol) {
return;
}
// cache
keysCache.push(this);
val = {
parent: $parent,
dropdown: $dropdown,
ddcontrol: ddcontrol
};
hookUpDropDown($trigger, $parent, $dropdown, ddcontrol);
valuesCache.push(val);
}
handleEvent(event, $trigger, $dropdown, ddcontrol);
});
} else {
// handling for jQuery collections
if (this instanceof _jquery2.default) {
dropdownParents = this;
// handling for selectors
} else {
dropdownParents = (0, _jquery2.default)(options.selector);
}
// a series of checks to ensure we are dealing with valid dropdowns
dropdownParents = dropdownParents.not('.dd-allocated').filter(':has(' + options.dropDown + ')').filter(':has(' + options.trigger + ')');
dropdownParents.each(function () {
var $parent = (0, _jquery2.default)(this);
var $dropdown = (0, _jquery2.default)(options.dropDown, this);
var $trigger = (0, _jquery2.default)(options.trigger, this);
var ddcontrol = dropDown($dropdown, options)[0];
// extend to control to have any additional properties/methods
_jquery2.default.extend(ddcontrol, { trigger: $trigger });
hookUpDropDown($trigger, $parent, $dropdown, ddcontrol);
$trigger.click(function (e) {
handleEvent(e, $trigger, $dropdown, ddcontrol);
});
// add control to the response
res.push(ddcontrol);
});
}
return res;
};
/**
* A NewStandard dropdown, however, with the ability to populate its content's via ajax.
*
* @class Ajax
* @constructor
* @namespace dropDown
* @param {Object} options
* @return {Object} dropDown instance
*/
dropDown.Ajax = function (usroptions) {
var dropdowns;
var options = { cache: true };
// extend defaults with user options
_jquery2.default.extend(options, usroptions || {});
// we call with "this" in case we are called in the context of a jQuery collection
dropdowns = dropDown.Standard.call(this, options);
(0, _jquery2.default)(dropdowns).each(function () {
var ddcontrol = this;
_jquery2.default.extend(ddcontrol, {
getAjaxOptions: function getAjaxOptions(opts) {
var success = function success(response) {
if (options.formatResults) {
response = options.formatResults(response);
}
if (options.cache) {
ddcontrol.cache.set(ddcontrol.getAjaxOptions(), response);
}
ddcontrol.refreshSuccess(response);
};
if (options.ajaxOptions) {
if (_jquery2.default.isFunction(options.ajaxOptions)) {
return _jquery2.default.extend(options.ajaxOptions.call(ddcontrol), { success: success });
} else {
return _jquery2.default.extend(options.ajaxOptions, { success: success });
}
}
return _jquery2.default.extend(opts, { success: success });
},
refreshSuccess: function refreshSuccess(response) {
this.$.html(response);
},
cache: function () {
var c = {};
return {
get: function get(ajaxOptions) {
var data = ajaxOptions.data || '';
return c[(ajaxOptions.url + data).replace(/[\?\&]/gi, '')];
},
set: function set(ajaxOptions, responseData) {
var data = ajaxOptions.data || '';
c[(ajaxOptions.url + data).replace(/[\?\&]/gi, '')] = responseData;
},
reset: function reset() {
c = {};
}
};
}(),
show: function (superMethod) {
return function () {
if (options.cache && !!ddcontrol.cache.get(ddcontrol.getAjaxOptions())) {
ddcontrol.refreshSuccess(ddcontrol.cache.get(ddcontrol.getAjaxOptions()));
superMethod.call(ddcontrol);
} else {
(0, _jquery2.default)(_jquery2.default.ajax(ddcontrol.getAjaxOptions())).throbber({ target: ddcontrol.$,
end: function end() {
ddcontrol.reset();
}
});
superMethod.call(ddcontrol);
if (ddcontrol.iframeShim) {
ddcontrol.iframeShim.hide();
}
}
};
}(ddcontrol.show),
resetCache: function resetCache() {
ddcontrol.cache.reset();
}
});
ddcontrol.addCallback('refreshSuccess', function () {
ddcontrol.reset();
});
});
return dropdowns;
};
// OMG. No. Just no.
_jquery2.default.fn.dropDown = function (type, options) {
type = (type || 'Standard').replace(/^([a-z])/, function (match) {
return match.toUpperCase();
});
return dropDown[type].call(this, options);
};
_jquery2.default.fn.dropDown = deprecate.construct(_jquery2.default.fn.dropDown, 'Dropdown constructor', {
alternativeName: 'Dropdown2'
});
(0, _globalize2.default)('dropDown', dropDown);
exports.default = dropDown;
module.exports = exports['default'];
});
//# sourceMappingURL=drop-down.js.map