UNPKG

angular-cached-resource

Version:

An AngularJS module to interact with RESTful resources, even when browser is offline

281 lines 10.2 kB
/** * angular-strap * @version v2.0.1 - 2014-04-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', placement: 'bottom-left', template: 'select/select.tpl.html', trigger: 'focus', container: false, keyboard: true, html: false, delay: 0, multiple: false, sort: true, caretHtml: '&nbsp;<span class="caret"></span>', placeholder: 'Choose among the following...', maxLength: 3, maxLengthHtml: 'selected' }; this.$get = [ '$window', '$document', '$rootScope', '$tooltip', function ($window, $document, $rootScope, $tooltip) { var bodyEl = angular.element($window.document.body); var isTouch = 'createTouch' in $window.document; function SelectFactory(element, controller, config) { var $select = {}; // Common vars var options = angular.extend({}, defaults, config); $select = $tooltip(element, options); var parentScope = config.scope; var scope = $select.$scope; scope.$matches = []; scope.$activeIndex = 0; scope.$isMultiple = options.multiple; 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); }; // Public methods $select.update = function (matches) { scope.$matches = matches; $select.$updateActiveIndex(); }; $select.activate = function (index) { if (options.multiple) { scope.$activeIndex.sort(); $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; $select.activate(index); if (options.multiple) { controller.$setViewValue(scope.$activeIndex.map(function (index) { return scope.$matches[index].value; })); } else { controller.$setViewValue(value); } controller.$render(); if (parentScope) parentScope.$digest(); // Hide if single select if (!options.multiple) { $select.hide(); } // Emit event scope.$emit('$select.select', value, index); }; // 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(); // Select with enter if (evt.keyCode === 13 || evt.keyCode === 9) { return $select.select(scope.$activeIndex); } // Navigate with keyboard if (evt.keyCode === 38 && scope.$activeIndex > 0) scope.$activeIndex--; 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'); } setTimeout(function () { $select.$element.on(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown); if (options.keyboard) { element.on('keydown', $select.$onKeyDown); } }); }; var _hide = $select.hide; $select.hide = function () { $select.$element.off(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown); if (options.keyboard) { element.off('keydown', $select.$onKeyDown); } _hide(); }; 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 }; angular.forEach([ 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'placeholder', 'multiple', 'maxLength', 'maxLengthHtml' ], 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 ngOptions var parsedOptions = $parseOptions(attr.ngOptions); // Initialize select var select = $select(element, controller, options); // Watch ngOptions 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(); }, 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 : attr.placeholder || defaults.placeholder) + defaults.caretHtml); }; // Garbage collection scope.$on('$destroy', function () { select.destroy(); options = null; select = null; }); } }; } ]);