340 lines
12 KiB
JavaScript
340 lines
12 KiB
JavaScript
/**
|
|
* angular-strap
|
|
* @version v2.2.1 - 2015-03-10
|
|
* @link http://mgcrea.github.io/angular-strap
|
|
* @author Olivier Louvignes (olivier@mg-crea.com)
|
|
* @license MIT License, http://www.opensource.org/licenses/MIT
|
|
*/
|
|
'use strict';
|
|
|
|
angular.module('mgcrea.ngStrap.select', ['mgcrea.ngStrap.tooltip', 'mgcrea.ngStrap.helpers.parseOptions'])
|
|
|
|
.provider('$select', function() {
|
|
|
|
var defaults = this.defaults = {
|
|
animation: 'am-fade',
|
|
prefixClass: 'select',
|
|
prefixEvent: '$select',
|
|
placement: 'bottom-left',
|
|
template: 'select/select.tpl.html',
|
|
trigger: 'focus',
|
|
container: false,
|
|
keyboard: true,
|
|
html: false,
|
|
delay: 0,
|
|
multiple: false,
|
|
allNoneButtons: false,
|
|
sort: true,
|
|
caretHtml: ' <span class="caret"></span>',
|
|
placeholder: 'Choose among the following...',
|
|
allText: 'All',
|
|
noneText: 'None',
|
|
maxLength: 3,
|
|
maxLengthHtml: 'selected',
|
|
iconCheckmark: 'glyphicon glyphicon-ok'
|
|
};
|
|
|
|
this.$get = ["$window", "$document", "$rootScope", "$tooltip", "$timeout", function($window, $document, $rootScope, $tooltip, $timeout) {
|
|
|
|
var bodyEl = angular.element($window.document.body);
|
|
var isNative = /(ip(a|o)d|iphone|android)/ig.test($window.navigator.userAgent);
|
|
var isTouch = ('createTouch' in $window.document) && isNative;
|
|
|
|
function SelectFactory(element, controller, config) {
|
|
|
|
var $select = {};
|
|
|
|
// Common vars
|
|
var options = angular.extend({}, defaults, config);
|
|
|
|
// parse sort option value to support attribute as string
|
|
// when binded to interpolated value
|
|
options.sort = options.sort.toString().match(/true|1/i);
|
|
|
|
$select = $tooltip(element, options);
|
|
var scope = $select.$scope;
|
|
|
|
scope.$matches = [];
|
|
scope.$activeIndex = -1;
|
|
scope.$isMultiple = options.multiple;
|
|
scope.$showAllNoneButtons = options.allNoneButtons && options.multiple;
|
|
scope.$iconCheckmark = options.iconCheckmark;
|
|
scope.$allText = options.allText;
|
|
scope.$noneText = options.noneText;
|
|
|
|
scope.$activate = function(index) {
|
|
scope.$$postDigest(function() {
|
|
$select.activate(index);
|
|
});
|
|
};
|
|
|
|
scope.$select = function(index, evt) {
|
|
scope.$$postDigest(function() {
|
|
$select.select(index);
|
|
});
|
|
};
|
|
|
|
scope.$isVisible = function() {
|
|
return $select.$isVisible();
|
|
};
|
|
|
|
scope.$isActive = function(index) {
|
|
return $select.$isActive(index);
|
|
};
|
|
|
|
scope.$selectAll = function () {
|
|
for (var i = 0; i < scope.$matches.length; i++) {
|
|
if (!scope.$isActive(i)) {
|
|
scope.$select(i);
|
|
}
|
|
}
|
|
};
|
|
|
|
scope.$selectNone = function () {
|
|
for (var i = 0; i < scope.$matches.length; i++) {
|
|
if (scope.$isActive(i)) {
|
|
scope.$select(i);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Public methods
|
|
|
|
$select.update = function(matches) {
|
|
scope.$matches = matches;
|
|
$select.$updateActiveIndex();
|
|
};
|
|
|
|
$select.activate = function(index) {
|
|
if(options.multiple) {
|
|
$select.$isActive(index) ? scope.$activeIndex.splice(scope.$activeIndex.indexOf(index), 1) : scope.$activeIndex.push(index);
|
|
if(options.sort) scope.$activeIndex.sort();
|
|
} else {
|
|
scope.$activeIndex = index;
|
|
}
|
|
return scope.$activeIndex;
|
|
};
|
|
|
|
$select.select = function(index) {
|
|
var value = scope.$matches[index].value;
|
|
scope.$apply(function() {
|
|
$select.activate(index);
|
|
if(options.multiple) {
|
|
controller.$setViewValue(scope.$activeIndex.map(function(index) {
|
|
return scope.$matches[index].value;
|
|
}));
|
|
} else {
|
|
controller.$setViewValue(value);
|
|
// Hide if single select
|
|
$select.hide();
|
|
}
|
|
});
|
|
// Emit event
|
|
scope.$emit(options.prefixEvent + '.select', value, index, $select);
|
|
};
|
|
|
|
// Protected methods
|
|
|
|
$select.$updateActiveIndex = function() {
|
|
if(controller.$modelValue && scope.$matches.length) {
|
|
if(options.multiple && angular.isArray(controller.$modelValue)) {
|
|
scope.$activeIndex = controller.$modelValue.map(function(value) {
|
|
return $select.$getIndex(value);
|
|
});
|
|
} else {
|
|
scope.$activeIndex = $select.$getIndex(controller.$modelValue);
|
|
}
|
|
} else if(scope.$activeIndex >= scope.$matches.length) {
|
|
scope.$activeIndex = options.multiple ? [] : 0;
|
|
}
|
|
};
|
|
|
|
$select.$isVisible = function() {
|
|
if(!options.minLength || !controller) {
|
|
return scope.$matches.length;
|
|
}
|
|
// minLength support
|
|
return scope.$matches.length && controller.$viewValue.length >= options.minLength;
|
|
};
|
|
|
|
$select.$isActive = function(index) {
|
|
if(options.multiple) {
|
|
return scope.$activeIndex.indexOf(index) !== -1;
|
|
} else {
|
|
return scope.$activeIndex === index;
|
|
}
|
|
};
|
|
|
|
$select.$getIndex = function(value) {
|
|
var l = scope.$matches.length, i = l;
|
|
if(!l) return;
|
|
for(i = l; i--;) {
|
|
if(scope.$matches[i].value === value) break;
|
|
}
|
|
if(i < 0) return;
|
|
return i;
|
|
};
|
|
|
|
$select.$onMouseDown = function(evt) {
|
|
// Prevent blur on mousedown on .dropdown-menu
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
// Emulate click for mobile devices
|
|
if(isTouch) {
|
|
var targetEl = angular.element(evt.target);
|
|
targetEl.triggerHandler('click');
|
|
}
|
|
};
|
|
|
|
$select.$onKeyDown = function(evt) {
|
|
if (!/(9|13|38|40)/.test(evt.keyCode)) return;
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
|
|
// release focus on tab
|
|
if (options.multiple && evt.keyCode === 9) {
|
|
return $select.hide();
|
|
}
|
|
|
|
// Select with enter
|
|
if(!options.multiple && (evt.keyCode === 13 || evt.keyCode === 9)) {
|
|
return $select.select(scope.$activeIndex);
|
|
}
|
|
|
|
if (!options.multiple) {
|
|
// Navigate with keyboard
|
|
if(evt.keyCode === 38 && scope.$activeIndex > 0) scope.$activeIndex--;
|
|
else if(evt.keyCode === 38 && scope.$activeIndex < 0) scope.$activeIndex = scope.$matches.length - 1;
|
|
else if(evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) scope.$activeIndex++;
|
|
else if(angular.isUndefined(scope.$activeIndex)) scope.$activeIndex = 0;
|
|
scope.$digest();
|
|
}
|
|
};
|
|
|
|
// Overrides
|
|
|
|
var _show = $select.show;
|
|
$select.show = function() {
|
|
_show();
|
|
if(options.multiple) {
|
|
$select.$element.addClass('select-multiple');
|
|
}
|
|
// use timeout to hookup the events to prevent
|
|
// event bubbling from being processed imediately.
|
|
$timeout(function() {
|
|
$select.$element.on(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
|
|
if(options.keyboard) {
|
|
element.on('keydown', $select.$onKeyDown);
|
|
}
|
|
}, 0, false);
|
|
};
|
|
|
|
var _hide = $select.hide;
|
|
$select.hide = function() {
|
|
if(!options.multiple && !controller.$modelValue) {
|
|
scope.$activeIndex = -1;
|
|
}
|
|
$select.$element.off(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
|
|
if(options.keyboard) {
|
|
element.off('keydown', $select.$onKeyDown);
|
|
}
|
|
_hide(true);
|
|
};
|
|
|
|
return $select;
|
|
|
|
}
|
|
|
|
SelectFactory.defaults = defaults;
|
|
return SelectFactory;
|
|
|
|
}];
|
|
|
|
})
|
|
|
|
.directive('bsSelect', ["$window", "$parse", "$q", "$select", "$parseOptions", function($window, $parse, $q, $select, $parseOptions) {
|
|
|
|
var defaults = $select.defaults;
|
|
|
|
return {
|
|
restrict: 'EAC',
|
|
require: 'ngModel',
|
|
link: function postLink(scope, element, attr, controller) {
|
|
|
|
// Directive options
|
|
var options = {scope: scope, placeholder: defaults.placeholder};
|
|
angular.forEach(['placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'placeholder', 'multiple', 'allNoneButtons', 'maxLength', 'maxLengthHtml', 'allText', 'noneText', 'iconCheckmark', 'autoClose', 'id', 'sort', 'caretHtml'], function(key) {
|
|
if(angular.isDefined(attr[key])) options[key] = attr[key];
|
|
});
|
|
|
|
// Add support for select markup
|
|
if(element[0].nodeName.toLowerCase() === 'select') {
|
|
var inputEl = element;
|
|
inputEl.css('display', 'none');
|
|
element = angular.element('<button type="button" class="btn btn-default"></button>');
|
|
inputEl.after(element);
|
|
}
|
|
|
|
// Build proper bsOptions
|
|
var parsedOptions = $parseOptions(attr.bsOptions);
|
|
|
|
// Initialize select
|
|
var select = $select(element, controller, options);
|
|
|
|
// Watch bsOptions values before filtering for changes
|
|
var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').trim();
|
|
scope.$watch(watchedOptions, function(newValue, oldValue) {
|
|
// console.warn('scope.$watch(%s)', watchedOptions, newValue, oldValue);
|
|
parsedOptions.valuesFn(scope, controller)
|
|
.then(function(values) {
|
|
select.update(values);
|
|
controller.$render();
|
|
});
|
|
}, true);
|
|
|
|
// Watch model for changes
|
|
scope.$watch(attr.ngModel, function(newValue, oldValue) {
|
|
// console.warn('scope.$watch(%s)', attr.ngModel, newValue, oldValue);
|
|
select.$updateActiveIndex();
|
|
controller.$render();
|
|
}, true);
|
|
|
|
// Model rendering in view
|
|
controller.$render = function () {
|
|
// console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
|
|
var selected, index;
|
|
if(options.multiple && angular.isArray(controller.$modelValue)) {
|
|
selected = controller.$modelValue.map(function(value) {
|
|
index = select.$getIndex(value);
|
|
return angular.isDefined(index) ? select.$scope.$matches[index].label : false;
|
|
}).filter(angular.isDefined);
|
|
if(selected.length > (options.maxLength || defaults.maxLength)) {
|
|
selected = selected.length + ' ' + (options.maxLengthHtml || defaults.maxLengthHtml);
|
|
} else {
|
|
selected = selected.join(', ');
|
|
}
|
|
} else {
|
|
index = select.$getIndex(controller.$modelValue);
|
|
selected = angular.isDefined(index) ? select.$scope.$matches[index].label : false;
|
|
}
|
|
element.html((selected ? selected : options.placeholder) + (options.caretHtml ? options.caretHtml : defaults.caretHtml));
|
|
};
|
|
|
|
if(options.multiple){
|
|
controller.$isEmpty = function(value){
|
|
return !value || value.length === 0;
|
|
};
|
|
}
|
|
|
|
// Garbage collection
|
|
scope.$on('$destroy', function() {
|
|
if (select) select.destroy();
|
|
options = null;
|
|
select = null;
|
|
});
|
|
|
|
}
|
|
};
|
|
|
|
}]);
|