在AngularJS中的选择下拉框中添加搜索过滤器

12

我希望在AngularJS中的选择下拉列表中添加搜索过滤器。

我已经使用了ng-options来列出选项,并使用筛选器在搜索框中过滤数据,但问题是搜索框不在选择下拉列表内(或下方)。 (当我点击选择下拉列表时,它会显示一个搜索过滤器,下面有所有选项)

以下是您参考的代码:

<div class="rowMargin">
<label class="control-label" for="entitySel">Entity:</label>
<div class="controls">
    <select id="entityId" class="input-medium" type="text" name="entityId" ng-model="payment.entityId" ng-options="entityOpt for entityOpt in paymentEntityOptions">
        <option value="">Select</option>
    </select>
    <span ng-show=" submitted && addPayment.entityId.$error.required">
        <label class="error">Please provide entity Id </label>
    </span>
    <div ng-show="payment.entityId == \'Individual\'">
        <span>
            <select ng-model="payment.entity.individual" ng-options = "individual for individual in individualEntities | filter : filterEntity">
                <option value="">Select Individual Entity</option>
                <option>
                    <input type="search" placeholder="Search" ng-model="filterEntity"></input>
                </option>
            </select>
        </span>
    </div>
    <div ng-show="payment.entityId == \'Group\'">
        <span>
            <select ng-model="payment.entity.group" ng-options = "group for group in groupEntities | filter : filterEntity">
                <option value="">Select Group Entity</option>
                <input type="search" placeholder="Search" ng-model="filterEntity"></input>
            </select>
        </span>
    </div>
</div>

:段落标签
4个回答

10

我使用了class为'dropdown-toggle'的Bootstrap按钮,当点击该按钮时,我按照以下方式附加了一个搜索框:

<div class="dropdown pull-right makePaymentDropdownMainDiv" auto-close="outsideClick">
<button class="btn btn-default dropdown-toggle makePaymentDropdownBtn" type="button" id="individualDrop" data-toggle="dropdown">{{payment.entity}}<span class="caret pull-right"></span></button>
<span ng-show="submitted"><label class="error">Select an Individual</label></span>
<ul class="dropdown-menu makePaymentDropdownUlStyle" role="menu" aria-labelledby="individualDrop">
    <input disable-auto-close type="search" ng-model="serchFilter" class="makePaymentDropdownSearchBox" placeholder="Search"></input>
    <li role="presentation" ng-repeat="indi in individuals | filter: serchFilter"><a role="menuitem" ng-click="selectEntity(indi)">{{indi}}</a></li>  
</ul>
</div>

使用ng-repeat来展示

  • 记得在下拉菜单中添加auto-close="outsideClick",这样它就不会在尝试过滤时关闭。


  • 你如何将所选选项绑定到ng-model以便稍后使用? - Krishnaveni B

    6

    抱歉,我来晚了,但是对我来说,听起来你需要一个名为acute-select的开源扩展(MIT许可证)用于Angular,可以在不依赖其他内容的情况下实现这一点。

    他们还有一个演示页面,很好地展示了它的功能。


    你有使用 TypeScript 的 Angular 代码吗? - Agnum

    4

    在AngularJS中,您可以使用简单、最佳的方法来搜索筛选下拉列表中的内容。

    演示示例:http://plnkr.co/edit/o767Mg6fQoyc7jKq77If?p=preview

    (function (angular, undefined) {
        'use strict';
    
        // TODO: Move to polyfill?
        if (!String.prototype.trim) {
            String.prototype.trim = function () {
                return this.replace(/^\s+|\s+$/g, '');
            };
        }
    
        /**
         * A replacement utility for internationalization very similar to sprintf.
         *
         * @param replace {mixed} The tokens to replace depends on type
         *  string: all instances of $0 will be replaced
         *  array: each instance of $0, $1, $2 etc. will be placed with each array item in corresponding order
         *  object: all attributes will be iterated through, with :key being replaced with its corresponding value
         * @return string
         *
         * @example: 'Hello :name, how are you :day'.format({ name:'John', day:'Today' })
         * @example: 'Records $0 to $1 out of $2 total'.format(['10', '20', '3000'])
         * @example: '$0 agrees to all mentions $0 makes in the event that $0 hits a tree while $0 is driving drunk'.format('Bob')
         */
        function format(value, replace) {
            if (!value) {
                return value;
            }
            var target = value.toString();
            if (replace === undefined) {
                return target;
            }
            if (!angular.isArray(replace) && !angular.isObject(replace)) {
                return target.split('$0').join(replace);
            }
            var token = angular.isArray(replace) && '$' || ':';
    
            angular.forEach(replace, function (value, key) {
                target = target.split(token + key).join(value);
            });
            return target;
        }
    
        var module = angular.module('AxelSoft', []);
    
        module.value('customSelectDefaults', {
            displayText: 'Select...',
            emptyListText: 'There are no items to display',
            emptySearchResultText: 'No results match "$0"',
            addText: 'Add',
            searchDelay: 300
        });
    
        module.directive('customSelect', ['$parse', '$compile', '$timeout', '$q', 'customSelectDefaults', function ($parse, $compile, $timeout, $q, baseOptions) {
            var CS_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
    
            return {
                restrict: 'A',
                require: 'ngModel',
                link: function (scope, elem, attrs, controller) {
                    var customSelect = attrs.customSelect;
                    if (!customSelect) {
                        throw new Error('Expected custom-select attribute value.');
                    }
    
                    var match = customSelect.match(CS_OPTIONS_REGEXP);
    
                    if (!match) {
                        throw new Error("Expected expression in form of " +
                            "'_select_ (as _label_)? for _value_ in _collection_[ track by _id_]'" +
                            " but got '" + customSelect + "'.");
                    }
    
                    elem.addClass('dropdown custom-select');
    
                    // Ng-Options break down
                    var displayFn = $parse(match[2] || match[1]),
                        valueName = match[3],
                        valueFn = $parse(match[2] ? match[1] : valueName),
                        values = match[4],
                        valuesFn = $parse(values),
                        track = match[5],
                        trackByExpr = track ? " track by " + track : "",
                        dependsOn = attrs.csDependsOn;
    
                    var options = getOptions(),
                        timeoutHandle,
                        lastSearch = '',
                        focusedIndex = -1,
                            matchMap = {};
    
                    var itemTemplate = elem.html().trim() || '{{' + (match[2] || match[1]) + '}}',
    
                        dropdownTemplate =
                        '<a class="dropdown-toggle" data-toggle="dropdown" href ng-class="{ disabled: disabled }">' +
                            '<span>{{displayText}}</span>' +
                            '<b></b>' +
                        '</a>' +
                        '<div class="dropdown-menu">' +
                            '<div stop-propagation="click" class="custom-select-search">' +
                                '<input class="' + attrs.selectClass + '" type="text" autocomplete="off" ng-model="searchTerm" />' +
                            '</div>' +
                            '<ul role="menu">' +
                                '<li role="presentation" ng-repeat="' + valueName + ' in matches' + trackByExpr + '">' +
                                    '<a role="menuitem" tabindex="-1" href ng-click="select(' + valueName + ')">' +
                                        itemTemplate +
                                    '</a>' +
                                '</li>' +
                                '<li ng-hide="matches.length" class="empty-result" stop-propagation="click">' +
                                    '<em class="muted">' +
                                        '<span ng-hide="searchTerm">{{emptyListText}}</span>' +
                                        '<span class="word-break" ng-show="searchTerm">{{ format(emptySearchResultText, searchTerm) }}</span>' +
                                    '</em>' +
                                '</li>' +
                            '</ul>' +
                            '<div class="custom-select-action">' +
                                (typeof options.onAdd === "function" ?
                                '<button type="button" class="btn btn-primary btn-block add-button" ng-click="add()">{{addText}}</button>' : '') +
                            '</div>' +
                        '</div>';
    
                    // Clear element contents
                    elem.empty();
    
                    // Create dropdown element
                    var dropdownElement = angular.element(dropdownTemplate),
                        anchorElement = dropdownElement.eq(0).dropdown(),
                        inputElement = dropdownElement.eq(1).find(':text'),
                        ulElement = dropdownElement.eq(1).find('ul');
    
                    // Create child scope for input and dropdown
                    var childScope = scope.$new(true);
                    configChildScope();
    
                    // Click event handler to set initial values and focus when the dropdown is shown
                    anchorElement.on('click', function (event) {
                        if (childScope.disabled) {
                            return;
                        }
                        childScope.$apply(function () {
                            lastSearch = '';
                            childScope.searchTerm = '';
                        });
    
                        focusedIndex = -1;
                        inputElement.focus();
    
                        // If filter is not async, perform search in case model changed
                        if (!options.async) {
                            getMatches('');
                        }
                    });
    
                    if (dependsOn) {
                        scope.$watch(dependsOn, function (newVal, oldVal) {
                            if (newVal !== oldVal) {
                                childScope.matches = [];
                                childScope.select(undefined);
                            }
                        });
                    }
    
                    // Event handler for key press (when the user types a character while focus is on the anchor element)
                    anchorElement.on('keypress', function (event) {
                        if (!(event.altKey || event.ctrlKey)) {
                            anchorElement.click();
                        }
                    });
    
                    // Event handler for Esc, Enter, Tab and Down keys on input search
                    inputElement.on('keydown', function (event) {
                        if (!/(13|27|40|^9$)/.test(event.keyCode)) return;
                        event.preventDefault();
                        event.stopPropagation();
    
                        switch (event.keyCode) {
                            case 27: // Esc
                                anchorElement.dropdown('toggle');
                                break;
                            case 13: // Enter
                                selectFromInput();
                                break;
                            case 40: // Down
                                focusFirst();
                                break;
                            case 9:// Tab
                                anchorElement.dropdown('toggle');
                                break;
                        }
                    });
    
                    // Event handler for Up and Down keys on dropdown menu
                    ulElement.on('keydown', function (event) {
                        if (!/(38|40)/.test(event.keyCode)) return;
                        event.preventDefault();
                        event.stopPropagation();
    
                        var items = ulElement.find('li > a');
    
                        if (!items.length) return;
                        if (event.keyCode == 38) focusedIndex--;                                    // up
                        if (event.keyCode == 40 && focusedIndex < items.length - 1) focusedIndex++; // down
                        //if (!~focusedIndex) focusedIndex = 0;
    
                        if (focusedIndex >= 0) {
                            items.eq(focusedIndex)
                                .focus();
                        } else {
                            focusedIndex = -1;
                            inputElement.focus();
                        }
                    });
    
                    resetMatches();
    
                    // Compile template against child scope
                    $compile(dropdownElement)(childScope);
                    elem.append(dropdownElement);
    
                    // When model changes outside of the control, update the display text
                    controller.$render = function () {
                        setDisplayText();
                    };
    
                    // Watch for changes in the default display text
                    childScope.$watch(getDisplayText, setDisplayText);
    
                    childScope.$watch(function () { return elem.attr('disabled'); }, function (value) {
                        childScope.disabled = value;
                    });
    
                    childScope.$watch('searchTerm', function (newValue) {
                        if (timeoutHandle) {
                            $timeout.cancel(timeoutHandle);
                        }
    
                        var term = (newValue || '').trim();
                        timeoutHandle = $timeout(function () {
                            getMatches(term);
                        },
                        // If empty string, do not delay
                        (term && options.searchDelay) || 0);
                    });
    
                    // Support for autofocus
                    if ('autofocus' in attrs) {
                        anchorElement.focus();
                    }
    
                    var needsDisplayText;
                    function setDisplayText() {
                        var locals = { };
                        locals[valueName] = controller.$modelValue;
                        var text = displayFn(scope, locals);
    
                        if (text === undefined) {
                            var map = matchMap[hashKey(controller.$modelValue)];
                            if (map) {
                                text = map.label;
                            }
                        }
    
                        needsDisplayText = !text;
                        childScope.displayText = text || options.displayText;
                    }
    
                    function getOptions() {
                        return angular.extend({}, baseOptions, scope.$eval(attrs.customSelectOptions));
                    }
    
                    function getDisplayText() {
                        options = getOptions();
                        return options.displayText;
                    }
    
                    function focusFirst() {
                        var opts = ulElement.find('li > a');
                        if (opts.length > 0) {
                            focusedIndex = 0;
                            opts.eq(0).focus();
                        }
                    }
    
                    // Selects the first element on the list when the user presses Enter inside the search input
                    function selectFromInput() {
                        var opts = ulElement.find('li > a');
                        if (opts.length > 0) {
                            var ngRepeatItem = opts.eq(0).scope();
                            var item = ngRepeatItem[valueName];
                            childScope.$apply(function () {
                                childScope.select(item);
                            });
                            anchorElement.dropdown('toggle');
                        }
                    }
    
                    function getMatches(searchTerm) {
                        var locals = { $searchTerm: searchTerm }
                        $q.when(valuesFn(scope, locals)).then(function (matches) {
                            if (!matches) return;
    
                            if (searchTerm === inputElement.val().trim()/* && hasFocus*/) {
                                matchMap = {};
                                childScope.matches.length = 0;
                                for (var i = 0; i < matches.length; i++) {
                                    locals[valueName] = matches[i];
                                    var value = valueFn(scope, locals),
                                        label = displayFn(scope, locals);
    
                                    matchMap[hashKey(value)] = {
                                        value: value,
                                        label: label/*,
                                        model: matches[i]*/
                                    };
    
                                    childScope.matches.push(matches[i]);
                                }
                                //childScope.matches = matches;
                            }
    
                            if (needsDisplayText) setDisplayText();
                        }, function() {
                            resetMatches();
                        });
                    }
    
                    function resetMatches() {
                        childScope.matches = [];
                        focusedIndex = -1;
                    };
    
                    function configChildScope() {
                        childScope.addText = options.addText;
                        childScope.emptySearchResultText = options.emptySearchResultText;
                        childScope.emptyListText = options.emptyListText;
    
                        childScope.select = function (item) {
                            var locals = {};
                            locals[valueName] = item;
                            var value = valueFn(childScope, locals);
                            //setDisplayText(displayFn(scope, locals));
                            childScope.displayText = displayFn(childScope, locals) || options.displayText;
                            controller.$setViewValue(value);
    
                            anchorElement.focus();
    
                            typeof options.onSelect === "function" && options.onSelect(item);
                        };
    
                        childScope.add = function () {
                            $q.when(options.onAdd(), function (item) {
                                if (!item) return;
    
                                var locals = {};
                                locals[valueName] = item;
                                var value = valueFn(scope, locals),
                                    label = displayFn(scope, locals);
    
                                matchMap[hashKey(value)] = {
                                    value: value,
                                    label: label/*,
                                        model: matches[i]*/
                                };
    
                                childScope.matches.push(item);
                                childScope.select(item);
                            });
                        };
    
                        childScope.format = format;
    
                        setDisplayText();
                    }
    
                    var current = 0;
                    function hashKey(obj) {
                        if (obj === undefined) return 'undefined';
    
                        var objType = typeof obj,
                            key;
    
                        if (objType == 'object' && obj !== null) {
                            if (typeof (key = obj.$$hashKey) == 'function') {
                                // must invoke on object to keep the right this
                                key = obj.$$hashKey();
                            } else if (key === undefined) {
                                key = obj.$$hashKey = 'cs-' + (current++);
                            }
                        } else {
                            key = obj;
                        }
    
                        return objType + ':' + key;
                    }
                }
            };
        }]);
    
        module.directive('stopPropagation', function () {
            return {
                restrict: 'A',
                link: function (scope, elem, attrs, ctrl) {
                    var events = attrs['stopPropagation'];
                    elem.bind(events, function (event) {
                        event.stopPropagation();
                    });
                }
            };
        });
    })(angular);
    
    <body ng-app="Demo">
            <div class="container" ng-controller="DemoController">
                <label>Level 1</label>
                <div custom-select="g for g in nestedItemsLevel1 | filter: $searchTerm" custom-select-options="level1Options" ng-model="level1"></div>
                <label>Level 2</label>
                <div custom-select="g for g in nestedItemsLevel2 | filter: $searchTerm" ng-model="level2" cs-depends-on="level1"></div>
            </div>
            <!-- basic scripts -->
    
            <!--[if !IE]> -->
    
            <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
            <!-- <![endif]-->
    
            <!--[if IE]>
            <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
            <![endif]-->
    
            <script src="http://maxcdn.bootstrapcdn.com/bootstrap/2.3.2/js/bootstrap.min.js"></script>
            <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
            <script src="js/customSelect.js"></script>
                <script>
                    (function () {
                        var app = angular.module('Demo', ['AxelSoft']);
    
                        app.controller('DemoController', ['$scope', '$timeout', '$q', function ($scope, $timeout, $q) {
                            $scope.searchAsync = function (term) {
                                    // No search term: return initial items
                                if (!term) {
                                    return  ['Item 1', 'Item 2', 'Item 3'];
                                }
                                var deferred = $q.defer();
                                $timeout(function () {
                                    var result = [];
                                    for (var i = 1; i <= 3; i++)
                                    {
                                        result.push(term + ' ' + i);
                                    }
                                    deferred.resolve(result);
                                }, 300);
                                return deferred.promise;
                            };
                            $scope.nestedItemsLevel1 = ['Item 1', 'Item 2', 'Item 3'];
                            $scope.level1 = $scope.nestedItemsLevel1[0];
                            $scope.level1Options = {
                                onSelect: function (item) {
                                    var items = [];
                                    for (var i = 1; i <= 5; i++) {
                                        items.push(item + ': ' + 'Nested ' + i);
                                    }
                                    $scope.nestedItemsLevel2 = items;
                                }
                            };
                            $scope.nestedItemsLevel2 = [];
                            $scope.level1Options.onSelect($scope.nestedItemsLevel1[0]);
                        }]);
    
                    })();
                </script>
        </body>
    

    你好,感谢您提供这个优雅的解决方案。我在 getMatches 函数中遇到了困难,我传递了一个对象而不是一个字符串,并且我想只在特定的键上进行搜索。您能告诉我 valuesFn 是如何工作的吗? - codingbbq

    -3

    是的,我知道...那不是我需要的。我正在尝试使用ng-repeat,并使用<li>填充下拉菜单。不过还是谢谢你的回复 :) - Vaibhav Pachauri

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接