Search code examples
angularjsangular-ui-bootstraptypeaheadangular-ui-typeahead

AngularJS [typeahead] reopen result dropdown on onFocus


I have problem in following use case in typeahead:

Problem:

  • user starts typing, dropdown opens and shows results
  • user clicks out of the input field (nothing is deleted from the input field), dropdown closes
  • user clicks back into the typeahead input field (and does NOT start to type anything) and nothing happens. → the desired behavior is: dropdown opens again and shows the same result list as last time (of course, that only happens, if there is anything in the input field)
  • When user search anything and if result found and focus lost occurs and string does not get cleared again focus nothing happen, no hints
  • When user seach anything and if result not found and focus lost occurs string get cleared and there is also message of no data found.

So, Moral of the story is that in first case string should get cleared if it is not selected from list and it is substring of list string...

So, maybe a typeahead-search-on-focus setting?

HTML:

<input type="text" focus-me="opened" ng-focus="onFocus($event)" ng-show="opened" ng-trim="false" ng-model="selected" empty-typeahead typeahead="state for state in states | filter:$viewValue:stateComparator" class="form-control" />

JS:

(function () {
var secretEmptyKey = '[$empty$]'

angular.module('plunker', ['ui.bootstrap'])
.directive('focusMe', function($timeout, $parse) {
  return {
      //scope: true,   // optionally create a child scope
      link: function(scope, element, attrs) {
          var model = $parse(attrs.focusMe);
          scope.$watch(model, function(value) {
              if(value === true) { 
                  $timeout(function() {
                      element[0].focus();
                  });
              }
          });
      }
  };
})
.directive('emptyTypeahead', function () {
  return {
    require: 'ngModel',
    link: function (scope, element, attrs, modelCtrl) {
      // this parser run before typeahead's parser
      modelCtrl.$parsers.unshift(function (inputValue) {
        var value = (inputValue ? inputValue : secretEmptyKey); // replace empty string with secretEmptyKey to bypass typeahead-min-length check
        modelCtrl.$viewValue = value; // this $viewValue must match the inputValue pass to typehead directive
        return value;
      });
      
      // this parser run after typeahead's parser
      modelCtrl.$parsers.push(function (inputValue) {
        return inputValue === secretEmptyKey ? '' : inputValue; // set the secretEmptyKey back to empty string
      });
    }
  }
})
.controller('TypeaheadCtrl', function($scope, $http, $timeout) {
  $scope.selected = undefined;
  $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Wyoming'];
  $scope.opened = true;
  
  $scope.stateComparator = function (state, viewValue) {
    return viewValue === secretEmptyKey || (''+state).toLowerCase().indexOf((''+viewValue).toLowerCase()) > -1;
  };
  
  $scope.onFocus = function (e) {
    $timeout(function () {
      $(e.target).trigger('input');
      $(e.target).trigger('change'); // for IE
    });
  };
});
}());

I have this Plunker to show how my code looks like.

There is also GitHub Issue.


Solution

  • Hello Everyone,

    After some efforts i got the proper solution...

    There are some directives that can be useful for the solution,

    Add above condition in typeaheadFocus directive,

        if ((e.type == "focus" && viewValue != ' ') && (e.type == "focus" && viewValue != '')){
            return;
        }
    

    Add following function in typeaheadFocus directive and typeaheadOnDownArrow directive,

        //compare function that treats the empty space as a match
        scope.$emptyOrView = function(actual) {
            if(ngModel.$viewValue.trim()) {
                return actual ? actual.value.toString().toLowerCase().indexOf(ngModel.$viewValue.toLowerCase()) > -1 : false;
            }
            return true;
        };            
    

    Change this condition in ui-bootstrap.js,

      if (inputFormatter) {
         locals.$model = modelValue;
         if(modelValue){
         locals.$label = view_value;
      } else {
         locals.$label = '';
      }
    

    Add following event in ui-bootstrap js,

    element.bind('blur', function(evt) {
       hasFocus = false;
       if (!isEditable && modelCtrl.$error.editable) {
          element.val('');
          modelCtrl.$viewValue = ' ';
       }
       if(!isEditable && scope.no_data_found) {
             //ngModel.$viewValue = '';
             modelCtrl.$viewValue = ' ';
             scope.no_data_found = false;
             popUpEl.find('.typeaheadNoData').remove();
             resetMatches();
             scope.$digest();
             element.val("");
       }
    });
    

    Add this in ui-bootstrap.js,

            // I listen for emptyTypeAhead events from the parent scope chain.
        originalScope.$on(
            "emptyTypeAhead",
            function handlePingEvent( event, newVal1) {
                var scope = originalScope.$new();
                modelCtrl.$setViewValue(newVal1);
                return newVal1;
            }
        );
    

    Add the above function with ui-bootstrap.js in all directive,

    .directive('shouldFocus', function($parse) {
        return {
            restrict: 'A',
            link: function(scope, element, attrs) {
                scope.$watch(attrs.shouldFocus, function(newVal, oldVal) {
                    var isActive = scope.$eval(attrs.shouldFocus);
                    if(isActive) {
                        var ele = element[0];
                        var parent = ele.parentNode
    
                        if(ele.offsetTop + ele.offsetHeight > parent.offsetHeight + parent.scrollTop){
                            parent.scrollTop = ele.offsetTop;
                        } else if(parent.scrollTop > ele.offsetTop) {
                            parent.scrollTop = ele.offsetTop;
                        }
                    }
                });
            }
        }
    });
    

    Example:

    <input type="text" ng-model="inquiry.account" 
     placeholder="Select Account" 
     typeahead="account.id as account.text for account in account_typeahead_json" 
     typeahead-input-formatter="formatModel($model,$label)" 
     typeahead-editable="false" typeahead-on-down-arrow typeahead-focus />
    

    Hope this answer will be helpful for you.

    Here I have added ui-bootstrap.js