@spalger/kibana
Version:
Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elastic
242 lines (197 loc) • 6.86 kB
JavaScript
define(function (require) {
var _ = require('lodash');
var typeahead = require('ui/modules').get('kibana/typeahead');
require('ui/typeahead/typeahead.less');
require('ui/typeahead/_input');
require('ui/typeahead/_items');
typeahead.directive('kbnTypeahead', function () {
var keyMap = {
ESC: 27,
UP: 38,
DOWN: 40,
TAB: 9,
ENTER: 13
};
return {
restrict: 'A',
scope: {
historyKey: ''
},
controllerAs: 'typeahead',
controller: function ($scope, $element, $timeout, PersistedLog, config) {
var self = this;
self.form = $element.closest('form');
self.query = '';
self.hidden = true;
self.focused = false;
self.mousedOver = false;
// instantiate history and add items to the scope
self.history = new PersistedLog('typeahead:' + $scope.historyKey, {
maxLength: config.get('history:limit'),
filterDuplicates: true
});
$scope.items = self.history.get();
$scope.filteredItems = [];
self.setInputModel = function (model) {
$scope.inputModel = model;
// watch for changes to the query parameter, delegate to typeaheadCtrl
$scope.$watch('inputModel.$viewValue', self.filterItemsByQuery);
};
self.setHidden = function (hidden) {
self.hidden = !!(hidden);
};
self.setFocused = function (focused) {
self.focused = !!(focused);
};
self.setMouseover = function (mousedOver) {
self.mousedOver = !!(mousedOver);
};
// activation methods
self.activateItem = function (item) {
self.active = item;
};
self.getActiveIndex = function () {
if (!self.active) {
return;
}
return $scope.filteredItems.indexOf(self.active);
};
self.getItems = function () {
return $scope.filteredItems;
};
self.activateNext = function () {
var index = self.getActiveIndex();
if (index == null) {
index = 0;
} else if (index < $scope.filteredItems.length - 1) {
++index;
}
self.activateItem($scope.filteredItems[index]);
};
self.activatePrev = function () {
var index = self.getActiveIndex();
if (index > 0 && index != null) {
--index;
} else if (index === 0) {
self.active = false;
return;
}
self.activateItem($scope.filteredItems[index]);
};
self.isActive = function (item) {
return item === self.active;
};
// selection methods
self.selectItem = function (item, ev) {
self.hidden = true;
self.active = false;
$scope.inputModel.$setViewValue(item);
$scope.inputModel.$render();
self.persistEntry();
if (ev && ev.type === 'click') {
$timeout(function () {
self.submitForm();
});
}
};
self.submitForm = function () {
if (self.form.length) {
self.form.submit();
}
};
self.persistEntry = function () {
if ($scope.inputModel.$viewValue.length) {
// push selection into the history
$scope.items = self.history.add($scope.inputModel.$viewValue);
}
};
self.selectActive = function () {
if (self.active) {
self.selectItem(self.active);
}
};
self.keypressHandler = function (ev) {
var keyCode = ev.which || ev.keyCode;
if (self.focused) {
self.hidden = false;
}
// hide on escape
if (_.contains([keyMap.ESC], keyCode)) {
self.hidden = true;
self.active = false;
}
// change selection with arrow up/down
// on down key, attempt to load all items if none are loaded
if (_.contains([keyMap.DOWN], keyCode) && $scope.filteredItems.length === 0) {
$scope.filteredItems = $scope.items;
$scope.$digest();
} else if (_.contains([keyMap.UP, keyMap.DOWN], keyCode)) {
if (self.isVisible() && $scope.filteredItems.length) {
ev.preventDefault();
if (keyCode === keyMap.DOWN) {
self.activateNext();
} else {
self.activatePrev();
}
}
}
// persist selection on enter, when not selecting from the list
if (_.contains([keyMap.ENTER], keyCode)) {
if (!self.active) {
self.persistEntry();
}
}
// select on enter or tab
if (_.contains([keyMap.ENTER, keyMap.TAB], keyCode)) {
self.selectActive();
self.hidden = true;
}
};
self.filterItemsByQuery = function (query) {
// cache query so we can call it again if needed
if (query) {
self.query = query;
}
// if the query is empty, clear the list items
if (!self.query.length) {
$scope.filteredItems = [];
return;
}
// update the filteredItems using the query
var beginningMatches = $scope.items.filter(function (item) {
return item.indexOf(query) === 0;
});
var otherMatches = $scope.items.filter(function (item) {
return item.indexOf(query) > 0;
});
$scope.filteredItems = beginningMatches.concat(otherMatches);
};
self.isVisible = function () {
return !self.hidden && ($scope.filteredItems.length > 0) && (self.focused || self.mousedOver);
};
// handle updates to parent scope history
$scope.$watch('items', function (items) {
if (self.query) {
self.filterItemsByQuery(self.query);
}
});
// watch for changes to the filtered item list
$scope.$watch('filteredItems', function (filteredItems) {
// if list is empty, or active item is missing, unset active item
if (!filteredItems.length || !_.contains(filteredItems, self.active)) {
self.active = false;
}
});
},
link: function ($scope, $el, attr) {
// should be defined via setInput() method
if (!$scope.inputModel) {
throw new Error('kbn-typeahead-input must be defined');
}
$scope.$watch('typeahead.isVisible()', function (vis) {
$el.toggleClass('visible', vis);
});
}
};
});
});