define('jira/ajs/list/list', ['jira/util/logger', 'jira/util/formatter', 'jira/util/strings', 'jira/ajs/control', 'jira/ajs/list/group-descriptor', 'jira/ajs/list/item-descriptor', 'jira/ajs/input/mouse', 'jira/util/assistive', 'jira/util/browser', 'jquery', 'underscore'], function (logger, formatter, strings, Control, GroupDescriptor, ItemDescriptor, Mouse, Assistive, Browser, jQuery, _) { 'use strict'; function buildElementId(id) { return id.trim().toLowerCase().replace(/[\s\.]+/g, "-"); } // id of particular item on the list // this is simply incremented on render and is needed to make sure element's id is unique var globalItemId = 0; /** * @class List * @extends Control * @deprecated Use {@link Group} instead */ return Control.extend({ MAX_RESULT_LIMIT: 2000, init: function init(options) { options = options || {}; if (options) { this.options = jQuery.extend(true, this._getDefaultOptions(options), options); } else { this.options = this._getDefaultOptions(options); } this.maxInlineResultsDisplayed = this.options.maxInlineResultsDisplayed || this.MAX_RESULT_LIMIT; this._renders = jQuery.extend({}, this._renders, this.options.renderers); this.disabled = true; this.reset(); var instance = this; if (this.options.selectionHandler) { this.$container.delegate(this.options.itemSelector, this.options.selectionEvent, function (e) { instance.options.selectionHandler.call(instance, e); }); } // Initialise the mouse motion detector. this.motionDetector.wait(); this.$container.delegate(this.options.itemSelector, "mousemove", function () { if (!instance.disabled && (instance.motionDetector.moved || Browser.isSelenium())) { // too hard to simulate mouse move reliably in selenium instance.index = jQuery.inArray(this, instance.$visibleItems); instance.focus(true); } }); }, _getDefaultOptions: function _getDefaultOptions() { return { selectionEvent: "click", delegateTarget: document, matchingStrategy: "(^|.*?(\\s+|\\())({0})(.*)", // match start of words, including after '(' itemSelector: "li.aui-list-item", listItemTag: "a", hasLinks: true, stallEventBind: true, expandAllResults: false, suggestionRole: "option", subtextPrefix: "" }; }, index: 0, /** * Focuses first item element in list. If the mutliselectMode is set to true will toogle that elements focused state. */ moveToFirst: function moveToFirst() { if (this.$visibleItems.length > 0) { this.index = 0; this.focus(); } }, /** * Focuses item element in list. If the mutliselectMode is set to true will toogle that elements focused state. */ moveToNext: function moveToNext() { if (this.index < this.maxIndex) { ++this.index; // increase the index this.focus(); // focus it } else if (this.options.expandAllResults && this.hasTooManySuggestions) { this._expandAllResults(); ++this.index; // increase the index this.focus(); // focus it } else if (this.$visibleItems.length > 1) { this.index = 0; this.focus(); } }, container: function container(_container) { if (_container) { this.$container = jQuery(_container); this.containerSelector = _container; } else { return this.$container; } }, getItemsByDescriptor: function getItemsByDescriptor(descriptorToMatch) { var $matches = jQuery(); this.$container.find(this.options.itemSelector).each(function () { var descriptor = jQuery.data(this, "descriptor"); if (descriptor && descriptor.value() === descriptorToMatch.value()) { $matches = $matches.add(this); } }); return $matches; }, scrollContainer: function scrollContainer() { return this.options.scrollContainer ? this.$container.find(this.options.scrollContainer) : this.$container.parent(); }, /** * @private * @return {boolean} * * Indicates whether the scrollContainer or the document should be * scrolled. If a scroll container is declared explicitly, or a * dialog is currently limiting the viewport's scrolling, this * method will return true. */ _isScrollConstrained: function _isScrollConstrained() { var scrollContainer = this.scrollContainer()[0]; return scrollContainer.clientHeight < scrollContainer.scrollHeight; }, /** * Focuses item element in list. If the mutliselectMode is set to true will toogle that elements focused state. */ moveToPrevious: function moveToPrevious() { if (this.index > 0) { --this.index; // decrease the index this.focus(); } else if (this.$visibleItems.length > 0) { this.index = this.$visibleItems.length - 1; this.focus(); } }, /** * Scroll the container to ensure the active element is visible. * * Notes: * * 1. The scrolling algorithm chosen depends on the value of * this._isScrollConstrained(). If constrained, we cannot * scroll the documentElement, so instead we scroll * this.scrollContainer(). * * 2. We assume $scrollContainer has borderTop, borderLeft, * paddingTop, paddingLeft are all zero. * * 3. We assume the active element will fit vertically inside * the scrolling region. */ scrollActiveItemIntoView: function scrollActiveItemIntoView() { var $activeItem = this.$visibleItems.filter(".active"); var $scrollContainer = this.scrollContainer(); var scrollTop = $scrollContainer.scrollTop(); if ($activeItem.length === 0) { // Sanity check. return; } if (!this._isScrollConstrained()) { // Scrolling is unconstrained, so we are allowed to scroll the // documentElement to ensure $activeItem is in view. $activeItem.scrollIntoView({ callback: jQuery.proxy(this.motionDetector, "wait") }); return; } // Scrolling is constrained to within $scrollContainer so assume // it's scrollable and scroll it in place to ensure $activeItem is // in view. if ($activeItem.closest($scrollContainer).length === 0) { // If $activeItem is not a descendant of $scrollContainer, we // don't need to scroll. (Most likely, $activeItem resides in // the ".aui-list-fixed" container.) return; } if ($activeItem.is($scrollContainer.find(this.items).first())) { // Take a shortcut: If $activeItem is the first selectable item // in $scrollContainer, just scroll straight to the top. This // ensures optgroup headings are visible when autoscrolling. this._scrollContainerTo(0); return; } var roomAbove = $activeItem.offset().top - $scrollContainer.offset().top; var roomBelow = $scrollContainer.outerHeight() - $activeItem.outerHeight() - roomAbove; if (roomAbove >= 0) { if (roomBelow < 0) { // Scroll $scrollContainer so that $activeItem aligns with // the baseline of $scrollContainer's scrolling region. this._scrollContainerTo(scrollTop - roomBelow); } } else { // Scroll $scrollContainer so that $activeItem aligns with the // top of $scrollContainer's scrolling region. this._scrollContainerTo(scrollTop + roomAbove); } }, /** * Helper method to ensure autoscrolling always evades mousemove events. * @private * @param {number} scrollTop */ _scrollContainerTo: function _scrollContainerTo(scrollTop) { var $scrollContainer = this.scrollContainer(); if ($scrollContainer.scrollTop() !== scrollTop) { this.motionDetector.unbind(); this.motionDetector.wait(); $scrollContainer.scrollTop(scrollTop); } }, /** * Focus the item at this.index. * * @param {boolean=} noScroll * -- whether to automatically scroll the active item into view */ focus: function focus(noScroll) { this.$visibleItems.removeClass("active"); var $target = this.$visibleItems.eq(this.index); $target.addClass("active"); this.lastFocusedItemDescriptor = $target.data("descriptor"); this.trigger("itemFocus", $target, this.lastFocusedItemDescriptor); if (!noScroll) { this.scrollActiveItemIntoView(); } }, motionDetector: new Mouse.MotionDetector(), disable: function disable() { if (this.disabled) { return; } this._unassignEvents("delegateTarget", this.options.delegateTarget); this.disabled = true; this.lastFocusedItemDescriptor = null; }, enable: function enable() { var instance = this; if (!instance.disabled) { return; } if (this.options.stallEventBind) { window.setTimeout(function () { instance._assignEvents("delegateTarget", instance.options.delegateTarget); }, 0); } else { instance._assignEvents("delegateTarget", instance.options.delegateTarget); } instance.disabled = false; this._scrollContainerTo(0); }, getFocused: function getFocused() { return this.$visibleItems.filter(".active"); }, reset: function reset(index) { this.$container = jQuery(this.options.containerSelector); this.items = jQuery(this.options.itemSelector, this.$container).not(".no-suggestions"); this.$visibleItems = this._computeVisibleItems(); this.groups = jQuery(this.options.groupSelector, this.$container); this.maxIndex = this.$visibleItems.length - 1; this.index = this.$visibleItems[index] ? index : 0; this.focus(true); }, _computeVisibleItems: function _computeVisibleItems() { return this.items.not(".hidden, .disabled"); }, selectValue: function selectValue(value) { var matchedItem = this.$container.find(this.options.itemSelector).filter(function () { return jQuery(this).parent().data('descriptor').value() === value; }); if (!matchedItem.length) { logger.log("WARN: No List item found with Decriptor value '" + value + "'"); } matchedItem.click(); }, _getLinkFromItem: function _getLinkFromItem(item) { var link; item = jQuery(item); if (item.is("a")) { link = item; } else { link = item.find("a"); } return link; }, _makeResultDiv: function _makeResultDiv(data, query) { var $fixedContainer = jQuery('