<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js@1.4.0" data-semver="1.4.0" src="https://code.angularjs.org/1.4.0/angular.js"></script>
<link rel="stylesheet" href="angucomplete-alt.css" />
<link rel="stylesheet" href="style.css" />
<script src="angucomplete-alt.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="app" ng-controller="MainCtrl">
<h1>Example</h1>
<div angucomplete-alt id="ex1" template-url="template" placeholder="Search countries" maxlength="50" pause="100" selected-object="selectedCountry" local-data="countries" search-fields="name" title-field="name" minlength="1" input-class="form-control form-control-small" match-class="highlight" class="ng-isolate-scope"> </div>
<p>{{selectedCountry.title}}</p>
<script type="text/ng-template" id="template">
<div class="angucomplete-holder" ng-class="{'angucomplete-dropdown-visible': showDropdown}">
<input autofocus id="{{id}}_value" name={{inputName}} ng-class="{'angucomplete-input-not-empty': notEmpty}" ng-model="searchStr" ng-disabled="disableInput" type="{{type}}" placeholder="{{placeholder}}" maxlength="{{maxlength}}" ng-focus="onFocusHandler()" class="{{inputClass}}" ng-focus="resetHideResults()" ng-blur="hideResults($event)" autocapitalize="off" autocorrect="off" autocomplete="off" ng-change="inputChangeHandler(searchStr)"/>
<div id="{{id}}_dropdown" class="angucomplete-dropdown" ng-show="showDropdown">
<div class="angucomplete-searching" ng-show="searching" ng-bind="textSearching"></div>
<div class="angucomplete-searching" ng-show="!searching && (!results || results.length == 0)" ng-bind="textNoResults"></div>
<div class="angucomplete-row" ng-repeat="result in results" ng-click="selectResult(result)" ng-mouseenter="hoverRow($index)" ng-class="{'angucomplete-selected-row': $index == currentIndex}">
<div ng-if="imageField" class="angucomplete-image-holder">
<img ng-if="result.image && result.image != ''" ng-src="{{result.image}}" class="angucomplete-image"/>
<div ng-if="!result.image && result.image != ''" class="angucomplete-image-default"></div>
</div>
<div class="angucomplete-title" ng-if="matchClass" ng-bind-html="result.title"></div>
<div class="angucomplete-title" ng-if="!matchClass">{{ result.title }}</div>
<div ng-if="matchClass && result.description && result.description != ''" class="angucomplete-description" ng-bind-html="result.description"></div>
<div ng-if="!matchClass && result.description && result.description != ''" class="angucomplete-description">{{result.description}}</div>
</div>
</div>
</div>
</script>
</body>
</html>
var app = angular.module('app', ["angucomplete-alt"]);
app.controller('MainCtrl', ['$scope', '$http',
function MainCtrl($scope, $http) {
$scope.remoteUrlRequestFn = function(str) {
return {q: str};
};
$scope.countrySelected = function(selected) {
window.alert('You have selected ' + selected.title);
};
$scope.countries = [
{name: 'Afghanistan', code: 'AF'},
{name: 'Aland Islands', code: 'AX'},
{name: 'Albania', code: 'AL'},
{name: 'Algeria', code: 'DZ'},
{name: 'American Samoa', code: 'AS'},
{name: 'AndorrA', code: 'AD'},
{name: 'Angola', code: 'AO'},
{name: 'Anguilla', code: 'AI'},
{name: 'Antarctica', code: 'AQ'},
{name: 'Antigua and Barbuda', code: 'AG'},
{name: 'Argentina', code: 'AR'},
{name: 'Armenia', code: 'AM'},
{name: 'Aruba', code: 'AW'},
{name: 'Australia', code: 'AU'},
{name: 'Austria', code: 'AT'},
{name: 'Azerbaijan', code: 'AZ'},
{name: 'Bahamas', code: 'BS'},
{name: 'Bahrain', code: 'BH'},
{name: 'Bangladesh', code: 'BD'},
{name: 'Barbados', code: 'BB'},
{name: 'Belarus', code: 'BY'},
{name: 'Belgium', code: 'BE'},
{name: 'Belize', code: 'BZ'},
{name: 'Benin', code: 'BJ'},
{name: 'Bermuda', code: 'BM'},
{name: 'Bhutan', code: 'BT'},
{name: 'Bolivia', code: 'BO'},
{name: 'Bosnia and Herzegovina', code: 'BA'},
{name: 'Botswana', code: 'BW'},
{name: 'Bouvet Island', code: 'BV'},
{name: 'Brazil', code: 'BR'},
{name: 'British Indian Ocean Territory', code: 'IO'},
{name: 'Brunei Darussalam', code: 'BN'},
{name: 'Bulgaria', code: 'BG'},
{name: 'Burkina Faso', code: 'BF'},
{name: 'Burundi', code: 'BI'},
{name: 'Cambodia', code: 'KH'},
{name: 'Cameroon', code: 'CM'},
{name: 'Canada', code: 'CA'},
{name: 'Cape Verde', code: 'CV'},
{name: 'Cayman Islands', code: 'KY'},
{name: 'Central African Republic', code: 'CF'},
{name: 'Chad', code: 'TD'},
{name: 'Chile', code: 'CL'},
{name: 'China', code: 'CN'},
{name: 'Christmas Island', code: 'CX'},
{name: 'Cocos (Keeling) Islands', code: 'CC'},
{name: 'Colombia', code: 'CO'},
{name: 'Comoros', code: 'KM'},
{name: 'Congo', code: 'CG'},
{name: 'Congo, The Democratic Republic of the', code: 'CD'},
{name: 'Cook Islands', code: 'CK'},
{name: 'Costa Rica', code: 'CR'},
{name: 'Cote D\'Ivoire', code: 'CI'},
{name: 'Croatia', code: 'HR'},
{name: 'Cuba', code: 'CU'},
{name: 'Cyprus', code: 'CY'},
{name: 'Czech Republic', code: 'CZ'},
{name: 'Denmark', code: 'DK'},
{name: 'Djibouti', code: 'DJ'},
{name: 'Dominica', code: 'DM'},
{name: 'Dominican Republic', code: 'DO'},
{name: 'Ecuador', code: 'EC'},
{name: 'Egypt', code: 'EG'},
{name: 'El Salvador', code: 'SV'},
{name: 'Equatorial Guinea', code: 'GQ'},
{name: 'Eritrea', code: 'ER'},
{name: 'Estonia', code: 'EE'},
{name: 'Ethiopia', code: 'ET'},
{name: 'Falkland Islands (Malvinas)', code: 'FK'},
{name: 'Faroe Islands', code: 'FO'},
{name: 'Fiji', code: 'FJ'},
{name: 'Finland', code: 'FI'},
{name: 'France', code: 'FR'},
{name: 'French Guiana', code: 'GF'},
{name: 'French Polynesia', code: 'PF'},
{name: 'French Southern Territories', code: 'TF'},
{name: 'Gabon', code: 'GA'},
{name: 'Gambia', code: 'GM'},
{name: 'Georgia', code: 'GE'},
{name: 'Germany', code: 'DE'},
{name: 'Ghana', code: 'GH'},
{name: 'Gibraltar', code: 'GI'},
{name: 'Greece', code: 'GR'},
{name: 'Greenland', code: 'GL'},
{name: 'Grenada', code: 'GD'},
{name: 'Guadeloupe', code: 'GP'},
{name: 'Guam', code: 'GU'},
{name: 'Guatemala', code: 'GT'},
{name: 'Guernsey', code: 'GG'},
{name: 'Guinea', code: 'GN'},
{name: 'Guinea-Bissau', code: 'GW'},
{name: 'Guyana', code: 'GY'},
{name: 'Haiti', code: 'HT'},
{name: 'Heard Island and Mcdonald Islands', code: 'HM'},
{name: 'Holy See (Vatican City State)', code: 'VA'},
{name: 'Honduras', code: 'HN'},
{name: 'Hong Kong', code: 'HK'},
{name: 'Hungary', code: 'HU'},
{name: 'Iceland', code: 'IS'},
{name: 'India', code: 'IN'},
{name: 'Indonesia', code: 'ID'},
{name: 'Iran, Islamic Republic Of', code: 'IR'},
{name: 'Iraq', code: 'IQ'},
{name: 'Ireland', code: 'IE'},
{name: 'Isle of Man', code: 'IM'},
{name: 'Israel', code: 'IL'},
{name: 'Italy', code: 'IT'},
{name: 'Jamaica', code: 'JM'},
{name: 'Japan', code: 'JP'},
{name: 'Jersey', code: 'JE'},
{name: 'Jordan', code: 'JO'},
{name: 'Kazakhstan', code: 'KZ'},
{name: 'Kenya', code: 'KE'},
{name: 'Kiribati', code: 'KI'},
{name: 'Korea, Democratic People\'S Republic of', code: 'KP'},
{name: 'Korea, Republic of', code: 'KR'},
{name: 'Kuwait', code: 'KW'},
{name: 'Kyrgyzstan', code: 'KG'},
{name: 'Lao People\'S Democratic Republic', code: 'LA'},
{name: 'Latvia', code: 'LV'},
{name: 'Lebanon', code: 'LB'},
{name: 'Lesotho', code: 'LS'},
{name: 'Liberia', code: 'LR'},
{name: 'Libyan Arab Jamahiriya', code: 'LY'},
{name: 'Liechtenstein', code: 'LI'},
{name: 'Lithuania', code: 'LT'},
{name: 'Luxembourg', code: 'LU'},
{name: 'Macao', code: 'MO'},
{name: 'Macedonia, The Former Yugoslav Republic of', code: 'MK'},
{name: 'Madagascar', code: 'MG'},
{name: 'Malawi', code: 'MW'},
{name: 'Malaysia', code: 'MY'},
{name: 'Maldives', code: 'MV'},
{name: 'Mali', code: 'ML'},
{name: 'Malta', code: 'MT'},
{name: 'Marshall Islands', code: 'MH'},
{name: 'Martinique', code: 'MQ'},
{name: 'Mauritania', code: 'MR'},
{name: 'Mauritius', code: 'MU'},
{name: 'Mayotte', code: 'YT'},
{name: 'Mexico', code: 'MX'},
{name: 'Micronesia, Federated States of', code: 'FM'},
{name: 'Moldova, Republic of', code: 'MD'},
{name: 'Monaco', code: 'MC'},
{name: 'Mongolia', code: 'MN'},
{name: 'Montserrat', code: 'MS'},
{name: 'Morocco', code: 'MA'},
{name: 'Mozambique', code: 'MZ'},
{name: 'Myanmar', code: 'MM'},
{name: 'Namibia', code: 'NA'},
{name: 'Nauru', code: 'NR'},
{name: 'Nepal', code: 'NP'},
{name: 'Netherlands', code: 'NL'},
{name: 'Netherlands Antilles', code: 'AN'},
{name: 'New Caledonia', code: 'NC'},
{name: 'New Zealand', code: 'NZ'},
{name: 'Nicaragua', code: 'NI'},
{name: 'Niger', code: 'NE'},
{name: 'Nigeria', code: 'NG'},
{name: 'Niue', code: 'NU'},
{name: 'Norfolk Island', code: 'NF'},
{name: 'Northern Mariana Islands', code: 'MP'},
{name: 'Norway', code: 'NO'},
{name: 'Oman', code: 'OM'},
{name: 'Pakistan', code: 'PK'},
{name: 'Palau', code: 'PW'},
{name: 'Palestinian Territory, Occupied', code: 'PS'},
{name: 'Panama', code: 'PA'},
{name: 'Papua New Guinea', code: 'PG'},
{name: 'Paraguay', code: 'PY'},
{name: 'Peru', code: 'PE'},
{name: 'Philippines', code: 'PH'},
{name: 'Pitcairn', code: 'PN'},
{name: 'Poland', code: 'PL'},
{name: 'Portugal', code: 'PT'},
{name: 'Puerto Rico', code: 'PR'},
{name: 'Qatar', code: 'QA'},
{name: 'Reunion', code: 'RE'},
{name: 'Romania', code: 'RO'},
{name: 'Russian Federation', code: 'RU'},
{name: 'RWANDA', code: 'RW'},
{name: 'Saint Helena', code: 'SH'},
{name: 'Saint Kitts and Nevis', code: 'KN'},
{name: 'Saint Lucia', code: 'LC'},
{name: 'Saint Pierre and Miquelon', code: 'PM'},
{name: 'Saint Vincent and the Grenadines', code: 'VC'},
{name: 'Samoa', code: 'WS'},
{name: 'San Marino', code: 'SM'},
{name: 'Sao Tome and Principe', code: 'ST'},
{name: 'Saudi Arabia', code: 'SA'},
{name: 'Senegal', code: 'SN'},
{name: 'Serbia and Montenegro', code: 'CS'},
{name: 'Seychelles', code: 'SC'},
{name: 'Sierra Leone', code: 'SL'},
{name: 'Singapore', code: 'SG'},
{name: 'Slovakia', code: 'SK'},
{name: 'Slovenia', code: 'SI'},
{name: 'Solomon Islands', code: 'SB'},
{name: 'Somalia', code: 'SO'},
{name: 'South Africa', code: 'ZA'},
{name: 'South Georgia and the South Sandwich Islands', code: 'GS'},
{name: 'Spain', code: 'ES'},
{name: 'Sri Lanka', code: 'LK'},
{name: 'Sudan', code: 'SD'},
{name: 'Suriname', code: 'SR'},
{name: 'Svalbard and Jan Mayen', code: 'SJ'},
{name: 'Swaziland', code: 'SZ'},
{name: 'Sweden', code: 'SE'},
{name: 'Switzerland', code: 'CH'},
{name: 'Syrian Arab Republic', code: 'SY'},
{name: 'Taiwan, Province of China', code: 'TW'},
{name: 'Tajikistan', code: 'TJ'},
{name: 'Tanzania, United Republic of', code: 'TZ'},
{name: 'Thailand', code: 'TH'},
{name: 'Timor-Leste', code: 'TL'},
{name: 'Togo', code: 'TG'},
{name: 'Tokelau', code: 'TK'},
{name: 'Tonga', code: 'TO'},
{name: 'Trinidad and Tobago', code: 'TT'},
{name: 'Tunisia', code: 'TN'},
{name: 'Turkey', code: 'TR'},
{name: 'Turkmenistan', code: 'TM'},
{name: 'Turks and Caicos Islands', code: 'TC'},
{name: 'Tuvalu', code: 'TV'},
{name: 'Uganda', code: 'UG'},
{name: 'Ukraine', code: 'UA'},
{name: 'United Arab Emirates', code: 'AE'},
{name: 'United Kingdom', code: 'GB'},
{name: 'United States', code: 'US'},
{name: 'United States Minor Outlying Islands', code: 'UM'},
{name: 'Uruguay', code: 'UY'},
{name: 'Uzbekistan', code: 'UZ'},
{name: 'Vanuatu', code: 'VU'},
{name: 'Venezuela', code: 'VE'},
{name: 'Vietnam', code: 'VN'},
{name: 'Virgin Islands, British', code: 'VG'},
{name: 'Virgin Islands, U.S.', code: 'VI'},
{name: 'Wallis and Futuna', code: 'WF'},
{name: 'Western Sahara', code: 'EH'},
{name: 'Yemen', code: 'YE'},
{name: 'Zambia', code: 'ZM'},
{name: 'Zimbabwe', code: 'ZW'}
];
}
]);
/* Styles go here */
/*
* angucomplete-alt
* Autocomplete directive for AngularJS
* This is a fork of Daryl Rowland's angucomplete with some extra features.
* By Hidenari Nozaki
*/
/*! Copyright (c) 2014 Hidenari Nozaki and contributors | Licensed under the MIT license */
'use strict';
(function (root, factory) {
if (typeof module !== 'undefined' && module.exports) {
// CommonJS
module.exports = factory(require('angular'));
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['angular'], factory);
} else {
// Global Variables
factory(root.angular);
}
}(window, function (angular) {
angular.module('angucomplete-alt', [] )
.directive('angucompleteAlt', ['$q', '$parse', '$http', '$sce', '$timeout', '$templateCache', function ($q, $parse, $http, $sce, $timeout, $templateCache) {
// keyboard events
var KEY_DW = 40;
var KEY_RT = 39;
var KEY_UP = 38;
var KEY_LF = 37;
var KEY_ES = 27;
var KEY_EN = 13;
var KEY_BS = 8;
var KEY_DEL = 46;
var KEY_TAB = 9;
var MIN_LENGTH = 3;
var MAX_LENGTH = 524288; // the default max length per the html maxlength attribute
var PAUSE = 500;
var BLUR_TIMEOUT = 200;
// string constants
var REQUIRED_CLASS = 'autocomplete-required';
var TEXT_SEARCHING = 'Searching...';
var TEXT_NORESULTS = 'No results found';
var TEMPLATE_URL = '/angucomplete-alt/index.html';
// Set the default template for this directive
$templateCache.put(TEMPLATE_URL,
'<div class="angucomplete-holder" ng-class="{\'angucomplete-dropdown-visible\': showDropdown}">' +
' <input id="{{id}}_value" name={{inputName}} ng-class="{\'angucomplete-input-not-empty\': notEmpty}" ng-model="searchStr" ng-disabled="disableInput" type="{{type}}" placeholder="{{placeholder}}" maxlength="{{maxlength}}" ng-focus="onFocusHandler()" class="{{inputClass}}" ng-focus="resetHideResults()" ng-blur="hideResults($event)" autocapitalize="off" autocorrect="off" autocomplete="off" ng-change="inputChangeHandler(searchStr)"/>' +
' <div id="{{id}}_dropdown" class="angucomplete-dropdown" ng-show="showDropdown">' +
' <div class="angucomplete-searching" ng-show="searching" ng-bind="textSearching"></div>' +
' <div class="angucomplete-searching" ng-show="!searching && (!results || results.length == 0)" ng-bind="textNoResults"></div>' +
' <div class="angucomplete-row" ng-repeat="result in results" ng-click="selectResult(result)" ng-mouseenter="hoverRow($index)" ng-class="{\'angucomplete-selected-row\': $index == currentIndex}">' +
' <div ng-if="imageField" class="angucomplete-image-holder">' +
' <img ng-if="result.image && result.image != \'\'" ng-src="{{result.image}}" class="angucomplete-image"/>' +
' <div ng-if="!result.image && result.image != \'\'" class="angucomplete-image-default"></div>' +
' </div>' +
' <div class="angucomplete-title" ng-if="matchClass" ng-bind-html="result.title"></div>' +
' <div class="angucomplete-title" ng-if="!matchClass">{{ result.title }}</div>' +
' <div ng-if="matchClass && result.description && result.description != \'\'" class="angucomplete-description" ng-bind-html="result.description"></div>' +
' <div ng-if="!matchClass && result.description && result.description != \'\'" class="angucomplete-description">{{result.description}}</div>' +
' </div>' +
' </div>' +
'</div>'
);
return {
restrict: 'EA',
require: '^?form',
scope: {
selectedObject: '=',
disableInput: '=',
initialValue: '=',
localData: '=',
remoteUrlRequestFormatter: '=',
remoteUrlRequestWithCredentials: '@',
remoteUrlResponseFormatter: '=',
remoteUrlErrorCallback: '=',
remoteApiHandler: '=',
id: '@',
type: '@',
placeholder: '@',
remoteUrl: '@',
remoteUrlDataField: '@',
titleField: '@',
descriptionField: '@',
imageField: '@',
inputClass: '@',
pause: '@',
searchFields: '@',
minlength: '@',
matchClass: '@',
clearSelected: '@',
overrideSuggestions: '@',
fieldRequired: '@',
fieldRequiredClass: '@',
inputChanged: '=',
autoMatch: '@',
focusOut: '&',
focusIn: '&',
inputName: '@'
},
templateUrl: function(element, attrs) {
return attrs.templateUrl || TEMPLATE_URL;
},
link: function(scope, elem, attrs, ctrl) {
var inputField = elem.find('input');
var minlength = MIN_LENGTH;
var searchTimer = null;
var hideTimer;
var requiredClassName = REQUIRED_CLASS;
var responseFormatter;
var validState = null;
var httpCanceller = null;
var dd = elem[0].querySelector('.angucomplete-dropdown');
var isScrollOn = false;
var mousedownOn = null;
var unbindInitialValue;
elem.on('mousedown', function(event) {
if (event.target.id) {
mousedownOn = event.target.id;
}
else {
mousedownOn = event.target.className;
}
});
scope.currentIndex = null;
scope.searching = false;
unbindInitialValue = scope.$watch('initialValue', function(newval, oldval) {
if (newval) {
unbindInitialValue();
if (typeof newval === 'object') {
scope.searchStr = extractTitle(newval);
callOrAssign({originalObject: newval});
} else if (typeof newval === 'string' && newval.length > 0) {
scope.searchStr = newval;
} else {
if (console && console.error) {
console.error('Tried to set initial value of angucomplete to', newval, 'which is an invalid value');
}
}
handleRequired(true);
}
});
scope.$on('angucomplete-alt:clearInput', function (event, elementId) {
if (!elementId || elementId === scope.id) {
scope.searchStr = null;
handleRequired(false);
clearResults();
}
});
// for IE8 quirkiness about event.which
function ie8EventNormalizer(event) {
return event.which ? event.which : event.keyCode;
}
function callOrAssign(value) {
if (typeof scope.selectedObject === 'function') {
scope.selectedObject(value);
}
else {
scope.selectedObject = value;
}
if (value) {
handleRequired(true);
}
else {
handleRequired(false);
}
}
function callFunctionOrIdentity(fn) {
return function(data) {
return scope[fn] ? scope[fn](data) : data;
};
}
function setInputString(str) {
callOrAssign({originalObject: str});
if (scope.clearSelected) {
scope.searchStr = null;
}
clearResults();
}
function extractTitle(data) {
// split title fields and run extractValue for each and join with ' '
return scope.titleField.split(',')
.map(function(field) {
return extractValue(data, field);
})
.join(' ');
}
function extractValue(obj, key) {
var keys, result;
if (key) {
keys= key.split('.');
result = obj;
keys.forEach(function(k) { result = result[k]; });
}
else {
result = obj;
}
return result;
}
function findMatchString(target, str) {
var result, matches, re;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
// Escape user input to be treated as a literal string within a regular expression
re = new RegExp(str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
if (!target) { return; }
matches = target.match(re);
if (matches) {
result = target.replace(re,
'<span class="'+ scope.matchClass +'">'+ matches[0] +'</span>');
}
else {
result = target;
}
return $sce.trustAsHtml(result);
}
function handleRequired(valid) {
scope.notEmpty = valid;
validState = scope.searchStr;
if (scope.fieldRequired && ctrl) {
ctrl.$setValidity(requiredClassName, valid);
}
}
function keyupHandler(event) {
var which = ie8EventNormalizer(event);
if (which === KEY_LF || which === KEY_RT) {
// do nothing
return;
}
if (which === KEY_UP || which === KEY_EN) {
event.preventDefault();
}
else if (which === KEY_DW) {
event.preventDefault();
if (!scope.showDropdown && scope.searchStr && scope.searchStr.length >= minlength) {
initResults();
scope.searching = true;
searchTimerComplete(scope.searchStr);
}
}
else if (which === KEY_ES) {
clearResults();
scope.$apply(function() {
inputField.val(scope.searchStr);
});
}
else {
if (minlength === 0 && !scope.searchStr) {
return;
}
if (!scope.searchStr || scope.searchStr === '') {
scope.showDropdown = false;
} else if (scope.searchStr.length >= minlength) {
initResults();
if (searchTimer) {
$timeout.cancel(searchTimer);
}
scope.searching = true;
searchTimer = $timeout(function() {
searchTimerComplete(scope.searchStr);
}, scope.pause);
}
if (validState && validState !== scope.searchStr && !scope.clearSelected) {
callOrAssign(undefined);
}
}
}
function handleOverrideSuggestions(event) {
if (scope.overrideSuggestions &&
!(scope.selectedObject && scope.selectedObject.originalObject === scope.searchStr)) {
if (event) {
event.preventDefault();
}
setInputString(scope.searchStr);
}
}
function dropdownRowOffsetHeight(row) {
var css = getComputedStyle(row);
return row.offsetHeight +
parseInt(css.marginTop, 10) + parseInt(css.marginBottom, 10);
}
function dropdownHeight() {
return dd.getBoundingClientRect().top +
parseInt(getComputedStyle(dd).maxHeight, 10);
}
function dropdownRow() {
return elem[0].querySelectorAll('.angucomplete-row')[scope.currentIndex];
}
function dropdownRowTop() {
return dropdownRow().getBoundingClientRect().top -
(dd.getBoundingClientRect().top +
parseInt(getComputedStyle(dd).paddingTop, 10));
}
function dropdownScrollTopTo(offset) {
dd.scrollTop = dd.scrollTop + offset;
}
function updateInputField(){
var current = scope.results[scope.currentIndex];
if (scope.matchClass) {
inputField.val(extractTitle(current.originalObject));
}
else {
inputField.val(current.title);
}
}
function keydownHandler(event) {
var which = ie8EventNormalizer(event);
var row = null;
var rowTop = null;
if (which === KEY_EN && scope.results) {
if (scope.currentIndex >= 0 && scope.currentIndex < scope.results.length) {
event.preventDefault();
scope.selectResult(scope.results[scope.currentIndex]);
} else {
handleOverrideSuggestions(event);
clearResults();
}
scope.$apply();
} else if (which === KEY_DW && scope.results) {
event.preventDefault();
if ((scope.currentIndex + 1) < scope.results.length && scope.showDropdown) {
scope.$apply(function() {
scope.currentIndex ++;
updateInputField();
});
if (isScrollOn) {
row = dropdownRow();
if (dropdownHeight() < row.getBoundingClientRect().bottom) {
dropdownScrollTopTo(dropdownRowOffsetHeight(row));
}
}
}
} else if (which === KEY_UP && scope.results) {
event.preventDefault();
if (scope.currentIndex >= 1) {
scope.$apply(function() {
scope.currentIndex --;
updateInputField();
});
if (isScrollOn) {
rowTop = dropdownRowTop();
if (rowTop < 0) {
dropdownScrollTopTo(rowTop - 1);
}
}
}
else if (scope.currentIndex === 0) {
scope.$apply(function() {
scope.currentIndex = -1;
inputField.val(scope.searchStr);
});
}
} else if (which === KEY_TAB) {
if (scope.results && scope.results.length > 0 && scope.showDropdown) {
if (scope.currentIndex === -1 && scope.overrideSuggestions) {
// intentionally not sending event so that it does not
// prevent default tab behavior
handleOverrideSuggestions();
}
else {
if (scope.currentIndex === -1) {
scope.currentIndex = 0;
}
scope.selectResult(scope.results[scope.currentIndex]);
scope.$digest();
}
}
else {
// no results
// intentionally not sending event so that it does not
// prevent default tab behavior
if (scope.searchStr && scope.searchStr.length > 0) {
handleOverrideSuggestions();
}
}
}
}
function httpSuccessCallbackGen(str) {
return function(responseData, status, headers, config) {
// normalize return obejct from promise
if (!status && !headers && !config) {
responseData = responseData.data;
}
scope.searching = false;
processResults(
extractValue(responseFormatter(responseData), scope.remoteUrlDataField),
str);
};
}
function httpErrorCallback(errorRes, status, headers, config) {
// normalize return obejct from promise
if (!status && !headers && !config) {
status = errorRes.status;
}
if (status !== 0) {
if (scope.remoteUrlErrorCallback) {
scope.remoteUrlErrorCallback(errorRes, status, headers, config);
}
else {
if (console && console.error) {
console.error('http error');
}
}
}
}
function cancelHttpRequest() {
if (httpCanceller) {
httpCanceller.resolve();
}
}
function getRemoteResults(str) {
var params = {},
url = scope.remoteUrl + encodeURIComponent(str);
if (scope.remoteUrlRequestFormatter) {
params = {params: scope.remoteUrlRequestFormatter(str)};
url = scope.remoteUrl;
}
if (!!scope.remoteUrlRequestWithCredentials) {
params.withCredentials = true;
}
cancelHttpRequest();
httpCanceller = $q.defer();
params.timeout = httpCanceller.promise;
$http.get(url, params)
.success(httpSuccessCallbackGen(str))
.error(httpErrorCallback);
}
function getRemoteResultsWithCustomHandler(str) {
cancelHttpRequest();
httpCanceller = $q.defer();
scope.remoteApiHandler(str, httpCanceller.promise)
.then(httpSuccessCallbackGen(str))
.catch(httpErrorCallback);
}
function clearResults() {
scope.showDropdown = false;
scope.results = [];
if (dd) {
dd.scrollTop = 0;
}
}
function initResults() {
scope.showDropdown = true;
scope.currentIndex = -1;
scope.results = [];
}
function getLocalResults(str) {
var i, match, s, value,
searchFields = scope.searchFields.split(','),
matches = [];
for (i = 0; i < scope.localData.length; i++) {
match = false;
for (s = 0; s < searchFields.length; s++) {
value = extractValue(scope.localData[i], searchFields[s]) || '';
match = match || (value.toLowerCase().indexOf(str.toLowerCase()) >= 0);
}
if (match) {
matches[matches.length] = scope.localData[i];
}
}
scope.searching = false;
processResults(matches, str);
}
function checkExactMatch(result, obj, str){
if (!str) { return; }
for(var key in obj){
if(obj[key].toLowerCase() === str.toLowerCase()){
scope.selectResult(result);
return;
}
}
}
function searchTimerComplete(str) {
// Begin the search
if (!str || str.length < minlength) {
return;
}
if (scope.localData) {
scope.$apply(function() {
getLocalResults(str);
});
}
else if (scope.remoteApiHandler) {
getRemoteResultsWithCustomHandler(str);
} else {
getRemoteResults(str);
}
}
function processResults(responseData, str) {
var i, description, image, text, formattedText, formattedDesc;
if (responseData && responseData.length > 0) {
scope.results = [];
for (i = 0; i < responseData.length; i++) {
if (scope.titleField && scope.titleField !== '') {
text = formattedText = extractTitle(responseData[i]);
}
description = '';
if (scope.descriptionField) {
description = formattedDesc = extractValue(responseData[i], scope.descriptionField);
}
image = '';
if (scope.imageField) {
image = extractValue(responseData[i], scope.imageField);
}
if (scope.matchClass) {
formattedText = findMatchString(text, str);
formattedDesc = findMatchString(description, str);
}
scope.results[scope.results.length] = {
title: formattedText,
description: formattedDesc,
image: image,
originalObject: responseData[i]
};
if (scope.autoMatch) {
checkExactMatch(scope.results[scope.results.length-1],
{title: text, desc: description || ''}, scope.searchStr);
}
}
} else {
scope.results = [];
}
}
function showAll() {
if (scope.localData) {
processResults(scope.localData, '');
}
else if (scope.remoteApiHandler) {
getRemoteResultsWithCustomHandler('');
}
else {
getRemoteResults('');
}
}
scope.onFocusHandler = function() {
if (scope.focusIn) {
scope.focusIn();
}
if (minlength === 0 && (!scope.searchStr || scope.searchStr.length === 0)) {
scope.showDropdown = true;
showAll();
}
};
scope.hideResults = function(event) {
if (mousedownOn && mousedownOn.indexOf('angucomplete') >= 0) {
mousedownOn = null;
}
else {
hideTimer = $timeout(function() {
clearResults();
scope.$apply(function() {
if (scope.searchStr && scope.searchStr.length > 0) {
inputField.val(scope.searchStr);
}
});
}, BLUR_TIMEOUT);
cancelHttpRequest();
if (scope.focusOut) {
scope.focusOut();
}
if (scope.overrideSuggestions) {
if (scope.searchStr && scope.searchStr.length > 0 && scope.currentIndex === -1) {
handleOverrideSuggestions();
}
}
}
};
scope.resetHideResults = function() {
if (hideTimer) {
$timeout.cancel(hideTimer);
}
};
scope.hoverRow = function(index) {
scope.currentIndex = index;
};
scope.selectResult = function(result) {
// Restore original values
if (scope.matchClass) {
result.title = extractTitle(result.originalObject);
result.description = extractValue(result.originalObject, scope.descriptionField);
}
if (scope.clearSelected) {
scope.searchStr = null;
}
else {
scope.searchStr = result.title;
}
callOrAssign(result);
clearResults();
};
scope.inputChangeHandler = function(str) {
if (str.length < minlength) {
clearResults();
}
else if (str.length === 0 && minlength === 0) {
scope.searching = false;
showAll();
}
if (scope.inputChanged) {
str = scope.inputChanged(str);
}
return str;
};
// check required
if (scope.fieldRequiredClass && scope.fieldRequiredClass !== '') {
requiredClassName = scope.fieldRequiredClass;
}
// check min length
if (scope.minlength && scope.minlength !== '') {
minlength = parseInt(scope.minlength, 10);
}
// check pause time
if (!scope.pause) {
scope.pause = PAUSE;
}
// check clearSelected
if (!scope.clearSelected) {
scope.clearSelected = false;
}
// check override suggestions
if (!scope.overrideSuggestions) {
scope.overrideSuggestions = false;
}
// check required field
if (scope.fieldRequired && ctrl) {
// check initial value, if given, set validitity to true
if (scope.initialValue) {
handleRequired(true);
}
else {
handleRequired(false);
}
}
scope.type = attrs.type ? attrs.type : 'text';
// set strings for "Searching..." and "No results"
scope.textSearching = attrs.textSearching ? attrs.textSearching : TEXT_SEARCHING;
scope.textNoResults = attrs.textNoResults ? attrs.textNoResults : TEXT_NORESULTS;
// set max length (default to maxlength deault from html
scope.maxlength = attrs.maxlength ? attrs.maxlength : MAX_LENGTH;
// register events
inputField.on('keydown', keydownHandler);
inputField.on('keyup', keyupHandler);
// set response formatter
responseFormatter = callFunctionOrIdentity('remoteUrlResponseFormatter');
scope.$on('$destroy', function() {
// take care of required validity when it gets destroyed
handleRequired(true);
});
// set isScrollOn
$timeout(function() {
var css = getComputedStyle(dd);
isScrollOn = css.maxHeight && css.overflowY === 'auto';
});
}
};
}]);
}));
.angucomplete-holder {
position: relative;
}
.angucomplete-dropdown {
border-color: #ececec;
border-width: 1px;
border-style: solid;
border-radius: 2px;
width: 250px;
padding: 6px;
cursor: pointer;
z-index: 9999;
position: absolute;
/*top: 32px;
left: 0px;
*/
margin-top: -6px;
background-color: #ffffff;
}
.angucomplete-searching {
color: #acacac;
font-size: 14px;
}
.angucomplete-description {
font-size: 14px;
}
.angucomplete-row {
padding: 5px;
color: #000000;
margin-bottom: 4px;
clear: both;
}
.angucomplete-selected-row {
background-color: lightblue;
color: #ffffff;
}
.angucomplete-image-holder {
padding-top: 2px;
float: left;
margin-right: 10px;
margin-left: 5px;
}
.angucomplete-image {
height: 34px;
width: 34px;
border-radius: 50%;
border-color: #ececec;
border-style: solid;
border-width: 1px;
}
.angucomplete-image-default {
/* Add your own default image here
background-image: url('/assets/default.png');
*/
background-position: center;
background-size: contain;
height: 34px;
width: 34px;
}