javascript - AngularJS Show typeahead on button click -
i using typeahead directive in angularjs , works fine. however, have button outside of input when clicked show typeahead dropdown. here snippet of after...
<li class="input"> <input focus-me="click" ng-model="something" typeahead="state 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>
ok, having absolutely terrible time trying create jsfiddle or plunkr this, give code directive.
this directive comes from..
..and stole , played it. if use it, need "bootstrap" (its subset of angular directives) library linked to. can make own subset of library, not entirely sure of of dependencies directive has using entire library in project. basically, need directive starts "typeahead".
as can see, have named directive wwtypeahead
(that "ww" webwanderer!). easy use directive , works original.
<input class="form-control" type="text" spellcheck="false" ng-model="selection" ng-trim="false" placeholder="search here" ww-typeahead="key key.label key in list" typeahead-on-select="selectionmade($item, $model, $label)" typeahead-min-length="0" />
the important part note attribute typeahead-min-length="0"
has been heart of many discussions online. managed make work.
this directive meant take place of typeahead
directive in library linked to. typeahead list shown on focus
of input box. no, list not show on click of button, getting there baby-steps here. if need implementing that, happy help.
/* note: following directive modification of angular typeahead directive. normal directives, unfortunately, not allow matching on 0 length values , user may want returned list of values during lack of input. directives taken ... 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 needs 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 restrict model values ones selected popup only? var iseditable = originalscope.$eval(attrs.typeaheadeditable) !== false; //binding variable indicates if matches being retrieved asynchronously var isloadingsetter = $parse(attrs.typeaheadloading).assign || angular.noop; //a callback executed when match 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 typeahead var parserresult = typeaheadparser.parse(attrs.cmctypeahead); //pop-up element used 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 child scope typeahead directive 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 several async queries in progress if user typing fast //but interested in responses correspond current view value //if(matches && inputvalue === modelctrl.$viewvalue) /* ehh.. didn't seem work when "cleared" input box */ if(matches) { if(matches.length > 0) { scope.activeidx = 0; scope.matches.length = 0; //transform labels for(var = 0; < 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 matches - need re-calculate position each time opening window //with matches pop-up might absolute-positioned , position of input might have changed on page //due 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 make work...*/ if(attrs.hasownproperty('typeaheadbindmatchreloader')) { $parse(attrs.typeaheadbindmatchreloader).assign(scope, function() { getmatchesasync(element[0].value); }); } //we need propagate user's query can higlight matches scope.query = undefined; //declare timeout promise var outside function scope stacked calls can cancelled later var timeoutpromise; //plug $parsers pipeline open typeahead on view changes initiated dom //$parsers kick-in on changes coming view manually triggered $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 don't have enough info render input value //we need check situation , return model value if 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 within $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 input element if mach selected via mouse click event element[0].focus(); }; //bind keyboard events: arrows up(38) / down(40), enter(13) , tab(9), esc(27) element.bind('keydown', function(evt) { //typeahead open , "interesting" key 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 click handler 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)); } }; }]);
call action:
somebody please make working example of typeahead
directive! forever in debt you! (well, not make me happy)
disclaimer:
i understand answer in no way orthodox. did not provide askee (askee?) direct answer question, yet did provide tools believe needed his/her answer. understand should spend time make working example, busy man , wished share work community, have seen question asked many times while sit , hold answer. please let me know if have issues, questions, or complications. happy help.
thanks!
Comments
Post a Comment