summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoristeven <ignatius.steven@gmail.com>2015-02-20 15:00:42 +0800
committeristeven <ignatius.steven@gmail.com>2015-02-20 15:00:42 +0800
commita0f4fd6a7da66d406658aa22e4768fd8d10ed3a9 (patch)
tree86237a8c6fa85e86dda63db03d39389333555a86
parentc6b6d50f88c66cfe00941dc821b96002c998b074 (diff)
downloadangular-multi-select-a0f4fd6a7da66d406658aa22e4768fd8d10ed3a9.zip
angular-multi-select-a0f4fd6a7da66d406658aa22e4768fd8d10ed3a9.tar.gz
angular-multi-select-a0f4fd6a7da66d406658aa22e4768fd8d10ed3a9.tar.bz2
v3.0.0
-rw-r--r--CHANGELOG.md22
-rw-r--r--LICENSE.txt2
-rw-r--r--README.md23
-rw-r--r--bower.json8
-rw-r--r--isteven-multi-select.css (renamed from angular-multi-select.css)53
-rw-r--r--isteven-multi-select.js (renamed from angular-multi-select.js)689
6 files changed, 494 insertions, 303 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09c61f2..2c570b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,23 @@
+### v3.0.0
+##### Added / Updated
+- Customized text on helper elements
+- 5 new callbacks
+- You can now set minimum characters required to trigger the search functionality
+- You can now define which input-model properties to search from (previously, all input-model properties are searched)
+- On close, parent button will now receive focus.
+- Using proper semantics (well at least better than previous version).
+- Various small optimizations.
+
+##### Deprecated / Breaking Changes
+- File name and the directive name have been changed. I am really sorry for this, but this is the only workaround to prevent wrong language statistic in Github (they don''t count files whose name starts with "angular"). The repository name stays the same.
+- output-model is now required.
+- input-model is now static (not dynamically updated), hence why we need output-model. On the plus side, you now can re-use the input model where necessary.
+- default-label is deprecated. Custom text and translations can be done using the translation attribute.
+
+### v2.0.2
+##### Added / Updated
+- Bring back CSS into bower.json.
+
### v2.0.1
##### Added / Updated
- <a href="https://github.com/isteven/angular-multi-select/issues/52">#52</a> Form tag is now properly closed
@@ -13,7 +33,7 @@
- on-open and on-close callbacks will now pass the multi-select element (HTML) to the callback function.
- max-height attribute. You can define the height of the selection items container.
-##### Removed / Deprecated:
+##### Deprecated / Breaking Changes
- on-focus attribute is deprecated.
- on-blur attribute is deprecated. Use on-close instead, as it will be triggered when user close a directive by clicking outside the directive.
- IE8 will no longer be supported.
diff --git a/LICENSE.txt b/LICENSE.txt
index 8439bd9..e723f53 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2014 Ignatius Steven (https://github.com/isteven)
+Copyright (c) 2014-2015 Ignatius Steven (https://github.com/isteven)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 480b9c9..7351888 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
# AngularJS MultiSelect
Pure AngularJS directive which creates a dropdown button with multiple or single selections.
Doesn't require jQuery and works well with other Javascript libraries.
-Fully configurable through element attributes and CSS.
![Screenshot](https://raw.githubusercontent.com/isteven/angular-multi-select/master/screenshot.png)
@@ -9,27 +8,27 @@ Fully configurable through element attributes and CSS.
Go to http://isteven.github.io/angular-multi-select
### Current Version
-2.0.1
+3.0.0
### Change Log
-See <a href="https://github.com/isteven/angular-multi-select/blob/master/CHANGELOG.md">CHANGELOG.md</a>
+See <a href="https://github.com/isteven/angular-multi-select/blob/master/CHANGELOG.md">CHANGELOG.md</a>.
+For those who's upgrading from version 2.x.x, do note that this version is not backward-compatible. Please read the manual
+thoroughly and update your code accordingly.
### Bug Reporting
-- Search in the issue section first. Somebody might have reported the same bug and/or asked similar question. If there's none, then please create a new issue.
-- Try to reproduce the problem in JSFiddle or Plunker (or any other online JS collaboration tool), and include the URL in the issue you are creating.
-
-
-### Note
-- This directive is a practical solution, not a performance champion. It will not win any code efficiency competition.
-- As for the moment, developments are on going, so I am not accepting pull requests. I will choose & add them manually instead.
-- If you like / use this directive in your awesome projects, star this repo. It's a huge motivation for me. Would also love to hear from you if you use it in an open source project. Thanks!
+Please follow these steps:
+1. READ THE MANUAL AGAIN. You might have missed something. This includes the MINIMUM ANGULARJS VERSION and the SUPPORTED BROWSERS.
+2. The next step is to search in Github's issue section first. There might already be an answer for similar issue. Do check both open and closed issues.
+3. If there's no previous issue found, then please create a new issue in https://github.com/isteven/angular-multi-select/issues.
+4. Please replicate the problem in JSFiddle or Plunker (or any other online JS collaboration tool), and include the URL in the issue you are creating.
+5. When you're done, please close the issue you've created.
### Licence
Released under the MIT license:
The MIT License (MIT)
-Copyright (c) 2014 Ignatius Steven (https://github.com/isteven)
+Copyright (c) 2014-2015 Ignatius Steven (https://github.com/isteven)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/bower.json b/bower.json
index 6443140..4ca85b0 100644
--- a/bower.json
+++ b/bower.json
@@ -1,14 +1,16 @@
{
"name" : "isteven-angular-multiselect",
- "version" : "v2.0.1",
+ "version" : "v3.0.0",
"main" : [
- "angular-multi-select.js"
+ "isteven-multi-select.js",
+ "isteven-multi-select.css",
],
"ignore" : [
".git",
".gitignore",
"bower.json",
"README.md",
- "screenshot.png"
+ "screenshot.png",
+ "/doc"
]
}
diff --git a/angular-multi-select.css b/isteven-multi-select.css
index efaf4db..72837a3 100644
--- a/angular-multi-select.css
+++ b/isteven-multi-select.css
@@ -4,7 +4,7 @@
/* ! vertical layout */
.multiSelect .vertical {
- float: none;
+ float: none;
}
/* ! horizontal layout */
@@ -14,8 +14,10 @@
/* ! create a "row" */
.multiSelect .line {
- max-height: 34px;
+ padding: 2px 0px 4px 0px;
+ max-height: 30px;
overflow: hidden;
+ box-sizing: content-box;
}
/* ! create a "column" */
@@ -30,8 +32,8 @@
}
/* the multiselect button */
-.multiSelect .button {
- display: block;
+.multiSelect > button {
+ display: inline-block;
position: relative;
text-align: center;
cursor: pointer;
@@ -52,7 +54,7 @@
}
/* button: hover */
-.multiSelect .button:hover {
+.multiSelect > button:hover {
background-image: linear-gradient(#fff, #e9e9e9);
}
@@ -90,14 +92,13 @@
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
min-width:278px;
- display: none !important;
- margin-right: 30px;
+ display: none !important;
}
/* container of helper elements */
.multiSelect .helperContainer {
border-bottom: 1px solid #ddd;
- padding: 8px 8px 0px 8px;
+ padding: 8px 8px 0px 8px;
}
/* helper buttons (select all, none, reset); */
@@ -115,6 +116,14 @@
margin: 0px 0px 8px 0px;
}
+.multiSelect .helperButton.reset{
+ float: right;
+}
+
+.multiSelect .helperButton:not( .reset ) {
+ margin-right: 4px;
+}
+
/* clear button */
.multiSelect .clearButton {
position: absolute;
@@ -130,7 +139,7 @@
background-color: #f1f1f1;
line-height: 1.4;
right : 2px;
- top: 2px;
+ top: 4px;
}
/* filter */
@@ -146,6 +155,8 @@
box-sizing: border-box; /* Opera/IE 8+ */
color: #888;
margin: 0px 0px 8px 0px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
}
/* helper elements on hover & focus */
@@ -160,13 +171,16 @@
.multiSelect .helperButton:focus,
.multiSelect .inputFilter:focus {
border: 1px solid #66AFE9 !important;
- box-shadow: inset 0 0px 1px rgba(0,0,0,.035), 0 0 5px rgba(82,168,236,.7) !important;
+ outline: 0;
+ -webkit-box-shadow: box-shadow: inset 0 0 1px rgba(0,0,0,.065), 0 0 5px rgba(102, 175, 233, .6) !important;
+ box-shadow: inset 0 0 1px rgba(0,0,0,.065), 0 0 5px rgba(102, 175, 233, .6) !important;
}
/* container of multi select items */
.multiSelect .checkBoxContainer {
- display: inline-block;
+ display: block;
padding: 8px;
+ overflow: hidden;
}
/* ! to show / hide the checkbox layer above */
@@ -187,10 +201,11 @@
user-select: none;
border: 1px solid transparent;
position: relative;
- min-width:278px;
+ min-width:278px;
+ min-height: 32px;
}
-/* Item labels: selected - Enable this if you want to apply styling on selected items */
+/* Styling on selected items */
.multiSelect .multiSelectItem:not(.multiSelectGroup).selected
{
background-image: linear-gradient( #e9e9e9, #f1f1f1 );
@@ -201,6 +216,14 @@
border-right: 1px solid #d9d9d9;
}
+.multiSelect .multiSelectItem .acol label {
+ display: inline-block;
+ padding-right: 30px;
+ margin: 0px;
+ font-weight: normal;
+ line-height: normal;
+}
+
/* item labels focus on mouse hover */
.multiSelect .multiSelectItem:hover,
.multiSelect .multiSelectGroup:hover {
@@ -260,6 +283,6 @@
.multiSelect img {
vertical-align: middle;
margin-bottom:0px;
- height: 22px;
- width:22px;
+ max-height: 22px;
+ max-width:22px;
}
diff --git a/angular-multi-select.js b/isteven-multi-select.js
index bb30eb2..59ac06b 100644
--- a/angular-multi-select.js
+++ b/isteven-multi-select.js
@@ -3,7 +3,7 @@
* Creates a dropdown-like button with checkboxes.
*
* Project started on: Tue, 14 Jan 2014 - 5:18:02 PM
- * Current version: 2.0.1
+ * Current version: 3.0.0
*
* Released under the MIT License
* --------------------------------------------------------------------------------
@@ -31,14 +31,13 @@
* --------------------------------------------------------------------------------
*/
-angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$timeout', function ( $sce, $timeout ) {
+'use strict'
+
+angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' , [ '$sce', '$timeout', '$templateCache', function ( $sce, $timeout, $templateCache ) {
return {
restrict:
'AE',
- replace:
- true,
-
scope:
{
// models
@@ -47,89 +46,142 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
// settings based on attribute
buttonLabel : '@',
- defaultLabel : '@',
directiveId : '@',
helperElements : '@',
isDisabled : '=',
itemLabel : '@',
maxLabels : '@',
orientation : '@',
- selectionMode : '@',
+ selectionMode : '@',
+ minSearchLength : '@', // 3.0.0 - OK
// settings based on input model property
tickProperty : '@',
disableProperty : '@',
groupProperty : '@',
- maxHeight : '@',
+ searchProperty : '@', // 3.0.0 - OK
+ maxHeight : '@',
+ lazyLoading : '@', // 3.0.0
// callbacks
- onClose : '&',
- onItemClick : '&',
- onOpen : '&'
+ onClear : '&', // 3.0.0 - OK
+ onClose : '&',
+ onSearchChange : '&', // 3.0.0 - OK
+ onItemClick : '&',
+ onOpen : '&',
+ onReset : '&', // 3.0.0 - OK
+ onSelectAll : '&', // 3.0.0 - OK
+ onSelectNone : '&', // 3.0.0 - OK
+
+ // i18n
+ translation : '=' // 3.0.0 - OK
},
template:
- '<span class="multiSelect inlineBlock">' +
- '<button type="button" class="button multiSelectButton" ng-click="toggleCheckboxes( $event ); refreshSelectedItems(); refreshButton();" ng-bind-html="varButtonLabel">' +
- '</button>' +
- '<div class="checkboxLayer">' +
- '<form>' +
- '<div class="helperContainer" ng-if="displayHelper( \'filter\' ) || displayHelper( \'all\' ) || displayHelper( \'none\' ) || displayHelper( \'reset\' )">' +
- '<div class="line" ng-if="displayHelper( \'all\' ) || displayHelper( \'none\' ) || displayHelper( \'reset\' )">' +
- '<button type="button" ng-click="select( \'all\', $event );" class="helperButton" ng-if="!isDisabled && displayHelper( \'all\' )"> &#10003;&nbsp; Select All</button> ' +
- '<button type="button" ng-click="select( \'none\', $event );" class="helperButton" ng-if="!isDisabled && displayHelper( \'none\' )"> &times;&nbsp; Select None</button>' +
- '<button type="button" ng-click="select( \'reset\', $event );" class="helperButton" ng-if="!isDisabled && displayHelper( \'reset\' )" style="float:right">&#8630;&nbsp; Reset</button>' +
- '</div>' +
- '<div class="line" style="position:relative" ng-if="displayHelper( \'filter\' )">' +
- '<input placeholder="Search..." type="text" ng-click="select( \'filter\', $event )" ng-model="inputLabel.labelFilter" ng-change="updateFilter();$scope.getFormElements();" class="inputFilter" />' +
- '<button type="button" class="clearButton" ng-click="inputLabel.labelFilter=\'\';updateFilter();prepareGrouping();prepareIndex();select( \'clear\', $event )">&times;</button> ' +
- '</div>' +
- '</div>' +
- '<div class="checkBoxContainer" style="{{setHeight();}}">' +
- '<div ng-repeat="item in filteredModel | filter:removeGroupEndMarker" class="multiSelectItem"' +
- 'ng-class="{selected: item[ tickProperty ], horizontal: orientationH, vertical: orientationV, multiSelectGroup:item[ groupProperty ], disabled:itemIsDisabled( item )}"' +
- 'ng-click="syncItems( item, $event, $index );"' +
- 'ng-mouseleave="removeFocusStyle( tabIndex );">' +
- '<div class="acol" ng-if="item[ spacingProperty ] > 0" ng-repeat="i in numberToArray( item[ spacingProperty ] ) track by $index">&nbsp;</div>' +
- '<div class="acol">' +
- '<label>' +
- '<input class="checkbox focusable" type="checkbox" ng-disabled="itemIsDisabled( item )" ng-checked="item[ tickProperty ]" ng-click="syncItems( item, $event, $index )" />' +
- '<span ng-class="{disabled:itemIsDisabled( item )}" ng-bind-html="writeLabel( item, \'itemLabel\' )"></span>' +
- '</label>' +
- '</div>' +
- '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
- '<span class="tickMark" ng-if="item[ groupProperty ] !== true && item[ tickProperty ] === true">&#10004;</span>' +
- '</div>' +
+ '<span class="multiSelect inlineBlock" id={{directiveId}}>' +
+ '<button type="button"' +
+ 'ng-click="toggleCheckboxes( $event ); refreshSelectedItems(); refreshButton(); prepareGrouping; prepareIndex();"' +
+ 'ng-bind-html="varButtonLabel">' +
+ '</button>' +
+ '<div class="checkboxLayer">' +
+
+ '<div class="helperContainer" ng-if="displayHelper( \'filter\' ) || displayHelper( \'all\' ) || displayHelper( \'none\' ) || displayHelper( \'reset\' )">' +
+ '<div class="line" ng-if="displayHelper( \'all\' ) || displayHelper( \'none\' ) || displayHelper( \'reset\' )">' +
+
+ '<button type="button" class="helperButton"' +
+ 'ng-if="!isDisabled && displayHelper( \'all\' )"' +
+ 'ng-click="select( \'all\', $event );"' +
+ 'ng-bind-html="lang.selectAll">' +
+ '</button>'+
+
+ '<button type="button" class="helperButton"' +
+ 'ng-if="!isDisabled && displayHelper( \'none\' )"' +
+ 'ng-click="select( \'none\', $event );"' +
+ 'ng-bind-html="lang.selectNone">' +
+ '</button>'+
+
+ '<button type="button" class="helperButton reset"' +
+ 'ng-if="!isDisabled && displayHelper( \'reset\' )"' +
+ 'ng-click="select( \'reset\', $event );"' +
+ 'ng-bind-html="lang.reset">'+
+ '</button>' +
'</div>' +
- '</form>' +
- '</div>' +
- '</span>',
- link: function ( $scope, element, attrs ) {
+ '<div class="line" style="position:relative" ng-if="displayHelper( \'filter\' )">'+
+
+ '<input placeholder="{{lang.search}}" type="text"' +
+ 'ng-click="select( \'filter\', $event )" '+
+ 'ng-model="inputLabel.labelFilter" '+
+ 'ng-change="searchChanged()" class="inputFilter"'+
+ '/>'+
+
+ '<button type="button" class="clearButton" ng-click="clearClicked( $event )" >×</button> '+
+ '</div> '+
+ '</div> '+
+
+ '<div class="checkBoxContainer">'+
+ '<div '+
+ 'ng-repeat="item in filteredModel | filter:removeGroupEndMarker" class="multiSelectItem"'+
+ 'ng-class="{selected: item[ tickProperty ], horizontal: orientationH, vertical: orientationV, multiSelectGroup:item[ groupProperty ], disabled:itemIsDisabled( item )}"'+
+ 'ng-click="syncItems( item, $event, $index );" '+
+ 'ng-mouseleave="removeFocusStyle( tabIndex );"> '+
+
+ '<div class="acol" ng-if="item[ spacingProperty ] > 0" ng-repeat="i in numberToArray( item[ spacingProperty ] ) track by $index">'+
+
+ '</div> '+
+
+ '<div class="acol">'+
+
+ '<label>'+
+ '<input class="checkbox focusable" type="checkbox" '+
+ 'ng-disabled="itemIsDisabled( item )" '+
+ 'ng-checked="item[ tickProperty ]" '+
+ 'ng-click="syncItems( item, $event, $index )" />'+
+
+ '<span '+
+ 'ng-class="{disabled:itemIsDisabled( item )}" '+
+ 'ng-bind-html="writeLabel( item, \'itemLabel\' )">'+
+ '</span>'+
+ '</label>'+
+ '</div>'+
+
+ '<span class="tickMark" ng-if="item[ groupProperty ] !== true && item[ tickProperty ] === true">✔</span>'+
+ '</div>'+
+ '</div>'+
+ '</div>'+
+ '</span>',
+
+ link: function ( $scope, element, attrs ) {
$scope.backUp = [];
- $scope.varButtonLabel = '';
- $scope.scrolled = false;
+ $scope.varButtonLabel = '';
$scope.spacingProperty = '';
- $scope.indexProperty = '';
- $scope.checkBoxLayer = '';
+ $scope.indexProperty = '';
$scope.orientationH = false;
$scope.orientationV = true;
$scope.filteredModel = [];
- $scope.inputLabel = { labelFilter: '' };
- $scope.selectedItems = [];
- $scope.formElements = [];
- $scope.tabIndex = 0;
- $scope.clickedItem = null;
- prevTabIndex = 0;
- helperItems = [];
- helperItemsLength = 0;
-
- // If user specify a height, call this function
- $scope.setHeight = function() {
- if ( typeof $scope.maxHeight !== 'undefined' ) {
- return 'max-height: ' + $scope.maxHeight + '; overflow-y:scroll';
- }
+ $scope.inputLabel = { labelFilter: '' };
+ $scope.tabIndex = 0;
+ $scope.lang = {};
+ $scope.localModel = [];
+
+ var
+ prevTabIndex = 0,
+ helperItems = [],
+ helperItemsLength = 0,
+ checkBoxLayer = '',
+ scrolled = false,
+ selectedItems = [],
+ formElements = [],
+ vMinSearchLength = 0,
+ clickedItem = null;
+
+ // v3.0.0
+ // clear button clicked
+ $scope.clearClicked = function( e ) {
+ $scope.inputLabel.labelFilter = '';
+ $scope.updateFilter();
+ $scope.select( 'clear', e );
}
// A little hack so that AngularJS ng-repeat can loop using start and end index like a normal loop
@@ -138,65 +190,132 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
return new Array( num );
}
+ // Call this function when user type on the filter field
+ $scope.searchChanged = function() {
+ if ( $scope.inputLabel.labelFilter.length < vMinSearchLength && $scope.inputLabel.labelFilter.length > 0 ) {
+ return false;
+ }
+ $scope.updateFilter();
+ }
+
$scope.updateFilter = function()
- {
- // we check by looping from end of array
- $scope.filteredModel = [];
+ {
+ // we check by looping from end of input-model
+ $scope.filteredModel = [];
var i = 0;
- if ( typeof $scope.inputModel === 'undefined' ) {
- return [];
+ if ( typeof $scope.localModel === 'undefined' ) {
+ return false;
}
- for( i = $scope.inputModel.length - 1; i >= 0; i-- ) {
+ for( i = $scope.localModel.length - 1; i >= 0; i-- ) {
- // if it's group end
- if ( typeof $scope.inputModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.inputModel[ i ][ $scope.groupProperty ] === false ) {
- $scope.filteredModel.push( $scope.inputModel[ i ] );
+ // if it's group end, we push it to filteredModel[];
+ if ( typeof $scope.localModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.localModel[ i ][ $scope.groupProperty ] === false ) {
+ $scope.filteredModel.push( $scope.localModel[ i ] );
}
// if it's data
var gotData = false;
- if ( typeof $scope.inputModel[ i ][ $scope.groupProperty ] === 'undefined' ) {
+ if ( typeof $scope.localModel[ i ][ $scope.groupProperty ] === 'undefined' ) {
+
+ // If we set the search-key attribute, we use this loop.
+ if ( typeof attrs.searchProperty !== 'undefined' && $scope.searchProperty !== '' ) {
+
+ for (var key in $scope.localModel[ i ] ) {
+ if (
+ typeof $scope.localModel[ i ][ key ] !== 'boolean'
+ && String( $scope.localModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0
+ && $scope.searchProperty.indexOf( key ) > -1
+ ) {
+ gotData = true;
+ break;
+ }
+ }
+ }
+ // if there's no search-key attribute, we use this one. Much better on performance.
+ else {
+ for ( var key in $scope.localModel[ i ] ) {
+ if (
+ typeof $scope.localModel[ i ][ key ] !== 'boolean'
+ && String( $scope.localModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0
+ ) {
+ gotData = true;
+ break;
+ }
+ }
+ }
- for (var key in $scope.inputModel[ i ] ) {
- // if filter string is in one of object property
- if ( typeof $scope.inputModel[ i ][ key ] !== 'boolean' && String( $scope.inputModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0 ) {
- gotData = true;
- break;
- }
- }
if ( gotData === true ) {
// push
- $scope.filteredModel.push( $scope.inputModel[ i ] );
+ $scope.filteredModel.push( $scope.localModel[ i ] );
}
}
// if it's group start
- if ( typeof $scope.inputModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.inputModel[ i ][ $scope.groupProperty ] === true ) {
+ if ( typeof $scope.localModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.localModel[ i ][ $scope.groupProperty ] === true ) {
- if ( typeof $scope.filteredModel[ $scope.filteredModel.length - 1 ][ $scope.groupProperty ] !== 'undefined' && $scope.filteredModel[ $scope.filteredModel.length - 1 ][ $scope.groupProperty ] === false ) {
+ if ( typeof $scope.filteredModel[ $scope.filteredModel.length - 1 ][ $scope.groupProperty ] !== 'undefined'
+ && $scope.filteredModel[ $scope.filteredModel.length - 1 ][ $scope.groupProperty ] === false ) {
$scope.filteredModel.pop();
}
else {
- $scope.filteredModel.push( $scope.inputModel[ i ] );
+ $scope.filteredModel.push( $scope.localModel[ i ] );
}
}
}
$scope.filteredModel.reverse();
- $timeout( function() {
+
+ $timeout( function() {
+
$scope.getFormElements();
+
+ // Callback: on filter change
+ if ( $scope.inputLabel.labelFilter.length > vMinSearchLength ) {
+
+ var filterObj = [];
+
+ angular.forEach( $scope.filteredModel, function( value, key ) {
+ if ( typeof value !== 'undefined' ) {
+ if ( typeof value[ $scope.groupProperty ] === 'undefined' ) {
+ var tempObj = angular.copy( value );
+ var index = filterObj.push( tempObj );
+ delete filterObj[ index - 1 ][ $scope.indexProperty ];
+ delete filterObj[ index - 1 ][ $scope.spacingProperty ];
+ }
+ }
+ });
+
+ $scope.onSearchChange({
+ data:
+ {
+ keyword: $scope.inputLabel.labelFilter,
+ result: filterObj
+ }
+ });
+ }
},0);
};
// List all the input elements.
// This function will be called everytime the filter is updated. Not good for performance, but oh well..
- $scope.getFormElements = function() {
- $scope.formElements = [];
- for ( var i = 0; i < element[ 0 ].getElementsByTagName( 'FORM' )[ 0 ].elements.length ; i++ ) {
- $scope.formElements.push( element[ 0 ].getElementsByTagName( 'FORM' )[ 0 ].elements[ i ] );
- }
+ $scope.getFormElements = function() {
+ formElements = [];
+ // Get helper - select & reset buttons
+ var selectButtons = element.children().children().next().children().children()[ 0 ].getElementsByTagName( 'button' );
+ // Get helper - search
+ var inputField = element.children().children().next().children().children().next()[ 0 ].getElementsByTagName( 'input' );
+ // Get helper - clear button
+ var clearButton = element.children().children().next().children().children().next()[ 0 ].getElementsByTagName( 'button' );
+ // Get checkboxes
+ var checkboxes = element.children().children().next().children().next()[ 0 ].getElementsByTagName( 'input' );
+ // Push them into global array formElements[]
+ for ( var i = 0; i < selectButtons.length ; i++ ) { formElements.push( selectButtons[ i ] ); }
+ for ( var i = 0; i < inputField.length ; i++ ) { formElements.push( inputField[ i ] ); }
+ for ( var i = 0; i < clearButton.length ; i++ ) { formElements.push( clearButton[ i ] ); }
+ for ( var i = 0; i < checkboxes.length ; i++ ) { formElements.push( checkboxes[ i ] ); }
+
}
// check if an item has $scope.groupProperty (be it true or false)
@@ -258,33 +377,35 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
}
// call this function when an item is clicked
- $scope.syncItems = function( item, e, ng_repeat_index ) {
+ $scope.syncItems = function( item, e, ng_repeat_index ) {
e.preventDefault();
e.stopPropagation();
- // if it's globaly disabled, then don't do anything
+ // if the directive is globaly disabled, do nothing
if ( typeof attrs.disableProperty !== 'undefined' && item[ $scope.disableProperty ] === true ) {
return false;
}
- // don't change disabled items
+ // if item is disabled, do nothing
if ( typeof attrs.isDisabled !== 'undefined' && $scope.isDisabled === true ) {
return false;
}
- // we don't care about end of group markers
+ // if end group marker is clicked, do nothing
if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === false ) {
return false;
}
- index = $scope.filteredModel.indexOf( item );
+ var index = $scope.filteredModel.indexOf( item );
- // process items if the start of group marker is clicked ( only for multiple selection! )
- // if, in a group, there are items which are not selected, then they all will be selected
- // if, in a group, all items are selected, then they all will be de-selected
+ // if the start of group marker is clicked ( only for multiple selection! )
+ // how it works:
+ // - if, in a group, there are items which are not selected, then they all will be selected
+ // - if, in a group, all items are selected, then they all will be de-selected
if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === true ) {
+ // this is only for multiple selection, so if selection mode is single, do nothing
if ( attrs.selectionMode && $scope.selectionMode.toUpperCase() === 'SINGLE' ) {
return false;
}
@@ -293,16 +414,21 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
var startIndex = 0;
var endIndex = $scope.filteredModel.length - 1;
var tempArr = [];
+
+ // nest level is to mark the depth of the group.
+ // when you get into a group (start group marker), nestLevel++
+ // when you exit a group (end group marker), nextLevel--
var nestLevel = 0;
+ // we loop throughout the filtered model (not whole model)
for( i = index ; i < $scope.filteredModel.length ; i++) {
+ // this break will be executed when we're done processing each group
if ( nestLevel === 0 && i > index )
{
break;
}
- // if group start
if ( typeof $scope.filteredModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.filteredModel[ i ][ $scope.groupProperty ] === true ) {
// To cater multi level grouping
@@ -338,13 +464,13 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
$scope.filteredModel[ j ][ $scope.tickProperty ] = false;
// we refresh input model as well
inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
- $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = false;
+ $scope.localModel[ inputModelIndex ][ $scope.tickProperty ] = false;
}
else if ( $scope.filteredModel[ j ][ $scope.disableProperty ] !== true ) {
$scope.filteredModel[ j ][ $scope.tickProperty ] = false;
// we refresh input model as well
inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
- $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = false;
+ $scope.localModel[ inputModelIndex ][ $scope.tickProperty ] = false;
}
}
}
@@ -357,14 +483,14 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
$scope.filteredModel[ j ][ $scope.tickProperty ] = true;
// we refresh input model as well
inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
- $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = true;
+ $scope.localModel[ inputModelIndex ][ $scope.tickProperty ] = true;
}
else if ( $scope.filteredModel[ j ][ $scope.disableProperty ] !== true ) {
$scope.filteredModel[ j ][ $scope.tickProperty ] = true;
// we refresh input model as well
inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
- $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = true;
+ $scope.localModel[ inputModelIndex ][ $scope.tickProperty ] = true;
}
}
}
@@ -379,7 +505,7 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
}
}
- // single item click
+ // if an item (not group marker) is clicked
else {
// If it's single selection mode
@@ -389,13 +515,14 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
for( i=0 ; i < $scope.filteredModel.length ; i++) {
$scope.filteredModel[ i ][ $scope.tickProperty ] = false;
}
- for( i=0 ; i < $scope.inputModel.length ; i++) {
- $scope.inputModel[ i ][ $scope.tickProperty ] = false;
+ for( i=0 ; i < $scope.localModel.length ; i++) {
+ $scope.localModel[ i ][ $scope.tickProperty ] = false;
}
// then set the clicked item to true
$scope.filteredModel[ index ][ $scope.tickProperty ] = true;
+ // we then hide the checkbox layer
$scope.toggleCheckboxes( e );
}
@@ -405,11 +532,23 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
}
// we refresh input model as well
- inputModelIndex = $scope.filteredModel[ index ][ $scope.indexProperty ];
- $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = $scope.filteredModel[ index ][ $scope.tickProperty ];
+ var inputModelIndex = $scope.filteredModel[ index ][ $scope.indexProperty ];
+ $scope.localModel[ inputModelIndex ][ $scope.tickProperty ] = $scope.filteredModel[ index ][ $scope.tickProperty ];
}
- $scope.clickedItem = angular.copy( item );
+ // we execute the callback function here
+ clickedItem = angular.copy( item );
+ if ( clickedItem !== null ) {
+ $timeout( function() {
+ delete clickedItem[ $scope.indexProperty ];
+ delete clickedItem[ $scope.spacingProperty ];
+ $scope.onItemClick( { data: clickedItem } );
+ clickedItem = null;
+ }, 0 );
+ }
+
+ $scope.refreshOutputModel();
+ $scope.refreshButton();
// We update the index here
prevTabIndex = $scope.tabIndex;
@@ -423,59 +562,52 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
$scope.setFocusStyle( $scope.tabIndex );
}
- // update $scope.selectedItems
- // this variable is used in $scope.outputModel and to refresh the button label
- $scope.refreshSelectedItems = function() {
- $scope.selectedItems = [];
- angular.forEach( $scope.inputModel, function( value, key ) {
+ // update $scope.outputModel
+ $scope.refreshOutputModel = function() {
+
+ $scope.outputModel = [];
+
+ angular.forEach( $scope.localModel, function( value, key ) {
if ( typeof value !== 'undefined' ) {
if ( typeof value[ $scope.groupProperty ] === 'undefined' ) {
if ( value[ $scope.tickProperty ] === true ) {
- $scope.selectedItems.push( value );
+ // selectedItems.push( value );
+ var temp = angular.copy( value );
+ var index = $scope.outputModel.push( temp );
+ delete $scope.outputModel[ index - 1 ][ $scope.indexProperty ];
+ delete $scope.outputModel[ index - 1 ][ $scope.spacingProperty ];
}
}
}
});
}
- // refresh output model as well
- $scope.refreshOutputModel = function() {
- if ( typeof attrs.outputModel !== 'undefined' ) {
- $scope.outputModel = angular.copy( $scope.selectedItems );
- angular.forEach( $scope.outputModel, function( value, key ) {
- // remove the index number and spacing number from output model
- delete value[ $scope.indexProperty ];
- delete value[ $scope.spacingProperty ];
- });
- }
- }
-
// refresh button label
$scope.refreshButton = function() {
$scope.varButtonLabel = '';
- ctr = 0;
+ var ctr = 0;
// refresh button label...
- if ( $scope.selectedItems.length === 0 ) {
+ if ( $scope.outputModel.length === 0 ) {
// https://github.com/isteven/angular-multi-select/pull/19
- $scope.varButtonLabel = ( typeof $scope.defaultLabel !== 'undefined' ) ? $scope.defaultLabel : 'None selected';
+ $scope.varButtonLabel = $scope.lang.nothingSelected;
}
else {
- var tempMaxLabels = $scope.selectedItems.length;
+ var tempMaxLabels = $scope.outputModel.length;
if ( typeof $scope.maxLabels !== 'undefined' && $scope.maxLabels !== '' ) {
tempMaxLabels = $scope.maxLabels;
}
// if max amount of labels displayed..
- if ( $scope.selectedItems.length > tempMaxLabels ) {
+ if ( $scope.outputModel.length > tempMaxLabels ) {
$scope.more = true;
}
else {
$scope.more = false;
}
- angular.forEach( $scope.selectedItems, function( value, key ) {
+ angular.forEach( $scope.outputModel, function( value, key ) {
if ( typeof value !== 'undefined' ) {
if ( ctr < tempMaxLabels ) {
$scope.varButtonLabel += ( $scope.varButtonLabel.length > 0 ? '</div>, <div class="buttonLabel">' : '<div class="buttonLabel">') + $scope.writeLabel( value, 'buttonLabel' );
@@ -489,7 +621,7 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
if (tempMaxLabels > 0) {
$scope.varButtonLabel += ', ... ';
}
- $scope.varButtonLabel += '(Total: ' + $scope.selectedItems.length + ')';
+ $scope.varButtonLabel += '(' + $scope.outputModel.length + ')';
}
}
$scope.varButtonLabel = $sce.trustAsHtml( $scope.varButtonLabel + '<span class="caret"></span>' );
@@ -513,72 +645,59 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
}
- // A simple function to parse the item label settings
+ // A simple function to parse the item label settings. Used on the buttons and checkbox labels.
$scope.writeLabel = function( item, type ) {
- var label = '';
- var temp = $scope[ type ].split( ' ' );
- angular.forEach( temp, function( value2, key2 ) {
- if ( typeof value2 !== 'undefined' ) {
- angular.forEach( item, function( value1, key1 ) {
- if ( key1 == value2 ) {
- label += '&nbsp;' + value1;
- }
- });
- }
+
+ // type is either 'itemLabel' or 'buttonLabel'
+ var temp = $scope[ type ].split( ' ' );
+ var label = '';
+
+ angular.forEach( temp, function( value, key ) {
+ item[ value ] && ( label += '&nbsp;' + value.split( '.' ).reduce( function( prev, current ) {
+ return prev[ current ];
+ }, item ));
});
if ( type.toUpperCase() === 'BUTTONLABEL' ) {
return label;
}
return $sce.trustAsHtml( label );
- }
+ }
// UI operations to show/hide checkboxes based on click event..
- $scope.toggleCheckboxes = function( e ) {
-
- // We grab the checkboxLayer
- $scope.checkBoxLayer = element.children()[1];
-
+ $scope.toggleCheckboxes = function( e ) {
+
// We grab the button
- clickedEl = element.children()[0];
+ var clickedEl = element.children()[0];
// Just to make sure.. had a bug where key events were recorded twice
- angular.element( document ).unbind( 'click', $scope.externalClickListener );
- angular.element( document ).unbind( 'keydown', $scope.keyboardListener );
+ angular.element( document ).off( 'click', $scope.externalClickListener );
+ angular.element( document ).off( 'keydown', $scope.keyboardListener );
// clear filter
$scope.inputLabel.labelFilter = '';
- $scope.updateFilter();
-
- // close if ESC key is pressed.
- if ( e.keyCode === 27 ) {
- angular.element( $scope.checkBoxLayer ).removeClass( 'show' );
- angular.element( clickedEl ).removeClass( 'buttonClicked' );
- angular.element( document ).unbind( 'click', $scope.externalClickListener );
- angular.element( document ).unbind( 'keydown', $scope.keyboardListener );
-
- // clear the focused element;
- $scope.removeFocusStyle( $scope.tabIndex );
-
- // close callback
- $scope.onClose( { data: element } );
- return true;
- }
+ $scope.updateFilter();
// The idea below was taken from another multi-select directive - https://github.com/amitava82/angular-multiselect
- // His version is awesome if you need a more simple multi-select approach.
+ // His version is awesome if you need a more simple multi-select approach.
// close
- if ( angular.element( $scope.checkBoxLayer ).hasClass( 'show' )) {
- angular.element( $scope.checkBoxLayer ).removeClass( 'show' );
+ if ( angular.element( checkBoxLayer ).hasClass( 'show' )) {
+
+ angular.element( checkBoxLayer ).removeClass( 'show' );
angular.element( clickedEl ).removeClass( 'buttonClicked' );
- angular.element( document ).unbind( 'click', $scope.externalClickListener );
- angular.element( document ).unbind( 'keydown', $scope.keyboardListener );
+ angular.element( document ).off( 'click', $scope.externalClickListener );
+ angular.element( document ).off( 'keydown', $scope.keyboardListener );
// clear the focused element;
$scope.removeFocusStyle( $scope.tabIndex );
// close callback
- $scope.onClose( { data: element } );
+ $timeout( function() {
+ $scope.onClose();
+ }, 0 );
+
+ // set focus on button again
+ element.children().children()[ 0 ].focus();
}
// open
else
@@ -586,10 +705,13 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
helperItems = [];
helperItemsLength = 0;
- angular.element( $scope.checkBoxLayer ).addClass( 'show' );
- angular.element( clickedEl ).addClass( 'buttonClicked' );
- angular.element( document ).bind( 'click', $scope.externalClickListener );
- angular.element( document ).bind( 'keydown', $scope.keyboardListener );
+ angular.element( checkBoxLayer ).addClass( 'show' );
+ angular.element( clickedEl ).addClass( 'buttonClicked' );
+
+ // Attach change event listener on the input filter.
+ // We need this because ng-change is apparently not an event listener.
+ angular.element( document ).on( 'click', $scope.externalClickListener );
+ angular.element( document ).on( 'keydown', $scope.keyboardListener );
// to get the initial tab index, depending on how many helper elements we have.
// priority is to always focus it on the input filter
@@ -599,7 +721,7 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
var helperContainer = angular.element( element[ 0 ].querySelector( '.helperContainer' ) )[0];
if ( typeof helperContainer !== 'undefined' ) {
- for ( i = 0; i < helperContainer.getElementsByTagName( 'BUTTON' ).length ; i++ ) {
+ for ( var i = 0; i < helperContainer.getElementsByTagName( 'BUTTON' ).length ; i++ ) {
helperItems[ i ] = helperContainer.getElementsByTagName( 'BUTTON' )[ i ];
}
helperItemsLength = helperItems.length + helperContainer.getElementsByTagName( 'INPUT' ).length;
@@ -607,57 +729,47 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
// focus on the filter element on open.
if ( element[ 0 ].querySelector( '.inputFilter' ) ) {
- element[ 0 ].querySelector( '.inputFilter' ).focus();
+ element[ 0 ].querySelector( '.inputFilter' ).focus();
$scope.tabIndex = $scope.tabIndex + helperItemsLength - 2;
}
// if there's no filter then just focus on the first checkbox item
else {
- $scope.formElements[ $scope.tabIndex ].focus();
+ formElements[ $scope.tabIndex ].focus();
}
// open callback
- $scope.onOpen( { data: element } );
+ $scope.onOpen();
}
}
// handle clicks outside the button / multi select layer
$scope.externalClickListener = function( e ) {
- targetsArr = element.find( e.target.tagName );
+
+ var targetsArr = element.find( e.target.tagName );
for (var i = 0; i < targetsArr.length; i++) {
if ( e.target == targetsArr[i] ) {
return;
}
}
- angular.element( $scope.checkBoxLayer.previousSibling ).removeClass( 'buttonClicked' );
- angular.element( $scope.checkBoxLayer ).removeClass( 'show' );
- angular.element( document ).unbind( 'click', $scope.externalClickListener );
- angular.element( document ).unbind( 'keydown', $scope.keyboardListener );
+ angular.element( checkBoxLayer.previousSibling ).removeClass( 'buttonClicked' );
+ angular.element( checkBoxLayer ).removeClass( 'show' );
+ angular.element( document ).off( 'click', $scope.externalClickListener );
+ angular.element( document ).off( 'keydown', $scope.keyboardListener );
// close callback
$timeout( function() {
- $scope.onClose( { data: element } );
+ $scope.onClose();
}, 0 );
+
+ // set focus on button again
+ element.children().children()[ 0 ].focus();
}
- // traverse up to find the button tag
- // http://stackoverflow.com/questions/7332179/how-to-recursively-search-all-parentnodes
- $scope.findUpTag = function ( el, tag, className ) {
- while ( el.parentNode ) {
- el = el.parentNode;
- if ( typeof el.tagName !== 'undefined' ) {
- if ( el.tagName.toUpperCase() === tag.toUpperCase() && el.className.indexOf( className ) > -1 ) {
- return el;
- }
- }
- }
- return null;
- }
-
// select All / select None / reset buttons
$scope.select = function( type, e ) {
- helperIndex = helperItems.indexOf( e.target );
+ var helperIndex = helperItems.indexOf( e.target );
$scope.tabIndex = helperIndex;
switch( type.toUpperCase() ) {
@@ -668,7 +780,10 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
value[ $scope.tickProperty ] = true;
}
}
- });
+ });
+ $scope.refreshOutputModel();
+ $scope.refreshButton();
+ $scope.onSelectAll();
break;
case 'NONE':
angular.forEach( $scope.filteredModel, function( value, key ) {
@@ -678,17 +793,24 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
}
}
});
+ $scope.refreshOutputModel();
+ $scope.refreshButton();
+ $scope.onSelectNone();
break;
case 'RESET':
angular.forEach( $scope.filteredModel, function( value, key ) {
if ( typeof value[ $scope.groupProperty ] === 'undefined' && typeof value !== 'undefined' && value[ $scope.disableProperty ] !== true ) {
- temp = value[ $scope.indexProperty ];
+ var temp = value[ $scope.indexProperty ];
value[ $scope.tickProperty ] = $scope.backUp[ temp ][ $scope.tickProperty ];
}
});
+ $scope.refreshOutputModel();
+ $scope.refreshButton();
+ $scope.onReset();
break;
case 'CLEAR':
$scope.tabIndex = $scope.tabIndex + 1;
+ $scope.onClear();
break;
case 'FILTER':
$scope.tabIndex = helperItems.length - 1;
@@ -698,7 +820,7 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
}
// just to create a random variable name
- genRandomString = function( length ) {
+ function genRandomString( length ) {
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var temp = '';
for( var i=0; i < length; i++ ) {
@@ -723,7 +845,7 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
// prepare original index
$scope.prepareIndex = function() {
- ctr = 0;
+ var ctr = 0;
angular.forEach( $scope.filteredModel, function( value, key ) {
value[ $scope.indexProperty ] = ctr;
ctr++;
@@ -734,28 +856,30 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
$scope.keyboardListener = function( e ) {
var key = e.keyCode ? e.keyCode : e.which;
- var isNavigationKey = false;
+ var isNavigationKey = false;
// ESC key (close)
if ( key === 27 ) {
+ e.preventDefault();
$scope.toggleCheckboxes( e );
}
// next element ( tab, down & right key )
else if ( key === 40 || key === 39 || ( !e.shiftKey && key == 9 ) ) {
+
isNavigationKey = true;
prevTabIndex = $scope.tabIndex;
$scope.tabIndex++;
- if ( $scope.tabIndex > $scope.formElements.length - 1 ) {
+ if ( $scope.tabIndex > formElements.length - 1 ) {
$scope.tabIndex = 0;
- prevTabIndex = $scope.formElements.length - 1;
+ prevTabIndex = formElements.length - 1;
}
- while ( $scope.formElements[ $scope.tabIndex ].disabled === true ) {
+ while ( formElements[ $scope.tabIndex ].disabled === true ) {
$scope.tabIndex++;
- if ( $scope.tabIndex > $scope.formElements.length - 1 ) {
+ if ( $scope.tabIndex > formElements.length - 1 ) {
$scope.tabIndex = 0;
}
- }
+ }
}
// prev element ( shift+tab, up & left key )
@@ -764,28 +888,26 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
prevTabIndex = $scope.tabIndex;
$scope.tabIndex--;
if ( $scope.tabIndex < 0 ) {
- $scope.tabIndex = $scope.formElements.length - 1;
+ $scope.tabIndex = formElements.length - 1;
prevTabIndex = 0;
}
- while ( $scope.formElements[ $scope.tabIndex ].disabled === true ) {
+ while ( formElements[ $scope.tabIndex ].disabled === true ) {
$scope.tabIndex--;
if ( $scope.tabIndex < 0 ) {
- $scope.tabIndex = $scope.formElements.length - 1;
+ $scope.tabIndex = formElements.length - 1;
}
}
- }
+ }
- if ( isNavigationKey === true ) {
+ if ( isNavigationKey === true ) {
+
e.preventDefault();
- e.stopPropagation();
// set focus on the checkbox
- $scope.formElements[ $scope.tabIndex ].focus();
+ formElements[ $scope.tabIndex ].focus();
+ var actEl = document.activeElement;
- // css styling
- var actEl = document.activeElement;
-
if ( actEl.type.toUpperCase() === 'CHECKBOX' ) {
$scope.setFocusStyle( $scope.tabIndex );
$scope.removeFocusStyle( prevTabIndex );
@@ -793,7 +915,7 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
else {
$scope.removeFocusStyle( prevTabIndex );
$scope.removeFocusStyle( helperItemsLength );
- $scope.removeFocusStyle( $scope.formElements.length - 1 );
+ $scope.removeFocusStyle( formElements.length - 1 );
}
}
@@ -801,63 +923,101 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
}
// set (add) CSS style on selected row
- $scope.setFocusStyle = function( tabIndex ) {
- angular.element( $scope.formElements[ tabIndex ] ).parent().parent().parent().addClass( 'multiSelectFocus' );
+ $scope.setFocusStyle = function( tabIndex ) {
+ angular.element( formElements[ tabIndex ] ).parent().parent().parent().addClass( 'multiSelectFocus' );
}
// remove CSS style on selected row
$scope.removeFocusStyle = function( tabIndex ) {
- angular.element( $scope.formElements[ tabIndex ] ).parent().parent().parent().removeClass( 'multiSelectFocus' );
+ angular.element( formElements[ tabIndex ] ).parent().parent().parent().removeClass( 'multiSelectFocus' );
}
- ///////////////////////////////////////////////////////
- //
- // Logic starts here, initiated by watch 1 & watch 2.
- //
- ///////////////////////////////////////////////////////
-
+ /*****************************************************
+ *
+ * Initializations
+ *
+ *****************************************************/
+
+ // Unfortunately I need to add these grouping properties
var tempStr = genRandomString( 5 );
$scope.indexProperty = 'idx_' + tempStr;
$scope.spacingProperty = 'spc_' + tempStr;
// set orientation css
if ( typeof attrs.orientation !== 'undefined' ) {
+
if ( attrs.orientation.toUpperCase() === 'HORIZONTAL' ) {
$scope.orientationH = true;
$scope.orientationV = false;
}
- else {
+ else
+ {
$scope.orientationH = false;
$scope.orientationV = true;
}
}
+
+ // get elements required for DOM operation
+ checkBoxLayer = element.children().children().next()[0];
+
+ // set max-height property if provided
+ if ( typeof attrs.maxHeight !== 'undefined' ) {
+ var layer = element.children().children().children()[0];
+ angular.element( layer ).attr( "style", "height:" + $scope.maxHeight + "; overflow-y:scroll;" );
+ }
+
+ // icons.. I guess you can use <img> tag here if you want to.
+ var icon = {};
+ icon.selectAll = '&#10003;' // a tick icon
+ icon.selectNone = '&times;' // x icon
+ icon.reset = '&#8630;' // undo icon
+
+ // configurable button labels
+ if ( typeof attrs.translation !== 'undefined' ) {
+ $scope.lang.selectAll = $sce.trustAsHtml( icon.selectAll + '&nbsp;&nbsp;' + $scope.translation.selectAll );
+ $scope.lang.selectNone = $sce.trustAsHtml( icon.selectNone + '&nbsp;&nbsp;' + $scope.translation.selectNone );
+ $scope.lang.reset = $sce.trustAsHtml( icon.reset + '&nbsp;&nbsp;' + $scope.translation.reset );
+ $scope.lang.search = $scope.translation.search;
+ $scope.lang.nothingSelected = $sce.trustAsHtml( $scope.translation.nothingSelected );
+ }
+ else {
+ $scope.lang.selectAll = $sce.trustAsHtml( icon.selectAll + '&nbsp;&nbsp;Select All' );
+ $scope.lang.selectNone = $sce.trustAsHtml( icon.selectNone + '&nbsp;&nbsp;Select None' );
+ $scope.lang.reset = $sce.trustAsHtml( icon.reset + '&nbsp;&nbsp;Reset' );
+ $scope.lang.search = 'Search...';
+ $scope.lang.nothingSelected = 'None Selected';
+ }
+
+ // min length of keyword to trigger the filter function
+ if ( typeof attrs.MinSearchLength !== 'undefined' && parseInt( attrs.MinSearchLength ) > 0 ) {
+ vMinSearchLength = Math.floor( parseInt( attrs.MinSearchLength ) );
+ }
+
+ /****************************************************
+ *
+ * Logic starts here, initiated by watch 1 & watch 2
+ *
+ ****************************************************/
// watch1, for changes in input model property
// updates multi-select when user select/deselect a single checkbox programatically
// https://github.com/isteven/angular-multi-select/issues/8
$scope.$watch( 'inputModel' , function( newVal ) {
- if ( newVal ) {
- $scope.refreshSelectedItems();
- $scope.refreshOutputModel();
- $scope.refreshButton();
- if ( $scope.clickedItem !== null ) {
- $timeout( function() {
- $scope.onItemClick( { data: $scope.clickedItem } );
- $scope.clickedItem = null;
- }, 0 );
- }
+ if ( newVal ) {
+ $scope.localModel = angular.copy( $scope.inputModel );
+ $scope.refreshOutputModel();
+ $scope.refreshButton();
}
- }, true);
+ }, true );
// watch2 for changes in input model as a whole
// this on updates the multi-select when a user load a whole new input-model. We also update the $scope.backUp variable
- $scope.$watch( 'inputModel' , function( newVal ) {
+ $scope.$watch( 'localModel' , function( newVal ) {
if ( newVal ) {
- $scope.backUp = angular.copy( $scope.inputModel );
+ $scope.backUp = angular.copy( $scope.localModel );
$scope.updateFilter();
$scope.prepareGrouping();
- $scope.prepareIndex();
- $scope.refreshSelectedItems();
+ $scope.prepareIndex();
$scope.refreshOutputModel();
$scope.refreshButton();
}
@@ -869,32 +1029,19 @@ angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$
});
// this is for touch enabled devices. We don't want to hide checkboxes on scroll.
- angular.element( document ).bind( 'touchstart', function( e ) {
+ angular.element( document ).on( 'touchstart', function( e ) {
$scope.$apply( function() {
- $scope.scrolled = false;
+ scrolled = false;
});
});
// also for touch enabled devices
- angular.element( document ).bind( 'touchmove', function( e ) {
+ angular.element( document ).on( 'touchmove', function( e ) {
$scope.$apply( function() {
- $scope.scrolled = true;
+ scrolled = true;
});
- });
-
- // for IE8, perhaps. Not sure if this is really executed.
- if ( !Array.prototype.indexOf ) {
- Array.prototype.indexOf = function(what, i) {
- i = i || 0;
- var L = this.length;
- while (i < L) {
- if(this[i] === what) return i;
- ++i;
- }
- return -1;
- };
- }
- }
+ });
+ }
}
}]);