Search box in angular js - angularjs

I want to implement a search box in my angularJs application. As soon as user starts typing some name in the search box , some REST service should be called and it should fetch all the names which matches the name typed in the search text box. Note that there is no button , the result should come automatically as soon as user starts typing. The REST service is already there. I just need to invoke the REST service when the user starts typing and return the result as a list.
For ex:- If I type James then all the user whose name starts with James should come as a list in the search box.
Once the list of name comes , the user can click on one of the name and his information should be loaded in the current page.
How can I implement such type-on search box in angular js? Is there any directive for it? Can anyone please give me some direction.

You should define a directive that listen onkeypress.
app.directive('myOnKeyDownCall', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
scope.$apply(function (){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
});
};
});
HTML
<input type="text" my-on-key-down-call="callRestService()">
CONTROLLER
$scope.callRestService= function() {
$http({method: 'GET', url: '/someUrl'}).
success(function(data, status, headers, config) {
$scope.results.push(data); //retrieve results and add to existing results
})
}
Would be nice to wait until 3 keys has been typed, for that in directive:
var numKeysPress=0;
element.bind("keydown keypress", function (event) {
numKeysPress++;
if(numKeysPress>=3){
scope.$apply(function (){
scope.$eval(attrs.myOnKeyDownCall);
});
event.preventDefault();
}
});
Perhaps, exists typeahead directive from angular-ui that you can use:
angular-ui typeahead
I hope it helps you

Found this to be a simplification of the accepted answer.
// Directive
app.directive('search', function () {
return function ($scope, element) {
element.bind("keyup", function (event) {
var val = element.val();
if(val.length > 2) {
$scope.search(val);
}
});
};
});
// In Controller
$scope.search= function(val) {
// fetch data
}
// HTML
<input type="text" search>

Not sure if you already solved this, but I recommend looking at this tutorial: http://angular.github.io/angular-phonecat/step-12/app/#/phones
Essentially it does what you're interested in, but instead it filters out the results while typing. If you got this working, I'm interested in how you did it. I'm trying this as well.

Why so much drama, directives, and Glyptodon blood?
Since angular already has
ng-keypress
ng-keyup
ng-keydown
Use any of those to invoke REST service just as you would with ng-click.
HTML
<input type="search" ng-model="vm.query" ng-keyup="vm.search()" />
JS
vm.search = search;
function search() {
// Call your cool REST service and attach data to it
vm.data = MyCoolService.myGetFunctionWhatever();
// You can use vm.query ng-model to limit search after 2 input values for example
// if(vm.query.length > 2) do your magic
};

Bootstrap's "Typeahead, Asynchronous results" does exactly what you want, easily. Go to https://angular-ui.github.io/bootstrap/ then scroll down near to the bottom of the page. I used it in my CRUDiest movies database project: https://crudiest-firebase.firebaseapp.com/#/movies

Related

AngularJS Ngblur get incomplete input value

Just wondering why the ngblur directive did not get the complete value of a text input. I am using easyautocomplete plugin to do search through a JSON file.
When it found it will list out several suggestions that could be match to the input. The problem is when we do not type the full text and choose to select from the provided list, ngblur will only capture what we have typed to the input box instead of what we choose from the list.
Here is the fiddle https://jsfiddle.net/zvezrg6j/
json country is Indonesia or Singapore or Thailand
UPDATE
What i wanted to do is when i choose from the list or when i leave the text input with full text, the app will look up for some details, eg. after typing the country the app will look for its capital.
This is the solution you are looking for.
Updated JSFiddle
Instead of calling FindMaterialDetail method on blur, call it onHideListEvent of auto-complete options.
angular.module('myapp', [])
.controller('MyCtrl', function($scope) {
$scope.data = {};
$scope.FindMaterialDetail = function(country) {
var scope = angular.element($("#outer")).scope();
scope.$apply(function(){
scope.data.result = country;
})
}
var options = {
url: "https://api.myjson.com/bins/1rjn5",
getValue: "country",
listLocation: "data",
list: {
match: {
enabled: true
},
onHideListEvent: function() {
$scope.FindMaterialDetail($("#Autocomplete").val());
}
}
};
$("#Autocomplete").easyAutocomplete(options);
});
The option you can try is using $scope.$watch for the model. Instead of calling the function in ngblur try adding the following snippet in your script.
$scope.$watch("data.country", function(event) {
$scope.data.result = $scope.data.country;
});
Please find the modified fiddle.
This issue is occurring because blur event is firing before value update in 'data.country', You can resolve this issue by using below code. Create A directive like below-
angular.module('app', []).directive('easyAutoComplete', function() {
return {
restrict: 'A',
require: 'ngModel',
priority: 1, // needed for angular 1.2.x
link: function(scope, elm, attr, ngModelCtrl) {
var options = {
url: "https://api.myjson.com/bins/1rjn5",
getValue: "country",
listLocation: "data",
list: {
match: {
enabled: true
},
onChooseEvent: function() {
scope.$apply(function() {
ngModelCtrl.$setViewValue($(elm).getSelectedItemData().country);
});
}
},
};
$(elm).easyAutocomplete(options);
}
};
});
then use it like as -
<input type="text" ng-model="data.country" easy-auto-complete ng-change="FindMaterialDetail(data.country)" id="Autocomplete">
There is a problem on loading jquery autocomplete Plugin in your angular code , as the angular code got initialized first followed by the autocomplete .
So i've converted the above code to Angular Bootstrap Typehead which is exactly does the autocomplete written in angular way .It's also recommended to use angular directives rather than jquery plugins for building angular applications
Check for Custom templates for results on this link.
Angular Autocomplete TypeAhead
Here is the Fiddle
<input type="text" ng-model="data.country" placeholder="Custom template" uib-typeahead="country as country for country in Countries" typeahead-show-hint="true" typeahead-min-length="0" ng-blur="FindMaterialDetail(data.country)" class="form-control">

Programmatically changed value not detected by AngularJS

This is only my 2nd AngularJS project. I'm trying to add new functionality to an existing jQuery app rather than starting over.
The jQuery portion populates a div with several image thumbnails. The purpose of the new AngularJS features is to allow the user to edit the information for the photos - caption, photographer credit, and such. Clicking a thumbnail should trigger the Angular app to retrieve its info from the database for edit. My problem is, the image URL is never picked up by Angular.
The ng-app tag is placed on the page's body element, so everything should be visible to Angular. I have tried $scope.$watch and $scope.$apply with no success. The URL of the clicked image does appear in expected locations, but the Angular model element "imgurl" never seems to get updated despite using ng-model="imgurl" on the text input field.
I have looked at similar questions such as these:
I can't detect programmatically value change in angularjs
AngularJS : ng-model binding not updating when changed with jQuery
My existing jQuery, trying to push data into Angular
function populateImageForm(imageURL) {
jQuery("#imageurltest").html("clicked image is " + imageURL); //show the URL of the clicked image
//updates the image, but Angular doesn't see change: jQuery("#working-image").attr("src", imageURL);
jQuery("#working-image").attr("ng-src", imageURL);
jQuery("#wiurl").val(imageURL);
}
HTML elements using Angular:
<img id="working-image" class="center-block img-responsive" ng-src={{imgurl}} imageonload />
<input id="wiurl" type="text" size="65" ng-model="imgurl" />
Angular controller code:
app.controller("imageInfoControl", function($scope, $http) {
$scope.imgurl = "http://example.org/placeholder.jpg";
$http.get("ng-get-image.php", {params:{"url": $scope.imgurl}} ).success(function(response){
console.log (response);
$scope.message = response.title;
});
$scope.$watch('imgurl',
function(newVal, oldVal) {
console.log('imgurl programmatically changed');
});
$scope.$watch(function() {
return $scope.message
}, function(newVal, oldVal) {
if(newVal !== null) {
console.log('message programmatically changed to ' + $scope.message);
}
});
});
app.directive('imageonload', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('load', function() {
console.log (element);
console.log (attrs);
//doesn't seem to do anything: scope.$apply(attrs.imageonload); https://stackoverflow.com/questions/17884399/image-loaded-event-in-for-ng-src-in-angularjs
scope.$digest;
scope.message = attrs.src;
console.log(scope.message);
//alert('image is loaded');
});
element.bind('error', function(){
alert('image could not be loaded');
});
}
};
});
Sample Link
Make it as separate js file and call it in index under angular and jquery script
(function($, ng) {
var $val = $.fn.val; // save original jQuery function
// override jQuery function
$.fn.val = function (value) {
// if getter, just return original
if (!arguments.length) {
return $val.call(this);
}
// get result of original function
var result = $val.call(this, value);
// trigger angular input (this[0] is the DOM object)
ng.element(this[0]).triggerHandler('input');
// return the original result
return result;
}
})(window.jQuery, window.angular);
You need to use model as a property of an object.
Please try ng-model="data.imgurl".
Of course, you need to declare data object in the scope.
$scope.data = {}:
$scope.data.imgurl = "something.jpg";
Like this
Thank you

select2, ng-model and angular

Using jquery-select2 (not ui-select) and angular, I'm trying to set the value to the ng-model.
I've tried using $watch and ng-change, but none seem to fire after selecting an item with select2.
Unfortunately, I am using a purchased template and cannot use angular-ui.
HTML:
<input type="hidden" class="form-control select2remote input-medium"
ng-model="contact.person.id"
value="{{ contact.person.id }}"
data-display-value="{{ contact.person.name }}"
data-remote-search-url="api_post_person_search"
data-remote-load-url="api_get_person"
ng-change="updatePerson(contact, contact.person)">
ClientController:
$scope.updatePerson = function (contact, person) {
console.log('ng change');
console.log(contact);
console.log(person);
} // not firing
$scope.$watch("client", function () {
console.log($scope.client);
}, true); // not firing either
JQuery integration:
var handleSelect2RemoteSelection = function () {
if ($().select2) {
var $elements = $('input[type=hidden].select2remote');
$elements.each(function(){
var $this = $(this);
if ($this.data('remote-search-url') && $this.data('remote-load-url')) {
$this.select2({
placeholder: "Select",
allowClear: true,
minimumInputLength: 1,
ajax: { // instead of writing the function to execute the request we use Select2's convenient helper
url: Routing.generate($this.data('remote-search-url'), {'_format': 'json'}),
type: 'post',
dataType: 'json',
delay: 250,
data: function (term, page) {
return {
query: term, // search term
};
},
results: function (data, page) { // parse the results into the format expected by Select2.
return {
results: $.map(data, function (datum) {
var result = {
'id': datum.id,
'text': datum.name
};
for (var prop in datum) {
if (datum.hasOwnProperty(prop)) {
result['data-' + prop] = datum[prop];
}
}
return result;
})
}
}
},
initSelection: function (element, callback) {
// the input tag has a value attribute preloaded that points to a preselected movie's id
// this function resolves that id attribute to an object that select2 can render
// using its formatResult renderer - that way the movie name is shown preselected
var id = $(element).val(),
displayValue = $(element).data('display-value');
if (id && id !== "") {
if (displayValue && displayValue !== "") {
callback({'id': $(element).val(), 'text': $(element).data('display-value')});
} else {
$.ajax(Routing.generate($this.data('remote-load-url'), {'id': id, '_format': 'json'}), {
dataType: "json"
}).done(function (data) {
callback({'id': data.id, 'text': data.name});
});
}
}
},
});
}
});
}
};
Any advice would be greatly appreciated! :)
UPDATE
I've managed to put together a plunk which seems to similarly reproduce the problem - it now appears as if the ng-watch and the $watch events are fired only when first changing the value.
Nevertheless, in my code (and when adding further complexity like dynamically adding and removing from the collection), it doesn't even seem to fire once.
Again, pointers in the right direction (or in any direction really) would be greatly appreciated!
There are a number of issues with your example. I'm not sure I am going to be able to provide an "answer", but hopefully the following suggestions and explanations will help you out.
First, you are "mixing" jQuery and Angular. In general, this really doesn't work. For example:
In script.js, you run
$(document).ready(function() {
var $elements = $('input[type=hidden].select2remote');
$elements.each(function() {
//...
});
});
This code is going to run once, when the DOM is initially ready. It will select hidden input elements with the select2remote class that are currently in the DOM and initialized the select2 plugin on them.
The problem is that any new input[type=hidden].select2remote elements added after this function is run will not be initialized at all. This would happen if you are loading data asynchronously and populating an ng-repeat, for example.
The fix is to move the select2 initialization code to a directive, and place this directive on each input element. Abridged, this directive might look like:
.directive('select2', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ngModel) {
//$this becomes element
element.select2({
//options removed for clarity
});
element.on('change', function() {
console.log('on change event');
var val = $(this).value;
scope.$apply(function(){
//will cause the ng-model to be updated.
ngModel.setViewValue(val);
});
});
ngModel.$render = function() {
//if this is called, the model was changed outside of select, and we need to set the value
//not sure what the select2 api is, but something like:
element.value = ngModel.$viewValue;
}
}
}
});
I apologize that I'm not familiar enough with select2 to know the API for getting and setting the current value of the control. If you provide that to me in a comment I can modify the example.
Your markup would change to:
<input select2 type="hidden" class="form-control select2remote input-medium"
ng-model="contact.person.id"
value="{{ contact.person.id }}"
data-display-value="{{ contact.person.name }}"
data-remote-search-url="api_post_person_search"
data-remote-load-url="api_get_person"
ng-change="updatePerson(contact, contact.person)">
After implementing this directive, you could remove the entirety of script.js.
In your controller you have the following:
$('.select2remote').on('change', function () {
console.log('change');
var value = $(this).value;
$scope.$apply(function () {
$scope.contact.person.id = value;
});
});
There are two problems here:
First, you are using jQuery in a controller, which you really shouldn't do.
Second, this line of code is going to fire a change event on every element with the select2remote class in the entire application that was in the DOM when the controller was instatiated.
It is likely that elements added by Angular (i.e through ng-repeat) will not have the change listener registered on them because they will be added to the DOM after the controller is instantiated (at the next digest cycle).
Also, elements outside the scope of the controller that have change events will modify the state of the controller's $scope. The solution to this, again, is to move this functionality into the directive and rely on ng-model functionality.
Remember that anytime you leave Angular's context (i.e if you are using jQuery's $.ajax functionality), you have to use scope.$apply() to reenter Angular's execution context.
I hope these suggestions help you out.

AngularJS - how to focus on an element via a controller

I know the question has been asked multiple time, but I can't seem to find anywhere how to focus to an element from within a controller. What is the best approach? Would it be better to do a directive? But if so, then how would I call it within my controller? Or is it better to create a service then?
What I already have and works properly from within HTML code is a directive:
.directive('ngxFocus', ['$timeout', function($timeout) {
return function(scope, element, attr) {
$timeout(function () {
element.focus();
}, 10);
};
}])
Can I call directive within controller? I'm still learning AngularJS and I'm a bit confused on what the best approach is in this case. I really want to do it via the controller, at the moment I use a simple 1 line of jQuery to focus, but yeah it's not the Angular way and so I'd like to go with the correct way.
Note
To be more specific with an example, let say I have 10 inputs in the HTML and let say that inside the execution of a function (defined in the controller), I want to focus on 1 of the multiple inputs directly from the function (again this is all declared inside the controller). I would rather not write anything inside the HTML code, if possible, but instead call a focus function or something that will focus to the input I chose. I know I could write it simply in jQuery with $('input12').focus(); but I want to know how to do it the AngularJS way. All the answers I get so far are based on writing a Directive, that also equals to writing something inside the HTML, isn't there any other way???
Example
For more explicit example of my form... I have a first input connected to a Yahoo web service (stock market), this input filled by the user will hold a stock quotes symbol that can be anywhere in the world and then the user will choose (from a dropdown) his Bank account... now from there, my controller will check that the stock quotes market is in the same currency as the user's bank account (ex.: GOOG is US currency, if user's account is in $CAD, it will fail because GOOG is in $USD). If currency isn't the same, I want to advise my user and to do so I would seriously prefer to focus on the field so he could change his symbol if he made an error.
I you're trying to work with elements in controller, be sure you're going wrong, the controller's target in to bind data received from services to view, not to manipulate view.
If you want to focus on an element with route change:
app.directive('focuser', ['$location', function ($location) {
return {
restrict: 'A',
link: function ($scope, $element) {
$scope.$watch(function () {
//simply focus
$element.focus();
//or more specific
if ($location.$$url == '/specific/path') {
$element.focus();
}
});
}
};
}]);
I've made this directive:
app.directive('rfocus',function(){
return {
restrict: 'A',
controller: function($scope, $element, $attrs){
var fooName = 'setFocus' + $attrs.rfocus;
$scope[fooName] = function(){
$element.focus();
}
},
}
});
It adds to controller's $scope function to set focus on element. Name of the function is based on value given in attribute.
Using: <input type="text" rfocus="Input1"/> will create function setFocusInput1() which you can use in your controller.
Here is the fiddle: http://jsfiddle.net/aartek/2PJMQ/
I've recently started to learn Angular, too, but hopefully I can provide you a different way of approaching this.
I've been using some basic jQuery to focus, too, so in that regard, I can't really help you. However, with regard to calling a directive within a controller, I can't find any articles that say "yes, you can", or "no, you can't". I know that you can declare a controller within a directive, though, so you miiiiight be able to do something like this:
.directive('ngxFocus', function() {
return {
restrict: 'A',
controller: //do your controller stuff here
}])
I know it's an old question, but here's an other approach, where you set to true a variable in the controller, and that's this action that set the focus to your element.
Try this:
myAngularModule.directive('goFocus', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(attrs.goFocus, function (newValue) {
if (newValue) {
$timeout(function () {
element[0].focus();
}, 100);
}
});
element.bind("blur", function (e) {
$timeout(function () {
scope.$apply(attrs.goFocus + "=false");
}, 10);
});
element.bind("focus", function (e) {
$timeout(function () {
scope.$apply(attrs.goFocus + "=true");
}, 10);
});
}
}
}]);
In HTML template:
<input go-focus="focusvar1" type="text" ng-model="mytext1" />
<input go-focus="focusvar2" type="text" ng-model="mytext2" />
<input go-focus="focusvar3" type="text" ng-model="mytext3" />
<button ng-click="doFocus()">OK</button>
In javascript angular controller:
myAngularModule.controller('myController', function () {
var self = this;
self.doFocus = function () {
// do some logic and focus your field
self.focusvar2 = true;
};
});

AngularJS modal window directive

I'm trying to make a directive angularJS directive for Twitter Bootstrap Modal.
var demoApp = angular.module('demoApp', []);
demoApp.controller('DialogDemoCtrl', function AutocompleteDemoCtrl($scope) {
$scope.Langs = [
{Id:"1", Name:"ActionScript"},
{Id:"2", Name:"AppleScript"},
{Id:"3", Name:"Asp"},
{Id:"4", Name:"BASIC"},
{Id:"5", Name:"C"},
{Id:"6", Name:"C++"}
];
$scope.confirm = function (id) {
console.log(id);
var item = $scope.Langs.filter(function (item) { return item.Id == id })[0];
var index = $scope.Langs.indexOf(item);
$scope.Langs.splice(index, 1);
};
});
demoApp.directive('modal', function ($compile, $timeout) {
var modalTemplate = angular.element("<div id='{{modalId}}' class='modal' style='display:none' tabindex='-1' role='dialog' aria-labelledby='myModalLabel' aria-hidden='true'><div class='modal-header'><h3 id='myModalLabel'>{{modalHeaderText}}</h3></div><div class='modal-body'><p>{{modalBodyText}}</p></div><div class='modal-footer'><a class='{{cancelButtonClass}}' data-dismiss='modal' aria-hidden='true'>{{cancelButtonText}}</a><a ng-click='handler()' class='{{confirmButtonClas}}'>{{confirmButtonText}}</a></div></div>");
var linkTemplate = "<a href='#{{modalId}}' id= role='button' data-toggle='modal' class='btn small_link_button'>{{linkTitle}}</a>"
var linker = function (scope, element, attrs) {
scope.confirmButtonText = attrs.confirmButtonText;
scope.cancelButtonText = attrs.cancelButtonText;
scope.modalHeaderText = attrs.modalHeaderText;
scope.modalBodyText = attrs.modalBodyText;
scope.confirmButtonClass = attrs.confirmButtonClass;
scope.cancelButtonClass = attrs.cancelButtonClass;
scope.modalId = attrs.modalId;
scope.linkTitle = attrs.linkTitle;
$compile(element.contents())(scope);
var newTemplate = $compile(modalTemplate)(scope);
$(newTemplate).appendTo('body');
$("#" + scope.modalId).modal({
backdrop: false,
show: false
});
}
var controller = function ($scope) {
$scope.handler = function () {
$timeout(function () {
$("#"+ $scope.modalId).modal('hide');
$scope.confirm();
});
}
}
return {
restrict: "E",
rep1ace: true,
link: linker,
controller: controller,
template: linkTemplate
scope: {
confirm: '&'
}
};
});​
Here is JsFiddle example http://jsfiddle.net/okolobaxa/unyh4/15/
But handler() function runs as many times as directives on page. Why? What is the right way?
I've found that just using twitter bootstrap modals the way the twitter bootstrap docs say to is enough to get them working.
I am using a modal to house a user edit form on my admin page. The button I use to launch it has an ng-click attribute that passes the user ID to a function of that scope, which in turn passes that off to a service. The contents of the modal is tied to its own controller that listens for changes from the service and updates values to display on the form.
So.. the ng-click attribute is actually only passing data off, the modal is still triggered with the data-toggle and href tags. As for the content of the modal itself, that's a partial. So, I have multiple buttons on the page that all trigger the single instance of the modal that's in the markup, and depending on the button clicked, the values on the form in that modal are different.
I'll take a look at my code and see if I can pull any of it out to build a plnkr demo.
EDIT:
I've thrown together a quick plunker demo illustrating essentially what I'm using in my app: http://embed.plnkr.co/iqVl0Wb57rmKymza7AlI/preview
Bonus, it's got some tests to ensure two password fields match (or highlights them as errored), and disables the submit button if the passwords don't match, or for new users username and password fields are empty. Of course, save doesn't do anything, since it's just a demo.
Enjoy.
There is a working native implementation in AngularStrap for Bootstrap3 that leverages ngAnimate from AngularJS v1.2+
Demo : http://mgcrea.github.io/angular-strap/##modals
You may also want to checkout:
Source : https://github.com/mgcrea/angular-strap/blob/master/src/modal/modal.js
Plunkr : http://plnkr.co/edit/vFslNmBAoKPVXtdmBXgv?p=preview
Well, unless you want to reinvent this, otherwise I think there is already a solution.
Check out this from AngularUI. It runs without twitter bootstrap.
I know it might be late but i started trying to figure out why the handler got called several times as an exercise and I couldn't stop until done :P
The reason was simply that each div you created for each modal had no unique id, once I fixed that everything started working. Don't ask me as to what the exact reason for this is though, probably has something to do with the $('#' + scope.modalId).modal() call.
Just though I should post my finding if someone else is trying to figure this out :)

Resources