(function () {
"use strict";
var KEY = {
ENTER: 13,
SHIFT: 16,
CTRL: 17,
};
/**
* Add querySelectorAll() to jqLite.
*
* jqLite find() is limited to lookups by tag name.
* TODO This will change with future versions of AngularJS, to be removed when this happens
*
* See jqLite.find - why not use querySelectorAll? https://github.com/angular/angular.js/issues/3586
* See feat(jqLite): use querySelectorAll instead of getElementsByTagName in jqLite.find https://github.com/angular/angular.js/pull/3598
*/
if (angular.element.prototype.querySelectorAll === undefined) {
angular.element.prototype.querySelectorAll = function(selector) {
return angular.element(this[0].querySelectorAll(selector));
};
}
angular.module('ui.select', [])
.constant('uiSelectConfig', {
searchEnabled: true,
placeholder: '', // Empty by default, like HTML tag <select>
refreshDelay: 1000 // In milliseconds
})
.service('listboxMinErr', function() {
var minErr = angular.$$minErr('rw.listbox');
return function() {
var error = minErr.apply(this, arguments);
var message = error.message.replace(new RegExp('\nhttp://errors.angularjs.org/.*'), '');
return new Error(message);
};
})
/**
* Parses "repeat" attribute.
*
* Taken from AngularJS ngRepeat source code
* See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L211
*
* Original discussion about parsing "repeat" attribute instead of fully relying on ng-repeat:
* https://github.com/angular-ui/ui-select/commit/5dd63ad#commitcomment-5504697
*/
.service('RepeatParser', ['listboxMinErr','$parse', function(listboxMinErr, $parse) {
var self = this;
/**
* Example:
* expression = "person in people | filter: {firstName: $select.search} track by $index"
* itemName = "person",
* source = "people | filter: {firstName: $select.search}",
* trackByExp = "$index",
*/
self.parse = function(expression) {
var match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?([\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
if (!match) {
throw listboxMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
expression);
}
return {
itemName: match[2], // (lhs) Left-hand side,
source: $parse(match[3]),
trackByExp: match[4],
modelMapper: $parse(match[1] || match[2])
};
};
self.getGroupNgRepeatExpression = function() {
return '$group in $select.groups';
};
self.getNgRepeatExpression = function(itemName, source, trackByExp, grouped) {
var expression = itemName + ' in ' + (grouped ? '$group.items' : source);
if (trackByExp) {
expression += ' track by ' + trackByExp;
}
return expression;
};
}])
/**
* Contains ui-select "intelligence".
*
* The goal is to limit dependency on the DOM whenever possible and
* put as much logic in the controller (instead of the link functions) as possible so it can be easily tested.
*/
.controller('uiSelectCtrl',
['$scope', '$element', '$timeout', 'RepeatParser', 'listboxMinErr',
function($scope, $element, $timeout, RepeatParser, listboxMinErr) {
var ctrl = this;
var EMPTY_SEARCH = '';
ctrl.placeholder = undefined;
ctrl.search = EMPTY_SEARCH;
ctrl.activeIndex = 0;
ctrl.activeMatchIndex = -1;
ctrl.items = [];
ctrl.selected = undefined;
ctrl.focus = false;
ctrl.disabled = undefined; // Initialized inside uiSelect directive link function
ctrl.searchEnabled = undefined; // Initialized inside uiSelect directive link function
ctrl.resetSearchInput = undefined; // Initialized inside uiSelect directive link function
ctrl.refreshDelay = undefined; // Initialized inside uiSelectChoices directive link function
ctrl.multiple = false; // Initialized inside uiSelect directive link function
ctrl.disableChoiceExpression = undefined; // Initialized inside uiSelect directive link function
ctrl.onSelectionChange = undefined;
ctrl.isEmpty = function() {
return angular.isUndefined(ctrl.selected) || ctrl.selected === null || ctrl.selected === '';
};
var _searchInput = $element.querySelectorAll('input.ui-select-search');
if (_searchInput.length !== 1) {
throw listboxMinErr('searchInput', "Expected 1 input.ui-select-search but got '{0}'.", _searchInput.length);
}
ctrl.findGroupByName = function(name) {
return ctrl.groups && ctrl.groups.filter(function(group) {
return group.name === name;
})[0];
};
ctrl.parseRepeatAttr = function(repeatAttr, groupByExp) {
function updateGroups(items) {
ctrl.groups = [];
angular.forEach(items, function(item) {
var groupFn = $scope.$eval(groupByExp);
var groupName = angular.isFunction(groupFn) ? groupFn(item) : item[groupFn];
var group = ctrl.findGroupByName(groupName);
if(group) {
group.items.push(item);
}
else {
ctrl.groups.push({name: groupName, items: [item]});
}
});
ctrl.items = [];
ctrl.groups.forEach(function(group) {
ctrl.items = ctrl.items.concat(group.items);
});
}
function setPlainItems(items) {
ctrl.items = items;
}
var setItemsFn = groupByExp ? updateGroups : setPlainItems;
ctrl.parserResult = RepeatParser.parse(repeatAttr);
ctrl.isGrouped = !!groupByExp;
ctrl.itemProperty = ctrl.parserResult.itemName;
// See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L259
$scope.$watchCollection(ctrl.parserResult.source, function(items) {
if (items === undefined || items === null) {
ctrl.items = [];
} else {
if (!angular.isArray(items)) {
throw listboxMinErr('items', "Expected an array but got '{0}'.", items);
} else {
setItemsFn(items);
}
}
});
};
ctrl.select = function(item, $event){
console.log(item);
item.isSelected = true;
};
var _refreshDelayPromise;
ctrl.setActiveItem = function(item) {
ctrl.activeIndex = ctrl.items.indexOf(item);
};
ctrl.isSelected = function(itemScope){
return itemScope.isSelected === true;
};
ctrl.isActive = function(itemScope) {
return ctrl.items.indexOf(itemScope[ctrl.itemProperty]) === ctrl.activeIndex;
};
}])
.directive('uiSelect',
['$document', 'uiSelectConfig', 'listboxMinErr', '$compile', '$parse',
function($document, uiSelectConfig, listboxMinErr, $compile, $parse) {
return {
restrict: 'EA',
templateUrl: function(tElement, tAttrs) {
return 'select2/select-multiple.tpl.html';
},
replace: true,
transclude: true,
require: ['uiSelect'],
scope: true,
controller: 'uiSelectCtrl',
controllerAs: '$select',
link: function(scope, element, attrs, ctrls, transcludeFn) {
var $select = ctrls[0];
var searchInput = element.querySelectorAll('input.ui-select-search');
$select.multiple = (angular.isDefined(attrs.multiple)) ? (attrs.multiple === '') ? true : (attrs.multiple.toLowerCase() === 'true') : false;
$select.onSelectionChange = attrs.onSelectionChange;
//$select.onSelectCallback = $parse(attrs.onSelect);
//$select.onRemoveCallback = $parse(attrs.onRemove);
//scope.$watch('searchEnabled', function() {
// var searchEnabled = scope.$eval(attrs.searchEnabled);
// $select.searchEnabled = searchEnabled !== undefined ? searchEnabled : true;
//});
//attrs.$observe('disabled', function() {
// No need to use $eval() (thanks to ng-disabled) since we already get a boolean instead of a string
// $select.disabled = attrs.disabled !== undefined ? attrs.disabled : false;
//});
//attrs.$observe('resetSearchInput', function() {
// $eval() is needed otherwise we get a string instead of a boolean
// var resetSearchInput = scope.$eval(attrs.resetSearchInput);
// $select.resetSearchInput = resetSearchInput !== undefined ? resetSearchInput : true;
//});
// Move transcluded elements to their correct position in main template
transcludeFn(scope, function(clone) {
// See Transclude in AngularJS http://blog.omkarpatil.com/2012/11/transclude-in-angularjs.html
// One day jqLite will be replaced by jQuery and we will be able to write:
// var transcludedElement = clone.filter('.my-class')
// instead of creating a hackish DOM element:
var transcluded = angular.element('<div>').append(clone);
var transcludedChoices = transcluded.querySelectorAll('.ui-select-choices');
transcludedChoices.removeAttr('ui-select-choices'); //To avoid loop in case directive as attr
if (transcludedChoices.length !== 1) {
throw listboxMinErr('transcluded', "Expected 1 .ui-select-choices but got '{0}'.", transcludedChoices.length);
}
element.querySelectorAll('.ui-select-choices').replaceWith(transcludedChoices);
});
}
};
}])
.directive('uiSelectChoices',
['uiSelectConfig', 'RepeatParser', 'listboxMinErr', '$compile',
function(uiSelectConfig, RepeatParser, listboxMinErr, $compile) {
return {
restrict: 'EA',
require: '^uiSelect',
replace: true,
transclude: true,
templateUrl: function(tElement) {
return 'select2/choices.tpl.html';
},
compile: function(tElement, tAttrs) {
if (!tAttrs.repeat) throw listboxMinErr('repeat', "Expected 'repeat' expression.");
return function link(scope, element, attrs, $select, transcludeFn) {
var groupByExp = attrs.groupBy;
$select.parseRepeatAttr(attrs.repeat, groupByExp); //Result ready at $select.parserResult
$select.disableChoiceExpression = attrs.uiDisableChoice;
if(groupByExp) {
var groups = element.querySelectorAll('.ui-select-choices-group');
if (groups.length !== 1) throw listboxMinErr('rows', "Expected 1 .ui-select-choices-group but got '{0}'.", groups.length);
groups.attr('ng-repeat', RepeatParser.getGroupNgRepeatExpression());
}
var choices = element.querySelectorAll('.ui-select-choices-row');
if (choices.length !== 1) {
throw listboxMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
}
choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression($select.parserResult.itemName, '$select.items', $select.parserResult.trackByExp, groupByExp))
.attr('ng-mouseenter', '$select.setActiveItem('+$select.parserResult.itemName +')')
.attr('ng-click', '$select.select(' + $select.parserResult.itemName + ', $event)');
//ng-class=\"{\'select2-highlighted\': $select.isActive(this), \'listbox-selected\': $select.isSelected(this)}\"
var rowsInner = element.querySelectorAll('.ui-select-choices-row-inner');
if (rowsInner.length !== 1) throw listboxMinErr('rows', "Expected 1 .ui-select-choices-row-inner but got '{0}'.", rowsInner.length);
rowsInner.attr('uis-transclude-append', ''); //Adding uisTranscludeAppend directive to row element after choices element has ngRepeat
$compile(element, transcludeFn)(scope); //Passing current transcludeFn to be able to append elements correctly from uisTranscludeAppend
//scope.$watch('$select.search', function(newValue) {
// if(newValue && !$select.open && $select.multiple) $select.activate(false, true);
// $select.activeIndex = 0;
// $select.refresh(attrs.refresh);
//});
//attrs.$observe('refreshDelay', function() {
// $eval() is needed otherwise we get a string instead of a number
// var refreshDelay = scope.$eval(attrs.refreshDelay);
// $select.refreshDelay = refreshDelay !== undefined ? refreshDelay : uiSelectConfig.refreshDelay;
//});
};
}
};
}])
// Recreates old behavior of ng-transclude. Used internally.
.directive('uisTranscludeAppend', function () {
return {
link: function (scope, element, attrs, ctrl, transclude) {
transclude(scope, function (clone) {
element.append(clone);
});
}
};
})
/**
* Highlights text that matches $select.search.
*
* Taken from AngularUI Bootstrap Typeahead
* See https://github.com/angular-ui/bootstrap/blob/0.10.0/src/typeahead/typeahead.js#L340
*/
.filter('highlight', function() {
function escapeRegexp(queryToEscape) {
return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
}
return function(matchItem, query) {
return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<span class="ui-select-highlight">$&</span>') : matchItem;
};
});
}());
angular.module("ui.select").run(["$templateCache", function($templateCache) {
$templateCache.put("select2/choices.tpl.html","<ul class=\"ui-select-choices ui-select-choices-content select2-results\"><li class=\"ui-select-choices-group\" ng-class=\"{\'select2-result-with-children\': $select.isGrouped}\"><div ng-show=\"$select.isGrouped\" class=\"ui-select-choices-group-label select2-result-label\">{{$group.name}}</div><ul ng-class=\"{\'select2-result-sub\': $select.isGrouped, \'select2-result-single\': !$select.isGrouped}\"><li class=\"ui-select-choices-row\" ng-class=\"{\'select2-highlighted\': $select.isActive(this), \'listbox-selected\': $select.isSelected(this)}\"><div class=\"select2-result-label ui-select-choices-row-inner\"></div></li></ul></li></ul>");
$templateCache.put("select2/select-multiple.tpl.html","<div><div><input type=\"text\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" class=\"select2-input ui-select-search\" ng-disabled=\"$select.disabled\" ng-hide=\"$select.disabled\" ng-model=\"$select.search\"style=\"width: 100%;\"></div><div class=\"ui-select-choices\"></div></div>");}]);
ul {
padding: 0;
margin: 0;
list-style-type: none;
}
.listbox-selected{
background: black;
color: white;
}
'use strict';
var app = angular.module('demo', ['ngSanitize', 'ui.select']);
app.controller('DemoCtrl', function($scope, $http, $timeout) {
$scope.person = {};
$scope.people = [
{ name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States' },
{ name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina' },
{ name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina' },
{ name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador' },
{ name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador' },
{ name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States' },
{ name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia' },
{ name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador' },
{ name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia' },
{ name: 'Nicolás', email: 'nicolas@email.com', age: 43, country: 'Colombia' }
];
$scope.multipleDemo = {};
$scope.multipleDemo.selectedPeople = [$scope.people[5], $scope.people[4]];
$scope.showSomething = function(){
alert("hello");
};
});
<!DOCTYPE html>
<html lang="en" ng-app="demo">
<head>
<meta charset="utf-8">
<title>AngularJS ui-select</title>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular-sanitize.js"></script>
<script src="select.js"></script>
<link rel="stylesheet" href="select.css">
<script src="demo.js"></script>
<style>
body {
padding: 15px;
}
</style>
</head>
<body ng-controller="DemoCtrl">
<h3>Person List</h3>
<div style="height: 400px; width: 100px">
<ui-select multiple ng-model="multipleDemo.selectedPeople" on-selection-change="showSomething();">
<ui-select-choices repeat="person in people">
<div ng-bind-html="person.name | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
</div>
</body>
</html>