web-dev-qa-db-fra.com

Utilisation de Bootstrap typeahead avec Angular

Je développe actuellement une application web qui utilise Twitter-bootstrap et Angularjs en bonne harmonie. Cependant, j'ai des problèmes avec la typeahead et je l'utilise comme ng-model.

Tout fonctionne correctement lors de la frappe, mais lorsque je sélectionne un élément (une suggestion), la valeur ne se reflète pas dans le contrôleur Angular sauf si je modifie la valeur de la zone de texte après avoir sélectionné une valeur. Tapez -> Sélectionner -> Tapez fonctionne. Tapez -> Sélectionner ne fonctionne pas.

HTML:

<form ng-submit="addAssignment(assignName)">
  <div class="input-append">
    <input type="text" placeholder="Type a name" ng-model="assignName" ng-change="dostuff()" data-provide="typeahead" data-source="{{ teamNames }}">
    <button class="btn" type="submit">Add</button>
 </div>
</form>

Code angulaire:

 $scope.addAssignment = function(name) {
    alert(name);
    return;
 }

J'ai ajouté une fonction ng-change juste pour vérifier quand la valeur du modèle est modifiée. Il n'est modifié que lors de la saisie manuelle, et NON lorsqu'une valeur est sélectionnée dans la liste qui apparaît sur la tête de frappe.

J'apprécie toute réponse qui pourrait aider à résoudre ce problème. Merci!

20
Kjetil Haaheim

Il existe une implémentation native fonctionnelle dans AngularStrap pour Bootstrap3 qui tire parti de ngAnimate d'AngularJS v1.2 +

Vous pouvez également commander:

21
Olivier

Je suggère de consulter la directive typeahead du référentiel AngularUI/boostrap: http://angular-ui.github.com/bootstrap/

Il s'agit d'une implémentation native dans AngularJS pur, il ne nécessite donc aucune dépendance tierce. En plus de cela, il est très bien intégré à l'écosystème AngularJS car il: * utilise une syntaxe concise connue de la directive select * comprend les promesses AngularJS afin que les résultats puissent être récupérés dynamiquement en utilisant $http avec un minimum d'effort.

22

J'ai fait cette implémentation native de typeahead en se basant uniquement sur angular (et bootstrap css pour le style)), pourrait aider quiconque cherche à faire cela.

Démo ici: https://jsfiddle.net/bh29tesc/

Directive angulaire:

angular.module('app').
directive('typeahead', ['$compile', '$timeout', function($compile, $timeout)   {
    return {
        restrict: 'A',
        transclude: true,
        scope: {
            ngModel: '=',
            typeahead: '=',
            typeaheadCallback: "="
        },
        link: function(scope, elem, attrs) {
            var template = '<div class="dropdown"><ul class="dropdown-menu" style="display:block;" ng-hide="!ngModel.length || !filitered.length || selected"><li ng-repeat="item in filitered = (typeahead | filter:{name:ngModel} | limitTo:5) track by $index" ng-click="click(item)" style="cursor:pointer" ng-class="{active:$index==active}" ng-mouseenter="mouseenter($index)"><a>{{item.name}}</a></li></ul></div>'

            elem.bind('blur', function() {
                $timeout(function() {
                    scope.selected = true
                }, 100)
            })

            elem.bind("keydown", function($event) {
                if($event.keyCode == 38 && scope.active > 0) { // arrow up
                    scope.active--
                    scope.$digest()
                } else if($event.keyCode == 40 && scope.active < scope.filitered.length - 1) { // arrow down
                    scope.active++
                    scope.$digest()
                } else if($event.keyCode == 13) { // enter
                    scope.$apply(function() {
                        scope.click(scope.filitered[scope.active])
                    })
                }
            })

            scope.click = function(item) {
                scope.ngModel = item.name
                scope.selected = item
                if(scope.typeaheadCallback) {
                    scope.typeaheadCallback(item)
                }
                elem[0].blur()
            }

            scope.mouseenter = function($index) {
                scope.active = $index
            }

            scope.$watch('ngModel', function(input) {
                if(scope.selected && scope.selected.name == input) {
                    return
                }

                scope.active = 0
                scope.selected = false

                // if we have an exact match and there is only one item in the list, automatically select it
                if(input && scope.filitered.length == 1 && scope.filitered[0].name.toLowerCase() == input.toLowerCase()) {
                    scope.click(scope.filitered[0])
                }
            })

            elem.after($compile(template)(scope))
        }
    }
}]);

Usage:

<input class="form-control" type="text" autocomplete="false" ng-model="input" placeholder="Start typing a country" typeahead="countries" typeahead-callback="callback" />
9
ropsnou

Eh bien, j'ai créé une solution de contournement sale. Sur la base de l'exemple situé ici: https://groups.google.com/forum/#!topic/angular/FqIqrs-IR0w/discussion , j'ai créé un nouveau module pour le contrôle typeahead:

angular.module('storageApp', []).directive('typeahead', function () {
return {
    restrict:'E',
    replace:true,
    scope:{
        model:'=',
        source:'&'
    },
    template:'<input type="text" ng-model="model"/>',
    link:function (scope, element, attrs) {
        console.log(scope.source);
        $(element).typeahead({
            source:scope.source,
            updater:function (item) {
                scope.$apply(read(item));
                return item;
            }
        });

        function read(value) {
            scope.model = value;
        }
    } // end link function
}; // end return
}); // end angular function

J'ai eu quelques problèmes avec la liaison de données, les options de remplissage automatique sont rassemblées à partir d'un contrôle Angular, et j'ai eu le problème que le contrôle a été créé avant que ces informations soient prêtes. Par conséquent, j'ai ajouté un html-attribute (datasource) au contrôle typeahead, et configurez une fonction $ observe dans le constructeur.

<typeahead id="addSupplier" model="addSupplier" placeholder="Skriv inn leverandør" class="typeahead" source="getSuppliers()" ></typeahead>

Je pense que c'est une sale solution, donc si quelqu'un a une meilleure idée, je suis la bienvenue pour l'entendre :). Le bug est décrit ici: https://github.com/angular/angular.js/issues/1284

3
Kjetil Haaheim

Ceci est basé sur l'implémentation de @ zgohr

$('#your-input-id-here').change((event)->
  angular.element("#your-input-id-here").scope().$apply((scope)->
    scope.your_ng_model = event.target.value
  )
)
0
Hengjie

Une autre alternative

En HTML

    <form ng-submit="submitRegion()">
        <input type="text" ng-model="currentRegion" id="region-typeahead" data-source="{{ defaultRegions }}"  data-provide="typeahead"/>
        <button type="submit" class="btn">Add</button>
    </form>

Dans votre contrôleur

    $scope.defaultRegions = ["Afghanistan", "Australia", "Bahrain", "New Zealand" ];

    $scope.submitRegion = function(){
        $scope.currentRegion = $('#region-typeahead').val();
        $scope.addRegion(); //your add or click function you defined
        $scope.currentRegion = ''; //clear
    }
0
Yodacheese

Voici une autre méthode que j'ai utilisée. C'est sale aussi. Ce code peut être déposé dans votre contrôleur.

$('#id_of_your_typeahead_input').change(function(event){
  $scope.$apply(function(scope){
    scope.your_ng_model = event.target.value;
  });
  $scope.your_ng_click_function();
});
0
zgohr