695 lines
20 KiB
JavaScript
695 lines
20 KiB
JavaScript
/*
|
|
* angular-ui-bootstrap
|
|
* http://angular-ui.github.io/bootstrap/
|
|
|
|
* Version: 0.11.2 - 2014-09-26
|
|
* License: MIT
|
|
* Changes: Added keyboard support for menu
|
|
*/
|
|
angular.module('ui.bootstrap.dropdown', [])
|
|
|
|
.constant('dropdownConfig', {
|
|
openClass: 'open'
|
|
})
|
|
|
|
.service('dropdownService', ['$document', function ($document) {
|
|
var openScope = null;
|
|
|
|
this.open = function (dropdownScope) {
|
|
if (!openScope) {
|
|
$document.bind('click', closeDropdown);
|
|
$document.bind('keydown', onKeydown);
|
|
}
|
|
|
|
if (openScope && openScope !== dropdownScope) {
|
|
openScope.isOpen = false;
|
|
}
|
|
|
|
openScope = dropdownScope;
|
|
};
|
|
|
|
this.close = function (dropdownScope) {
|
|
if (openScope === dropdownScope) {
|
|
openScope = null;
|
|
$document.unbind('click', closeDropdown);
|
|
$document.unbind('keydown', onKeydown);
|
|
}
|
|
};
|
|
|
|
var closeDropdown = function (evt) {
|
|
var toggleElement = openScope.getToggleElement();
|
|
if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
|
|
return;
|
|
}
|
|
|
|
openScope.$apply(function () {
|
|
openScope.isOpen = false;
|
|
});
|
|
};
|
|
|
|
var onKeydown = function (evt) {
|
|
if (evt.which === 40) {
|
|
try {
|
|
$(evt.target).parent().next().find('[role=menuitem],[role=menuitemcheckbox]')[0].focus();
|
|
} catch (ex) {
|
|
}
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
}
|
|
else if (evt.which === 38) {
|
|
try {
|
|
$(evt.target).parent().prev().find('[role=menuitem],[role=menuitemcheckbox]')[0].focus();
|
|
} catch (ex) {
|
|
}
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
}
|
|
else if (evt.which === 27) {
|
|
openScope.focusToggleElement();
|
|
closeDropdown();
|
|
}
|
|
else if (evt.which === 13) {
|
|
var target = evt.target;
|
|
if (target.getAttribute("role") === "menuitem" || target.getAttribute("role") === "menuitemcheckbox") {
|
|
setTimeout(function () {
|
|
try {
|
|
angular.element(target).trigger('click');
|
|
} catch (ex) {
|
|
}
|
|
}, 0);
|
|
}
|
|
}
|
|
|
|
};
|
|
}])
|
|
|
|
.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate',
|
|
function ($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) {
|
|
var self = this,
|
|
scope = $scope.$new(), // create a child scope so we are not polluting original one
|
|
openClass = dropdownConfig.openClass,
|
|
getIsOpen,
|
|
unregisterWatch,
|
|
setIsOpen = angular.noop,
|
|
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop;
|
|
|
|
this.init = function (element) {
|
|
self.$element = element;
|
|
|
|
if ($attrs.isOpen) {
|
|
getIsOpen = $parse($attrs.isOpen);
|
|
setIsOpen = getIsOpen.assign;
|
|
|
|
$scope.$watch(getIsOpen, function (value) {
|
|
scope.isOpen = !!value;
|
|
});
|
|
}
|
|
};
|
|
|
|
this.toggle = function (open) {
|
|
return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
|
|
};
|
|
|
|
// Allow other directives to watch status
|
|
this.isOpen = function () {
|
|
return scope.isOpen;
|
|
};
|
|
|
|
scope.getToggleElement = function () {
|
|
return self.toggleElement;
|
|
};
|
|
|
|
scope.focusToggleElement = function () {
|
|
if (self.toggleElement) {
|
|
self.toggleElement[0].focus();
|
|
}
|
|
};
|
|
|
|
scope.$watch('isOpen', function (isOpen, wasOpen) {
|
|
$animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass);
|
|
|
|
if (isOpen) {
|
|
scope.focusToggleElement();
|
|
dropdownService.open(scope);
|
|
|
|
if (!_.isEmpty(getFirstMenuItem())) {
|
|
setTimeout(focusFirstItem, 0);
|
|
} else {
|
|
unregisterWatch = scope.$watch(function () {
|
|
return self.$element.find('.dropdown-menu').children().length;
|
|
}, function () {
|
|
if (!_.isEmpty(getFirstMenuItem())) {
|
|
setTimeout(function () {
|
|
focusFirstItem();
|
|
unregisterWatch();
|
|
}, 0);
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
dropdownService.close(scope);
|
|
if (unregisterWatch) {
|
|
unregisterWatch();
|
|
}
|
|
}
|
|
|
|
setIsOpen($scope, isOpen);
|
|
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
|
|
toggleInvoker($scope, { open: !!isOpen });
|
|
}
|
|
});
|
|
|
|
$scope.$on('$locationChangeSuccess', function () {
|
|
scope.isOpen = false;
|
|
});
|
|
|
|
$scope.$on('$destroy', function () {
|
|
//if (stopTime) {
|
|
// $interval.cancel(stopTime);
|
|
//}
|
|
|
|
if (unregisterWatch) {
|
|
unregisterWatch();
|
|
}
|
|
scope.$destroy();
|
|
});
|
|
|
|
function getFirstMenuItem() {
|
|
return self.$element.find('.dropdown-menu [role=menuitem],[role=menuitemcheckbox], .dropdown-menu input').first();
|
|
}
|
|
|
|
function focusFirstItem() {
|
|
getFirstMenuItem().focus();
|
|
}
|
|
}])
|
|
|
|
.directive('dropdown', function () {
|
|
return {
|
|
restrict: 'CA',
|
|
controller: 'DropdownController',
|
|
link: function (scope, element, attrs, dropdownCtrl) {
|
|
dropdownCtrl.init(element);
|
|
}
|
|
};
|
|
})
|
|
|
|
.directive('dropdownToggle', function () {
|
|
return {
|
|
restrict: 'CA',
|
|
require: '?^dropdown',
|
|
link: function (scope, element, attrs, dropdownCtrl) {
|
|
if (!dropdownCtrl) {
|
|
return;
|
|
}
|
|
|
|
dropdownCtrl.toggleElement = element;
|
|
|
|
var toggleDropdown = function (event) {
|
|
if (event.which !== 13 && event.type === "keydown") {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
|
|
if (!element.hasClass('disabled') && !attrs.disabled) {
|
|
scope.$apply(function () {
|
|
dropdownCtrl.toggle();
|
|
});
|
|
}
|
|
};
|
|
|
|
element.bind('click', toggleDropdown);
|
|
element.bind('keydown', toggleDropdown);
|
|
|
|
// WAI-ARIA
|
|
element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
|
|
scope.$watch(dropdownCtrl.isOpen, function (isOpen) {
|
|
element.attr('aria-expanded', !!isOpen);
|
|
});
|
|
|
|
scope.$on('$destroy', function () {
|
|
element.unbind('click', toggleDropdown);
|
|
element.unbind('keydown', toggleDropdown);
|
|
});
|
|
}
|
|
};
|
|
});
|
|
|
|
angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
|
|
|
|
/**
|
|
* A helper service that can parse typeahead's syntax (string provided by users)
|
|
* Extracted to a separate service for ease of unit testing
|
|
*/
|
|
.factory('typeaheadParser', ['$parse', function ($parse) {
|
|
|
|
// 00000111000000000000022200000000000000003333333333333330000000000044000
|
|
var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
|
|
|
|
return {
|
|
parse: function (input) {
|
|
|
|
var match = input.match(TYPEAHEAD_REGEXP);
|
|
if (!match) {
|
|
throw new Error(
|
|
'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
|
|
' but got "' + input + '".');
|
|
}
|
|
|
|
return {
|
|
itemName: match[3],
|
|
source: $parse(match[4]),
|
|
viewMapper: $parse(match[2] || match[1]),
|
|
modelMapper: $parse(match[1])
|
|
};
|
|
}
|
|
};
|
|
}])
|
|
|
|
.directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
|
|
function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
|
|
|
|
var HOT_KEYS = [9, 13, 27, 38, 40];
|
|
|
|
return {
|
|
require: 'ngModel',
|
|
link: function (originalScope, element, attrs, modelCtrl) {
|
|
|
|
//SUPPORTED ATTRIBUTES (OPTIONS)
|
|
|
|
//minimal no of characters that needs to be entered before typeahead kicks-in
|
|
var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 0; //Viktor before: var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
|
|
|
|
//minimal wait time after last character typed before typeahead kicks-in
|
|
var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
|
|
|
|
//should it restrict model values to the ones selected from the popup only?
|
|
var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
|
|
|
|
//binding to a variable that indicates if matches are being retrieved asynchronously
|
|
var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
|
|
|
|
//a callback executed when a match is selected
|
|
var onSelectCallback = $parse(attrs.typeaheadOnSelect);
|
|
|
|
var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
|
|
|
|
var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
|
|
|
|
var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
|
|
|
|
//INTERNAL VARIABLES
|
|
|
|
//model setter executed upon match selection
|
|
var $setModelValue = $parse(attrs.ngModel).assign;
|
|
|
|
//expressions used by typeahead
|
|
var parserResult = typeaheadParser.parse(attrs.typeahead);
|
|
|
|
var hasFocus;
|
|
|
|
//create a child scope for the typeahead directive so we are not polluting original scope
|
|
//with typeahead-specific data (matches, query etc.)
|
|
var scope = originalScope.$new();
|
|
originalScope.$on('$destroy', function () {
|
|
scope.$destroy();
|
|
});
|
|
|
|
// WAI-ARIA
|
|
var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
|
|
element.attr({
|
|
'aria-autocomplete': 'list',
|
|
'aria-expanded': false,
|
|
'aria-owns': popupId
|
|
});
|
|
|
|
//pop-up element used to display matches
|
|
var popUpEl = angular.element('<div typeahead-popup></div>');
|
|
popUpEl.attr({
|
|
id: popupId,
|
|
matches: 'matches',
|
|
active: 'activeIdx',
|
|
select: 'select(activeIdx)',
|
|
query: 'query',
|
|
position: 'position'
|
|
});
|
|
//custom item template
|
|
if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
|
|
popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
|
|
}
|
|
|
|
var resetMatches = function () {
|
|
scope.matches = [];
|
|
scope.activeIdx = -1;
|
|
element.attr('aria-expanded', false);
|
|
};
|
|
|
|
var getMatchId = function (index) {
|
|
return popupId + '-option-' + index;
|
|
};
|
|
|
|
// Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
|
|
// This attribute is added or removed automatically when the `activeIdx` changes.
|
|
scope.$watch('activeIdx', function (index) {
|
|
if (index < 0) {
|
|
element.removeAttr('aria-activedescendant');
|
|
} else {
|
|
element.attr('aria-activedescendant', getMatchId(index));
|
|
}
|
|
});
|
|
|
|
var getMatchesAsync = function (inputValue) {
|
|
|
|
var locals = { $viewValue: inputValue };
|
|
isLoadingSetter(originalScope, true);
|
|
$q.when(parserResult.source(originalScope, locals)).then(function (matches) {
|
|
|
|
//it might happen that several async queries were in progress if a user were typing fast
|
|
//but we are interested only in responses that correspond to the current view value
|
|
var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
|
|
if (onCurrentRequest && hasFocus) {
|
|
if (matches && matches.length > 0) {
|
|
|
|
scope.activeIdx = focusFirst ? 0 : -1;
|
|
scope.matches.length = 0;
|
|
|
|
//transform labels
|
|
for (var i = 0; i < matches.length; i++) {
|
|
locals[parserResult.itemName] = matches[i];
|
|
scope.matches.push({
|
|
id: getMatchId(i),
|
|
label: parserResult.viewMapper(scope, locals),
|
|
model: matches[i]
|
|
});
|
|
}
|
|
|
|
scope.query = inputValue;
|
|
//position pop-up with matches - we need to re-calculate its position each time we are opening a window
|
|
//with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
|
|
//due to other elements being rendered
|
|
scope.position = appendToBody ? $position.offset(element) : $position.position(element);
|
|
scope.position.top = scope.position.top + element.prop('offsetHeight');
|
|
|
|
element.attr('aria-expanded', true);
|
|
} else {
|
|
resetMatches();
|
|
}
|
|
}
|
|
if (onCurrentRequest) {
|
|
isLoadingSetter(originalScope, false);
|
|
}
|
|
}, function () {
|
|
resetMatches();
|
|
isLoadingSetter(originalScope, false);
|
|
});
|
|
};
|
|
|
|
resetMatches();
|
|
|
|
//we need to propagate user's query so we can higlight matches
|
|
scope.query = undefined;
|
|
|
|
//Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
|
|
var timeoutPromise;
|
|
|
|
var scheduleSearchWithTimeout = function (inputValue) {
|
|
timeoutPromise = $timeout(function () {
|
|
getMatchesAsync(inputValue);
|
|
}, waitTime);
|
|
};
|
|
|
|
var cancelPreviousTimeout = function () {
|
|
if (timeoutPromise) {
|
|
$timeout.cancel(timeoutPromise);
|
|
}
|
|
};
|
|
|
|
//plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
|
|
//$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
|
|
modelCtrl.$parsers.unshift(function (inputValue) {
|
|
//Viktor new:
|
|
if (inputValue === ' ') {
|
|
inputValue = ''
|
|
modelCtrl.$setViewValue(inputValue);
|
|
return inputValue;
|
|
}
|
|
//Viktor new end
|
|
|
|
hasFocus = true;
|
|
|
|
if (minSearch === 0 || inputValue && inputValue.length >= minSearch) { // Viktor before: if (inputValue && inputValue.length >= minSearch) {
|
|
if (waitTime > 0) {
|
|
cancelPreviousTimeout();
|
|
scheduleSearchWithTimeout(inputValue);
|
|
} else {
|
|
getMatchesAsync(inputValue);
|
|
}
|
|
} else {
|
|
isLoadingSetter(originalScope, false);
|
|
cancelPreviousTimeout();
|
|
resetMatches();
|
|
}
|
|
|
|
if (isEditable) {
|
|
return inputValue;
|
|
} else {
|
|
if (!inputValue) {
|
|
// Reset in case user had typed something previously.
|
|
modelCtrl.$setValidity('editable', true);
|
|
return inputValue;
|
|
} else {
|
|
modelCtrl.$setValidity('editable', false);
|
|
return undefined;
|
|
}
|
|
}
|
|
});
|
|
|
|
modelCtrl.$formatters.push(function (modelValue) {
|
|
|
|
var candidateViewValue, emptyViewValue;
|
|
var locals = {};
|
|
|
|
// The validity may be set to false via $parsers (see above) if
|
|
// the model is restricted to selected values. If the model
|
|
// is set manually it is considered to be valid.
|
|
if (!isEditable) {
|
|
modelCtrl.$setValidity('editable', true);
|
|
}
|
|
|
|
if (inputFormatter) {
|
|
|
|
locals.$model = modelValue;
|
|
return inputFormatter(originalScope, locals);
|
|
|
|
} else {
|
|
|
|
//it might happen that we don't have enough info to properly render input value
|
|
//we need to check for this situation and simply return model value if we can't apply custom formatting
|
|
locals[parserResult.itemName] = modelValue;
|
|
candidateViewValue = parserResult.viewMapper(originalScope, locals);
|
|
locals[parserResult.itemName] = undefined;
|
|
emptyViewValue = parserResult.viewMapper(originalScope, locals);
|
|
|
|
return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
|
|
}
|
|
});
|
|
|
|
scope.select = function (activeIdx) {
|
|
//called from within the $digest() cycle
|
|
var locals = {};
|
|
var model, item;
|
|
|
|
locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
|
|
model = parserResult.modelMapper(originalScope, locals);
|
|
$setModelValue(originalScope, model);
|
|
modelCtrl.$setValidity('editable', true);
|
|
modelCtrl.$setValidity('parse', true);
|
|
|
|
onSelectCallback(originalScope, {
|
|
$item: item,
|
|
$model: model,
|
|
$label: parserResult.viewMapper(originalScope, locals)
|
|
});
|
|
|
|
resetMatches();
|
|
|
|
//return focus to the input element if a match was selected via a mouse click event
|
|
// use timeout to avoid $rootScope:inprog error
|
|
$timeout(function () {
|
|
element[0].focus();
|
|
}, 0, false);
|
|
};
|
|
|
|
//bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
|
|
element.bind('keydown', function (evt) {
|
|
|
|
//typeahead is open and an "interesting" key was pressed
|
|
if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
|
|
return;
|
|
}
|
|
|
|
// if there's nothing selected (i.e. focusFirst) and enter is hit, don't do anything
|
|
if (scope.activeIdx === -1 && (evt.which === 13 || evt.which === 9)) {
|
|
// Sun new : begin
|
|
// this is a known issue in typeahead, suggest list should be close if user hit
|
|
// enter or tab when using the typeahead-focus-first="false" option
|
|
// This fix will have no impact on all our current typeahead fields since we always use
|
|
// typeahead-focus-first="true" (default option)
|
|
// The only field we use the false option is the global search, which is what this fix is about
|
|
resetMatches();
|
|
scope.$digest();
|
|
// Sun new : end
|
|
return;
|
|
}
|
|
|
|
evt.preventDefault();
|
|
|
|
|
|
if (evt.which === 40) {
|
|
scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
|
|
scope.$digest();
|
|
|
|
} else if (evt.which === 38) {
|
|
scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
|
|
scope.$digest();
|
|
|
|
} else if (evt.which === 13 || evt.which === 9) {
|
|
|
|
scope.$apply(function () {
|
|
scope.select(scope.activeIdx);
|
|
});
|
|
|
|
} else if (evt.which === 27) {
|
|
evt.stopPropagation();
|
|
|
|
resetMatches();
|
|
scope.$digest();
|
|
}
|
|
});
|
|
|
|
element.bind('blur', function () {
|
|
hasFocus = false;
|
|
});
|
|
|
|
//Viktor new:
|
|
element.bind('focus', function () {
|
|
if (!modelCtrl.$viewValue && minSearch === 0) {
|
|
modelCtrl.$setViewValue(' ');
|
|
|
|
}
|
|
});
|
|
//Viktor new end
|
|
|
|
// Keep reference to click handler to unbind it.
|
|
var dismissClickHandler = function (evt) {
|
|
if (element[0] !== evt.target) {
|
|
resetMatches();
|
|
scope.$digest();
|
|
}
|
|
};
|
|
|
|
$document.bind('click', dismissClickHandler);
|
|
|
|
originalScope.$on('$destroy', function () {
|
|
$document.unbind('click', dismissClickHandler);
|
|
if (appendToBody) {
|
|
$popup.remove();
|
|
}
|
|
// Prevent jQuery cache memory leak
|
|
popUpEl.remove();
|
|
});
|
|
|
|
var $popup = $compile(popUpEl)(scope);
|
|
|
|
if (appendToBody) {
|
|
$document.find('body').append($popup);
|
|
} else {
|
|
element.after($popup);
|
|
}
|
|
}
|
|
};
|
|
|
|
}])
|
|
|
|
.directive('typeaheadPopup', function () {
|
|
return {
|
|
restrict: 'EA',
|
|
scope: {
|
|
matches: '=',
|
|
query: '=',
|
|
active: '=',
|
|
position: '=',
|
|
select: '&'
|
|
},
|
|
replace: true,
|
|
templateUrl: 'template/typeahead/typeahead-popup.html',
|
|
link: function (scope, element, attrs) {
|
|
|
|
scope.templateUrl = attrs.templateUrl;
|
|
|
|
scope.isOpen = function () {
|
|
return scope.matches.length > 0;
|
|
};
|
|
|
|
scope.isActive = function (matchIdx) {
|
|
return scope.active === matchIdx;
|
|
};
|
|
|
|
scope.selectActive = function (matchIdx) {
|
|
scope.active = matchIdx;
|
|
};
|
|
|
|
scope.selectMatch = function (activeIdx) {
|
|
scope.select({ activeIdx: activeIdx });
|
|
};
|
|
}
|
|
};
|
|
})
|
|
|
|
.directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', function ($templateRequest, $compile, $parse) {
|
|
return {
|
|
restrict: 'EA',
|
|
scope: {
|
|
index: '=',
|
|
match: '=',
|
|
query: '='
|
|
},
|
|
link: function (scope, element, attrs) {
|
|
var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
|
|
$templateRequest(tplUrl).then(function (tplContent) {
|
|
$compile(tplContent.trim())(scope, function (clonedElement) {
|
|
element.replaceWith(clonedElement);
|
|
});
|
|
});
|
|
}
|
|
};
|
|
}])
|
|
|
|
.filter('typeaheadHighlight', function () {
|
|
|
|
function escapeRegexp(queryToEscape) {
|
|
return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
|
|
}
|
|
|
|
return function (matchItem, query) {
|
|
return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
|
|
};
|
|
});
|
|
|
|
angular.module("template/tabs/tab.html", []).run(["$templateCache", function ($templateCache) {
|
|
$templateCache.put("template/tabs/tab.html",
|
|
"<li role=\"presentation\" ng-class=\"{active: active, disabled: disabled}\">\n" +
|
|
" <a role=\"tab\" href='javascript:' ng-click=\"select()\" aria-selected=\"{{active}}\" tab-heading-transclude>{{heading}}</a>\n" +
|
|
"</li>\n" +
|
|
"");
|
|
}]);
|
|
|
|
angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function ($templateCache) {
|
|
$templateCache.put("template/typeahead/typeahead-popup.html",
|
|
"<ul class=\"dropdown-menu\" ng-show=\"isOpen()\" ng-style=\"{top: position.top+'px', left: position.left+'px'}\" style=\"display: block;\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" +
|
|
" <li ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\" role=\"option\" id=\"{{match.id}}\">\n" +
|
|
" <div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
|
|
" </li>\n" +
|
|
"</ul>\n" +
|
|
"");
|
|
}]);
|