AngularJS 在按钮单击时显示预先输入

2023-11-23

我在 AngularJS 中使用 typeahead 指令,它工作得很好。但是,我希望在输入之外有一个按钮,单击该按钮会显示预输入下拉列表。这是我所追求的一个片段......

<li class="input">
   <input focus-me="click" ng-model="something" 
    typeahead="state for state in Suggestions | filter:$viewValue:stateComparator" typeahead-focus typeahead-focus-first="false" typeahead-on-select="updateTagInput(newTagName)">
   <a href="" ng-click="openTypeAhead()">Open</a>
</li>

好吧,我在尝试为此创建 JSFiddle 甚至 Plunkr 时遇到了非常糟糕的时间,所以我只会为您提供该指令的代码。

该指令最初来自..

这个史诗般的 Bootstrap 库!

..我偷了它并玩了它。如果您想使用它,您将需要我链接到的“Bootstrap”(它实际上是角度指令的子集)库。您可以创建您自己的该库的子集,但我并不完全确定我的指令具有的所有依赖项,因为我在项目中使用整个库。基本上,您需要任何以“typeahead”开头的指令。

如您所见,我已将该指令命名为wwTypeahead(“ww”代表 WebWanderer!)。这是一个非常易于使用的指令,并且其工作原理与原始指令一样。

<input 
    class="form-control" 
    type="text" 
    spellcheck="false" 
    ng-model="selection" 
    ng-trim="false" 
    placeholder="Search Here" 
    ww-typeahead="key as key.label for key in list" 
    typeahead-on-select="selectionMade($item, $model, $label)" 
    typeahead-min-length="0" 
/>

真正需要注意的重要部分是属性typeahead-min-length="0"这确实是网上许多讨论的核心。我成功地做到了这一点。

该指令旨在取代typeahead我链接到的库中的指令。您的预输入列表将显示在focus你的输入框。不,该列表不会通过单击按钮来显示,但希望从这里开始就能快速实现。如果您需要帮助来实现这一点,我将很乐意提供帮助。

/*
    NOTE:

    The following directive is a modification of the
    Angular typeahead directive. The normal directives,
    unfortunately, do not allow matching on 0 length values
    and the user may want a returned list of all values during
    the lack of input.

    This directives was taken from ... 

        http://angular-ui.github.io/bootstrap/  

    ..and modified.
*/
angular.module('ui.directives', []).directive('wwTypeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
function($compile, $parse, $q, $timeout, $document, $position, typeaheadParser)
{
    var HOT_KEYS = [9, 13, 27, 38, 40];

    return {
        require:'ngModel',
        link:function(originalScope, element, attrs, modelCtrl)
        {
            //SUPPORTED ATTRIBUTES (OPTIONS)

            //minimal no of characters that needs to be entered before typeahead kicks-in
            //var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
            var testEval = originalScope.$eval(attrs.typeaheadMinLength);
            var minSearch = !isNaN(parseFloat(testEval)) && isFinite(testEval) || 1;

            //minimal wait time after last character typed before typehead kicks-in
            var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;

            //should it restrict model values to the ones selected from the popup only?
            var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;

            //binding to a variable that indicates if matches are being retrieved asynchronously
            var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;

            //a callback executed when a match is selected
            var onSelectCallback = $parse(attrs.typeaheadOnSelect);

            var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;

            //INTERNAL VARIABLES

            //model setter executed upon match selection
            var $setModelValue = $parse(attrs.ngModel).assign;

            //expressions used by typeahead
            var parserResult = typeaheadParser.parse(attrs.cmcTypeahead);


            //pop-up element used to display matches
            var popUpEl = angular.element('<typeahead-popup></typeahead-popup>');
            popUpEl.attr({
                matches: 'matches',
                active: 'activeIdx',
                select: 'select(activeIdx)',
                query: 'query',
                position: 'position'
            });
            //custom item template
            if(angular.isDefined(attrs.typeaheadTemplateUrl))
            {
                popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
            }

            //create a child scope for the typeahead directive so we are not polluting original scope
            //with typeahead-specific data (matches, query etc.)
            var scope = originalScope.$new();
            originalScope.$on('$destroy', function()
            {
                scope.$destroy();
            });

            var resetMatches = function()
            {
                scope.matches = [];
                scope.activeIdx = -1;
            };

            var getMatchesAsync = function(inputValue)
            {
                var matchParsePrefix = originalScope.$eval(attrs.typeaheadParsePrefix);
                var locals = {
                    $viewValue: inputValue.indexOf(matchParsePrefix) === 0 ? inputValue.substring(matchParsePrefix.length, (inputValue.length + 1)) : inputValue
                };
                isLoadingSetter(originalScope, true);
                $q.when(parserResult.source(scope, locals)).then(function(matches)
                {
                    //it might happen that several async queries were in progress if a user were typing fast
                    //but we are interested only in responses that correspond to the current view value
                    //if(matches && inputValue === modelCtrl.$viewValue)

                    /*
                        Ehh.. that didn't seem to work when I "cleared" the input box
                    */
                    if(matches)
                    {
                        if(matches.length > 0)
                        {
                            scope.activeIdx = 0;
                            scope.matches.length = 0;

                            //transform labels
                            for(var i = 0; i < matches.length; i++)
                            {
                                locals[parserResult.itemName] = matches[i];
                                scope.matches.push({
                                    label: parserResult.viewMapper(scope, locals),
                                    model: matches[i]
                                });
                            }

                            scope.query = inputValue;
                            //position pop-up with matches - we need to re-calculate its position each time we are opening a window
                            //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
                            //due to other elements being rendered
                            scope.position = $position.position(element);
                            scope.position.top = scope.position.top + element.prop('offsetHeight');

                        }
                        else if(minSearch === 0)
                        {
                            resetMatches();//temp
                        }
                        else
                        {
                            resetMatches();
                        }
                        isLoadingSetter(originalScope, false);
                    }
                }, function()
                {
                    resetMatches();
                    isLoadingSetter(originalScope, false);
                });
            };

            resetMatches();

            /*
                Can't figure out how to make this work...*/
            if(attrs.hasOwnProperty('typeaheadBindMatchReloader'))
            {
                $parse(attrs.typeaheadBindMatchReloader).assign(scope, function()
                {
                    getMatchesAsync(element[0].value);
                });
            }




            //we need to propagate user's query so we can higlight matches
            scope.query = undefined;

            //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later 
            var timeoutPromise;

            //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
            //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
            modelCtrl.$parsers.unshift(function(inputValue)
            {
                resetMatches();
                if((inputValue && inputValue.length >= minSearch)
                || minSearch === 0)
                {
                    if(waitTime > 0)
                    {
                        if(timeoutPromise)
                        {
                            $timeout.cancel(timeoutPromise);//cancel previous timeout
                        }

                        timeoutPromise = $timeout(function()
                        {
                            getMatchesAsync(inputValue);
                        }, waitTime);
                    }
                    else
                    {
                        getMatchesAsync(inputValue);
                    }
                }

                if(isEditable)
                {
                    return inputValue;
                }
                else
                {
                    modelCtrl.$setValidity('editable', false);
                    return undefined;
                }
            });

            modelCtrl.$formatters.push(function(modelValue)
            {
                var candidateViewValue, emptyViewValue;
                var locals = {};

                if(inputFormatter)
                {
                    locals['$model'] = modelValue;
                    return inputFormatter(originalScope, locals);
                }
                else
                {
                    //it might happen that we don't have enough info to properly render input value
                    //we need to check for this situation and simply return model value if we can't apply custom formatting
                    locals[parserResult.itemName] = modelValue;
                    candidateViewValue = parserResult.viewMapper(originalScope, locals);
                    locals[parserResult.itemName] = undefined;
                    emptyViewValue = parserResult.viewMapper(originalScope, locals);

                    return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
                }
            });

            scope.select = function(activeIdx)
            {
                //called from within the $digest() cycle
                var locals = {};
                var model, item;

                locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
                model = parserResult.modelMapper(originalScope, locals);
                $setModelValue(originalScope, model);
                modelCtrl.$setValidity('editable', true);

                onSelectCallback(originalScope, {
                    $item: item,
                    $model: model,
                    $label: parserResult.viewMapper(originalScope, locals)
                });

                resetMatches();

                //return focus to the input element if a mach was selected via a mouse click event
                element[0].focus();
            };

            //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
            element.bind('keydown', function(evt)
            {
                //typeahead is open and an "interesting" key was pressed
                if(scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1)
                    return;

                evt.preventDefault();

                if(evt.which === 40)
                {
                    scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
                    scope.$digest();
                }
                else if(evt.which === 38)
                {
                    scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
                    scope.$digest();
                }
                else if(evt.which === 13 || evt.which === 9)
                {
                    scope.$apply(function()
                    {
                        scope.select(scope.activeIdx);
                    });
                }
                else if(evt.which === 27)
                {
                    evt.stopPropagation();
                    resetMatches();
                    scope.$digest();
                }
            });

            // Keep reference to click handler to unbind it.
            var dismissClickHandler = function(evt)
            {
                if(element[0] !== evt.target)
                {
                    resetMatches();
                    scope.$digest();
                }
                else
                {
                    getMatchesAsync(element[0].value);
                }
            };

            $document.bind('click', dismissClickHandler);

            originalScope.$on('$destroy', function()
            {
                $document.unbind('click', dismissClickHandler);
            });

            element.after($compile(popUpEl)(scope));
        }
    };
}]);

呼吁采取行动:

有人PLEASE举一个可行的例子typeahead指示!我将永远欠你的债! (嗯,不是真的,但这会让我很高兴)

免责声明:

我知道这个答案绝不是正统的。我没有向被询问者(被询问者?)提供问题的直接答案,但我确实提供了我认为获得他/她的答案所需的工具。我知道我应该花时间制作一个工作示例,但我是一个非常忙碌的人,只是希望与社区分享我的工作,因为我已经看到这个问题被问了太多次,而我却坐下来等待答案。如果您有任何问题、疑问或并发症,请告诉我。我很乐意提供帮助。

Thanks!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AngularJS 在按钮单击时显示预先输入 的相关文章

随机推荐

  • 从下拉列表中复制选项列表。 jQuery

    网页代码
  • 如何在flutter中点击扩展卡片?

    我想立即实现材料设计卡片的行为 当我点击它时 它应该展开全屏并显示其他内容 新页面 我该如何实现它 https material io design components cards html behavior 我尝试使用 Navigato
  • detector.isOperational() 在 Android 上始终为 false

    我正在使用新的谷歌播放服务 条码检测器 对于这个海豚 我正在遵循本教程 https search codelabs appspot com codelabs bar codes 但是当我在真实设备 Asus Nexus 7 上运行该应用程序
  • 将 SVG 元素导出为 PDF?

    我有一个由 d3 生成的可视化 一个类似于 Protovis 或 Raphael 的 JavaScript 可视化库 它使用 SVG 元素绘制内容 vis 是交互式的 因此用户可以与其交互并对其进行编辑 一旦用户对他 她的可视化感到满意 我
  • 在 HTTPS 网站上嵌入 Youtube 时 Internet Explorer 出现警告?

    2011 年 3 月 22 日编辑 这个问题不再那么重要 因为 Youtube 现在提供 HTTPS 访问 http apiblog youtube com 2011 02 https support for youtube embeds
  • 将数字划分为(几乎)相等的整数的算法

    我遇到的情况是 我收到的发票电子表格包含跨越多个月的单行 其中数量列包含跨越所有月份的数量总和 为了运行逐月分析 我们需要将总量分成 n 行中的相等数量 其中 n 是跨度的月数 这些数字可能会相差一两个 但每个元素之间的差异越小越好 我用
  • 嵌套查询与连接

    如果我使用谁会更有效率nestted subquery JOINs或者可能temp tables 另一个问题 在子查询中 如果我对同一查询使用 IN 子句两次 它也应该执行两次 像这样 Select From X Where Exists
  • 如何转义 Hibernate HQL 中的保留字

    我使用以下查询来获取java util Map带索引id text and object Query q mySession createQuery SELECT u id AS id u name AS text u AS object
  • C++ 中的迭代器类别如何工作?

    我试图理解迭代器的实现 在研究源代码时 我看到了这样的语句 typedef output iterator tag iterator category 我不明白这个 typedef 在类中如何工作 它有什么副作用 有人可以引导我完成这个吗
  • 如何将 Pandas 数据框中的多个列值连接到单个列中

    这个问题与此贴早些时候 我想连接三列而不是连接两列 这是组合两列 df DataFrame foo a b c bar 1 2 3 new apple banana pear df combined df apply lambda x s
  • Python 中的半正矢公式(两个 GPS 点之间的方位角和距离)

    Problem 我想知道如何获取两个 GPS 点之间的距离和方位 我已经研究过半正矢距离 有人告诉我 我也可以使用相同的数据找到轴承 一切工作正常 但轴承尚未完全正常工作 轴承输出负值 但应在 0 360 度之间 设定的数据应使水平方位96
  • 将依赖项注入 IErrorHandler 实现

    我正在实施IErrorHandler为了将我的 WCF 服务的所有错误处理集中在一处 这工作得相当好 public class ServiceErrorHandler IErrorHandler public bool HandleErro
  • 如何修复损坏的表

    我有mysql表称为Sample 我已经编辑了文件 var lib mysql Sample MYI具有一些价值观 现在检查表示例查询显示为 表 Sample 的密钥文件不正确 尝试修复它 为了修复这个问题 我尝试使用以下命令myisamc
  • 如何使用 lxml 更新 XML 文件

    我想使用 lxml 库用新信息更新 xml 文件 例如 我有这样的代码 gt gt gt from lxml import etree gt gt gt gt gt gt tree etree parse books xml 其中 book
  • UIDocumentInteractionController 打开菜单取消回调

    我目前正在开发一个专门针对 iOS7 的应用程序 该应用程序利用在菜单中打开的 UIDocumentInteractionController 并需要一种方法来在用户取消且未选择可用选项时通知我 UIDocumentInteractionC
  • 如何为所有提交移动 Git 存储库中的目录?

    假设我有一个包含以下目录结构的存储库 repo blog posts some post html another file txt 我想搬家 posts到存储库的顶层 因此结构将如下所示 repo posts some post html
  • 具有自定义基础集合的 Linq

    我经常发现 linq 在使用自定义集合对象时存在问题 他们经常被辩解为 基础集合 abstract class BaseCollection
  • 什么时候const被认为是真正的const?

    在阅读 stackoverflow 中的一些问题和答案时 我遇到了这个question 我试图理解它 但答案真的很难理解 尤其是像这样的术语 静态存储时间 在翻译阶段无法评估表达式 etc 此外 我认为常数始终是常数 这是我在学校学到的 请
  • 在 CMakeLists.txt 中,我如何判断它是否与 add_subdirectory() 一起使用? [复制]

    这个问题在这里已经有答案了 我有这个代码项目可以独立构建 也可以作为较大存储库的子项目 将其检查为子存储库 在后一种情况下 我有一个顶级CMakeLists txt对于主要项目有 add subdirectory MY SUBPROJ SU
  • AngularJS 在按钮单击时显示预先输入

    我在 AngularJS 中使用 typeahead 指令 它工作得很好 但是 我希望在输入之外有一个按钮 单击该按钮会显示预输入下拉列表 这是我所追求的一个片段 li class input li