Shane Woodruff
Shane Woodruff

Reputation: 13

Angular UI Typeahead can't set options (like footer template)

I'd like to add a custom footer to the results where I can wire up some paging. I see that I can specify a footer template in the options, but can't find any examples of how to set those options from a controller.

The footer template would display "Now showing 1-{{offsetEnd}} of {{result.count}} Load More

Thank you everyone!

This is the tag I'm using:

 < input type = "text"
 id = "search"
 name = "search"
 ng - model = "profile.selectedProfile"
 typeahead = "o.itemvalue as o.itemtext for o in getProfiles($viewValue) | filter: $viewValue"
 typeahead - input - formatter = "formatLabel($model)"
 class = "form-control"
 autocomplete = "off" / >

UPDATE I converted this to a directive, and incorporated a footer that displays the total record count.

function findProfile() {
  return {
    restrict: 'E',
    template: '<input type="text" id="search" name="search" ng-model="selectedProfile" typeahead="o as o.itemtext for o in getProfiles($viewValue) | filter: $viewValue" typeahead-input-formatter="formatLabel($model)" class="form-control" autocomplete="off" />',
    controller: function($scope, $http) {
      $scope.itemCount = 0;
      $scope.getProfiles = function(searchtext) {
        return $http.get('http://localhost:61402/api/Profile/FindProfile?searchText=' + searchtext)
          .then(function(response) {
            $scope.itemCount = response.data.itemcount;
            return response.data.items;
          });
      }

      $scope.formatLabel = function(model) {
        return model == undefined ? '' : model.itemtext;
      }

    }
  };
}

And the template:

angular.run(['$templateCache',
  function($templateCache) {
    var template = '' +
      '<ul class="dropdown-menu" ng-show="isOpen()" ng-style="{top: position.top+\'px\', left: position.left+\'px\'}" style="display: block;" role="listbox" aria-hidden="{{!isOpen()}}">' +
      '<li ng-repeat="match in matches track by $index" ng-class="{active: isActive($index) }" ng-mouseenter="selectActive($index)" ng-click="selectMatch($index)" role="option" id="{{match.id}}">' +
      '<div typeahead-match index="$index" match="match" query="query" template-url="templateUrl"></div>' +
      '</li>' +
      '<li>Records Returned: {{$parent.itemCount}}</li>' +
      '</ul>';

    $templateCache.put('template/typeahead/typeahead-popup.html', template);
  }
])

Upvotes: 1

Views: 1675

Answers (2)

Victor Aguilar
Victor Aguilar

Reputation: 455

You have to override the typeahead template.

angular.module('app', ['ui.bootstrap'])
  .run(['$templateCache', function ($templateCache) {
    
    var template = '' +
      '<ul class="dropdown-menu" ng-show="isOpen()" ng-style="{top: position.top+\'px\', left: position.left+\'px\'}" style="display: block;" role="listbox" aria-hidden="{{!isOpen()}}">' +
        '<li ng-repeat="match in matches track by $index" ng-class="{active: isActive($index) }" ng-mouseenter="selectActive($index)" ng-click="selectMatch($index)" role="option" id="{{match.id}}">' +
          '<div typeahead-match index="$index" match="match" query="query" template-url="templateUrl"></div>' +
        '</li>' +
        '<li>The footer</li>' +
      '</ul>';
    
    $templateCache.put('template/typeahead/typeahead-popup.html', template);
  }])
  
  .controller('mainController', function($scope) {

    $scope.selected = undefined;
    $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas'];
});

In this example, I copied the template of github-typeahead-template, the added a extra li (the footer) element inside of the ul element and, finally, I put the template in the angular's cache.

You can get a full example in custom angular-ui bootstrap and the template name https://github.com/angular-ui/bootstrap/blob/0.12.1/src/typeahead/typeahead.js#L452

This technique works with ui-bootstrap-tpls.js (include the templates) and the ui-bootstrap.js (without templates) but it is better to have the template in a file (typeahead-with-footer.html) than in a string. You have several options: use grunt, a script tag or create the static file (typeahead-with-footer.html) in you server (only works in with ui-bootstrap.js).

Upvotes: 0

WebWanderer
WebWanderer

Reputation: 10887

Ok, so I had to perform a bit of a hack here, but I liked your question.

What I ended up working with was the typeahead-template-url. Unfortunately, the documentation for it is very hard to find, yet I had done a lot of work with this in the past. For some of the documentation, try here. Even more unfortunate, I never documented where I found the default typeahead-template-url either, and I couldn't find it online today. Luckily, I did have my version of it on file.

Here is what I had before hacking at it:

<a>
    <span bind-html-unsafe="match.model.label | myModelLabelFilter"></span>
</a>

As you can guess, this is the template for each match shown in the Angular Typeahead, and placing a filter here (myModelLabelFilter in our example) is a great way to modify and inject mostly whatever you want into each shown match.

Now, this may not have been the best way to add one button to the bottom of the typeahead popup, but it's the only thing that I could think of. So, we will need to only show a button after the last match. How about ng-if.

<a>
    <span bind-html-unsafe="match.model.label | cmcTicketingTypeaheadFilter"></span>
</a>
<div    
    ng-if="($parent.$parent.matches.length - 1) === index" 
    style="border-top:1px solid #DDDDDD;" 
>
    Example 
    <button 
        ng-click="$emit('foo', this)" 
        class="bt btn-primary" 
    >
        Click To Emit
    </button>
</div>

...purposely formatted weird to avoid scroll-bars...

..and this is what I got:

Typeahead Template Footer

You may be wondering... what the $parent.$parent.matches thing is all about.

First of all, we are working in the scope of the Typeahead Directive. In this scope we have three values: match, index, and query. I needed to move up the scope levels until we reached a parent scope that had a listing of all returned matches. Every scope has the $parent attribute, showing its parent scope.

NOW, for your full answer! I am not sure how you plan to access your scope for your offsetEnd variables, but I would suggest making it accessible on your matches.

Instead of passing an array of strings, you can pass an array of objects. This is why my template shows match.model.label. I used an array of objects where each object was:

{
    label: 'Alabama'
    value: 0
}

This way I can grab any value I want from a user selection, not just the label. I would suggest placing a value on these options here somewhere and using ng-if to help you do the matches paging like you are talking about.

Good Luck! Hope that helped! And dang, as I was typing this up someone posted another answer!

Upvotes: 1

Related Questions