\r\n");}]);
\ No newline at end of file
+angular.module("am.multiselect").run(["$templateCache", function($templateCache) {$templateCache.put("multiselect.tmpl.html","
diff --git a/src/app.css b/src/app.css
new file mode 100644
index 0000000..0554d84
--- /dev/null
+++ b/src/app.css
@@ -0,0 +1,10 @@
+.fixed-width{
+ width: 150px;
+}
+
+am-multiselect .dropdown-menu > li > .word-wrapped
+{
+ word-wrap: break-word; !important;
+ word-break: break-all; !important;
+ white-space: normal; !important;
+}
\ No newline at end of file
diff --git a/src/app.js b/src/app.js
index 7f14db4..1c687f6 100644
--- a/src/app.js
+++ b/src/app.js
@@ -4,14 +4,19 @@ angular.module('app', ['am.multiselect'])
$scope.cars = [
{id:1, name: 'Audi'},
{id:2, name: 'BMW'},
- {id:3, name: 'Honda'}
+ {id:3, name: 'Honda'},
+ {id:4, name: 'A very very long name with spaces. It never ends at all. Keeps going on and on and on. So what can you do.'},
+ {id:5, name: 'AndThereComesTheNoSpaceMonsterThatYouHateToSeeInYourWebPageWhatCanYouDoAboutThis'}
];
$scope.selectedCar = [];
$scope.fruits = [
{id: 1, name: 'Apple'},
{id: 2, name: 'Orange'},
- {id: 3, name: 'Banana'}
+ {id: 3, name: 'Banana: with some long story like there usual travel across the atlantic from south worm world to cold dizzy northern world.'}
];
$scope.selectedFruit = null;
+ $scope.OnSelectionChange = function(event){
+ console.log(event);
+ }
}]);
diff --git a/src/index.html b/src/index.html
index b32f42e..a36c6d7 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,28 +1,64 @@
Angular multiselect
-
+
-
+
+
-
+
-
Example
-
-
- {{selectedCar}}
-
- Single select:
-
-
- {{selectedFruit}}
-
+
Example:Multiple selection with whole object selected items
+
+ Long List item ( with or without word spacing) are wrapped into multi line.
+ Width are fixed to 150px.
+
+<am-multiselect class="input-lg" multiple="true" ms-selected="There are {{selectedCar.length}} car(s) selected"
+ ng-model="selectedCar" ms-header="Select Some Cars"
+ options="c as c.name for c in cars" ms-id="id" list-css="fixed-width" list-item-css="word-wrapped"
+ change="selected()" onchange="OnSelectionChange(event)"> </am-multiselect>
+
+
+ With CSS:
+ .fixed-width
+ {
+ width: 150px;
+ }
+
+ am-multiselect .dropdown-menu > li > .word-wrapped
+ {
+ word-wrap: break-word; !important;
+ word-break: break-all; !important;
+ white-space: normal; !important;
+ }
+
+
+
+
+
+
+
+ {{selectedCar}}
+
+
Single select: with only display attribute as selected item.
+
+
+ <am-multiselect class="input-lg"
+ ng-model="selectedFruit"
+ options="c.name for c in fruits"
+ change="selected()"> </am-multiselect>
+
+
+
+
+ {{selectedFruit}}
+
diff --git a/src/multiselect.css b/src/multiselect.css
index 3b817ad..0cee690 100644
--- a/src/multiselect.css
+++ b/src/multiselect.css
@@ -6,6 +6,7 @@ am-multiselect .dropdown-menu{
am-multiselect .dropdown-menu > li > a {
padding: 3px 10px;
cursor:pointer;
+
}
am-multiselect .dropdown-menu > li.selected {
diff --git a/src/multiselect.js b/src/multiselect.js
index d107725..00de8a5 100644
--- a/src/multiselect.js
+++ b/src/multiselect.js
@@ -2,320 +2,351 @@
angular.module('am.multiselect', [])
// from bootstrap-ui typeahead parser
-.factory('optionParser', ['$parse', function ($parse) {
- // 00000111000000000000022200000000000000003333333333333330000000000044000
- var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
- return {
- parse:function (input) {
- var match = input.match(TYPEAHEAD_REGEXP);
- if (!match) {
- throw new Error(
- 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
- ' but got "' + input + '".');
+ .factory('optionParser', ['$parse', function ($parse) {
+ // 00000111000000000000022200000000000000003333333333333330000000000044000
+ var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
+ return {
+ parse: function (input) {
+ var match = input.match(TYPEAHEAD_REGEXP);
+ if (!match) {
+ throw new Error(
+ 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
+ ' but got "' + input + '".');
+ }
+ return {
+ itemName: match[3],
+ source: $parse(match[4]),
+ viewMapper: $parse(match[2] || match[1]),
+ modelMapper: $parse(match[1])
+ };
}
- return {
- itemName:match[3],
- source:$parse(match[4]),
- viewMapper:$parse(match[2] || match[1]),
- modelMapper:$parse(match[1])
- };
- }
- };
-}])
-
-.directive('amMultiselect', ['$parse', '$document', '$compile', '$interpolate', 'optionParser',
-
- function ($parse, $document, $compile, $interpolate, optionParser) {
- return {
- restrict: 'E',
- require: 'ngModel',
- link: function (originalScope, element, attrs, modelCtrl) {
- // Redefine isEmpty - this allows this to work on at least Angular 1.2.x
- var isEmpty = modelCtrl.$isEmpty;
- modelCtrl.$isEmpty = function(value) {
- return isEmpty(value) || (angular.isArray(value) && value.length == 0);
};
+ }])
- var exp = attrs.options,
- parsedResult = optionParser.parse(exp),
- isMultiple = attrs.multiple ? true : false,
- required = false,
- scope = originalScope.$new(),
- changeHandler = attrs.change || angular.noop;
-
- scope.items = [];
- scope.header = 'Select';
- scope.multiple = isMultiple;
- scope.disabled = false;
- scope.onBlur = attrs.ngBlur || angular.noop;
-
- originalScope.$on('$destroy', function () {
- scope.$destroy();
- });
-
- var popUpEl = angular.element('');
-
- // required validator
- if (attrs.required || attrs.ngRequired) {
- required = true;
- }
- attrs.$observe('required', function(newVal) {
- required = newVal;
- });
-
- // watch disabled state
- scope.$watch(function () {
- return $parse(attrs.disabled)(originalScope);
- }, function (newVal) {
- scope.disabled = newVal;
- });
-
- // watch single/multiple state for dynamically change single to multiple
- scope.$watch(function () {
- return $parse(attrs.multiple)(originalScope);
- }, function (newVal) {
- isMultiple = newVal || false;
- });
-
- // watch option changes for options that are populated dynamically
- scope.$watch(function () {
- return parsedResult.source(originalScope);
- }, function (newVal) {
- if (angular.isDefined(newVal))
- parseModel();
- }, true);
-
- // watch model change
- scope.$watch(function () {
- return modelCtrl.$modelValue;
- }, function (newVal, oldVal) {
- // when directive initialize, newVal usually undefined. Also, if model value already set in the controller
- // for preselected list then we need to mark checked in our scope item. But we don't want to do this every time
- // model changes. We need to do this only if it is done outside directive scope, from controller, for example.
- if (angular.isDefined(newVal)) {
- markChecked(newVal);
- scope.$eval(changeHandler);
- }
- getHeaderText();
- modelCtrl.$setValidity('required', scope.valid());
- }, true);
-
- function parseModel() {
- scope.items.length = 0;
- var model = parsedResult.source(originalScope);
- if(!angular.isDefined(model)) return;
- for (var i = 0; i < model.length; i++) {
- var local = {};
- local[parsedResult.itemName] = model[i];
- scope.items.push({
- label: parsedResult.viewMapper(local),
- model: parsedResult.modelMapper(local),
- checked: false
- });
- }
- }
+ .directive('amMultiselect', ['$parse', '$document', '$compile', '$interpolate', 'optionParser',
+
+ function ($parse, $document, $compile, $interpolate, optionParser) {
+ return {
+ restrict: 'E',
+ require: 'ngModel',
+ link: function (originalScope, element, attrs, modelCtrl) {
+ // Redefine isEmpty - this allows this to work on at least Angular 1.2.x
+ var isEmpty = modelCtrl.$isEmpty;
+ modelCtrl.$isEmpty = function (value) {
+ return isEmpty(value) || (angular.isArray(value) && value.length == 0);
+ };
+
+ var exp = attrs.options,
+ parsedResult = optionParser.parse(exp),
+ isMultiple = attrs.multiple ? true : false,
+ required = false,
+ scope = originalScope.$new(),
+ changeHandler = attrs.change || angular.noop,
+ onChange = attrs.onchange || angular.noop;
+
+ scope.items = [];
+ scope.header = 'Select';
+ scope.multiple = isMultiple;
+ scope.disabled = false;
+ scope.onBlur = attrs.ngBlur || angular.noop;
+ scope.id = attrs.msId ? attrs.msId : null;
+ scope.listCss = attrs.listCss ? attrs.listCss : null;
+ scope.listItemCss = attrs.listItemCss ? attrs.listItemCss : null;
+
+ scope.getListCss = function () {
+ var css = 'dropdown-menu';
+ if (scope.listCss)
+ css = css + ' ' + scope.listCss;
+ return css;
+ };
+
+ scope.getListItemCss = function () {
+ return scope.listItemCss ? scope.listItemCss : '';
+ };
+
+
+ originalScope.$on('$destroy', function () {
+ scope.$destroy();
+ });
- parseModel();
+ var popUpEl = angular.element('');
- element.append($compile(popUpEl)(scope));
+ // required validator
+ if (attrs.required || attrs.ngRequired) {
+ required = true;
+ }
+ attrs.$observe('required', function (newVal) {
+ required = newVal;
+ });
- function getHeaderText() {
- if (is_empty(modelCtrl.$modelValue)) return scope.header = attrs.msHeader || 'Select';
+ // watch disabled state
+ scope.$watch(function () {
+ return $parse(attrs.disabled)(originalScope);
+ }, function (newVal) {
+ scope.disabled = newVal;
+ });
- if (isMultiple) {
- if (attrs.msSelected) {
- scope.header = $interpolate(attrs.msSelected)(scope);
- } else {
- if (modelCtrl.$modelValue.length == 1) {
- for(var i = 0; i < scope.items.length; i++) {
- if(scope.items[i].model === modelCtrl.$modelValue[0]) {
- scope.header = scope.items[i].label;
- }
+ // watch single/multiple state for dynamically change single to multiple
+ scope.$watch(function () {
+ return $parse(attrs.multiple)(originalScope);
+ }, function (newVal) {
+ isMultiple = newVal || false;
+ });
+
+ // watch option changes for options that are populated dynamically
+ scope.$watch(function () {
+ return parsedResult.source(originalScope);
+ }, function (newVal) {
+ if (angular.isDefined(newVal))
+ parseModel();
+ }, true);
+
+ // watch model change
+ scope.$watch(function () {
+ return modelCtrl.$modelValue;
+ }, function (newVal, oldVal) {
+ // when directive initialize, newVal usually undefined. Also, if model value already set in the controller
+ // for preselected list then we need to mark checked in our scope item. But we don't want to do this every time
+ // model changes. We need to do this only if it is done outside directive scope, from controller, for example.
+ if (angular.isDefined(newVal)) {
+ markChecked(newVal);
+ scope.$eval(changeHandler);
+ }
+ getHeaderText();
+ modelCtrl.$setValidity('required', scope.valid());
+ }, true);
+
+ function parseModel() {
+ scope.items.length = 0;
+ var model = parsedResult.source(originalScope);
+ if (!angular.isDefined(model)) return;
+ for (var i = 0; i < model.length; i++) {
+ var local = {};
+ local[parsedResult.itemName] = model[i];
+ var item = {
+ label: parsedResult.viewMapper(local),
+ model: parsedResult.modelMapper(local),
+ checked: false
+ };
+ if (scope.id)
+ item.id = item.model[scope.id];
+
+ scope.items.push(item);
}
- } else {
- scope.header = modelCtrl.$modelValue.length + ' ' + 'selected';
}
- }
- } else {
- if(angular.isString(modelCtrl.$modelValue)){
- scope.header = modelCtrl.$modelValue;
- } else {
- var local = {};
- local[parsedResult.itemName] = modelCtrl.$modelValue;
- scope.header = parsedResult.viewMapper(local) || scope.items[modelCtrl.$modelValue].label;
- }
- }
- }
- function is_empty(obj) {
- if (angular.isNumber(obj)) return false;
- if (obj && obj.length && obj.length > 0) return false;
- for (var prop in obj) if (obj[prop]) return false;
- return true;
- };
+ parseModel();
+
+ element.append($compile(popUpEl)(scope));
+
+ function getHeaderText() {
+ if (is_empty(modelCtrl.$modelValue)) return scope.header = attrs.msHeader || 'Select';
+
+ if (isMultiple) {
+ if (attrs.msSelected) {
+ scope.header = $interpolate(attrs.msSelected)(scope);
+ } else {
+ if (modelCtrl.$modelValue.length == 1) {
+ for (var i = 0; i < scope.items.length; i++) {
+ if (scope.items[i].model === modelCtrl.$modelValue[0]) {
+ scope.header = scope.items[i].label;
+ }
+ }
+ } else {
+ scope.header = modelCtrl.$modelValue.length + ' ' + 'selected';
+ }
+ }
+ } else {
+ if (angular.isString(modelCtrl.$modelValue)) {
+ scope.header = modelCtrl.$modelValue;
+ } else {
+ var local = {};
+ local[parsedResult.itemName] = modelCtrl.$modelValue;
+ scope.header = parsedResult.viewMapper(local) || scope.items[modelCtrl.$modelValue].label;
+ }
+ }
+ }
- scope.valid = function validModel() {
- if(!required) return true;
- var value = modelCtrl.$modelValue;
- return (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value != null);
- };
+ function is_empty(obj) {
+ if (angular.isNumber(obj)) return false;
+ if (obj && obj.length && obj.length > 0) return false;
+ for (var prop in obj) if (obj[prop]) return false;
+ return true;
+ };
+
+ scope.valid = function validModel() {
+ if (!required) return true;
+ var value = modelCtrl.$modelValue;
+ return (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value != null);
+ };
+
+ function selectSingle(item) {
+ if (item.checked) {
+ scope.uncheckAll();
+ } else {
+ scope.uncheckAll();
+ item.checked = !item.checked;
+ }
+ setModelValue(false);
+ invokeOnChange(item);
+ }
- function selectSingle(item) {
- if (item.checked) {
- scope.uncheckAll();
- } else {
- scope.uncheckAll();
- item.checked = !item.checked;
- }
- setModelValue(false);
- }
+ function invokeOnChange(item) {
+ scope.$eval(onChange, {
+ event: {
+ data: item.model,
+ action: item.checked ? 'selected' : 'unselected'
+ }
+ });
+ }
- function selectMultiple(item) {
- item.checked = !item.checked;
- setModelValue(true);
- }
+ function selectMultiple(item) {
+ item.checked = !item.checked;
+ setModelValue(true);
+ invokeOnChange(item);
+ }
- function setModelValue(isMultiple) {
- var value = null;
-
- if (isMultiple) {
- value = [];
- angular.forEach(scope.items, function (item) {
- if (item.checked) value.push(item.model);
- })
- } else {
- angular.forEach(scope.items, function (item) {
- if (item.checked) {
- value = item.model;
- return false;
+ function setModelValue(isMultiple) {
+ var value = null;
+
+ if (isMultiple) {
+ value = [];
+ angular.forEach(scope.items, function (item) {
+ if (item.checked) value.push(item.model);
+ })
+ } else {
+ angular.forEach(scope.items, function (item) {
+ if (item.checked) {
+ value = item.model;
+ return false;
+ }
+ })
+ }
+ modelCtrl.$setViewValue(value);
}
- })
- }
- modelCtrl.$setViewValue(value);
- }
- function markChecked(newVal) {
- if (!angular.isArray(newVal)) {
- angular.forEach(scope.items, function (item) {
- if (angular.equals(item.model, newVal)) {
- scope.uncheckAll();
- item.checked = true;
- setModelValue(false);
- return false;
+ function markChecked(newVal) {
+ if (!angular.isArray(newVal)) {
+ angular.forEach(scope.items, function (item) {
+ if (angular.equals(item.model, newVal)) {
+ scope.uncheckAll();
+ item.checked = true;
+ setModelValue(false);
+ return false;
+ }
+ });
+ } else {
+ angular.forEach(scope.items, function (item) {
+ item.checked = false;
+ angular.forEach(newVal, function (i) {
+ if (angular.equals(item.model, i)) {
+ item.checked = true;
+ }
+ });
+ });
+ }
}
- });
- } else {
- angular.forEach(scope.items, function (item) {
- item.checked = false;
- angular.forEach(newVal, function (i) {
- if (angular.equals(item.model, i)) {
+
+ scope.checkAll = function () {
+ if (!isMultiple) return;
+ angular.forEach(scope.items, function (item) {
item.checked = true;
+ });
+ setModelValue(true);
+ };
+
+ scope.uncheckAll = function () {
+ angular.forEach(scope.items, function (item) {
+ item.checked = false;
+ });
+ setModelValue(true);
+ };
+
+ scope.select = function (item) {
+ if (isMultiple === false) {
+ selectSingle(item);
+ scope.toggleSelect();
+ } else {
+ selectMultiple(item);
}
- });
- });
- }
- }
-
- scope.checkAll = function () {
- if (!isMultiple) return;
- angular.forEach(scope.items, function (item) {
- item.checked = true;
- });
- setModelValue(true);
- };
-
- scope.uncheckAll = function () {
- angular.forEach(scope.items, function (item) {
- item.checked = false;
- });
- setModelValue(true);
- };
-
- scope.select = function (item) {
- if (isMultiple === false) {
- selectSingle(item);
- scope.toggleSelect();
- } else {
- selectMultiple(item);
- }
- }
- }
- };
-}])
-
-.directive('amMultiselectPopup', ['$document', '$filter', function ($document, $filter) {
- return {
- restrict: 'E',
- scope: false,
- replace: true,
- templateUrl: function (element, attr) {
- return attr.templateUrl || 'multiselect.tmpl.html';
- },
- link: function (scope, element, attrs) {
-
- scope.selectedIndex = null;
- scope.isVisible = false;
-
- scope.toggleSelect = function () {
- if (element.hasClass('open')) {
- element.removeClass('open');
- $document.unbind('click', clickHandler);
- scope.$parent.$eval(scope.onBlur);
- } else {
- element.addClass('open');
- $document.bind('click', clickHandler);
- scope.focus();
+ }
}
};
+ }])
+
+ .directive('amMultiselectPopup', ['$document', '$filter', function ($document, $filter) {
+ return {
+ restrict: 'E',
+ scope: false,
+ replace: true,
+ templateUrl: function (element, attr) {
+ return attr.templateUrl || 'multiselect.tmpl.html';
+ },
+ link: function (scope, element, attrs) {
+
+ scope.selectedIndex = null;
+ scope.isVisible = false;
+
+ scope.toggleSelect = function () {
+ if (element.hasClass('open')) {
+ element.removeClass('open');
+ $document.unbind('click', clickHandler);
+ scope.$parent.$eval(scope.onBlur);
+ } else {
+ element.addClass('open');
+ $document.bind('click', clickHandler);
+ scope.focus();
+ }
+ };
- function clickHandler(event) {
- if (elementMatchesAnyInArray(event.target, element.find(event.target.tagName))) {
- scope.$parent.$eval(scope.onBlur);
- } else {
- element.removeClass('open');
- $document.unbind('click', clickHandler);
- scope.$apply();
+ function clickHandler(event) {
+ if (elementMatchesAnyInArray(event.target, element.find(event.target.tagName))) {
+ scope.$parent.$eval(scope.onBlur);
+ } else {
+ element.removeClass('open');
+ $document.unbind('click', clickHandler);
+ scope.$apply();
+ }
}
- }
- scope.focus = function focus(){
- var searchBox = element.find('input')[0];
- if (searchBox) {
- searchBox.focus();
+ scope.focus = function focus() {
+ var searchBox = element.find('input')[0];
+ if (searchBox) {
+ searchBox.focus();
+ }
}
- }
- scope.keydown = function (event) {
- var list = $filter('filter')(scope.items, scope.searchText),
- keyCode = (event.keyCode || event.which);
+ scope.keydown = function (event) {
+ var list = $filter('filter')(scope.items, scope.searchText),
+ keyCode = (event.keyCode || event.which);
- if(keyCode === 13){ // On enter
- if(list[scope.selectedIndex]){
- scope.select(list[scope.selectedIndex]); // (un)select item
+ if (keyCode === 13) { // On enter
+ if (list[scope.selectedIndex]) {
+ scope.select(list[scope.selectedIndex]); // (un)select item
+ }
+ } else if (keyCode === 38) { // On arrow up
+ scope.selectedIndex = scope.selectedIndex === null ? list.length - 1 : scope.selectedIndex - 1;
+ } else if (keyCode === 40) { // On arrow down
+ scope.selectedIndex = scope.selectedIndex === null ? 0 : scope.selectedIndex + 1;
+ } else { // On any other key
+ scope.selectedIndex = null;
}
- }else if(keyCode === 38){ // On arrow up
- scope.selectedIndex = scope.selectedIndex===null ? list.length-1 : scope.selectedIndex-1;
- }else if(keyCode === 40){ // On arrow down
- scope.selectedIndex = scope.selectedIndex===null ? 0 : scope.selectedIndex+1;
- }else{ // On any other key
- scope.selectedIndex = null;
- }
- if(scope.selectedIndex < 0){ // Select last in list
- scope.selectedIndex = list.length-1;
- }else if(scope.selectedIndex > list.length-1){ // Set selection to first item in list
- scope.selectedIndex = 0;
- }
- };
+ if (scope.selectedIndex < 0) { // Select last in list
+ scope.selectedIndex = list.length - 1;
+ } else if (scope.selectedIndex > list.length - 1) { // Set selection to first item in list
+ scope.selectedIndex = 0;
+ }
+ };
- var elementMatchesAnyInArray = function (element, elementArray) {
- for (var i = 0; i < elementArray.length; i++)
- if (element == elementArray[i])
- return true;
- return false;
+ var elementMatchesAnyInArray = function (element, elementArray) {
+ for (var i = 0; i < elementArray.length; i++)
+ if (element == elementArray[i])
+ return true;
+ return false;
+ }
}
}
- }
-}]);
+ }]);
diff --git a/src/multiselect.tmpl.html b/src/multiselect.tmpl.html
index 4a57429..d934282 100644
--- a/src/multiselect.tmpl.html
+++ b/src/multiselect.tmpl.html
@@ -3,7 +3,7 @@
{{header}}
-