Angular JS - 自动聚焦输入并显示预先输入下拉菜单 - ui.bootstrap.typeahead

时间:2014-07-15 17:46:48

标签: angularjs angular-ui-bootstrap

我正在使用Angular JS - ui.bootstrap.typeahead:

我想单击一个按钮并聚焦输入字段并自动显示预先输入建议下拉列表。我有一个指令,当单击按钮时自动聚焦输入字段。如何自动显示下拉列表,以便用户可以使用向下箭头或单击,快速选择用户?

我创建了一个带有ui-bootstrap JS文件的Plunker,可以修改:

http://plnkr.co/edit/Z79LY0OYlwFc3wirjxol?p=preview

这是我的完整脚本:

<!doctype html>
<html ng-app="plunker">
  <head>
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular.js"></script>
    <script src="ui-bootstrap-tpls-0.10.0.js"></script>
  </head>
  <body>

<script>
  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(); 
                    });
                }
            });

        }
    };
});
function TypeaheadCtrl($scope, $http) {

  $scope.selected = undefined;
  $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Dakota', 'North Carolina', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'];
  $scope.opened = false;

  $scope.open = function() {
    $scope.opened = true;
  }
  $scope.close = function() {
    $scope.opened = false;
  }
}

</script>
<div class='container-fluid' ng-controller="TypeaheadCtrl">

    <h4>How can I open the typeahead dropdown automatically when button is pressed?</h4>
    <p>I have a directive that automatically focuses on the field but I can't seem to automatically show the typeahead. Even adding down arrow key click support would be great.

    <br/><br/>

    <button class="btn btn-default" ng-show="!opened" ng-click="open()">Open Input and show typeahead!</button>
    <button class="btn btn-default" ng-show="opened" ng-click="close()">Close Input</button>
    <br/><br/>

    <input type="text"
    focus-me="opened"
    ng-show="opened"
    ng-model="selected" 
    typeahead="state for state in states | filter:$viewValue | limitTo:8" 
    class="form-control">


    <br/>
    <pre ng-show="opened">Model: {{selected | json}}</pre>


</div>
  </body>
</html>

11 个答案:

答案 0 :(得分:55)

更新

我将指令添加到github以便于更新和访问。 您现在可以通过bower将其安装为依赖项。

原帖:

我提出了一个非常干净的黑客,需要对 ui-bootstrap-tpls 进行任何更改。想法是使用 $ setViewValue()来通过特殊过滤器比较器功能的组合来触发弹出窗口。

为了绕过 minLength 检查, $ setViewValue()必须设置为大于1的值,所以我使用一个空格字符串。比较器功能的作用是将一个空格视为与所有项目匹配,这样在点击空输入时它们都会显示出来。

我创建了一个简单的指令:

angular.module('app')
.directive('typeaheadFocus', function () {
  return {
    require: 'ngModel',
    link: function (scope, element, attr, ngModel) {

      //trigger the popup on 'click' because 'focus'
      //is also triggered after the item selection
      element.bind('click', function () {

        var viewValue = ngModel.$viewValue;

        //restore to null value so that the typeahead can detect a change
        if (ngModel.$viewValue == ' ') {
          ngModel.$setViewValue(null);
        }

        //force trigger the popup
        ngModel.$setViewValue(' ');

        //set the actual value in case there was already a value in the input
        ngModel.$setViewValue(viewValue || ' ');
      });

      //compare function that treats the empty space as a match
      scope.emptyOrMatch = function (actual, expected) {
        if (expected == ' ') {
          return true;
        }
        return actual.indexOf(expected) > -1;
      };
    }
  };
});

用法:

<input type="text" ng-model="selected" typeahead="item for item in items | filter:$viewValue:emptyOrMatch | limitTo:8" typeahead-focus >

答案 1 :(得分:21)

正如HarishR在评论中提到的,目前还没有对此功能的内置支持。

但我只想尝试黑客攻击,结果如下:http://plnkr.co/edit/Qrnat8yTvISuM1qHHDlA?p=preview

它包含许多黑客,使其有效:

  1. 包含jQuery以便使用.trigger(),可以用原生JS替换,但我很懒。
  2. 使用ng-focus调用.trigger('input')来触发打印头弹出
  3. 使用ng-trim =“false”禁用输入值自动修剪
  4. 一个自定义的empty-typeahead指令,它与ngModel的控制器交互,用于应用secretEmptyKey逻辑来绕过typeahead-min-length检查:

    .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
          });
        }
      }
    })
    
  5. 自定义过滤器比较器函数,当一个参数是secretEmptyKey时,它总是返回true(显示所有结果):

    $scope.stateComparator = function (state, viewValue) {
      return viewValue === secretEmptyKey || (''+state).toLowerCase().indexOf((''+viewValue).toLowerCase()) > -1;
    };
    
  6. 删除limitTo过滤器以显示所有结果

  7. 设置max-height和overflow css属性以在内容太长时显示滚动条
  8. 完成!

答案 2 :(得分:12)

我通过更改ui-bootstrap-tpls-0.10.0.js中的一些代码得到了一个有效的解决方案。 所以typeahead html标记没有区别。

您可以在http://plnkr.co/edit/LXHDpL?p=preview处查看。

要使用此修复程序,请使用Plunk中的ui-bootstrap-tpls-0.10.0.js。 要查看我的更改,请从Plunk打开ui-bootstrap-tpls-0.10.0.js并搜索“ahneo”。

 1. //minimal no of characters that needs to be entered before typeahead
    kicks-in
    // ahneo :: before
    //var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
    // ahneo :: after (changed minimal no of characters to 0 by default)
    var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 0;
 2. // ahneo :: new (set input value to empty string if it contains " " string value)
    if (inputValue === ' ') {
        inputValue = '';
        modelCtrl.$setViewValue('');
    }  
 3. // ahneo :: before
    //if (inputValue && inputValue.length >= minSearch) {
    // ahneo :: after (add new condition to get matches for min search = 0)
    if (minSearch === 0 || inputValue && inputValue.length >= minSearch) {
 4. // ahneo :: new (bind element to focus event to trigger modelCtrl.$parsers.unshift method)
    element.bind('focus', function (evt) {
        if (modelCtrl.$viewValue === '') {
            modelCtrl.$setViewValue(' ');
        }
    });

希望这有帮助

答案 3 :(得分:6)

现在,由于我没有足够的声誉来发表评论,我必须写一个新的答案来警告人们关于runTarm的答案。这是一个可行的解决方案,但它存在遇到以下错误的风险:

Error: [$rootScope:inprog] $apply already in progress

这似乎是由于ng-focus是同步事件(see discussion here)。相反,可以使用ng-click-attribute,并且不会发生此错误。

另外,我已经验证了

$element.triggerHandler('input');

与runTarm的答案中的jQuery触发器一样好。

答案 4 :(得分:5)

似乎内置的对此功能的支持即将在即将发布的版本中以typeahead-min-length属性支持值0的形式出现。

它在主分支https://github.com/angular-ui/bootstrap/commit/d859f42cc022a5d8779f1c7b358486bbdd04ed57中的此提交中实现,但是尚未发布此版本,并且它不在0.14.x分支中。

希望新版本能够快速推出,以便不再需要这些解决方法。

答案 5 :(得分:4)

我想要一些像OP的描述,我发现的唯一解决方案是提出一个结合了dropdown和typeahead指令的模板 - 也许是OP或其他人会觉得它很有用:

&#13;
&#13;
angular.module('app', ['ui.bootstrap'])
.controller('AppCtrl', function($scope) {
  $scope.model;
  $scope.options = [{label:'Option 1'}, {label:'Option 2'}, {label:'Option 3'}];
  
  $scope.onSelect = function($item, $model, $label) {
    $scope.model = angular.copy($item);
  }
});
&#13;
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js"></script>

<div ng-app='app' style='padding:10px'>
  <div ng-controller='AppCtrl'>
    <div class='dropdown' dropdown style='width:200px'>
      <!-- This needs UI Bootstrap 0.12 to work -->
      <div class='input-group'>
        <input type='text' class='form-control' ng-model='model.label' typeahead="op.label for op in options | filter:$viewValue | limitTo:8" typeahead-editable='false' />
        <span class='input-group-btn'>
          <button class='btn btn-default dropdown-toggle' dropdown-toggle>
            <span class='caret'></span>
          </button>
        </span>
      </div>
      <ul class="dropdown-menu" role='menu' style='max-height:200px;overflow-y:scroll'>
        <li ng-repeat='op in options'>
          <a href ng-click='onSelect(op)'>{{op.label}}</a>
        </li>
      </ul>
    </div>
  </div>
</div>
&#13;
&#13;
&#13;

当然,您可以简化它以使选项只是一个字符串数组 - 我将它们作为对象,因为这更像我需要的。

答案 6 :(得分:3)

预输入分钟长度=&#34; 0&#34;诀窍(我使用的是v0.4.0)

答案 7 :(得分:2)

我通过指令解决了这个问题。当您使用此指令时,显示没有某些过滤器的列表,然后键入搜索以查找元素。

angular.module('myapp')
.directive('typeaheadLikeSelect', 
['$parse',function($parse) {
    return {
        require: 'ngModel',
        link: function (scope, element, attr, ngModel){

            var aux_modelValue, aux_viewValue,
                modelGetter = $parse(attr.ngModel),
                modelSetter = modelGetter.assign;

            var noViewValue = function(){
              return
              ngModel.$$lastCommittedViewValue === undefined ||
              !ngModel.$$lastCommittedViewValue.trim();
            };

            var forceEvent = function(){
              ngModel.$setViewValue();
              ngModel.$viewValue = ' ';
              ngModel.$setViewValue(' ');
              ngModel.$render();
              scope.$apply();
              element.val(element.val().trim());
            };

            element.on('mousedown', function(e){
              e.stopPropagation();
              forceEvent();
            });

            element.on('blur', function(e){
              e.stopPropagation();
              if(aux_modelValue){
                modelSetter(scope, aux_modelValue);
                scope.$apply();
              }
            });

            scope.$watch(function () {
              return ngModel.$modelValue;
            }, function(newValue, oldValue){
              if(newValue || (!newValue && !oldValue))
                aux_modelValue = newValue;
            });

        }
    };
}]);

我留下了一个视图代码,用于测试上面的代码。

<script type="text/ng-template" id="customTemplate.html">
      <a>
          <span ng-bind-html="match.label.description | uibTypeaheadHighlight:query"></span>
      </a>
</script>
    <div class="form-group has-feedback" ng-class="{'has-success':items.mymodel}">
            <input
                placeholder="typeahead"
                ng-model="items.mymodel"
                uib-typeahead="responses as responses for responses in getResponses($viewValue)"
                typeahead-template-url="customTemplate.html"
                typeahead-input-formatter="$model.description"
                typeahead-loading="loadingResponses"
                typeahead-no-results="noResponses"
                typeahead-editable="false"
                typeahead-on-select="changeItem($item)"
                class="form-control"
              required="required"
              typeahead-like-select>
              <div ng-show="noResponses">
                <i class="glyphicon glyphicon-remove"></i> No Results Found
              </div>

              <span ng-show="!items.mymodel" class="glyphicon glyphicon-search form-control-feedback" aria-hidden="true"></span>
              <span ng-show="items.mymodel" class="glyphicon glyphicon-ok form-control-feedback" aria-hidden="true"></span>
          </div>

答案 8 :(得分:0)

每当输入元素具有焦点时,我希望打开输出。 @ yohairosen的解决方案在最新版本的Angular Bootstrap(版本:1.0.3)上对我无效。这是解决方案对我有用。它涉及手动调用由ui-bootstrap-typeahead附加的解析器,它填充了这些建议:

&#13;
&#13;
angular.module('app')
.directive('typeaheadFocus', function () {
  return {
      require: 'ngModel',
      link: function (scope, element, attr, ngModel) {
        element.bind('click', function () {
          ngModel.$parsers[0](ngModel.$viewValue);
        });
      }
    };
  };
});
&#13;
&#13;
&#13;

这可能是错误的,因为它假设ui-bootstrap-typeahead添加的解析器是唯一的。

答案 9 :(得分:0)

我们想要的是在聚焦时输入元素上的触发('输入')

在Angular中执行此操作的正确方法是在指令中执行此操作。

angular.module('app')    
.directive('showList', function() {
   return {
       restrict: 'A',
       link: function(scope, iEle) {
           iEle.focus(function() {
               iEle.trigger('input');
           });
       }
   };
});

在typeahead输入元素上使用此指令 -

<input show-list uib-typeahead="state for state in states | filter:$viewValue" typeahead-min-length="0" class="form-control">

答案 10 :(得分:0)

您可以通过此代码实现

 $scope.change = function() {
        var e = document.getElementById("test");
        var $e = angular.element(e);
        $e.triggerHandler('focus');
        $e.triggerHandler('input');
    }

将测试更改为您的类型头ID