2015-07-22 24 views
7

Sto utilizzando la direttiva typeahead in AngularJS e funziona correttamente. Tuttavia, mi piacerebbe avere un pulsante al di fuori dell'input che quando cliccato mostrerebbe il menu a discesa typeahead. Ecco un frammento di quello che sono dopo ...AngularJS Mostra typeahead sul pulsante clic

<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> 
+0

Che cosa ti aspetti di mostrare? – charlietfl

+0

Se il caso d'uso è reale, farebbe una grande richiesta di funzionalità sul proprio github https://github.com/angular-ui/bootstrap – Okazari

+0

@charlietfl quello che sto cercando è qualcosa di simile a questo [collegamento] (http: //plnkr.co/edit/bZMEOx0Qwo6VzW7oSuEE?p=preview) ma invece di avere il menu typeahead aperto sull'evento focus, voglio aggiungere un pulsante a destra per aprire il menu. – kgalb

risposta

1

Ok, sto avendo un tempo assolutamente terribile cercando di creare un JSFiddle o anche un Plunkr per questo, quindi mi limiterò a dare il codice per questo direttiva.

Questa direttiva è originaria ..

This epic Bootstrap library!

..e ho rubato e ha giocato con esso. Se vuoi usarlo, avrai bisogno della libreria "Bootstrap" (è proprio un sottoinsieme di direttive angolari) a cui ti ho collegato. Puoi creare il tuo sottoinsieme di questa libreria, ma non sono del tutto sicuro di tutte le dipendenze della mia direttiva poiché sto utilizzando l'intera libreria nel mio progetto. Fondamentalmente, hai bisogno di qualsiasi direttiva che inizi con "typeahead".

Come si può vedere, ho chiamato la direttiva wwTypeahead (che "ww" è per WebWanderer!). È una direttiva molto facile da usare e funziona esattamente come l'originale.

<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" 
/> 

La parte veramente importante da notare è l'attributo typeahead-min-length="0" che è stato veramente il cuore di molte discussioni online. Sono riuscito a fare quel lavoro.

Questa direttiva è destinata a sostituire la direttiva typeahead nella libreria a cui è collegato. La tua lista di caratteri verrà visualizzata su focus della casella di input. No, l'elenco non viene visualizzato con un clic di un pulsante, ma se tutto va bene ci saranno dei piccoli passi da qui. Se hai bisogno di aiuto per implementarlo, sarò felice di aiutarti.

/* 
    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)); 
     } 
    }; 
}]); 

Call To Action:

Qualcuno PREGA fare un esempio di lavoro di questa direttiva typeahead! Sarei per sempre in debito con te! (Beh, non proprio, ma mi farebbe molto felice)

NOTA BENE:

Capisco che questa risposta non è in alcun modo ortodosso. Non ho fornito l'askee (askee?) Con una risposta diretta alla domanda, ma ho fornito gli strumenti che ritengo siano necessari per arrivare alla sua risposta. Capisco che dovrei passare il tempo a fare un esempio funzionante, ma sono un uomo molto impegnato e desideravo semplicemente condividere il mio lavoro con la comunità, poiché ho visto questa domanda posta troppe volte mentre mi siedo e tengo la risposta . Per favore fatemi sapere se avete problemi, domande o complicazioni. Sono felice di aiutare.

Grazie!

Problemi correlati