diff --git a/README.md b/README.md index 9fc214f44..897054599 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ AngularJS-native version of [Select2](http://ivaynberg.github.io/select2/) and [ - [Demo](http://plnkr.co/edit/a3KlK8dKH3wwiiksDSn2?p=preview) - [Demo Multiselect](http://plnkr.co/edit/juqoNOt1z1Gb349XabQ2?p=preview) -- [Bootstrap theme](http://plnkr.co/edit/QCwSM75ilH2Vh6D9aMA4?p=preview) Check [examples](https://github.com/angular-ui/ui-select/blob/master/examples). diff --git a/bower.json b/bower.json index 88a3c0d04..f00f1010f 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "ui-select", - "version": "0.7.0", + "version": "0.8.3", "homepage": "https://github.com/angular-ui/ui-select", "authors": [ "AngularUI" @@ -13,14 +13,17 @@ "node_modules", "bower_components", "src", - "test" + "test", + "gulpfile.js", + "karma.conf.js", + "examples" ], "dependencies": { - "angular": "~1.2" + "angular": ">=1.2.18" }, "devDependencies": { "jquery": "~1.11", - "angular-sanitize": "~1.2", - "angular-mocks": "~1.2" + "angular-sanitize": ">=1.2.18", + "angular-mocks": ">=1.2.18" } } diff --git a/dist/select.css b/dist/select.css deleted file mode 100644 index e98302d0c..000000000 --- a/dist/select.css +++ /dev/null @@ -1,163 +0,0 @@ -/*! - * ui-select - * http://github.com/angular-ui/ui-select - * Version: 0.7.0 - 2014-10-06T12:29:59.832Z - * License: MIT - */ - - -/* Style when highlighting a search. */ -.ui-select-highlight { - font-weight: bold; -} - -.ui-select-offscreen { - clip: rect(0 0 0 0) !important; - width: 1px !important; - height: 1px !important; - border: 0 !important; - margin: 0 !important; - padding: 0 !important; - overflow: hidden !important; - position: absolute !important; - outline: 0 !important; - left: 0px !important; - top: 0px !important; -} - -/* Select2 theme */ - -/* Mark invalid Select2 */ -.ng-dirty.ng-invalid > a.select2-choice { - border-color: #D44950; -} - -.select2-result-single { - padding-left: 0; -} - -/* Selectize theme */ - -/* Helper class to show styles when focus */ -.selectize-input.selectize-focus{ - border-color: #007FBB !important; -} - -/* Fix input width for Selectize theme */ -.selectize-control > .selectize-input > input { - width: 100%; -} - -/* Fix dropdown width for Selectize theme */ -.selectize-control > .selectize-dropdown { - width: 100%; -} - -/* Mark invalid Selectize */ -.ng-dirty.ng-invalid > div.selectize-input { - border-color: #D44950; -} - - -/* Bootstrap theme */ - -/* Helper class to show styles when focus */ -.btn-default-focus { - color: #333; - background-color: #EBEBEB; - border-color: #ADADAD; - text-decoration: none; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); -} - - -/* Fix Bootstrap dropdown position when inside a input-group */ -.input-group > .ui-select-bootstrap.dropdown { - /* Instead of relative */ - position: static; -} - -.input-group > .ui-select-bootstrap > input.ui-select-search.form-control { - border-radius: 4px; /* FIXME hardcoded value :-/ */ - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ui-select-bootstrap > .ui-select-match { - /* Instead of center because of .btn */ - text-align: left; -} - -.ui-select-bootstrap > .ui-select-match > .caret { - position: absolute; - top: 45%; - right: 15px; -} - -/* See Scrollable Menu with Bootstrap 3 http://stackoverflow.com/questions/19227496 */ -.ui-select-bootstrap > .ui-select-choices { - width: 100%; - height: auto; - max-height: 200px; - overflow-x: hidden; -} - -.ui-select-multiple.ui-select-bootstrap { - height: auto; - padding: .3em; -} - -.ui-select-multiple.ui-select-bootstrap input.ui-select-search { - background-color: transparent !important; /* To prevent double background when disabled */ - border: none; - outline: none; - height: 1.666666em; -} - -.ui-select-multiple.ui-select-bootstrap .ui-select-match .close { - font-size: 1.6em; - line-height: 0.75; -} - -.ui-select-bootstrap .ui-select-choices-row>a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: 400; - line-height: 1.42857143; - color: #333; - white-space: nowrap; -} - -.ui-select-bootstrap .ui-select-choices-row>a:hover, .ui-select-bootstrap .ui-select-choices-row>a:focus { - text-decoration: none; - color: #262626; - background-color: #f5f5f5; -} - -.ui-select-bootstrap .ui-select-choices-row.active>a { - color: #fff; - text-decoration: none; - outline: 0; - background-color: #428bca; -} - -.ui-select-bootstrap .ui-select-choices-row.disabled>a, -.ui-select-bootstrap .ui-select-choices-row.active.disabled>a { - color: #777; - cursor: not-allowed; - background-color: #fff; -} - -/* fix hide/show angular animation */ -.ui-select-match.ng-hide-add, -.ui-select-search.ng-hide-add { - display: none !important; -} - -/* Mark invalid Bootstrap */ -.ui-select-bootstrap.ng-dirty.ng-invalid > button.btn.ui-select-match { - border-color: #D44950; -} diff --git a/dist/select.css.min.css b/dist/select.css.min.css deleted file mode 100644 index 181465e97..000000000 --- a/dist/select.css.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * ui-select - * http://github.com/angular-ui/ui-select - * Version: 0.7.0 - 2014-10-06T12:29:59.832Z - * License: MIT - */.ui-select-highlight{font-weight:700}.ui-select-offscreen{clip:rect(0 0 0 0)!important;width:1px!important;height:1px!important;border:0!important;margin:0!important;padding:0!important;overflow:hidden!important;position:absolute!important;outline:0!important;left:0!important;top:0!important}.ng-dirty.ng-invalid>a.select2-choice{border-color:#D44950}.select2-result-single{padding-left:0}.selectize-input.selectize-focus{border-color:#007FBB!important}.selectize-control>.selectize-dropdown,.selectize-control>.selectize-input>input{width:100%}.ng-dirty.ng-invalid>div.selectize-input{border-color:#D44950}.btn-default-focus{color:#333;background-color:#EBEBEB;border-color:#ADADAD;text-decoration:none;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.input-group>.ui-select-bootstrap.dropdown{position:static}.input-group>.ui-select-bootstrap>input.ui-select-search.form-control{border-radius:4px 0 0 4px}.ui-select-bootstrap>.ui-select-match{text-align:left}.ui-select-bootstrap>.ui-select-match>.caret{position:absolute;top:45%;right:15px}.ui-select-bootstrap>.ui-select-choices{width:100%;height:auto;max-height:200px;overflow-x:hidden}.ui-select-multiple.ui-select-bootstrap{height:auto;padding:.3em}.ui-select-multiple.ui-select-bootstrap input.ui-select-search{background-color:transparent!important;border:none;outline:0;height:1.666666em}.ui-select-multiple.ui-select-bootstrap .ui-select-match .close{font-size:1.6em;line-height:.75}.ui-select-bootstrap .ui-select-choices-row>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.ui-select-bootstrap .ui-select-choices-row>a:focus,.ui-select-bootstrap .ui-select-choices-row>a:hover{text-decoration:none;color:#262626;background-color:#f5f5f5}.ui-select-bootstrap .ui-select-choices-row.active>a{color:#fff;text-decoration:none;outline:0;background-color:#428bca}.ui-select-bootstrap .ui-select-choices-row.active.disabled>a,.ui-select-bootstrap .ui-select-choices-row.disabled>a{color:#777;cursor:not-allowed;background-color:#fff}.ui-select-match.ng-hide-add,.ui-select-search.ng-hide-add{display:none!important}.ui-select-bootstrap.ng-dirty.ng-invalid>button.btn.ui-select-match{border-color:#D44950} \ No newline at end of file diff --git a/dist/select.js b/dist/select.js deleted file mode 100755 index 8b9f1c3ab..000000000 --- a/dist/select.js +++ /dev/null @@ -1,966 +0,0 @@ -/*! - * ui-select - * http://github.com/angular-ui/ui-select - * Version: 0.7.0 - 2014-10-06T12:29:59.829Z - * License: MIT - */ - - -(function () { - "use strict"; - - var KEY = { - TAB: 9, - ENTER: 13, - ESC: 27, - SPACE: 32, - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - SHIFT: 16, - CTRL: 17, - ALT: 18, - PAGE_UP: 33, - PAGE_DOWN: 34, - HOME: 36, - END: 35, - BACKSPACE: 8, - DELETE: 46, - isControl: function (e) { - var k = e.which; - switch (k) { - case KEY.SHIFT: - case KEY.CTRL: - case KEY.ALT: - return true; - } - - if (e.metaKey) return true; - - return false; - }, - isFunctionKey: function (k) { - k = k.which ? k.which : k; - return k >= 112 && k <= 123; - }, - isVerticalMovement: function (k){ - return ~[KEY.UP, KEY.DOWN].indexOf(k); - }, - isHorizontalMovement: function (k){ - return ~[KEY.LEFT,KEY.RIGHT,KEY.BACKSPACE,KEY.DELETE].indexOf(k); - } - }; - - /** - * 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', { - theme: 'bootstrap', - searchEnabled: true, - placeholder: '', // Empty by default, like HTML tag "); - - if(attrs.tabindex){ - //tabindex might be an expression, wait until it contains the actual value before we set the focusser tabindex - attrs.$observe('tabindex', function(value) { - //If we are using multiple, add tabindex to the search input - if($select.multiple){ - searchInput.attr("tabindex", value); - } else { - focusser.attr("tabindex", value); - } - //Remove the tabindex on the parent so that it is not focusable - element.removeAttr("tabindex"); - }); - } - - $compile(focusser)(scope); - $select.focusser = focusser; - - if (!$select.multiple){ - - element.append(focusser); - focusser.bind("focus", function(){ - scope.$evalAsync(function(){ - $select.focus = true; - }); - }); - focusser.bind("blur", function(){ - scope.$evalAsync(function(){ - $select.focus = false; - }); - }); - focusser.bind("keydown", function(e){ - - if (e.which === KEY.BACKSPACE) { - e.preventDefault(); - e.stopPropagation(); - $select.select(undefined); - scope.$digest(); - return; - } - - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { - return; - } - - if (e.which == KEY.DOWN || e.which == KEY.UP || e.which == KEY.ENTER || e.which == KEY.SPACE){ - e.preventDefault(); - e.stopPropagation(); - $select.activate(); - } - - scope.$digest(); - }); - - focusser.bind("keyup input", function(e){ - - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC || e.which == KEY.ENTER || e.which === KEY.BACKSPACE) { - return; - } - - $select.activate(focusser.val()); //User pressed some regular key, so we pass it to the search input - focusser.val(''); - scope.$digest(); - - }); - - } - - - 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; - }); - - attrs.$observe('tagging', function() { - if(attrs.tagging !== undefined) - { - // $eval() is needed otherwise we get a string instead of a function or a boolean - var taggingEval = scope.$eval(attrs.tagging); - $select.tagging = {isActivated: true, fct: taggingEval !== true ? taggingEval : undefined}; - } - else - { - $select.tagging = {isActivated: false, fct: undefined}; - } - }); - - if ($select.multiple){ - scope.$watchCollection('$select.selected', function(newValue) { - //On v1.2.19 the 2nd and 3rd parameteres are ignored - //On v1.3.0-beta+ 3rd parameter (revalidate) is true, to force $parsers to recreate model - ngModel.$setViewValue(newValue, null, true); - }); - focusser.prop('disabled', true); //Focusser isn't needed if multiple - }else{ - scope.$watch('$select.selected', function(newValue) { - if (ngModel.$viewValue !== newValue) { - ngModel.$setViewValue(newValue); - } - }); - } - - ngModel.$render = function() { - if($select.multiple){ - // Make sure that model value is array - if(!angular.isArray(ngModel.$viewValue)){ - // Have tolerance for null or undefined values - if(angular.isUndefined(ngModel.$viewValue) || ngModel.$viewValue === null){ - $select.selected = []; - } else { - throw uiSelectMinErr('multiarr', "Expected model value to be array but got '{0}'", ngModel.$viewValue); - } - } - } - $select.selected = ngModel.$viewValue; - }; - - function onDocumentClick(e) { - var contains = false; - - if (window.jQuery) { - // Firefox 3.6 does not support element.contains() - // See Node.contains https://developer.mozilla.org/en-US/docs/Web/API/Node.contains - contains = window.jQuery.contains(element[0], e.target); - } else { - contains = element[0].contains(e.target); - } - - if (!contains) { - $select.close(); - scope.$digest(); - } - } - - // See Click everywhere but here event http://stackoverflow.com/questions/12931369 - $document.on('click', onDocumentClick); - - scope.$on('$destroy', function() { - $document.off('click', onDocumentClick); - }); - - // 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('
').append(clone); - - var transcludedMatch = transcluded.querySelectorAll('.ui-select-match'); - transcludedMatch.removeAttr('ui-select-match'); //To avoid loop in case directive as attr - if (transcludedMatch.length !== 1) { - throw uiSelectMinErr('transcluded', "Expected 1 .ui-select-match but got '{0}'.", transcludedMatch.length); - } - element.querySelectorAll('.ui-select-match').replaceWith(transcludedMatch); - - 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 uiSelectMinErr('transcluded', "Expected 1 .ui-select-choices but got '{0}'.", transcludedChoices.length); - } - element.querySelectorAll('.ui-select-choices').replaceWith(transcludedChoices); - }); - } - }; - }]) - - .directive('uiSelectChoices', - ['uiSelectConfig', 'RepeatParser', 'uiSelectMinErr', '$compile', - function(uiSelectConfig, RepeatParser, uiSelectMinErr, $compile) { - - return { - restrict: 'EA', - require: '^uiSelect', - replace: true, - transclude: true, - templateUrl: function(tElement) { - // Gets theme attribute from parent (ui-select) - var theme = tElement.parent().attr('theme') || uiSelectConfig.theme; - return theme + '/choices.tpl.html'; - }, - - compile: function(tElement, tAttrs) { - - if (!tAttrs.repeat) throw uiSelectMinErr('repeat', "Expected 'repeat' expression."); - - return function link(scope, element, attrs, $select, transcludeFn) { - - // var repeat = RepeatParser.parse(attrs.repeat); - 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 uiSelectMinErr('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 uiSelectMinErr('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 + ')'); - - var rowsInner = element.querySelectorAll('.ui-select-choices-row-inner'); - if (rowsInner.length !== 1) throw uiSelectMinErr('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 = $select.tagging.isActivated ? -1 : 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); - }); - } - }; - }) - .directive('uiSelectMatch', ['uiSelectConfig', function(uiSelectConfig) { - return { - restrict: 'EA', - require: '^uiSelect', - replace: true, - transclude: true, - templateUrl: function(tElement) { - // Gets theme attribute from parent (ui-select) - var theme = tElement.parent().attr('theme') || uiSelectConfig.theme; - var multi = tElement.parent().attr('multiple'); - return theme + (multi ? '/match-multiple.tpl.html' : '/match.tpl.html'); - }, - link: function(scope, element, attrs, $select) { - attrs.$observe('placeholder', function(placeholder) { - $select.placeholder = placeholder !== undefined ? placeholder : uiSelectConfig.placeholder; - }); - - if($select.multiple){ - $select.sizeSearchInput(); - } - - } - }; - }]) - - /** - * 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'), '$&') : matchItem; - }; - }); -}()); - -angular.module("ui.select").run(["$templateCache", function($templateCache) {$templateCache.put("bootstrap/choices.tpl.html",""); -$templateCache.put("bootstrap/match-multiple.tpl.html",""); -$templateCache.put("bootstrap/match.tpl.html",""); -$templateCache.put("bootstrap/select-multiple.tpl.html","
"); -$templateCache.put("bootstrap/select.tpl.html","
"); -$templateCache.put("select2/choices.tpl.html",""); -$templateCache.put("select2/match-multiple.tpl.html","
  • "); -$templateCache.put("select2/match.tpl.html","{{$select.placeholder}} "); -$templateCache.put("select2/select-multiple.tpl.html","
    "); -$templateCache.put("select2/select.tpl.html","
    "); -$templateCache.put("selectize/choices.tpl.html","
    {{$group.name}}
    "); -$templateCache.put("selectize/match.tpl.html","
    "); -$templateCache.put("selectize/select.tpl.html","
    ");}]); \ No newline at end of file diff --git a/dist/select.js.min.js b/dist/select.js.min.js deleted file mode 100755 index e99b45e53..000000000 --- a/dist/select.js.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * ui-select - * http://github.com/angular-ui/ui-select - * Version: 0.7.0 - 2014-10-06T12:29:59.829Z - * License: MIT - */ -!function(){"use strict";var e={TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,SHIFT:16,CTRL:17,ALT:18,PAGE_UP:33,PAGE_DOWN:34,HOME:36,END:35,BACKSPACE:8,DELETE:46,isControl:function(t){var c=t.which;switch(c){case e.SHIFT:case e.CTRL:case e.ALT:return!0}return t.metaKey?!0:!1},isFunctionKey:function(e){return e=e.which?e.which:e,e>=112&&123>=e},isVerticalMovement:function(t){return~[e.UP,e.DOWN].indexOf(t)},isHorizontalMovement:function(t){return~[e.LEFT,e.RIGHT,e.BACKSPACE,e.DELETE].indexOf(t)}};void 0===angular.element.prototype.querySelectorAll&&(angular.element.prototype.querySelectorAll=function(e){return angular.element(this[0].querySelectorAll(e))}),angular.module("ui.select",[]).constant("uiSelectConfig",{theme:"bootstrap",searchEnabled:!0,placeholder:"",refreshDelay:1e3}).service("uiSelectMinErr",function(){var e=angular.$$minErr("ui.select");return function(){var t=e.apply(this,arguments),c=t.message.replace(new RegExp("\nhttp://errors.angularjs.org/.*"),"");return new Error(c)}}).service("RepeatParser",["uiSelectMinErr","$parse",function(e,t){var c=this;c.parse=function(c){var s=c.match(/^\s*(?:([\s\S]+?)\s+as\s+)?([\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!s)throw e("iexp","Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",c);return{itemName:s[2],source:t(s[3]),trackByExp:s[4],modelMapper:t(s[1]||s[2])}},c.getGroupNgRepeatExpression=function(){return"$group in $select.groups"},c.getNgRepeatExpression=function(e,t,c,s){var l=e+" in "+(s?"$group.items":t);return c&&(l+=" track by "+c),l}}]).controller("uiSelectCtrl",["$scope","$element","$timeout","RepeatParser","uiSelectMinErr",function(t,c,s,l,i){function n(){d.resetSearchInput&&(d.search=p,d.selected&&d.items.length&&!d.multiple&&(d.activeIndex=d.items.indexOf(d.selected)))}function a(t){var c=!0;switch(t){case e.DOWN:!d.open&&d.multiple?d.activate(!1,!0):d.activeIndex0||0===d.search.length&&d.tagging.isActivated)&&d.activeIndex--;break;case e.TAB:d.multiple||d.select(d.items[d.activeIndex]);break;case e.ENTER:d.open?d.select(d.items[d.activeIndex]):d.activate(!1,!0);break;case e.ESC:d.close();break;default:c=!1}return c}function r(t){function c(){switch(t){case e.LEFT:return~d.activeMatchIndex?u:n;case e.RIGHT:return~d.activeMatchIndex&&a!==n?r:(d.activate(),!1);case e.BACKSPACE:return~d.activeMatchIndex?(d.removeChoice(a),u):n;case e.DELETE:return~d.activeMatchIndex?(d.removeChoice(d.activeMatchIndex),a):!1}}var s=o(h[0]),l=d.selected.length,i=0,n=l-1,a=d.activeMatchIndex,r=d.activeMatchIndex+1,u=d.activeMatchIndex-1,p=a;return s>0||d.search.length&&t==e.RIGHT?!1:(d.close(),p=c(),d.activeMatchIndex=d.selected.length&&p!==!1?Math.min(n,Math.max(i,p)):-1,!0)}function o(e){return angular.isNumber(e.selectionStart)?e.selectionStart:e.value.length}function u(){var e=c.querySelectorAll(".ui-select-choices-content"),t=e.querySelectorAll(".ui-select-choices-row");if(t.length<1)throw i("choices","Expected multiple .ui-select-choices-row but got '{0}'.",t.length);var s=t[d.activeIndex],l=s.offsetTop+s.clientHeight-e[0].scrollTop,n=e[0].offsetHeight;l>n?e[0].scrollTop+=l-n:l=d.items.length?0:d.activeIndex,s(function(){d.search=e||d.search,h[0].focus()}))},d.findGroupByName=function(e){return d.groups&&d.groups.filter(function(t){return t.name===e})[0]},d.parseRepeatAttr=function(e,c){function s(e){d.groups=[],angular.forEach(e,function(e){var s=t.$eval(c),l=angular.isFunction(s)?s(e):e[s],i=d.findGroupByName(l);i?i.items.push(e):d.groups.push({name:l,items:[e]})}),d.items=[],d.groups.forEach(function(e){d.items=d.items.concat(e.items)})}function n(e){d.items=e}var a=c?s:n;d.parserResult=l.parse(e),d.isGrouped=!!c,d.itemProperty=d.parserResult.itemName,t.$watchCollection(d.parserResult.source,function(e){if(void 0===e||null===e)d.items=[];else{if(!angular.isArray(e))throw i("items","Expected an array but got '{0}'.",e);if(d.multiple){var t=e.filter(function(e){return d.selected.indexOf(e)<0});a(t)}else a(e);d.ngModel.$modelValue=null}}),d.multiple&&t.$watchCollection("$select.selected",function(e){if(e){var c=d.parserResult.source(t),s=c.filter(function(t){return e.indexOf(t)<0});a(s)}})};var v;d.refresh=function(e){void 0!==e&&(v&&s.cancel(v),v=s(function(){t.$eval(e)},d.refreshDelay))},d.setActiveItem=function(e){d.activeIndex=d.items.indexOf(e)},d.isActive=function(e){return d.items.indexOf(e[d.itemProperty])===d.activeIndex},d.isDisabled=function(e){var t,c=d.items.indexOf(e[d.itemProperty]),s=!1;return c>=0&&!angular.isUndefined(d.disableChoiceExpression)&&(t=d.items[c],s=!!e.$eval(d.disableChoiceExpression),t._uiSelectChoiceDisabled=s),s},d.select=function(e){if(!e||!e._uiSelectChoiceDisabled){d.tagging.isActivated&&!e&&d.search.length>0&&(e=void 0!==d.tagging.fct?d.tagging.fct(d.search):d.search);var c={};c[d.parserResult.itemName]=e,d.onSelectCallback(t,{$item:e,$model:d.parserResult.modelMapper(t,c)}),d.multiple?(d.selected.push(e),d.sizeSearchInput()):d.selected=e,d.close()}},d.close=function(){d.open&&(n(),d.open=!1,s(function(){d.focusser[0].focus()},0,!1))},d.toggle=function(e){d.open?d.close():d.activate(),e.preventDefault(),e.stopPropagation()},d.removeChoice=function(e){d.selected.splice(e,1),d.activeMatchIndex=-1,d.sizeSearchInput()},d.getPlaceholder=function(){return d.multiple&&d.selected.length?void 0:d.placeholder},d.sizeSearchInput=function(){var e=h[0],t=h.parent().parent()[0];h.css("width","10px"),s(function(){var c=t.clientWidth-e.offsetLeft-10;50>c&&(c=t.clientWidth),h.css("width",c+"px")},0,!1)},h.on("keydown",function(c){var s=c.which;t.$apply(function(){var t=!1;d.multiple&&e.isHorizontalMovement(s)&&(t=r(s)),!t&&(d.items.length>0||d.tagging.isActivated)&&(t=a(s)),t&&s!=e.TAB&&(c.preventDefault(),c.stopPropagation())}),e.isVerticalMovement(s)&&d.items.length>0&&u()}),h.on("blur",function(){s(function(){d.activeMatchIndex=-1,d.activeIndex=0})}),t.$on("$destroy",function(){h.off("keydown blur")})}]).directive("uiSelect",["$document","uiSelectConfig","uiSelectMinErr","$compile","$parse",function(t,c,s,l,i){return{restrict:"EA",templateUrl:function(e,t){var s=t.theme||c.theme;return s+(angular.isDefined(t.multiple)?"/select-multiple.tpl.html":"/select.tpl.html")},replace:!0,transclude:!0,require:["uiSelect","ngModel"],scope:!0,controller:"uiSelectCtrl",controllerAs:"$select",link:function(c,n,a,r,o){function u(e){var t=!1;t=window.jQuery?window.jQuery.contains(n[0],e.target):n[0].contains(e.target),t||(d.close(),c.$digest())}var d=r[0],p=r[1],h=n.querySelectorAll("input.ui-select-search");d.multiple=angular.isDefined(a.multiple),d.onSelectCallback=i(a.onSelect),p.$parsers.unshift(function(e){var t,s={};if(d.multiple){for(var l=[],i=e.length-1;i>=0;i--)s={},s[d.parserResult.itemName]=e[i],t=d.parserResult.modelMapper(c,s),l.unshift(t);return l}return s={},s[d.parserResult.itemName]=e,t=d.parserResult.modelMapper(c,s)}),p.$formatters.unshift(function(e){var t,s=d.parserResult.source(c,{$select:{search:""}}),l={};if(s){if(d.multiple){var i=[],n=function(e,s){if(e&&e.length){for(var n=e.length-1;n>=0;n--)if(l[d.parserResult.itemName]=e[n],t=d.parserResult.modelMapper(c,l),t==s)return i.unshift(e[n]),!0;return!1}};if(!e)return i;for(var a=e.length-1;a>=0;a--)n(d.selected,e[a])||n(s,e[a]);return i}var r=function(s){return l[d.parserResult.itemName]=s,t=d.parserResult.modelMapper(c,l),t==e};if(d.selected&&r(d.selected))return d.selected;for(var o=s.length-1;o>=0;o--)if(r(s[o]))return s[o]}return e}),d.ngModel=p;var v=angular.element("");a.tabindex&&a.$observe("tabindex",function(e){d.multiple?h.attr("tabindex",e):v.attr("tabindex",e),n.removeAttr("tabindex")}),l(v)(c),d.focusser=v,d.multiple||(n.append(v),v.bind("focus",function(){c.$evalAsync(function(){d.focus=!0})}),v.bind("blur",function(){c.$evalAsync(function(){d.focus=!1})}),v.bind("keydown",function(t){return t.which===e.BACKSPACE?(t.preventDefault(),t.stopPropagation(),d.select(void 0),c.$digest(),void 0):(t.which===e.TAB||e.isControl(t)||e.isFunctionKey(t)||t.which===e.ESC||((t.which==e.DOWN||t.which==e.UP||t.which==e.ENTER||t.which==e.SPACE)&&(t.preventDefault(),t.stopPropagation(),d.activate()),c.$digest()),void 0)}),v.bind("keyup input",function(t){t.which===e.TAB||e.isControl(t)||e.isFunctionKey(t)||t.which===e.ESC||t.which==e.ENTER||t.which===e.BACKSPACE||(d.activate(v.val()),v.val(""),c.$digest())})),c.$watch("searchEnabled",function(){var e=c.$eval(a.searchEnabled);d.searchEnabled=void 0!==e?e:!0}),a.$observe("disabled",function(){d.disabled=void 0!==a.disabled?a.disabled:!1}),a.$observe("resetSearchInput",function(){var e=c.$eval(a.resetSearchInput);d.resetSearchInput=void 0!==e?e:!0}),a.$observe("tagging",function(){if(void 0!==a.tagging){var e=c.$eval(a.tagging);d.tagging={isActivated:!0,fct:e!==!0?e:void 0}}else d.tagging={isActivated:!1,fct:void 0}}),d.multiple?(c.$watchCollection("$select.selected",function(e){p.$setViewValue(e,null,!0)}),v.prop("disabled",!0)):c.$watch("$select.selected",function(e){p.$viewValue!==e&&p.$setViewValue(e)}),p.$render=function(){if(d.multiple&&!angular.isArray(p.$viewValue)){if(!angular.isUndefined(p.$viewValue)&&null!==p.$viewValue)throw s("multiarr","Expected model value to be array but got '{0}'",p.$viewValue);d.selected=[]}d.selected=p.$viewValue},t.on("click",u),c.$on("$destroy",function(){t.off("click",u)}),o(c,function(e){var t=angular.element("
    ").append(e),c=t.querySelectorAll(".ui-select-match");if(c.removeAttr("ui-select-match"),1!==c.length)throw s("transcluded","Expected 1 .ui-select-match but got '{0}'.",c.length);n.querySelectorAll(".ui-select-match").replaceWith(c);var l=t.querySelectorAll(".ui-select-choices");if(l.removeAttr("ui-select-choices"),1!==l.length)throw s("transcluded","Expected 1 .ui-select-choices but got '{0}'.",l.length);n.querySelectorAll(".ui-select-choices").replaceWith(l)})}}}]).directive("uiSelectChoices",["uiSelectConfig","RepeatParser","uiSelectMinErr","$compile",function(e,t,c,s){return{restrict:"EA",require:"^uiSelect",replace:!0,transclude:!0,templateUrl:function(t){var c=t.parent().attr("theme")||e.theme;return c+"/choices.tpl.html"},compile:function(l,i){if(!i.repeat)throw c("repeat","Expected 'repeat' expression.");return function(l,i,n,a,r){var o=n.groupBy;if(a.parseRepeatAttr(n.repeat,o),a.disableChoiceExpression=n.uiDisableChoice,o){var u=i.querySelectorAll(".ui-select-choices-group");if(1!==u.length)throw c("rows","Expected 1 .ui-select-choices-group but got '{0}'.",u.length);u.attr("ng-repeat",t.getGroupNgRepeatExpression())}var d=i.querySelectorAll(".ui-select-choices-row");if(1!==d.length)throw c("rows","Expected 1 .ui-select-choices-row but got '{0}'.",d.length);d.attr("ng-repeat",t.getNgRepeatExpression(a.parserResult.itemName,"$select.items",a.parserResult.trackByExp,o)).attr("ng-mouseenter","$select.setActiveItem("+a.parserResult.itemName+")").attr("ng-click","$select.select("+a.parserResult.itemName+")");var p=i.querySelectorAll(".ui-select-choices-row-inner");if(1!==p.length)throw c("rows","Expected 1 .ui-select-choices-row-inner but got '{0}'.",p.length);p.attr("uis-transclude-append",""),s(i,r)(l),l.$watch("$select.search",function(e){e&&!a.open&&a.multiple&&a.activate(!1,!0),a.activeIndex=a.tagging.isActivated?-1:0,a.refresh(n.refresh)}),n.$observe("refreshDelay",function(){var t=l.$eval(n.refreshDelay);a.refreshDelay=void 0!==t?t:e.refreshDelay})}}}}]).directive("uisTranscludeAppend",function(){return{link:function(e,t,c,s,l){l(e,function(e){t.append(e)})}}}).directive("uiSelectMatch",["uiSelectConfig",function(e){return{restrict:"EA",require:"^uiSelect",replace:!0,transclude:!0,templateUrl:function(t){var c=t.parent().attr("theme")||e.theme,s=t.parent().attr("multiple");return c+(s?"/match-multiple.tpl.html":"/match.tpl.html")},link:function(t,c,s,l){s.$observe("placeholder",function(t){l.placeholder=void 0!==t?t:e.placeholder}),l.multiple&&l.sizeSearchInput()}}}]).filter("highlight",function(){function e(e){return e.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(t,c){return c&&t?t.replace(new RegExp(e(c),"gi"),'$&'):t}})}(),angular.module("ui.select").run(["$templateCache",function(e){e.put("bootstrap/choices.tpl.html",''),e.put("bootstrap/match-multiple.tpl.html",''),e.put("bootstrap/match.tpl.html",''),e.put("bootstrap/select-multiple.tpl.html",''),e.put("bootstrap/select.tpl.html",''),e.put("select2/choices.tpl.html",'
    • {{$group.name}}
    '),e.put("select2/match-multiple.tpl.html",'
  • '),e.put("select2/match.tpl.html",'{{$select.placeholder}} '),e.put("select2/select-multiple.tpl.html",'
    '),e.put("select2/select.tpl.html",'
    '),e.put("selectize/choices.tpl.html",'
    {{$group.name}}
    '),e.put("selectize/match.tpl.html",'
    '),e.put("selectize/select.tpl.html",'
    ')}]); \ No newline at end of file diff --git a/examples/demo-multi-select.html b/examples/demo-multi-select.html index 9481b4d04..81e495ac7 100644 --- a/examples/demo-multi-select.html +++ b/examples/demo-multi-select.html @@ -113,7 +113,7 @@

    Multi select

    Multi Selection Demos

    Array of strings

    - + {{$item}} {{color}} @@ -122,7 +122,7 @@

    Array of strings

    Selected: {{multipleDemo.colors}}


    Array of objects

    - + {{$item.name}} <{{$item.email}}>
    @@ -134,9 +134,24 @@

    Array of objects

    Selected: {{multipleDemo.selectedPeople}}

    +
    +

    Deselect callback with single property binding

    + + {{$item.name}} <{{$item.email}}> + +
    + + email: {{person.email}} + age: + +
    +
    +

    Last removed item: {{lastRemoved.item}}

    +

    Last removed model: {{lastRemoved.model}}

    +

    Array of objects with single property binding

    - + {{$item.name}} <{{$item.email}}>
    @@ -150,7 +165,7 @@

    Array of objects with single property binding


    Array of objects (with groupBy)

    - + {{$item.name}} <{{$item.email}}>
    diff --git a/examples/demo-tagging.html b/examples/demo-tagging.html new file mode 100644 index 000000000..909c05ec6 --- /dev/null +++ b/examples/demo-tagging.html @@ -0,0 +1,122 @@ + + + + + AngularJS ui-select + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Tagging Demos

    + +

    Simple String Tags

    +

    (With Custom Tag Label)

    + + {{$item}} + + {{color}} + + +

    Selected: {{multipleDemo.colors}}

    +
    + +

    Simple String Tags

    +

    (Predictive Search Model / No Labels)

    + + {{$item}} + + {{color}} + + +

    Selected: {{multipleDemo.colors2}}

    +
    + +

    Object Tags

    + + {{$item.name}} <{{$item.email}}> + +
    +
    + + email: {{person.email}} + age: + +
    +
    +

    Selected: {{multipleDemo.selectedPeople}}

    +
    + +

    Object Tags with Tokenization (Space, Forward Slash, Comma)

    + Note that the SPACE character can't be used literally, use the keyword SPACE + + {{$item.name}} <{{$item.email}}> + +
    +
    + + email: {{person.email}} + age: + +
    +
    +

    Selected: {{multipleDemo.selectedPeople2}}

    + +
    + + + diff --git a/examples/demo.js b/examples/demo.js index a66599d60..402227995 100644 --- a/examples/demo.js +++ b/examples/demo.js @@ -99,6 +99,24 @@ app.controller('DemoCtrl', function($scope, $http, $timeout) { $scope.eventResult = {item: item, model: model}; }; + $scope.removed = function (item, model) { + $scope.lastRemoved = { + item: item, + model: model + }; + }; + + $scope.tagTransform = function (newTag) { + var item = { + name: newTag, + email: newTag+'@email.com', + age: 'unknown', + country: 'unknown' + }; + + return item; + }; + $scope.person = {}; $scope.people = [ { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States' }, @@ -117,7 +135,9 @@ app.controller('DemoCtrl', function($scope, $http, $timeout) { $scope.multipleDemo = {}; $scope.multipleDemo.colors = ['Blue','Red']; + $scope.multipleDemo.colors2 = ['Blue','Red']; $scope.multipleDemo.selectedPeople = [$scope.people[5], $scope.people[4]]; + $scope.multipleDemo.selectedPeople2 = $scope.multipleDemo.selectedPeople; $scope.multipleDemo.selectedPeopleWithGroupBy = [$scope.people[8], $scope.people[6]]; $scope.multipleDemo.selectedPeopleSimple = ['samantha@email.com','wladimir@email.com']; diff --git a/package.json b/package.json index faeafd949..f329c54d7 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "ui-select", + "main": "dist/select.js", "author": "http://github.com/angular-ui/ui-select/graphs/contributors", "homepage": "http://github.com/angular-ui/ui-select", "repository": { "url": "git://github.com/angular-ui/ui-select.git" }, - "version": "0.7.0", + "version": "0.8.3", "devDependencies": { "bower": "~1.3", "del": "~0.1.1", diff --git a/src/bootstrap/choices.tpl.html b/src/bootstrap/choices.tpl.html index daddb8684..a9a1eee3a 100644 --- a/src/bootstrap/choices.tpl.html +++ b/src/bootstrap/choices.tpl.html @@ -3,7 +3,7 @@ ng-show="$select.items.length > 0">
  • - +
    diff --git a/src/bootstrap/match-multiple.tpl.html b/src/bootstrap/match-multiple.tpl.html index be3c72fab..08e24556b 100644 --- a/src/bootstrap/match-multiple.tpl.html +++ b/src/bootstrap/match-multiple.tpl.html @@ -1,6 +1,6 @@ - + - \ No newline at end of file + diff --git a/src/bootstrap/match.tpl.html b/src/bootstrap/match.tpl.html index abd4c353d..b766d4680 100644 --- a/src/bootstrap/match.tpl.html +++ b/src/bootstrap/match.tpl.html @@ -3,7 +3,7 @@ ng-disabled="$select.disabled" ng-class="{'btn-default-focus':$select.focus}"; ng-click="$select.activate()"> - {{$select.placeholder}} + {{$select.placeholder}} diff --git a/src/select.css b/src/select.css index 7a18d3c10..519dfd99a 100644 --- a/src/select.css +++ b/src/select.css @@ -28,6 +28,10 @@ padding-left: 0; } +.select2-locked > .select2-search-choice-close{ + display:none; +} + /* Selectize theme */ /* Helper class to show styles when focus */ @@ -113,6 +117,10 @@ line-height: 0.75; } +.ui-select-multiple.ui-select-bootstrap .ui-select-match-item{ + outline: 0; +} + .ui-select-bootstrap .ui-select-choices-row>a { display: block; padding: 3px 20px; diff --git a/src/select.js b/src/select.js index e16d1f7dc..263d007b6 100644 --- a/src/select.js +++ b/src/select.js @@ -19,9 +19,15 @@ END: 35, BACKSPACE: 8, DELETE: 46, + COMMAND: 91, + + MAP: { 91 : "COMMAND", 8 : "BACKSPACE" , 9 : "TAB" , 13 : "ENTER" , 16 : "SHIFT" , 17 : "CTRL" , 18 : "ALT" , 19 : "PAUSEBREAK" , 20 : "CAPSLOCK" , 27 : "ESC" , 32 : "SPACE" , 33 : "PAGE_UP", 34 : "PAGE_DOWN" , 35 : "END" , 36 : "HOME" , 37 : "LEFT" , 38 : "UP" , 39 : "RIGHT" , 40 : "DOWN" , 43 : "+" , 44 : "PRINTSCREEN" , 45 : "INSERT" , 46 : "DELETE", 48 : "0" , 49 : "1" , 50 : "2" , 51 : "3" , 52 : "4" , 53 : "5" , 54 : "6" , 55 : "7" , 56 : "8" , 57 : "9" , 59 : ";", 61 : "=" , 65 : "A" , 66 : "B" , 67 : "C" , 68 : "D" , 69 : "E" , 70 : "F" , 71 : "G" , 72 : "H" , 73 : "I" , 74 : "J" , 75 : "K" , 76 : "L", 77 : "M" , 78 : "N" , 79 : "O" , 80 : "P" , 81 : "Q" , 82 : "R" , 83 : "S" , 84 : "T" , 85 : "U" , 86 : "V" , 87 : "W" , 88 : "X" , 89 : "Y" , 90 : "Z", 96 : "0" , 97 : "1" , 98 : "2" , 99 : "3" , 100 : "4" , 101 : "5" , 102 : "6" , 103 : "7" , 104 : "8" , 105 : "9", 106 : "*" , 107 : "+" , 109 : "-" , 110 : "." , 111 : "/", 112 : "F1" , 113 : "F2" , 114 : "F3" , 115 : "F4" , 116 : "F5" , 117 : "F6" , 118 : "F7" , 119 : "F8" , 120 : "F9" , 121 : "F10" , 122 : "F11" , 123 : "F12", 144 : "NUMLOCK" , 145 : "SCROLLLOCK" , 186 : ";" , 187 : "=" , 188 : "SPACE" , 189 : "-" , 190 : "." , 191 : "/" , 192 : "`" , 219 : "[" , 220 : "\\" , 221 : "]" , 222 : "'" + }, + isControl: function (e) { var k = e.which; switch (k) { + case KEY.COMMAND: case KEY.SHIFT: case KEY.CTRL: case KEY.ALT: @@ -65,7 +71,8 @@ theme: 'bootstrap', searchEnabled: true, placeholder: '', // Empty by default, like HTML tag