Create a gist now

Instantly share code, notes, and snippets.

angular typeahead
angular.module('ymusica').controller('AlbumSearch', ['$scope', 'Albums', 'Artists', '$q', function($scope, albums, artists, $q) {
$scope.albums = [];
$scope.artists = [];
var terms = new Rx.Subject();
$scope.searchMusic = terms.onNext.bind(terms);
terms.sample(250)
.select(function(term) {
var promise = $q.all([albums.query(term), artists.query(term)]);
return Rx.promiseToObservable(promise)
})
.switchLatest()
.select(function(promise) { return [promise[0].data.albums, promise[1].data.artists]; })
.subscribe(function(result) {
$scope.albums = result[0].slice(0, 5);
$scope.artists = result[1].slice(0, 5);
$scope.music = $scope.albums.concat($scope.artists);
});
$scope.selectMusic = function(item) {
console.log('music selected!', item);
$scope.term = item.name;
};
$scope.imageSource = function(item) {
return item.images['medium'];
};
$scope.hasAlbums = function() {
return $scope.albums.length > 0;
};
$scope.hasArtists = function() {
return $scope.artists.length > 0;
};
}]);
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" ng-app="ymusica"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>ymusica</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<link rel="stylesheet" href="/css/vendor/bootstrap.css">
<link rel="stylesheet" href="/css/main.css">
<script src="/js/vendor/modernizr.js"></script>
</head>
<body>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
<![endif]-->
<div class="container">
<div ng-controller="AlbumSearch" class="album-search text-center" ng-cloak>
<typeahead class="typeahead" items="music" term="term" search="searchMusic(term)" select="selectMusic(item)">
<div class="menu" ng-cloak>
<h3 ng-show="hasAlbums()">Albums</h3>
<ul>
<li typeahead-item="album" ng-repeat="album in albums" class="results">
<img ng-src="{{imageSource(album)}}"><p class="name">{{album.name}}</p><p class="artist">{{album.artist}}</p>
</li>
</ul>
<h3 ng-show="hasArtists()">Artists</h3>
<ul>
<li typeahead-item="artist" ng-repeat="artist in artists" class="results">
<img ng-src="{{imageSource(artist)}}"><p class="name">{{artist.name}}</p>
</li>
</ul>
</div>
</typeahead>
</div>
</div>
<script src="/js/vendor/jquery.js"></script>
<script src="/js/vendor/rx.js"></script>
<script src="/js/vendor/rx.time.js"></script>
<script src="/js/vendor/rx.coincidence.js"></script>
<script src="/js/vendor/angular.js"></script>
<script src="/js/vendor/angular-resource.js"></script>
<script src="/js/vendor/bootstrap.js"></script>
<script src="/js/ymusica.js"></script>
</body>
</html>
angular.module('ymusica').directive('typeahead', ["$timeout", function($timeout) {
return {
restrict: 'E',
transclude: true,
replace: true,
template: '<div><form><input ng-model="term" ng-change="query()" type="text" autocomplete="off" /></form><div ng-transclude></div></div>',
scope: {
search: "&",
select: "&",
items: "=",
term: "="
},
controller: ["$scope", function($scope) {
$scope.items = [];
$scope.hide = false;
this.activate = function(item) {
$scope.active = item;
};
this.activateNextItem = function() {
var index = $scope.items.indexOf($scope.active);
this.activate($scope.items[(index + 1) % $scope.items.length]);
};
this.activatePreviousItem = function() {
var index = $scope.items.indexOf($scope.active);
this.activate($scope.items[index === 0 ? $scope.items.length - 1 : index - 1]);
};
this.isActive = function(item) {
return $scope.active === item;
};
this.selectActive = function() {
this.select($scope.active);
};
this.select = function(item) {
$scope.hide = true;
$scope.focused = true;
$scope.select({item:item});
};
$scope.isVisible = function() {
return !$scope.hide && ($scope.focused || $scope.mousedOver);
};
$scope.query = function() {
$scope.hide = false;
$scope.search({term:$scope.term});
}
}],
link: function(scope, element, attrs, controller) {
var $input = element.find('form > input');
var $list = element.find('> div');
$input.bind('focus', function() {
scope.$apply(function() { scope.focused = true; });
});
$input.bind('blur', function() {
scope.$apply(function() { scope.focused = false; });
});
$list.bind('mouseover', function() {
scope.$apply(function() { scope.mousedOver = true; });
});
$list.bind('mouseleave', function() {
scope.$apply(function() { scope.mousedOver = false; });
});
$input.bind('keyup', function(e) {
if (e.keyCode === 9 || e.keyCode === 13) {
scope.$apply(function() { controller.selectActive(); });
}
if (e.keyCode === 27) {
scope.$apply(function() { scope.hide = true; });
}
});
$input.bind('keydown', function(e) {
if (e.keyCode === 9 || e.keyCode === 13 || e.keyCode === 27) {
e.preventDefault();
};
if (e.keyCode === 40) {
e.preventDefault();
scope.$apply(function() { controller.activateNextItem(); });
}
if (e.keyCode === 38) {
e.preventDefault();
scope.$apply(function() { controller.activatePreviousItem(); });
}
});
scope.$watch('items', function(items) {
controller.activate(items.length ? items[0] : null);
});
scope.$watch('focused', function(focused) {
if (focused) {
$timeout(function() { $input.focus(); }, 0, false);
}
});
scope.$watch('isVisible()', function(visible) {
if (visible) {
var pos = $input.position();
var height = $input[0].offsetHeight;
$list.css({
top: pos.top + height,
left: pos.left,
position: 'absolute',
display: 'block'
});
} else {
$list.css('display', 'none');
}
});
}
};
}]);
angular.module('ymusica').directive('typeaheadItem', function() {
return {
require: '^typeahead',
link: function(scope, element, attrs, controller) {
var item = scope.$eval(attrs.typeaheadItem);
scope.$watch(function() { return controller.isActive(item); }, function(active) {
if (active) {
element.addClass('active');
} else {
element.removeClass('active');
}
});
element.bind('mouseenter', function(e) {
scope.$apply(function() { controller.activate(item); });
});
element.bind('click', function(e) {
scope.$apply(function() { controller.select(item); });
});
}
};
});
@askucher

Why you need items="music" in typeahead class="typeahead" items="music" ... ?

@gearsdigital

It's been a while since you asked… but for the records: items="music" is needed to hold the concatenated server responses. Take a look at Line 20 in controller.js.

@sydneytester16

Will you be able to send me the complete working code for the application. I am new to angular and trying to work on some project. Thanks

@csharpforevermore

yeah - i cannot see how it gets the URL for the music, and also ymusica.js - where is this file? How does the HTML file include the typeahead.js etc? I thought this would make things clearer but they are more muddy :-/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment