@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
281 lines (232 loc) • 8.3 kB
JavaScript
define(function (require) {
var module = require('ui/modules').get('kibana');
var _ = require('lodash');
var rison = require('ui/utils/rison');
var keymap = require('ui/utils/key_map');
module.directive('savedObjectFinder', function (savedSearches, savedVisualizations, savedDashboards, $location, kbnUrl) {
var types = {
searches: {
service: savedSearches,
name: 'searches',
noun: 'Saved Search',
nouns: 'saved searches'
},
visualizations: {
service: savedVisualizations,
name: 'visualizations',
noun: 'Visualization',
nouns: 'visualizations'
},
dashboards: {
service: savedDashboards,
name: 'dashboards',
noun: 'Dashboard',
nouns: 'dashboards'
}
};
return {
restrict: 'E',
scope: {
type: '@',
title: '@?',
// optional make-url attr, sets the userMakeUrl in our scope
userMakeUrl: '=?makeUrl',
// optional on-choose attr, sets the userOnChoose in our scope
userOnChoose: '=?onChoose'
},
template: require('ui/partials/saved_object_finder.html'),
controllerAs: 'finder',
controller: function ($scope, $element, $timeout) {
var self = this;
// the text input element
var $input = $element.find('input[ng-model=filter]');
// the list that will hold the suggestions
var $list = $element.find('ul');
// the current filter string, used to check that returned results are still useful
var currentFilter = $scope.filter;
// the most recently entered search/filter
var prevSearch;
// the service we will use to find records
var service;
// the list of hits, used to render display
self.hits = [];
self.objectType = types[$scope.type];
filterResults();
/**
* Passed the hit objects and will determine if the
* hit should have a url in the UI, returns it if so
* @return {string|null} - the url or nothing
*/
self.makeUrl = function (hit) {
if ($scope.userMakeUrl) {
return $scope.userMakeUrl(hit);
}
if (!$scope.userOnChoose) {
return hit.url;
}
return '#';
};
self.preventClick = function ($event) {
$event.preventDefault();
};
/**
* Called when a hit object is clicked, can override the
* url behavior if necessary.
*/
self.onChoose = function (hit, $event) {
if ($scope.userOnChoose) {
$scope.userOnChoose(hit, $event);
}
var url = self.makeUrl(hit);
if (!url || url === '#' || url.charAt(0) !== '#') return;
$event.preventDefault();
// we want the '/path', not '#/path'
kbnUrl.change(url.substr(1));
};
$scope.$watch('filter', function (newFilter) {
// ensure that the currentFilter changes from undefined to ''
// which triggers
currentFilter = newFilter || '';
filterResults();
});
//manages the state of the keyboard selector
self.selector = {
enabled: false,
index: -1
};
//key handler for the filter text box
self.filterKeyDown = function ($event) {
switch (keymap[$event.keyCode]) {
case 'tab':
if (self.hitCount === 0) return;
self.selector.index = 0;
self.selector.enabled = true;
selectTopHit();
$event.preventDefault();
break;
case 'enter':
if (self.hitCount !== 1) return;
var hit = self.hits[0];
if (!hit) return;
self.onChoose(hit, $event);
$event.preventDefault();
break;
}
};
//key handler for the list items
self.hitKeyDown = function ($event, page, paginate) {
switch (keymap[$event.keyCode]) {
case 'tab':
if (!self.selector.enabled) break;
self.selector.index = -1;
self.selector.enabled = false;
//if the user types shift-tab return to the textbox
//if the user types tab, set the focus to the currently selected hit.
if ($event.shiftKey) {
$input.focus();
} else {
$list.find('li.active a').focus();
}
$event.preventDefault();
break;
case 'down':
if (!self.selector.enabled) break;
if (self.selector.index + 1 < page.length) {
self.selector.index += 1;
}
$event.preventDefault();
break;
case 'up':
if (!self.selector.enabled) break;
if (self.selector.index > 0) {
self.selector.index -= 1;
}
$event.preventDefault();
break;
case 'right':
if (!self.selector.enabled) break;
if (page.number < page.count) {
paginate.goToPage(page.number + 1);
self.selector.index = 0;
selectTopHit();
}
$event.preventDefault();
break;
case 'left':
if (!self.selector.enabled) break;
if (page.number > 1) {
paginate.goToPage(page.number - 1);
self.selector.index = 0;
selectTopHit();
}
$event.preventDefault();
break;
case 'escape':
if (!self.selector.enabled) break;
$input.focus();
$event.preventDefault();
break;
case 'enter':
if (!self.selector.enabled) break;
var hitIndex = ((page.number - 1) * paginate.perPage) + self.selector.index;
var hit = self.hits[hitIndex];
if (!hit) break;
self.onChoose(hit, $event);
$event.preventDefault();
break;
case 'shift':
break;
default:
$input.focus();
break;
}
};
self.hitBlur = function ($event) {
self.selector.index = -1;
self.selector.enabled = false;
};
self.manageObjects = function (type) {
$location.url('/settings/objects?_a=' + rison.encode({tab: type}));
};
self.hitCountNoun = function () {
return ((self.hitCount === 1) ? self.objectType.noun : self.objectType.nouns).toLowerCase();
};
function selectTopHit() {
setTimeout(function () {
//triggering a focus event kicks off a new angular digest cycle.
$list.find('a:first').focus();
}, 0);
}
function filterResults() {
if (!self.objectType) return;
if (!self.objectType.service) return;
// track the filter that we use for this search,
// but ensure that we don't search for the same
// thing twice. This is called from multiple places
// and needs to be smart about when it actually searches
var filter = currentFilter;
if (prevSearch === filter) return;
prevSearch = filter;
self.objectType.service.find(filter)
.then(function (hits) {
// ensure that we don't display old results
// as we can't really cancel requests
if (currentFilter === filter) {
self.hitCount = hits.total;
self.hits = _.sortBy(hits.hits, 'title');
}
});
}
function scrollIntoView($element, snapTop) {
var el = $element[0];
if (!el) return;
if ('scrollIntoViewIfNeeded' in el) {
el.scrollIntoViewIfNeeded(snapTop);
} else if ('scrollIntoView' in el) {
el.scrollIntoView(snapTop);
}
}
}
};
});
});