Ajax based AutoComplete / TypeAhead Directive in AngularJS

Previously we already discussed below Auto Complete components:

  1. Ajax Based Multiselect JQuery Autocomplete Control in ASP.Net
  2. AutoComplete Component in Visualforce using JQueryUI

In this article, we will be creating TypeAhead Directive (Auto Complete) again in Salesforce However this time we will use AngularJs. Why we are using AngularJS ? We discussed already in one of article.

Getting Remote Data in JSON format using AJAX from:

To get data from remote source, we are using same code “Visualforce Account_JSON” and “Controller AccountJSONCreator ” explained in this article. Only thing I have added in wrapper class is ID field.

Other than AngularJs, we are also using Bootstrap in this article to make slick UI.

TypeAhead Demo using AngularJs
TypeAhead Demo using AngularJs

Angular Directive :

Directive is great way of declaring your own component and re-use throughout page. write once and re-use every where. Our directive will look like :

<typeahead
title="AccName" subtitle="BillingCity"
displaykey="AccName" retkey="AccId"
modelret="valRet" modeldisplay="valDis" />

typeAhead – Name of Directive
title – After Ajax, value from JSON to display in first line
subtitle – After Ajax, value from JSON to display in second line
displaykey – When item is selected, which JSON attribute to display (Most of time same as title)
retkey – After Selecting value, what should be stored hidden (Something like ID)
modelret – Hidden field value returned by directive
modeldisplay – selected value returned by directive

Live Demo :

Complete Code :

Visualforce TypeAheadDemo :

<apex:page showHeader="false" sidebar="false">
<apex:includeScript value="{!$Resource.Angular}"/>
<apex:stylesheet value="{!URLFOR($Resource.BootStrap3, 'bootstrap-3.1.1-dist/css/bootstrap.min.css')}"/>

<div ng-app="app1">
        <div class="well-lg well">
            <h1>  Type Ahead Example in AngularJs | Shivasoft  </h1>
        </div>
    <div ng-controller="ItemCtrl" class="container"> 

        Angular Lookup : <div style="width:200px;">
                                    <typeahead
                                       title="AccName" subtitle="BillingCity"
                                       displaykey="AccName" retkey="AccId"
                                       modelret="valRet" modeldisplay="valDis"
                                       />
                         </div> <br />
                        <b> Value Selected</b>  {{valDis}}  <br />
                        <b> Selected Id (Hidden) </b> {{valRet}}
    </div>
</div>

<script>

var myApp = angular.module('app1',[]); 

 myApp.controller('ItemCtrl', ['$scope','$templateCache','$http',  function($scope,$templateCache,$http) {

 }]);

   myApp.directive('typeahead', function($timeout,$http) {
  return {
    restrict: 'AEC',
    scope: {
      title: '@',
      retkey: '@',
      displaykey:'@',
      modeldisplay:'=',
      subtitle: '@',
      modelret: '='
    },

    link: function(scope, elem, attrs) {
        scope.current = 0;
        scope.selected = false; 

      scope.da  = function(txt){
          scope.ajaxClass = 'loadImage';
          $http({method: 'Get', url: 'Account_JSON?AccName='+txt}).
                success(function(data, status) {
                  scope.TypeAheadData = data;
                  scope.ajaxClass = '';
                }) ;  

      }

      scope.handleSelection = function(key,val) {
        scope.modelret = key;
        scope.modeldisplay = val;
        scope.current = 0;
        scope.selected = true;
      }

      scope.isCurrent = function(index) {
        return scope.current == index;
      }

      scope.setCurrent = function(index) {
        scope.current = index;
      }

    },
    template: '<input type="text" ng-model="modeldisplay" ng-KeyPress="da(modeldisplay)"  ng-keydown="selected=false"'+
                'style="width:100%;" ng-class="ajaxClass">'+
                '<div class="list-group table-condensed overlap" ng-hide="!modeldisplay.length || selected" style="width:100%">'+
                    '<a href="javascript:void();" class="list-group-item noTopBottomPad" ng-repeat="item in TypeAheadData|filter:model  track by $index" '+
                       'ng-click="handleSelection(item[retkey],item[displaykey])" style="cursor:pointer" '+
                       'ng-class="{active:isCurrent($index)}" '+
                       'ng-mouseenter="setCurrent($index)">'+
                         ' {{item[title]}}<br />'+
                         '<i>{{item[subtitle]}} </i>'+
                    '</a> '+
                '</div>'+
                '</input>'
  };
});

</script>
<style>
     .noTopBottomPad
            {
                padding-top : 2px !important;
                padding-bottom : 2px !important;
            }
        .overlap {position: absolute !important;
                z-index: 900 !important; width: inherit  !important ;}
        .loadImage { background: white url('{!URLFOR($Resource.AutoCompleteWithModal, 'AjaxLoad.gif')}') right center no-repeat; }

</style>
</apex:page>

Visualforce Account_JSON (To Query and get data in JSON format) :

<apex:page Controller="AccountJSONCreator" contentType="application/x-JavaScript; charset=utf-8" showHeader="false" standardStylesheets="false" sidebar="false">
{!JSON}
</apex:page>

Controller AccountJSONCreator :

public with sharing class AccountJSONCreator {

    public String getJSON()
    {
        String AccountName = Apexpages.currentPage().getParameters().get('AccName');
        List<AccountWrapper> wrp = new List<AccountWrapper>();
        for (Account a : [Select a.Id, a.Website, a.Name, a.BillingCountry, a.BillingCity
                            From
                                Account a
                            WHERE Name Like : '%'+AccountName+'%' ]) {
               AccountWrapper w = new AccountWrapper (a.Name, nullToBlank (a.BillingCountry), nullToBlank (a.BillingCity), a.Id);
               wrp.add(w);
            }
        return JSON.serialize(wrp);
    }

    public String nullToBlank(String val)
    {
        return val == null ?'':val;
    }

    public class AccountWrapper
    {
        String AccName,BillingCountry,BillingCity,AccId;

        public AccountWrapper(String aName, String bCountry, String bCity, String i)
        {
            AccName = aName;
            BillingCountry = bCountry;
            BillingCity = bCity;
            AccId = i ;
        }
    }

    static testMethod void AccountJSONCreatorTest() {
        AccountJSONCreator obj = new AccountJSONCreator();
        obj.getJSON();
    }
}

Related posts

  • Charu

    Nice Article Jitendra

  • Matthias Jäkel

    Hi Jitendra,

    this is a very nice article and cool feature. However I did not manage to make it work in my Salesforce Sandbox. It seems that the Angular resource may not be loaded (see screenshot). I do not know how to make it work.

    Later I would like to adapt your idea and create an address input page for country, state postal code and street. So all fields are dependend on each other. Do you think this would work?

    Regards,

    Matthias

  • Tino

    Thank you for going through the trouble to write this detailed post. You saved me a ton of time from having to build this from scratch. Much appreciated!

  • Raj

    Hi Jitendra, Thanks for your wonderful post. However I was stuck in passing the selected record’s Id to controller, could you please let me know how to pass the value i.e. valRet to Controller.

  • Pramod Gavade

    It is really helpful blog. But I ran into some issue, i was getting “typeaheadParser is now deprecated. Use uibTypeaheadParser instead.” warning along with some errors “Error: [$parse:syntax]http://errors.angularjs.org/1.4.7/$parse/syntax?p0=input&p1=is%20an%20unexpected%20token&p2=14&p3=form-controlNaNnput-sm&p4=input-sm” on console. So I renamed directive to searchaccount instead of typeahead, it has resolved the warning issue, but the error is still there.

    • Pramod Gavade

      Also when we select a value from dropdown “Uncaught SyntaxError: Unexpected token )” is displayed on the console. Same thing is happening for this demo as well.

      • I am not getting any error on Javascript console, Which Browser or Angular you are using ? Check this URL – http://shivasoft-developer-edition.ap1.force.com/Exp/TypeAheadDemo I tested it in IE as well

        • Pramod Gavade

          Thanks a lot for taking time to look into it. I am using google chrome and angular 1.4, I have attached a screenshot for SyntaxError message. It is happening for the demo link you shared as well.

          • I am not getting above error, Try to disable all your chrome extensions or click on link where error is coming and share details

  • Dilip Mistry

    Hi Jeet, curious to know if the component supports navigation by arrow keys for selection of record?

    • It does not support navigation by arrow, you will need to tweak this little bit.

  • bramhaji

    Hi Sir, it’s good, this is static, how to create dynamically in Angularjs and Sql sServer using Asp 4.0
    Sir please send to me My mail Id sbramhaji@gmail.com