how to hide dropdown md-autocomplete - angularjs

How to Hide AutoComplete Dropdown after selected item i try many types but not success , anyone help me this , highly appreciated
<md-autocomplete id="autocomplete" st-search="campaign_name"
ng-disabled="Ctrlmain.isDisabled"
md-no-cache="true"
md-selected-item="Ctrlmain.selectedItem"
md-search-text="Ctrlmain.campaignname"
md-selected-item-change="Ctrlmain.filltextbox(item)"
md-items="item in Ctrlmain.getMatches(Ctrlmain.campaignname) | unique:'campaign_name'"
md-item-text="item.campaign_name"
md-min-length="0"
placeholder="Search Campaign"
md-menu-class="autocomplete-custom-template">
<md-item-template>
<span class="item-title">
<span> {{item.campaign_name}} </span>
</span>
</md-item-template>
</md-autocomplete>
Angularjs Code
filltextbox(st){
var autoChild = document.getElementById('autocomplete').firstElementChild;
console.log(autoChild)
var el = angular.element(autoChild);
console.log(el)
el.scope().$mdAutocompleteCtrl.hidden = true;
// return st;
}

I find in these scenarios it is often another $digest or DOM affecting async function that gets in the way of the standard close logic working. The quick hack is to try to close the dialog after a $timeout.
If you have other async processes running on the page, perhaps like executing a search query, and while the search is running a full screen loader is written into the DOM then it might be necessary to set the $timeout delay to a larger value than usual.
The following snippet tries to force the close immediately, and after a timeout to cover all bases, I use this for my md-autocompletes that are serving as a search input with best bet suggestions as well as search results.
filltextbox(st){
closeAutocomplete();
$timeout(closeAutocomplete, 100);
// return st;
}
closeAutocomplete () {
var autoChild = document.getElementById('autocomplete').firstElementChild;
var el = angular.element(autoChild);
el.scope().$mdAutocompleteCtrl.hidden = true;
}
For good measure, I make sure the other async functions that operate on the page also call closeAutocomplete. You could wrap the immediate and $timeout logic into a single call like this that allows you to pass in a variable delay
closeAutocomplete (delay) {
// declare function once, only one point to maintain
var fn = function() {
var autoChild = document.getElementById('autocomplete').firstElementChild;
var el = angular.element(autoChild);
el.scope().$mdAutocompleteCtrl.hidden = true;
};
fn(); // immediate
if(!delay) delay = 100;
$timeout(fn, delay);
}

Related

How to know when angular-busy's cg-busy is actively displaying (or how do I enforce a minDuration for a promise chain)?

I'm using angular-busy on an old angular 1.5.9 codebase (yeah, yeah, we're migrating soon, ...)
I am using it to do a "Check for updates ..." overlay and I want the busy message to show for a bit, so I set delay=0, minDuration=1500 just so it doesn't look like it flickers. The updates generally take < 200ms.
As part of the updates the content of what's in my $uibModal dialog updates and I'm using ng-show to hide it until my call has returned. That part works great. But because I'm using cg-busy's minDuration, the content shows before cg-busy's overlay has left - which I don't want.
cg-busy know's when it's active and not. Internally it uses a child scope for it's tracker class which has a method called $cgBusyIsActive() that it uses for it's own ng-show. It would great if I had access to that, but after researching a bit, alas, I can not find a way to get to it (and probably shouldn't even it I could).
Question 1: Does anyone know of a way to detect when cg-busy is active and use that to ng-show or ng-hide?
Here's a quick snippet showing what I would like:
<div cg-busy="$ctrl.updatesBusy">
<div ng-show="!$cgBusyIsActive() && $ctrl.updateResult.done">
...
</div>
</div>
Question 2: Or are there better/different ways of accomplishing my workaround below of forcing the promise chain to have a minDuration?
To get around this currently, I just create my own timer and shove a timeout in my promise chain that fires if there's any time left in minDuration, but I'd love a solution that uses cg-busy's minDuration.
const cgTimerStarted = Date.now();
$ctrl.updatesBusy.promise = myUpdateCallAsync()
.then(function(result) {
// blah blah, blah blah
return $q.resolve(); // return a promise obj or cg-busy will go away
})
.catch(function(err) {
// boo hoo, boo hoo
return $q.resolve(); // return a promise obj or cg-busy will go away
})
.then(function() {
const minDuration = $ctrl.updatesBusy.minDuration;
const timeTaken = Date.now() - cgTimerStarted;
const timeLeft = Math.max(minDuration - timeTaken, 0);
return $timeout(timeLeft);
})
.finally(function() {
$ctrl.updatesResult.done = true;
});

angular file upload in ng-repeat with preview input bind

I'm working on an angular 1.6 based image upload with ng-repeat, note the input is not multi, but there are multiple ng-repeated inputs, I have the image preview working as well as adding lines / removing lines, the only thing that seems to not be working is if I remove an item the file inputs do not update (I have code that does properly update the previews). Here is what I am working with:
<div ng-repeat="item in data.items track by $index">
<input ng-model="item.fileinput" type="file" name="image_{{$index}}" id="image_{{$index}}" onchange="angular.element(this).scope().imageChoose(this)"/><i ng-click="removeEvent($index)" class="fa fa-trash fa-lg"></i>
<img ng-if="!item.thumb" class="preview-image-small" ng-src="/images/general/placeholder.jpg"</img>
<img ng-if="item.thumb" class="preview-image-small" ng-src="{{item.thumb}}"</img>
</div>
Then in my controller I handle the imageChoose as follows:
$scope.imageChoose = function (data) {
var id = data.id.split("_");
id = id[id.length-1];
var elem = document.getElementById(data.id);
if (typeof (FileReader) != "undefined") {
var reader = new FileReader();
reader.onload = function (e) {
$scope.$apply(function() {
$scope.data.data.items[id].thumb = e.target.result;
});
};
reader.readAsDataURL(elem.files[0]);
} else {
alert("This browser does not support FileReader.");
}
};
This properly sets the image previews and when I run a remove on a line they re-order correctly due to the ng-src of event.thumb. The problem is the actual file input does not bind or update, here is the code for removing a line:
$scope.removeEvent = function (index) {
$scope.data.items.splice(index, 1);
};
I'm hoping there is a relatively simple way to bind the input or handle the remove so that the inputs stay correct. Thanks in advance for any ideas.
Your removeEvent method is not working because of using track by $index together with ng-repeat. This is a known side effect. Try removing it/using different track by expressions.

App Fails First Load When Listing Array's Item from a DataSnapshot from Firebase

I am storing a DataSnapshot into a $scope array, so the ng-repeat into HTML div updates my "result list"
The problem is, when I run the code, input a value and click to run the DataSnapshot function, the first result doesn't appear on the app screen, only on the app's log on browser's console. If I run the function again, the result appears. If I change the input and click the button (run the function) again, this input appears in the first try.
So, here's what you will probably need:
Print from first attempt(data appears on console but not on app):
Print from second attempt(data appears twice on console and once on app):
Print from third attempt with another input(data appears once on console and once on app):
Codes:
index.html
<div class="list card">
<div class="item item-body" ng-repeat="locker in lockers">
<a href="#" class="item item-icon-right text-center">
<img ng-src="{{imageSrc}}" style="width: 35px; height: auto;">
<p style="text-align: center">
Locker - {{locker.number}}
<br>
{{key}}
</p>
<i class="icon ion-chevron-right"></i>
</a>
</div>
app.js
angular.module('starter', ['ionic', 'firebase'])
.controller('LockerCtrl', ["$scope", "$firebaseArray", function($scope,$firebaseArray, snapshot){
var lockersRef = new Firebase('https://ifrjarmariosdb.firebaseio.com/armarios');
$scope.getButtonClicked = function() {
var lockernumber = document.getElementById('lockerNumberInput').value;
if(lockernumber.length > 0){
lockerInputedNumber = parseInt(lockernumber);
lockersRef.on('value', function(snapshot){
snapshot.forEach(function(data){
var number = data.val().number;
if(number == lockerInputedNumber){
$scope.key = data.key();
$scope.lockers = [data.val()];
console.log(data.val());
if(number == 101){ -
$scope.imageSrc = "img/locker_test2.png";
}
else{
$scope.imageSrc = "img/locker_test.jpg";
}
}
});
});
}
As you could see by the prints, I'm also facing some trouble to change the image source according to the number value from Firebase. If you could help me to solve that, it would be a great help.
Please, I'm not asking for the solution for this method, if you know a different method to do this, I ask you to post it too.
Thanks!
This code starts loading data from Firebase:
lockersRef.on('value', function(snapshot){
snapshot.forEach(function(data){
var number = data.val().number;
if(number == lockerInputedNumber){
$scope.key = data.key();
$scope.lockers = [data.val()];
console.log(data.val());
if(number == 101){ -
$scope.imageSrc = "img/locker_test2.png";
}
else{
$scope.imageSrc = "img/locker_test.jpg";
}
}
});
The loading happens asynchronously. Since it takes time before the data is available, the browser continues executing the JavaScript after this code.
When the data comes back from the server, it executes your callback function. But at that point, AngularJS is no longer expecting any changes to the $scope.
The solution is to make AngularJS aware of the fact that you've changed the scope. The easiest way to do that, is to wrap the callback into a $timeout() call:
lockersRef.on('value', function(snapshot){
$timeout(function() {
snapshot.forEach(function(data){
var number = data.val().number;
if(number == lockerInputedNumber){
$scope.key = data.key();
$scope.lockers = [data.val()];
console.log(data.val());
if(number == 101){ -
$scope.imageSrc = "img/locker_test2.png";
}
else{
$scope.imageSrc = "img/locker_test.jpg";
}
}
});
});
Some people that ran into the same problem:
Angular JS firebase.(child_added) not rendering on page
ng-show and ng-change unwanted delay
A few other things I note about your code:
you're downloading all lockers and then filtering for the one the user entered client-side. This is wasting bandwidth that your user might be paying for. A better way would be to leave the filtering to Firebase with a Query:
var query = lockersRef.orderByChild('number').equalTo(lockerInputedNumber);
query.on('value', function(snapshot){
there is a binding library for AngularJS+Firebase called AngularFire, which handles the $timeout() thing automatically. It's built on top of Firebase's JavaScript SDK that you're now using, so they interoperate perfectly.

ngChecked doesn't seem to want to bind to the model. Why?

I'm using Ionic and thus angularjs, and I'm trying to store a setting in localStorage.
I've a checkbox that I want to use to set analytics on or off. The html looks like this.
Analytics on/off
<label class="toggle toggle-balanced">
<input type="checkbox" ng-click="toggleAnalytics()" ng-checked="analytics">
<div class="track">
<div class="handle"></div>
</div>
</label>
and in the related controller I have:
$scope.analytics = $localstorage.get('analyticsOnOff');
$log.log("Analytics initialised at", $scope.analytics);
$scope.toggleAnalytics = function(){
if($scope.analytics===true){
$scope.analytics = false;
$localstorage.set('analyticsOnOff', false);
}else{
$scope.analytics = true;
$localstorage.set('analyticsOnOff', true);
}
$log.log("Analytics is ", $scope.analytics);
}
So as you can see, when you change the checkbox the toggleAnalytics() function toggles $scope.analytics between true and false and updates the localstorage to reflect that.
Note that I am using $localstorage methods. $localstorage is defined in another service. It's allows you to set and get localStorage objects easily.
Anyway, my issue is a really simple, but baffling one. I know I can alter the analytics value from the console logs, but no matter what I do the checkbox initialises as on.
Any ideas?
You should be using ng-model on your checkbox, this will handle setting the actual property - and you can use a change event then:
<input type="checkbox" ng-change="toggleAnalytics(analytics)" ng-model="analytics" />
And the controller:
$scope.toggleAnalytics = function(isChecked) {
$localstorage.set('analyticsOnOff', isChecked);
$log.log("Analytics is ", isChecked);
}
Thank you very much for the feedback guys.
#nickgraef for altering me to the real problem - that .get() returns a string.
#tymeJV for supplying me with a much more elegant version of my own code.
This is what I did in the end, it's a mash up of both tips. I convert the analytics variable into a boolean straight after I .get() it from localstorage.
$scope.analytics = $localstorage.get('analyticsOnOff');
$scope.analytics = ($scope.analytics == 'true' ? true : false);
$log.log("Analytics initialised at", $scope.analytics);
$scope.toggleAnalytics = function(isChecked) {
$localstorage.set('analyticsOnOff', isChecked);
$log.log("Analytics is ", isChecked);
}

How do I change AngularJS ng-src when API returns null value?

In working with the API from themoviedb.com, I'm having the user type into an input field, sending the API request on every keyup. In testing this, sometimes the movie poster would be "null" instead of the intended poster_path. I prefer to default to a placeholder image to indicate that a poster was not found with the API request.
So because the entire poster_path url is not offered by the API, and since I'm using an AngularJS ng-repeat, I have to structure the image tag like so (using dummy data to save on space):
<img ng-src="{{'http://example.com/'+movie.poster_path}}" alt="">
But then the console gives me an error due to a bad request since a full image path is not returned. I tried using the OR prompt:
{{'http://example.com/'+movie.poster_path || 'http://example.com/missing.jpg'}}
But that doesn't work in this case. So now with the javascript. I can't seem to get the image source by using getElementsByTagName or getElementByClass, and using getElementById seems to only grab the first repeat and nothing else, which I figured would be the case. But even then I can't seem to replace the image source. Here is the code structure I attempted:
<input type="text" id="search">
<section ng-controller="movieSearch">
<article ng-repeat="movie in movies">
<img id="myImage" src="{{'http://example.com/'+movie.poster_path}}" alt="">
</article>
</section>
<script>
function movieSearch($scope, $http){
var string,
replaced,
imgSrc,
ext,
missing;
$(document).on('keyup', function(){
string = document.getElementById('search').value.toLowerCase();
replaced = string.replace(/\s+/g, '+');
$http.jsonp('http://example.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
});
imgSrc = document.getElementById('myImage').src;
ext = imgSrc.split('.').pop();
missing='http://example.com/missing.jpg';
if(ext !== 'jpg'){
imgSrc = missing;
}
});
}
</script>
Any ideas with what I'm doing wrong, or if what I'm attempting can even be done at all?
The first problem I can see is that while you are setting the movies in a async callback, you are looking for the image source synchronously here:
$http.jsonp('http://domain.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
});
// This code will be executed before `movies` is populated
imgSrc = document.getElementById('myImage').src;
ext = img.split('.').pop();
However, moving the code merely into the callback will not solve the issue:
// THIS WILL NOT FIX THE PROBLEM
$http.jsonp('http://domain.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
// This will not solve the issue
imgSrc = document.getElementById('myImage').src;
ext = img.split('.').pop();
// ...
});
This is because the src fields will only be populated in the next digest loop.
In your case, you should prune the results as soon as you receive them from the JSONP callback:
function movieSearch($scope, $http, $timeout){
var string,
replaced,
imgSrc,
ext,
missing;
$(document).on('keyup', function(){
string = document.getElementById('search').value.toLowerCase();
replaced = string.replace(/\s+/g, '+');
$http.jsonp('http://domain.com/query='+replaced+'&callback=JSON_CALLBACK').success(function(data) {
console.dir(data.results);
$scope.movies = data.results;
$scope.movies.forEach(function (movie) {
var ext = movie.poster_path && movie.poster_path.split('.').pop();
// Assuming that the extension cannot be
// anything other than a jpg
if (ext !== 'jpg') {
movie.poster_path = 'missing.jpg';
}
});
});
});
}
Here, you modify only the model behind you view and do not do any post-hoc DOM analysis to figure out failures.
Sidenote: You could have used the ternary operator to solve the problem in the view, but this is not recommended:
<!-- NOT RECOMMENDED -->
{{movie.poster_path && ('http://domain.com/'+movie.poster_path) || 'http://domain.com/missing.jpg'}}
First, I defined a filter like this:
In CoffeeScript:
app.filter 'cond', () ->
(default_value, condition, value) ->
if condition then value else default_value
Or in JavaScript:
app.filter('cond', function() {
return function(default_value, condition, value) {
if (condition) {
return value;
} else {
return default_value;
}
};
});
Then, you can use it like this:
{{'http://domain.com/missing.jpg' |cond:movie.poster_path:('http://domain.com/'+movie.poster_path)}}

Resources