I was looking for a working solution to get autocomplete/typeahead with angularjs&bootstrap on elasticsearch server.
This is a working solution, not a question, but I want to share it hope it will help:
the html code to call the autocomplete function :
<input required type="text"
popover-trigger="focus"
placeholder="recherche globale"
class="form-control"
ng-model="simplequeryInput"
ng-model-onblur focus-on="focusMe"
ng-click="searchSimple=true" ng-keyup="$event.keyCode == 13 ? submitSimple() : null"
typeahead="item for item in autocomplete($viewValue) | limitTo:15 "
typeahead-on-select="simplequeryInput=$model"
/>
Include the elasticsearch (v2.4.0) script
available here
my elasticsearch service
interfaceApp.service('elasticQuery', function ($rootScope,esFactory) {
return esFactory({ host: $rootScope.elastic_host}); //'localhost:9200'
});
angularjs code querying elasticsearch :
'use strict';
var searchModules = angular.module('searchModules', ['ngRoute','ngDialog']);
searchModules.controller('searchCtrl', function (ngDialog,$scope, $http,$rootScope, elasticQuery) {
...
$scope.autocomplete = function(val) {
var keywords = [];
keywords.push(val);
// THIS RETURN IS VERY IMPORTANT
return elasticQuery.search({
index: 'YOUR_INDEX_NAME',
size: 15,
body: {
"fields" : ["T_FAMILY","T_GENUS","T_SCIENTIFICNAME"], // the fields you need
"query" : {
"bool" : {
"must" : [
{
"query_string" : {
"query" : "T_FAMILY:"+val // i want only source where FAMILY == val
}
}
]
}
}
}
}).then(function (response) {
for (var i in response.hits.hits) {
var fields = (response.hits.hits[i]).fields;
var tmpObject = fields["T_FAMILY"] +" " + fields["T_GENUS"] + " ( "+fields["T_SCIENTIFICNAME"] + " )";
keywords.push(tmpObject);
}
return keywords;
});
}
});
hope it helps
Related
I'm quite new to AngularJS and struggling a bit to have some input fields updated after an autocompletion event using google maps.
The idea is that when the user inputs his city/zip code, I would update 3 fields which are themselves linked to an object.
So far, I managed to have a working code except that sometimes the fields are not updated immediately : I have to autocomplete twice so that the good value will appear in the fields.
I've tweaked an existing angular directive in order to get what I want but since this is new to me, I dont know if I'm using the correct approach.
Below is the JS directive I use :
angular.module( "ngVilleAutocomplete", [])
.directive('ngAutocomplete', function($parse) {
return {
scope: {
details: '=',
ngAutocomplete: '=',
options: '=',
data: '='
},
link: function(scope, element, attrs, model) {
//options for autocomplete
var opts
//convert options provided to opts
var initOpts = function() {
opts = {}
if (scope.options) {
if (scope.options.types) {
opts.types = []
opts.types.push(scope.options.types)
}
if (scope.options.bounds) {
opts.bounds = scope.options.bounds
}
if (scope.options.country) {
opts.componentRestrictions = {
country: scope.options.country
}
}
}
}
initOpts()
//create new autocomplete
//reinitializes on every change of the options provided
var newAutocomplete = function() {
scope.gPlace = new google.maps.places.Autocomplete(element[0], opts);
google.maps.event.addListener(scope.gPlace, 'place_changed', function() {
scope.$apply(function() {
scope.details = scope.gPlace.getPlace();
//console.log(scope.details)
var HasCP = false;
for (var i=0 ; i<scope.details.address_components.length ; i++){
for (var j=0 ; j<scope.details.address_components[i].types.length ; j++){
if (scope.details.address_components[i].types[j] == 'postal_code' && scope.data.CP != 'undefined'){
scope.data.CP = scope.details.address_components[i].long_name;
HasCP = true;
} else if (scope.details.address_components[i].types[j] == 'locality' && scope.data.Ville != 'undefined') {
scope.data.Ville = scope.details.address_components[i].long_name;
} else if (scope.details.address_components[i].types[j] == 'country' && scope.data.Pays != 'undefined') {
scope.data.Pays = scope.details.address_components[i].long_name;
}
}
}
if (!HasCP){
var latlng = {lat: scope.details.geometry.location.lat(), lng: scope.details.geometry.location.lng()};
var geocoder = new google.maps.Geocoder;
geocoder.geocode({'location': latlng}, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
for (var i=0 ; i<results[0].address_components.length ; i++){
for (var j=0 ; j<results[0].address_components[i].types.length ; j++){
if (results[0].address_components[i].types[j] == 'postal_code' && scope.data.CP != 'undefined'){
scope.data.CP = results[0].address_components[i].long_name;
console.log('pc trouvé :' + scope.data.CP);
}
}
}
}
});
}
//console.log(scope.data)
scope.ngAutocomplete = element.val();
});
})
}
newAutocomplete()
//watch options provided to directive
scope.watchOptions = function () {
return scope.options
};
scope.$watch(scope.watchOptions, function () {
initOpts()
newAutocomplete()
element[0].value = '';
scope.ngAutocomplete = element.val();
}, true);
}
};
});
The matching HTML code is below :
<div class="form-group">
<lable>Code postal : </label>
<input type="text" id="Autocomplete" class="form-control" ng-autocomplete="cities_autocomplete" details="cities_autocomplete_details" options="cities_autocomplete_options" data="client" placeholder="Code postal" ng-model="client.CP" />
</div>
<div class="form-group">
<lable>Ville : </label>
<input type="text" id="Autocomplete" class="form-control" ng-autocomplete="cities_autocomplete" details="cities_autocomplete_details" options="cities_autocomplete_options" data="client" placeholder="Ville" ng-model="client.Ville" />
</div>
<div class="form-group">
<lable>Pays : </label>
<input type="text" class="form-control" name="Pays" ng-model="client.Pays" placeholder="Pays" />
</div>
You'll see that I pass the "client" object directly to my directive which then updates this object. I expected angular to update the html page as soon as the values of the client object are updated but I will not always be the case :
If I search twice the same city, the values are not updated
If I search a city, Google wont send me a zip code so I have to do another request to the geocoding service and I get the zipcode in return but while my client.CP field is correctly updated, changes are not visible in the CP input field until I do another search.
Thanks in advance for any advice on what I'm doing wrong.
I have successfully been able to send data to the firebase server however I am having trouble getting the data to send to the appropriate data subset I have created. When I send data made in the html form, It sends organized by ID number. I need it to be sent as a child to the 'groups' category in firebase.
here is a Plnkr with the server and $add working. Any suggestions I would really appreciate!
http://plnkr.co/edit/LZ24sRoSJjuCHQnEGzQz?p=linter
.controller('MainCtrl', ['$scope', 'groupsService', function( $scope, groupsService, $firebase ) {
$scope.newGroup = {
name: '',
status: ''
};
$scope.addGroup = function(newGroup) {
groupsService.addGroup(newGroup);
$scope.newGroup = {
name: '',
status: ''
};
};
$scope.updateGroup = function (id) {
groupsService.updateGroup(id);
};
$scope.removeGroup = function(id) {
groupsService.removeGroup(id);
};
}])
.factory('groupsService', ['$firebase', 'FIREBASE_URI',
function ($firebase, FIREBASE_URI) {
var ref = new Firebase(FIREBASE_URI);
var groups = $firebase(ref).$asArray();
var getGroups = function(){
return groups;
};
var addGroup = function (newGroup) {
console.log(newGroup)
groups.$add(newGroup);
};
var updateGroup = function (id){
groups.$save(id);
};
var removeGroup = function (id) {
groups.$remove(id);
};
return {
getGroups: getGroups,
addGroup: addGroup,
updateGroup: updateGroup,
removeGroup: removeGroup,
}
}]);
Thanks for responding! What I am trying to do is add dummy data (name and status) to the groups category like this:
{
Groups:[
"-JcFXid1A2G8EM7A_kwc" : {
"name" : "hi",
"status": "inactive"
},
"-JcFZP5FNtL4Yj6nja_7" : {
"name" : "hi"
"status": "inactive"
},
"-JcFtGoZL7J-CCIjTYcL" : {
"name" : "dfgdfg",
"status": "inactive"
}
]
}
would it make more sense to have them organized by active or inactive? I am afraid to nest too far in firebase...
like
{
Groups:[
"Active":[
"-JcFXid1A2G8EM7A_kwc" : {
"name" : "hi",
}
],
"Inactive":[
"-JcFZP5FNtL4Yj6nja_7" : {
"name" : "hi"
},
"-JcFtGoZL7J-CCIjTYcL" : {
"name" : "dfgdfg"
}
]
]
}
This isn't an answer to your question yet, because I first need to understand what you're trying to accomplish (and I can't fit this amount of information in a comment).
In your view you have a form that binds to the group's name and status:
<form role="form" ng-controller="MainCtrl" ng-submit="addGroup(newGroup)">
<div class="form-group">
<label for="groupName">Group Name</label>
<input type="text" class="form-control" id="groupName" ng-model="newGroup.name">
</div>
<div class="form-group">
<label for="groupStatus">Group Status</label>
<select class="form-control" ng-model="newGroup.status">
<option value="inactive">Inactive</option>
<option value="active">Active</option>
</select>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
In your GroupsService you essentially add a group like this:
var ref = new Firebase(FIREBASE_URI);
var groups = $firebase(ref).$asArray();
groups.$add(newGroup);
Which adds the group to the collection at that URL.
Which leads to this data structure:
{
"-JcFXid1A2G8EM7A_kwc" : {
"name" : "hi",
"status" : "inactive"
},
"-JcFZP5FNtL4Yj6nja_7" : {
"name" : "hi",
"status" : "active"
},
"-JcFtGoZL7J-CCIjTYcL" : {
"name" : "dfgdfg",
"status" : "active"
}
}
But if I understand you correctly you to want the data to be stored like this:
{
"inactive": [
"-JcFXid1A2G8EM7A_kwc" : {
"name" : "hi"
}
],
"active": [
"-JcFZP5FNtL4Yj6nja_7" : {
"name" : "hi"
},
"-JcFtGoZL7J-CCIjTYcL" : {
"name" : "dfgdfg",
}
]
}
Is this indeed what you're looking to do?
Are you ever going to display active and inactive groups combined in a list? This is important to know, since it is quite easy to filter a list in Angular, but I wouldn't know how to merge two lists.
I have this JSON object.
var UsersList=[
{
"User" : {
"IdUser" : "admin",
"FirstName" : "mirco",
"LastName" : "sabatino"
}
}, {
"User" : {
"IdUser" : "coordinator",
"FirstName" : "coordinator",
"LastName" : ""
}
}, {
"User" : {
"IdUser" : "test",
"FirstName" : "publisher",
"LastName" : "Diaz"
}
}, {
"User" : {
"IdUser" : "work",
"FirstName" : "ingester",
"LastName" : "Brown"
}
}
] ;
I want filter in ng-repeat for LastName value.
<div class="col-md-6" ng-repeat="users in UsersList | filter: {User.LastName : filterSearchLetter}">
filterSearchLetter value in my controler is populated by
$scope.filterLetterUser=function(letter){
$scope.filterSearchLetter=letter;
console.log($scope.filterSearchLetter);
};
But this don't work.
See my plnkr for the solution http://plnkr.co/edit/l27xUQ?p=preview. I also have implemented your required UI behavior using ng-change. Hope it helps.
For detailed explanation I offer you Todd Motto's blog - http://toddmotto.com/everything-about-custom-filters-in-angular-js/
To invoke a filter, the syntax is the filter function name followed by the parameters.
To define a filter you need to do (This is copy of Todd Motto's code modified for your data)
todos.filter('startsWithLetter', function () {
return function (items, letter) {
var filtered = [];
var letterMatch = new RegExp(letter, 'i');
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (letterMatch.test(item.User.LastName.substring(0, 1))) {
filtered.push(item);
}
}
return filtered;
};
});
This is not a valid object: {User.LastName : filterSearchLetter} It should actually throw an exception saying something like
Uncaught SyntaxError: Unexpected token .
What you probably want to try instead is: {"User": {"LastName" : filterSearchLetter} }
To iterate over an array of Objects see:
angularjs - ng-repeat: access key and value from JSON array object
You may have to convert to valid JS object using JS_Obj=JSON.parse(JSON_Obj);
Maybe something like this:
//iterate through array
<ul ng-repeat="users in UsersList">
//iterate through Object
<li ng-repeat=(key, value) in users | filter: {User.LastName : filterSearchLetter}>
{{key}} : {{value}}
</li>
</ul>
i'm using :
AngularJs : AngularJS v1.2.22
Bootstrap : Bootstrap v3.1.1 + ui-bootstrap-tpls-0.11.0.js
Elasticsearch.angular : elasticsearch - v2.4.0 - 2014-07-30
I work on a front end with angular and i want to make an autocompleted input using twitter-bootstap typeahead on an elasticsearch document.
I can query elasticsearch with angularJs and get the data correctly (by putting them in the scope) but when i try an asynchrone query it failed with an error **"Error: matches is undefined" ( full error her : http://pastebin.com/CJSubYbp )
In angularjs, service for elasticsearch :
interfaceApp.service('elasticQuery', function (esFactory) {
return esFactory({ host: 'localhost:9200' });
});
my controller :
'use strict';
/* Controllers */
var searchSimpleModules = angular.module('searchSimpleModules', ['ngRoute']);
searchSimpleModules.controller('searchSimpleCtrl', function ($scope, $rootScope, $routeParams, $location, elasticQuery) {
$scope.simplequery = "";
$scope.autocomplete = function(value) {
$scope.simplequery = value;
var index_p = 'donnees';
var size_p = 50;
var body_p = {
"query": {
"query_string": { "query" : value +"*" }
}
};
elasticQuery.search({
index: index_p,
size: size_p,
body: body_p
}).then(function (response) {
$scope.hits= response.hits.hits;
});
};
$scope.find = function () {
elasticQuery.search({
index: 'donnees',
size: 50,
body: { "query": { "query_string": { "query" : "P000023*" } } }
}).then(function (response) {
$scope.hits= response.hits.hits;
});
};
});
the html input:
<input required type="text"
popover="Rechercher un terme sur toute la partie {{simplesearch.domaine}}.Il est possible d'utiliser des *,? et double quote."
popover-trigger="focus"
placeholder="recherche globale"
class="form-control"
typeahead="entree for entree in autocomplete($viewValue)"
ng-model="simplequery"
>
In the firebug console i see that i get the error before the "http.get()" made by the service.
I make many search to debug this but i can't get out of it.
I read http://www.elasticsearch.org/guide/en/elasticsearch/client/javascript-api/current/browser-builds.html
Any advice are welcomed. thanks
Best regards
update 2: using elasticsearch-angular.js :
autocomplete/typeahead angularjs bootstrap on elasticsearch
( it was not working because of the return in "return elasticQuery.search({...})" was missing )
update 1 : i found another solution but not using the elastic.js
<input required type="text"
popover="Rechercher un terme sur toute la partie {{simplesearch.domaine}}.Il est possible d'utiliser des *,? et double quote."
popover-trigger="focus"
placeholder="recherche globale"
class="form-control search-query"
ng-model="simplequery"
typeahead="entree as entree.data for entree in autocomplete($viewValue) | filter:$viewValue | limitTo:15 "
typeahead-on-select="onSelect($model)"
/>
js:
$scope.simplequery = {id:"",data:""};
$scope.autocomplete = function(val) {
var body = '{"fields" : [ "O_OCCURRENCEID","C_COLLECTIONID", "C_COLLECTIONCODE", "I_INSTITUTIONID", "I_INSTITUTIONCODE" ], "query" : {"query_string" : { "query" : "*'+val+'*" }},"highlight":{"pre_tags" : ["<strong>"],"post_tags" : ["</strong>"],"fields":{"*": {}}}}';
return $http.get($rootScope.elastic_host + 'specimens/_search?', {
params: { // q: val
source : body
}
}).then(function(res){
var keywords = [];
for (var i in res.data.hits.hits) {
var highlights = (res.data.hits.hits[i]).highlight;
var ligneTmp ="";
for(var j in highlights){
ligneTmp += highlights[j][0] + " ";
}
keywords[i]= {id:"id"+i,data:ligneTmp};
}
return keywords;
});
};
$scope.onSelect = function (selection) {
$scope.simplequery.data = selection.data;
$scope.simplequery.id = selection.id;
...
};
I have a $scope.myData object that contain a chunk of data. What i am trying to do is display the data but filter out the nulls and empty strings:
$scope.myData = [
{
"ID" : "001",
"Message" : "test test test test"
},
{
"ID" : "002",
"Message" : "test test test test"
},
{
"ID" : "003",
"Message" : "test test test test"
},
{
"ID" : "004",
"Message" : "test test test test"
},
{
"ID" : "005",
"Message" : " "
},
{
"ID" : "006",
"Message" : "test test test test"
},
{
"ID" : "007",
"Message" : "test test test test"
},
{
"ID" : "007",
"Message" : null
}
]
I can perform an ng-repeat on the above and filter null's via:
<div ng-repeat="data in myData | filter:{Message: '!!'}">
{{ data.ID }}
{{ data.Message }}
</div>
But how can i filter the empty strings e.g:
"Message" : " "
Thanks
We can simply use ng-if here:
<div ng-repeat="data in myData " ng-if="data.Message">
{{ data.ID }}
{{ data.Message }}
</div>
You can use a function instead of an object like this
<div ng-repeat="data in myData | filter:emptyOrNull">
{{ data.ID }}
{{ data.Message }}
</div>
And in the controller
$scope.emptyOrNull = function(item){
return !(item.Message === null || item.Message.trim().length === 0)
}
Well you can create a custom filter:
.filter('hasSomeValue', [function(){
return function(input, param) {
var ret = [];
if(!angular.isDefined(param)) param = true;
angular.forEach(input, function(v){
if(angular.isDefined(v.Message) && v.Message) {
v.Message = v.Message.replace(/^\s*/g, '');
ret.push(v);
}
});
return ret;
};
}])
And in your HTML:
<div ng-repeat="data in myData | hasSomeValue: data.Message">
DEMO
You can use '' charter.
Try check like this.
<div ng-repeat="data in myData | filter:{Message: ''}">
You can use an angular filter for this:
Working Fiddle
Code Snippet:
.filter('filterData',function(){
return function(data) {
var dataToBePushed = [];
data.forEach(function(resultData){
if(resultData.Message && resultData.Message != " ")
dataToBePushed.push(resultData);
});
return dataToBePushed;
}
});
If you wanted to filter out values in an object that are empty, you could create a custom filter and filter each based on the value.
Something like this:
.filter("notEmpty",
function () {
return function (object) {
var filteredObj = {};
angular.forEach(object, function (val, key) {
if (val != null) {
if (typeof(val) === "object") {
if (Object.keys(val).length > 0) {
filteredObj[key] = val;
}
} else if (typeof(val) === "string") {
if (val.trim() !== "") {
filteredObj[key] = val;
}
} else {
filteredObj[key] = val;
}
}
});
return filteredObj;
};
});
jsFiddle example
You could also use the ng-if directive and a function that parses the objects as you see fit.