MySpace (yeah it still exists!) has a really cool UI search feature. Just start typing and a slick full screen search interface pops up. For a recent project we were building in Angular, we wanted to implement something similar. Sounds like the perfect place for some directives!
To create this feature, we're going to create two directives.
The first, keyPressEvents
will be work as a listener that will subscribe to all keypress events on the document and will use $rootScope
to broadcast those events to any other directives that care to listen.
The second, search
, will subscribe to the $rootScope keypress events and will pull up a search interface. It will also listen for specific keys, enter
and esc
to perform a search and to exit respectively.
Capturing Keypress Events
Our first directive will be a general purpose event listener that will listen for any keypress events on the document, and will then use $rootScope
to broadcast them throughout the application.
angular.module('recipes.keypress', [])
.directive('keypressEvents', [
'$document',
'$rootScope',
function($document, $rootScope) {
return {
restrict: 'A',
link: function() {
$document.bind('keydown', function(e) {
$rootScope.$broadcast('keypress', e);
$rootScope.$broadcast('keypress:' + e.which, e);
});
}
};
}
]);
On each keypress, this directive will broadcast two events on $rootScope
. The first is keypress
which will pass the keypress event as a parameter. The second, keypress:<pressed key>
, allows us to easily listen for specific keys being pressed.
Attach the keypressEvents
as an attribute on the body element in your index.html
file.
<body keypress-events>
//Awesome Angular App
</body>
Full Screen Search Directive
The search
directive is where most of the UI goodness will happen. This directive will listen for the keypress events broadcast on $rootScope
the from keypressEvents
directive and will display a fullscreen search element capturing the user's query.
It will listen for the enter
key to execute the search. It will use $rootScope
to broadcast the search event and pass the query string as a parameter to any listeners you want to set up to deal process the search.
It will also listen for esc
to hide itself.
In case you want to disable it, like you need to catch user input without the search popping up and ruining everything, we will create another $rootScope
variable, search
which by default is set to true
. If keyboard input is required outside of the search, the search can be disabled by setting $rootScope.search = false
.
angular.module('recipes.search', [])
.directive('search', ['$rootScope', '$state', function($rootScope, $state) {
return {
restrict: 'E',
replace: true,
templateUrl: 'app/directives/search/search.html',
controller: 'SearchCtrl',
link: function(scope, el, attr) {
// Default search to true
$rootScope.search = true;
var searchbar = el.find('#searchtext');
// Subscribe to $rootScope events from keypressEvents directive
scope.$on('keypress', function(onEvent, keypressEvent) {
// Disable searching if $rootScope.search is set to false anywhere in the app
if ($rootScope.search) {
// On escape
if (keypressEvent.which === 27) {
searchbar.val('').blur();
el.fadeOut(200);
// On enter
} else if (keypressEvent.which === 13) {
// Broadcast the search event on enter, clear the search input, and fade the search screen
$rootScope.$broadcast('search', searchbar.val());
searchbar.val('').blur();
el.fadeOut(200);
} else {
if(!el.is(':visible')){
// Add in the first key the user presses searchbar.val(String.fromCharCode(keypressEvent.which));
}
// Show the search input
scope.key = String.fromCharCode(keypressEvent.which);
searchbar.focus();
el.fadeIn(200);
}
}
});
}
};
}]);
The search bar doesn't actually do anything with the search other than broadcast it out into app-land, so to add some functionality you need to subscribe to it in any of your other directives or controllers. To do so, make sure and inject the $rootScope
dependency Then add in the following subscription.
$rootScope.$on('search', function(event, query){
// Process Search
});
With this include a template html file.
<div id="search">
<form ng-model="searchCriteria" id="searchform">
<label id="label" for="searchtext">search: </label>
<input id="searchtext" name="searchtext" type="text" />
</form>
</div>
You need to add some CSS, which I won't go into here, to make it full screen.
Add in the <search></search>
element on any page and you're good to go!