Как предотвратить игнорирование формы пользователя в angularjs angular-ui-bootstrap uib-typeahead

Я использую AngularJS v1.6.6 и версию angular-ui-bootstrap: 2.5.0 для создания поля автозаполнения.

Все работает правильно, но мне нужен способ убедиться, что пользователь действительно выбирает вариант из списка предложений.

Вот мой код:

HTML:

<div class='container-fluid typeahead-demo' ng-controller="TypeaheadCtrl">
<h4>How to prevent user from typing the whole word ignoring suggestions?</h4>
<div>Model: 
<pre>{{selected | json}}</pre>
</div>
<form role="form" name="chooseStateForm" autocomplete="off" novalidate>
    <div class="form-group required">
        <div>
            <label for="state" class="control-label col-sm-3">Choose a State:</label>
                <input type="text" 
                        class="form-control" 
                        required 
                        placeholder="Try typing the whole name of the state ignoring suggestion" 
                        name="state" 
                        ng-model="selected" 
                        uib-typeahead="option as option.name for option in states | filter:{name: $viewValue}" 
                        typeahead-min-length="1" 
                        typeahead-no-results="noresults" 
                        typeahead-show-hint="true" 
                        >
        </div>
        <div ng-if="noresults">
        <p>No match found!</p>
        </div>
    </div>
</form>
<br><br><br><br>
<div>
    <button class="btn btn-primary" type="button" ng-click="$ctrl.ok()" ng-disabled="chooseStateForm.$invalid">OK</button>
    <button class="btn btn-primary" type="button" ng-click="$ctrl.cancel()">Cancel</button>
</div>

JS:

angular.module('app', ['ui.bootstrap']);
angular.module('app').controller('TypeaheadCtrl', function($scope) {

$scope.selected = undefined;
$scope.states = [
{id: 1, name: 'Alabama'},  
{id: 2, name: 'California'}, 
{id: 3, name: 'Delaware'}, 
{id: 4, name: 'Florida'}, 
{id: 5, name: 'Georgia'}, 
{id: 6, name: 'Hawaii'}, 
{id: 7, name: 'Idaho'},  
{id: 8, name: 'Kansas'}, 
{id: 9, name: 'Louisiana'}, 
{id: 10, name: 'Maine'}, 
{id: 11, name: 'Nebraska'}, 
{id: 12, name: 'Ohio'}, 
{id: 13, name: 'Pennsylvania'}, 
{id: 14, name: 'Rhode Island'}, 
{id: 15, name: 'South Carolina'}, 
{id: 16, name: 'Tennessee'},
{id: 17, name: 'Utah'}, 
{id: 18, name: 'Vermont'}, 
{id: 19, name: 'Washington'}
];
});

Посмотрите этот jsfiddle, и вы поймете, что я имею в виду: http://jsfiddle.net/elenat82/yhpbdvva/ 20/

Если пользователь хочет, например, выбрать Огайо, так как это всего 4 буквы, ему будет проще просто набрать «Огайо», чем выбрать предложенный вариант.

Но при этом моя модель становится строкой, в то время как это объект, если он выбирает из списка предложений.

Да, я проверяю правильность модели в своем контроллере, но я хочу, чтобы это было сделано до того, как пользователь отправит форму, я хотел бы показать сообщение об ошибке, объясняющее пользователю, что он сделал неправильно.

---------- РЕДАКТИРОВАТЬ ----------

Я нашел другой способ добиться того же результата, но используя директиву и расширение объекта $validators.

Вот ссылка на обновленный jsfiddle: http://jsfiddle.net/elenat82/fL5fw1up/2/

И вот обновленный код:

HTML:

<div class='container-fluid typeahead-demo' ng-controller="TypeaheadCtrl">
<h4>How to prevent user from typing the whole word ignoring suggestions?</h4>
<div>Model: 
<pre>{{selected | json}}</pre>
<div>Errors: 
<pre>{{chooseStateForm.state.$error | json}}</pre>
</div>
<form role="form" name="chooseStateForm" autocomplete="off" novalidate>
    <div class="form-group required">
        <div>
            <label for="state" class="control-label col-sm-3">Choose a State:</label>
                <input type="text" 
                        class="form-control" 
                        required 
                        placeholder="Try typing the whole name of the state ignoring suggestion" 
                        name="state" 
                        ng-model="selected" 
                        uib-typeahead="option as option.name for option in states | filter:{name: $viewValue}" 
                        typeahead-min-length="1" 
                        typeahead-no-results="noresults" 
                        typeahead-show-hint="true" 
                        object
                        >
        </div>
        <div ng-if="noresults">
        <p>No match found!</p>
        </div>
    </div>
</form>
<br><br><br><br>
<div>
    <button class="btn btn-primary" type="button" ng-click="$ctrl.ok()" ng-disabled="chooseStateForm.$invalid">OK</button>
    <button class="btn btn-primary" type="button" ng-click="$ctrl.cancel()">Cancel</button>
</div>

JS:

angular.module('app', ['ui.bootstrap']);
angular.module('app').controller('TypeaheadCtrl', function($scope) {

$scope.selected = undefined;
$scope.states = [
{id: 1, name: 'Alabama'},  
{id: 2, name: 'California'}, 
{id: 3, name: 'Delaware'}, 
{id: 4, name: 'Florida'}, 
{id: 5, name: 'Georgia'}, 
{id: 6, name: 'Hawaii'}, 
{id: 7, name: 'Idaho'},  
{id: 8, name: 'Kansas'}, 
{id: 9, name: 'Louisiana'}, 
{id: 10, name: 'Maine'}, 
{id: 11, name: 'Nebraska'}, 
{id: 12, name: 'Ohio'}, 
{id: 13, name: 'Pennsylvania'}, 
{id: 14, name: 'Rhode Island'}, 
{id: 15, name: 'South Carolina'}, 
{id: 16, name: 'Tennessee'},
{id: 17, name: 'Utah'}, 
{id: 18, name: 'Vermont'}, 
{id: 19, name: 'Washington'}
];
});


angular.module('app').directive('object', [function() {
return {
    restrict: 'A',
    scope: {},
    require: 'ngModel',
    link: function (scope, element, attrs, ngModel) {

        ngModel.$validators.object = function(modelValue,viewValue){
            if (angular.isObject(modelValue)) {
                return true;
            }
            else {
                return false;
            }
          };
    }
};

}
]);

person elenat82    schedule 18.12.2017    source источник
comment
Сделать <select><option> ?   -  person Jeremy Thille    schedule 18.12.2017
comment
Я не могу, клиенты хотят поле автозаполнения   -  person elenat82    schedule 18.12.2017
comment
в таком случае, как вы хотите заставить пользователя щелкнуть одно из предложений вместо того, чтобы печатать? Я говорю с точки зрения UX, а не с технической точки зрения. Если вы поставите себя на место конечного пользователя, представьте, что у вас есть свободное текстовое поле с предложениями, как вы могли бы заставить перестать печатать и щелкнуть одно из предложений? Не звучит реалистично или удобно для меня   -  person Jeremy Thille    schedule 18.12.2017
comment
В этом-то и дело! Это не свободное текстовое поле, пользователь не может напечатать то, что хочет, он должен выбрать из списка! Поле автозаполнения предназначено для того, чтобы пользователь не просматривал сотни вариантов в списке выбора, когда он уже знает, что он хочет выбрать.   -  person elenat82    schedule 18.12.2017


Ответы (3)


Вы можете добавить метод для этой проверки, например:

$scope.isSelected = function() {
    return typeof $scope.selected == "object";
}
person Arkadiusz    schedule 18.12.2017
comment
Где запустить эту функцию? В директиве ng-change добавить тег ввода? - person elenat82; 18.12.2017
comment
Например, вы можете оставить кнопку OK отключенной, изменив этот ChooseStateForm.$invalid на этот ChooseStateForm.$invalid || !Выбрано(). Другой способ — использовать некоторый элемент, например, span с информацией: выберите значение из списка и установите значение ng-show для этого метода. Все зависит от требований, как пользователь хочет быть проинформирован об этом. Я изменил ваш пример: jsfiddle.net/yhpbdvva/ 21 - person Arkadiusz; 18.12.2017
comment
Спасибо, Аркадиуш, вы решили мою проблему как с технической, так и с пользовательской точки зрения. - person elenat82; 18.12.2017

Существует свойство под названием typeahead-editable="false". Если вы установите для него значение false, это не позволит пользователю «не» выбрать что-то, текстовое поле будет пустым.

Ссылка: https://angular-ui.github.io/bootstrap/#!#typeahead

typeahead-editable $ (по умолчанию: true) — следует ли ограничивать значения модели только теми, которые выбраны из всплывающего окна?

http://jsfiddle.net/yhpbdvva/25/

person Jens Stragier    schedule 18.12.2017
comment
Спасибо, Йенс, это работает, и ваш ответ абсолютно правильный, но мне просто не нравится пользовательский опыт, который он приносит, я думаю, что ответы Аркадиуша и Сварога немного лучше соответствуют моим потребностям. - person elenat82; 18.12.2017

Вам нужно будет пройтись по массиву и проверить, существует ли выбранное значение (ngModel) в этом массиве. Вы можете определить такую ​​функцию и вызвать ее, используя ngChange, которая будет выполняться, когда ты меняешь свою ценность

<input type="text" 
     class="form-control" 
     required
     name="state" 
     ng-model="selected" 
     .....
     ng-change="testIncluded(selected)">

И внутри контроллера сделайте что-то вроде этого (используя Array.prototype.find)

$scope.testIncluded = function(value) {
    let isSelectedFromStates = $scope.states.find((state) => {
        return state.name === value;
    })
    /// do something ...
}

Если имя существует, isSelectedFromStates не будет неопределенным

person svarog    schedule 18.12.2017
comment
Спасибо svarog, ваш ответ похож на тот, что я принял, я бы сам не смог найти решение! - person elenat82; 18.12.2017