草庐IT

javascript - AngularJS 在单击按钮时显示提前输入

coder 2024-07-26 原文

我在 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 时遇到了非常糟糕的时间,所以我将只为您提供该指令的代码。

这个指令最初来自..

This epic Bootstrap library!

..我偷了它玩了。如果您想使用它,您将需要我链接到的“Bootstrap”(它实际上是 Angular Directive(指令)的一个子集)库。您可以创建该库的自己的子集,但我不完全确定我的指令具有的所有依赖项,因为我在我的项目中使用了整个库。基本上,您需要任何以“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));
        }
    };
}]);

号召性用语:

有人为这个typeahead指令做一个工作示例!我会永远欠你的债! (嗯,不是真的,但它会让我很开心)

免责声明:

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

谢谢!

关于javascript - AngularJS 在单击按钮时显示提前输入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31564461/

有关javascript - AngularJS 在单击按钮时显示提前输入的更多相关文章

  1. ruby - 鸭子输入字符串、符号和数组的优雅方式? - 2

    这是针对我无法破坏的现有公共(public)API,但我确实希望对其进行扩展。目前,该方法采用字符串或符号或任何其他在作为第一个参数传递给send时有意义的内容我想添加发送字符串、符号等列表的功能。我可以只使用is_a吗?数组,但还有其他发送列表的方法,这不是很像ruby​​。我将调用列表中的map,所以第一个倾向是使用respond_to?:map。但是字符串也会响应:map,所以这行不通。 最佳答案 如何将它们全部视为数组?String的行为与仅包含String的Array相同:deffoo(obj,arg)[*arg].eac

  2. ruby-on-rails - Rails 单选按钮 - 模型中多列的一种选择 - 2

    我希望用户从一个模型的三个选项中选择一个。即我有一个模型视频,可以被评为正面/负面/未知目前我有三列bool值(pos/neg/unknown)。这是处理这种情况的最佳方式吗?为此,表单应该是什么样的?目前我有类似的东西但显然它允许多项选择,而我试图将它限制为只有一个..怎么办? 最佳答案 如果要使用字符串列,让我们说rating。然后在你的表单中:#...#...它只允许一个选择编辑完全相同但使用radio_button_tag: 关于ruby-on-rails-Rails单选按钮-模

  3. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  4. ruby - ruby 中的同一个程序如何接受来自用户的输入以及命令行参数 - 2

    我的ruby​​脚本从命令行参数获取某些输入。它检查是否缺少任何命令行参数,然后提示用户输入。但是我无法使用gets从用户那里获得输入。示例代码:test.rbname=""ARGV.eachdo|a|ifa.include?('-n')name=aputs"Argument:#{a}"endendifname==""puts"entername:"name=getsputsnameend运行脚本:rubytest.rbraghav-k错误结果:test.rb:6:in`gets':Nosuchfileordirectory-raghav-k(Errno::ENOENT)fromtes

  5. ruby-on-rails - 如何从按钮或链接单击的 View 调用 Rails 方法 - 2

    基本上,我试图在用户单击链接(或按钮或某种类型的交互元素)时执行Rails方法。我试着把它放在View中:但这似乎没有用。它最终只是在用户甚至没有点击“添加”链接的情况下调用该函数。我也用link_to试过了,但也没用。我开始认为没有一种干净的方法可以做到这一点。无论如何,感谢您的帮助。附言。我在ApplicationController中定义了该方法,它是一个辅助方法。 最佳答案 View和Controller是相互独立的。为了使链接在Controller内执行函数调用,您需要对应用程序中的端点执行ajax调用。该路由应调用rub

  6. ruby-on-rails - 如何在 Rails 中添加禁用的提交按钮 - 2

    我在ruby​​表单中有一个提交按钮f.submitbtn_text,class:"btnbtn-onemgt12mgb12",id:"btn_id"我想在不使用任何javascript的情况下通过ruby​​禁用此按钮 最佳答案 添加disabled:true选项。f.submitbtn_text,class:"btnbtn-onemgt12mgb12",id:"btn_id",disabled:true 关于ruby-on-rails-如何在Rails中添加禁用的提交按钮,我们在St

  7. ruby-on-rails - 为什么用户必须输入 7 位数的 Twitter PIN 才能授予我的应用程序访问权限? - 2

    我正在为我的用户实现一些ruby​​onrails代码推特内容。我正在创建正确的oauth链接...类似http://twitter.com/oauth/authorize?oauth_token=y2RkuftYAEkbEuIF7zKMuzWN30O2XxM8U9j0egtzKv但在我的测试帐户授予对twitter的访问权限后,它会弹出一个页面,上面写着“您已成功授予对.我不知道用户应该在哪里输入此PIN以及他们为什么必须这样做。我认为这不是必要的步骤。Twitter应该将用户重定向到我在应用程序设置中提供的回调URL。有谁知道为什么会这样?更新我找到了thisarticle声明我需

  8. ruby-on-rails - 从 ActiveAdmin has_many 表单助手中删除 "Add new"按钮 - 2

    我在事件管理员编辑页面中有嵌套资源,但我只想允许管理员编辑现有资源的内容,而不是添加新的嵌套资源。我的代码看起来像这样:formdo|f|f.inputsdof.input:authorf.input:contentf.has_many:commentsdo|comment_form|comment_form.input:contentcomment_form.input:_destroy,as::boolean,required:false,label:'Remove'endendf.actionsend但它在输入下添加了“添加新评论”按钮。我怎样才能禁用它,并只为主窗体保留f.ac

  9. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

    我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

  10. ruby-on-rails - 获取 ActionController::RoutingError(当尝试使用 AngularJS 将数据发布到 Rails 服务器时,没有路由匹配 [OPTIONS] "/users" - 2

    尝试从我的AngularJS端将数据发布到Rails服务器时出现问题。服务器错误:ActionController::RoutingError(Noroutematches[OPTIONS]"/users"):actionpack(4.1.9)lib/action_dispatch/middleware/debug_exceptions.rb:21:in`call'actionpack(4.1.9)lib/action_dispatch/middleware/show_exceptions.rb:30:in`call'railties(4.1.9)lib/rails/rack/logg

随机推荐