/div>'
59 | }
60 | }
61 | })
62 | .state('app.state1', {
63 | url: 'state1',
64 | templateUrl: 'state1.html',
65 | controller: 'state1Ctrl',
66 | controllerAs: 'vm',
67 | reloadOnSearch: false
68 | })
69 | }]);
70 |
71 | myApp.run(['$log', function ($log) {
72 | $log.log("Start.");
73 | }]);
74 | })()
--------------------------------------------------------------------------------
/demo/dateBeforeAfter/style.css:
--------------------------------------------------------------------------------
1 | small {
2 | font-size: 0.625em;
3 | font-style: italic;
4 | color: #a94442;
5 | display: block;
6 | float:right;
7 | }
8 |
9 | input {
10 | display:block !important;
11 | }
12 |
13 | label {
14 | display:block;
15 | }
16 |
17 | .container {
18 | width: auto !important;
19 | }
20 |
21 | .form-inline .form-group {
22 | display:inline-block;
23 | width: auto;
24 | }
25 |
26 | input {
27 | width: 250px !important;
28 | }
--------------------------------------------------------------------------------
/demo/isState/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
65 |
66 |
--------------------------------------------------------------------------------
/demo/isState/script.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | angular.module('myApp.controllers', []);
3 | angular.module('myApp.services', ['ngResource', 'ngAnimate']);
4 |
5 | var myApp = angular.module('myApp', [
6 | 'myApp.controllers',
7 | 'myApp.services',
8 | 'long2know',
9 | 'ngSanitize',
10 | 'ui.bootstrap',
11 | 'ui.router',
12 | 'ui']);
13 |
14 | var myController = function ($scope, $timeout, $animate, $log) {
15 | var vm = this,
16 | initItems = function () {
17 | vm.items = [];
18 | vm.items2 = [];
19 | for (var i = 1; i < 6; i++) {
20 | vm.items.push({ id: 'list1_' + i.toString(), name: "Item1 " + i.toString() });
21 | vm.items2.push({ id: 'list2_' + i.toString(), name: "Item2 " + i.toString() });
22 | }
23 | },
24 | init = function () {
25 | vm.timeout = 3000;
26 | vm.className = 'is-state';
27 | $timeout(function () { setTimeout(initItems(), 2000) }, 0);
28 | };
29 |
30 | vm.addItem = function () {
31 | vm.items.push({ name: "Item " + vm.items.length.toString() });
32 | };
33 |
34 | vm.itemListClick = function (item) {
35 | $timeout(function () { item.isSelected = false; }, vm.timeout);
36 | };
37 |
38 | init();
39 | };
40 |
41 | myController.$inject = ['$scope', '$timeout', '$animate', '$log'];
42 | angular.module('myApp.controllers')
43 | .controller('myCtrl', myController);
44 |
45 | myApp.run(['$log', function ($log) { $log.log("Start."); }]);
46 | })()
--------------------------------------------------------------------------------
/demo/isState/style.css:
--------------------------------------------------------------------------------
1 | .is-state,
2 | .is-state-add.is-state-add-active {
3 | color: #FFFFFF;
4 | background-color: #a94442 !important;
5 | }
6 |
7 | .is-state a, .is-state-add.is-state-add-active a {
8 | color: #FFFFFF !important;
9 | }
10 |
11 | .is-state-remove.is-state-remove-active {
12 | background-color: #FFFFFF !important;
13 | }
14 |
15 | .is-state-add, .is-state-remove {
16 | -webkit-transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
17 | -moz-transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
18 | -o-transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
19 | transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
20 | }
--------------------------------------------------------------------------------
/demo/multiselect/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
26 |
35 |
125 |
126 |
--------------------------------------------------------------------------------
/demo/multiselect/script.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | angular.module('myApp.controllers', []);
3 |
4 | var myApp = angular.module('myApp', [
5 | 'myApp.controllers',
6 | 'long2know',
7 | 'ngSanitize',
8 | 'ui.bootstrap',
9 | 'ui.router',
10 | 'ui']);
11 |
12 | var state1Ctrl = function () {
13 | var vm = this,
14 | getRandomInt = function(min, max) {
15 | return Math.floor(Math.random() * (max - min + 1) + min);
16 | };
17 |
18 | vm.options1 = [];
19 | for (var i = 0; i < 10; i++) {
20 | vm.options1.push({ key: i + 1, value: 'Prop' + (i + 1).toString() });
21 | }
22 |
23 | vm.options2 = [];
24 | for (var i = 0; i < 100; i++) {
25 | vm.options2.push({ key: i + 1, value: 'Prop' + (i + 1).toString() });
26 | }
27 |
28 | vm.option6 = 3;
29 | vm.option7 = [4, 11, 23];
30 |
31 | vm.clear = function() {
32 | vm.option1 = [];
33 | vm.option2 = [];
34 | vm.option3 = [];
35 | vm.option4 = [];
36 | vm.option5 = [];
37 | vm.option6 = [];
38 | vm.option7 = [];
39 | };
40 |
41 | vm.randomSelect = function() {
42 | vm.clear();
43 | var arrSelected = [ vm.option1, vm.option2, vm.option3, vm.option4, vm.option5, vm.option6, vm.option7];
44 | var arrOptions = [ vm.options1, vm.options2, vm.options2, vm.options1, vm.options1, vm.options1, vm.options2 ];
45 | var arrIsSingle = [ false, false, false, true, false, false, false ];
46 | var arrIsSimple = [ true, true, false, false, true, true, true ];
47 |
48 | for (var i = 0; i < arrSelected.length; i++) {
49 | var selected = arrSelected[i];
50 | var options = arrOptions[i];
51 | var isSingle = arrIsSingle[i];
52 | var isSimple = arrIsSimple[i];
53 | var min = 0;
54 | var max = options.length - 1;
55 | if (isSingle) {
56 | var randIndex = getRandomInt(min, max);
57 | if (isSimple) {
58 | selected.push(options[randIndex].key);
59 | } else {
60 | selected.push(options[randIndex]);
61 | }
62 | }
63 | else
64 | {
65 | var toSelectIndexes = [];
66 | var numItems = getRandomInt(0, options.length) + 1;
67 | for (var j = 0; j < getRandomInt(1, numItems); j++)
68 | {
69 | var randIndex = getRandomInt(min, max);
70 | var arrIndex = toSelectIndexes.indexOf(randIndex);
71 | if (arrIndex == -1) {
72 | toSelectIndexes.push(randIndex);
73 | if (isSimple) {
74 | selected.push(options[randIndex].key);
75 | } else {
76 | selected.push(options[randIndex]);
77 | }
78 | }
79 | }
80 | }
81 | }
82 | }
83 | };
84 |
85 | state1Ctrl.$inject = [];
86 |
87 | angular.module('myApp.controllers')
88 | .controller('state1Ctrl', state1Ctrl);
89 |
90 | myApp.config(['$locationProvider', '$stateProvider', '$urlRouterProvider',
91 |
92 | function ($locationProvider, $stateProvider, $urlRouterProvider) {
93 |
94 | $locationProvider.html5Mode(false);
95 |
96 | $urlRouterProvider.when('/', '/state1')
97 | .otherwise("/state1");
98 |
99 | $stateProvider.state('app', {
100 | abstract: true,
101 | url: '/',
102 | views: {
103 | 'main': {
104 | template: '
/div>'
105 | }
106 | }
107 | })
108 | .state('app.state1', {
109 | url: 'state1',
110 | templateUrl: 'state1.html',
111 | controller: 'state1Ctrl',
112 | controllerAs: 'vm',
113 | reloadOnSearch: false
114 | })
115 | }]);
116 |
117 | myApp.run(['$log', function ($log) {
118 | $log.log("Start.");
119 | }]);
120 | })()
--------------------------------------------------------------------------------
/demo/multiselect/style.css:
--------------------------------------------------------------------------------
1 | multiselect {
2 | display: block;
3 | }
4 |
5 | multiselect > .btn-group {
6 | min-width: 180px;
7 | }
8 |
9 | multiselect .btn {
10 | width: 100%;
11 | background-color: #FFF;
12 | }
13 |
14 | multiselect .btn.has-error {
15 | border: 1px solid #a94442 !important;
16 | color: #db524b;
17 | }
18 |
19 | multiselect .dropdown-menu {
20 | max-height: 300px;
21 | min-width: 200px;
22 | overflow-y: auto;
23 | }
24 |
25 | multiselect .dropdown-menu .filter > input {
26 | width: 99%;
27 | }
28 |
29 | multiselect .dropdown-menu .filter .glyphicon {
30 | cursor: pointer;
31 | pointer-events: all;
32 | }
33 |
34 | multiselect .dropdown-menu {
35 | width: 100%;
36 | box-sizing: border-box;
37 | padding: 2px;
38 | }
39 |
40 | multiselect > .btn-group > button {
41 | padding-right: 20px;
42 | }
43 |
44 | multiselect > .btn-group > button > .caret {
45 | right: 5px;
46 | top: 45%;
47 | position: absolute;
48 | }
49 |
50 | multiselect > .btn-group:not(.dropup) > button > .caret {
51 | border-left: 4px solid transparent;
52 | border-right: 4px solid transparent;
53 | border-top: 4px solid black;
54 | }
55 |
56 | multiselect .dropdown-menu > li > a {
57 | padding: 3px 10px;
58 | cursor: pointer;
59 | }
60 |
61 | multiselect .dropdown-menu > li > a i {
62 | margin-right: 4px;
63 | }
64 |
65 | .glyphicon-none:before {
66 | content: "\e013";
67 | color: transparent !important;
68 | }
--------------------------------------------------------------------------------
/demo/onRepeatFinish/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
OnNgRepeat Demo w/ Custom Animation
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
31 |
32 |
--------------------------------------------------------------------------------
/demo/onRepeatFinish/script.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | angular.module('myApp.controllers', []);
3 | angular.module('myApp.services', ['ngResource', 'ngAnimate']);
4 |
5 | var myApp = angular.module('myApp', [
6 | 'myApp.controllers',
7 | 'myApp.services',
8 | 'long2know',
9 | 'ngSanitize',
10 | 'ui.bootstrap',
11 | 'ui.router',
12 | 'ui']);
13 |
14 | var myController = function ($scope, $timeout, $animate, $log) {
15 | var vm = this,
16 | initItems = function () {
17 | vm.items = [];
18 | for (var i = 0; i < 10; i++) {
19 | vm.items.push({ name: "Item " + i.toString() });
20 | }
21 | },
22 | init = function () {
23 | $timeout(function () { setTimeout(initItems(), 2000) }, 0);
24 | };
25 |
26 | vm.addItem = function () {
27 | vm.items.push({ name: "Item " + vm.items.length.toString() });
28 | };
29 |
30 | $animate.enabled(false);
31 |
32 | var listener = $scope.$on('ngRepeatFinished', function () {
33 | $log.log("Message received - turning animations on");
34 | $animate.enabled(true);
35 |
36 | $log.log("Unregistering listener");
37 | listener();
38 | listener = null;
39 | });
40 |
41 | init();
42 | };
43 |
44 | var newItem = function ($timeout, $log) {
45 | var animation = {
46 | enter: function (element, done) {
47 | element.addClass('new-item');
48 | $timeout(function () {
49 | element.removeClass('new-item');
50 | done();
51 | }, 2000);
52 | }
53 | };
54 |
55 | return animation;
56 | };
57 |
58 | newItem.$inject = ['$timeout', '$log'];
59 | angular.module('myApp.services')
60 | .animation('.new-list-item', newItem);
61 |
62 | myController.$inject = ['$scope', '$timeout', '$animate', '$log'];
63 | angular.module('myApp.controllers')
64 | .controller('myCtrl', myController);
65 |
66 | myApp.run(['$log', function ($log) { $log.log("Start."); }]);
67 | })()
--------------------------------------------------------------------------------
/demo/onRepeatFinish/style.css:
--------------------------------------------------------------------------------
1 | .itemlist {
2 | list-style: none;
3 | margin: 0;
4 | padding: 0;
5 | background-color: white;
6 | max-height:90%;
7 | width: 100%;
8 | }
9 |
10 | .itemlist > li {
11 | line-height: 1.4em;
12 | background-color: white;
13 | color: black;
14 | cursor: pointer;
15 | padding: 6px 0 6px 5px;
16 | }
17 |
18 | .new-item {
19 | background-color: red !important;
20 | overflow: none;
21 | }
--------------------------------------------------------------------------------
/demo/triStateCheckbox/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Tri-State Checkbox Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
47 |
48 |
--------------------------------------------------------------------------------
/demo/triStateCheckbox/script.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | angular.module('myApp.controllers', []);
3 |
4 | var myApp = angular.module('myApp', [
5 | 'myApp.controllers',
6 | 'long2know',
7 | 'ngSanitize',
8 | 'ui.bootstrap',
9 | 'ui.router',
10 | 'ui']);
11 |
12 | var myController = function ($scope, $timeout, $animate, $log) {
13 | var vm = this,
14 | initItems = function () {
15 | vm.items = [];
16 | for (var i = 0; i < 5; i++) {
17 | vm.items.push({ id: vm.items.length, name: "Item " + i.toString() });
18 | }
19 | },
20 | init = function () {
21 | initItems();
22 | };
23 |
24 | vm.addItem = function () {
25 | vm.items.push({ id: vm.items.length, name: "Item " + vm.items.length.toString() });
26 | vm.selectionChanged(vm.items[vm.items.length - 1]);
27 | };
28 |
29 | vm.selectionChanged = function (item) {
30 | $scope.$broadcast("childClick", item);
31 | };
32 |
33 | init();
34 | };
35 |
36 | myController.$inject = ['$scope', '$timeout', '$animate', '$log'];
37 | angular.module('myApp.controllers')
38 | .controller('myCtrl', myController);
39 |
40 | myApp.config(['$modalProvider', '$locationProvider',
41 | function ($modalProvider, $locationProvider) {
42 | $modalProvider.options = { dialogFade: true, backdrop: 'static', keyboard: false };
43 | $locationProvider.html5Mode(false);
44 | }]);
45 |
46 | myApp.run(['$log', function ($log) { $log.log("Start."); }]);
47 | })()
--------------------------------------------------------------------------------
/demo/triStateCheckbox/style.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/long2know/angular-directives-general/da310867ec6e2c098f9763e1d679f7ef503a50a3/demo/triStateCheckbox/style.css
--------------------------------------------------------------------------------
/src/clipboardService.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var long2know;
3 | try {
4 | long2know = angular.module("long2know")
5 | } catch (err) {
6 | long2know = null;
7 | }
8 |
9 | if (!long2know) {
10 | angular.module('long2know.services', ['ngResource', 'ngAnimate']);
11 | angular.module('long2know.controllers', []);
12 | angular.module('long2know.directives', []);
13 | angular.module('long2know.constants', []);
14 | angular.module('long2know',
15 | [
16 | 'long2know.services',
17 | 'long2know.controllers',
18 | 'long2know.directives',
19 | 'long2know.constants'
20 | ]);
21 | }
22 |
23 | var clipboardService = function ($q, $sce, $window, dialogService, toastService) {
24 | var
25 | body = angular.element($window.document.body),
26 | textarea = angular.element('
');
27 | textarea.css({ position: 'fixed', opacity: '0' });
28 | var
29 | copy = function (value) {
30 | textarea.val(value);
31 | body.append(textarea);
32 | textarea[0].select();
33 |
34 | try {
35 | var successful = document.execCommand('copy');
36 | if (!successful) throw successful;
37 | toastService.success("Copied to clipboard!");
38 | } catch (err) {
39 | var
40 | errorTitle = "Error copying to clipboard",
41 | errorBody = "There was an error copying to the clipboard. Select the text to copy and use Ctrl+C.";
42 |
43 | dialogService.openDialog("modalError.html", ['$scope', '$uibModalInstance', function ($scope, $uibModalInstance) {
44 | $scope.modalHeader = $sce.trustAsHtml(errorTitle);
45 | $scope.modalBody = $sce.trustAsHtml(dialogService.stringFormat("
{0}
", errorBody));
46 | $scope.ok = function () {
47 | $uibModalInstance.close();
48 | };
49 | $scope.hasCancel = false;
50 | }]);
51 | }
52 |
53 | textarea.remove();
54 | };
55 |
56 | return {
57 | copy: copy
58 | };
59 | };
60 |
61 | clipboardService.$inject = ['$q', '$sce', '$window', 'dialogService', 'toastService'];
62 | angular.module('long2know.services')
63 | .factory('clipboardService', clipboardService);
64 | })();
--------------------------------------------------------------------------------
/src/copytoClipboard.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var long2know;
3 | try {
4 | long2know = angular.module("long2know")
5 | } catch (err) {
6 | long2know = null;
7 | }
8 |
9 | if (!long2know) {
10 | angular.module('long2know.services', ['ngResource', 'ngAnimate']);
11 | angular.module('long2know.controllers', []);
12 | angular.module('long2know.directives', []);
13 | angular.module('long2know.constants', []);
14 | angular.module('long2know',
15 | [
16 | 'long2know.services',
17 | 'long2know.controllers',
18 | 'long2know.directives',
19 | 'long2know.constants'
20 | ]);
21 | }
22 | var copyToClipboard = function (clipboardService) {
23 | var directive = {
24 | restrict: 'A',
25 | //scope: {
26 | // ngModel: '='
27 | //},
28 | require: ['?ngModel'],
29 | link: function (scope, el, attrs, ctrls) {
30 | el.on('click', function () {
31 | var value = undefined;
32 | if (ctrls && ctrls.length > 0 && ctrls[0]) {
33 | var ngModelCtrl = ctrls[0];
34 | value = ngModelCtrl.$viewValue;
35 | } else {
36 | value = scope.$eval(attrs.copyToClipboard);
37 | }
38 |
39 | clipboardService.copy(value);
40 | });
41 | }
42 | };
43 |
44 | return directive;
45 | };
46 |
47 | copyToClipboard.$inject = ['clipboardService'];
48 | angular.module("long2know.directives")
49 | .directive('copyToClipboard', copyToClipboard);
50 | })()
--------------------------------------------------------------------------------
/src/customselect.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var long2know;
3 | try {
4 | long2know = angular.module("long2know")
5 | } catch (err) {
6 | long2know = null;
7 | }
8 |
9 | if (!long2know) {
10 | angular.module('long2know.services', ['ngResource', 'ngAnimate']);
11 | angular.module('long2know.controllers', []);
12 | angular.module('long2know.directives', []);
13 | angular.module('long2know.constants', []);
14 | angular.module('long2know',
15 | [
16 | 'long2know.services',
17 | 'long2know.controllers',
18 | 'long2know.directives',
19 | 'long2know.constants'
20 | ]);
21 | }
22 |
23 | var customselectParser = function ($parse) {
24 | // 00000111000000000000022200000000000000003333333333333330000000000044000
25 | var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
26 |
27 | return {
28 | parse: function (input) {
29 |
30 | var match = input.match(TYPEAHEAD_REGEXP);
31 | if (!match) {
32 | throw new Error(
33 | 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
34 | ' but got "' + input + '".');
35 | }
36 |
37 | return {
38 | itemName: match[3],
39 | source: $parse(match[4]),
40 | viewMapper: $parse(match[2] || match[1]),
41 | modelMapper: $parse(match[1])
42 | };
43 | }
44 | };
45 | };
46 |
47 | var customselect = function ($q, $timeout, $parse, $filter, $document, $window, $position, $compile, optionParser) {
48 | return {
49 | restrict: 'EA',
50 | require: ['ngModel', '?^form'],
51 | link: function (originalScope, element, attrs, ctrls) {
52 | var modelCtrl = ctrls[0];
53 | var formCtrl = (ctrls.length > 1 && typeof (ctrls[1]) !== 'undefined') ? ctrls[1] : null;
54 |
55 | //model setter executed upon match selection
56 | var $setModelValue = $parse(attrs.ngModel).assign;
57 |
58 | var
59 | parserResult = optionParser.parse(attrs.options),
60 | isMultiple = attrs.multiple ? originalScope.$eval(attrs.multiple) : false,
61 | isAutoFocus = attrs.autoFocus ? originalScope.$eval(attrs.autoFocus) : false,
62 | isComplex = attrs.complexModels ? originalScope.$eval(attrs.complexModels) : false,
63 | enableFilter = attrs.enableFilter ? originalScope.$eval(attrs.enableFilter) : true,
64 | header = attrs.header ? attrs.header : "Select",
65 | selectedHeader = attrs.selectedHeader ? attrs.selectedHeader : 'selected',
66 | selectLimit = attrs.selectLimit ? originalScope.$eval(attrs.selectLimit) : 0,
67 | useFiltered = attrs.selectLimitUseFiltered ? originalScope.$eval(attrs.selectLimitUseFiltered) : true,
68 | filterPlaceholder = attrs.filterPlaceholder ? attrs.filterPlaceholder : "Filter ..",
69 | required = false,
70 | lastSelectedLabel = '',
71 | scope = originalScope.$new(true),
72 | changeHandler = attrs.change || angular.noop,
73 | onSelectCallback = $parse(attrs.onSelect),
74 | onCustomCallback = $parse(attrs.customButtonClick),
75 | hasCustomButton = attrs.customButtonText != '',
76 | customButtonText = attrs.customButtonText ? attrs.customButtonText : 'Click Me',
77 | customButtonShow = $parse(attrs.customButtonShow),
78 | isLoadingSetter = $parse(attrs.loading).assign || angular.noop,
79 | isNoResultsSetter = $parse(attrs.noResults).assign || angular.noop,
80 | appendToBody = attrs.appendToBody ? originalScope.$eval(attrs.appendToBody) : false,
81 | focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false,
82 | //If input matches an item of the list exactly, select it automatically
83 | selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false,
84 | eventDebounceTime = 200,
85 | waitTime = 100,
86 | timeoutEventPromise,
87 | timeoutPromise,
88 | minLength = 1,
89 | itemsSelected = false,
90 | popUpEl = angular.element('
'),
91 | isChecked = function (i) {
92 | return i.checked === true;
93 | },
94 | getFilteredItems = function () {
95 | var filteredItems = $filter("filter")(scope.items, scope.searchText);
96 | return filteredItems;
97 | },
98 | popupId = 'customselect-' + scope.$id + '-' + Math.floor(Math.random() * 10000),
99 | resetMatches = function () {
100 | scope.items = [];
101 | //setModelValue(isMultiple);
102 | scope.activeIdx = -1;
103 | },
104 | scheduleSearchWithTimeout = function (inputValue) {
105 | if (minLength === 0 || inputValue && inputValue.length >= minLength) {
106 | if (waitTime > 0) {
107 | cancelPreviousTimeout();
108 | timeoutPromise = $timeout(function () {
109 | getMatchesAsync(inputValue);
110 | }, waitTime);
111 | } else {
112 | getMatchesAsync(inputValue);
113 | }
114 | } else {
115 | isLoadingSetter(originalScope, false);
116 | scope.loading = false;
117 | cancelPreviousTimeout();
118 | resetMatches();
119 | }
120 | },
121 | cancelPreviousTimeout = function () {
122 | if (timeoutPromise) {
123 | $timeout.cancel(timeoutPromise);
124 | }
125 | },
126 | getMatchId = function (index) {
127 | return popupId + '-option-' + index;
128 | },
129 | getMatchesAsync = function (inputValue) {
130 | var locals = { $viewValue: inputValue };
131 | isLoadingSetter(originalScope, true);
132 | scope.loading = true;
133 | scope.isNoResults = false;
134 | isNoResultsSetter(originalScope, false);
135 | $q.when(parserResult.source(originalScope, locals)).then(function (matches) {
136 | //it might happen that several async queries were in progress if a user were typing fast
137 | //but we are interested only in responses that correspond to the current view value
138 | var onCurrentRequest = (inputValue === scope.searchText.label);
139 | if (onCurrentRequest) {
140 | if (matches && matches.length > 0) {
141 | isNoResultsSetter(originalScope, false);
142 | scope.isNoResults = false;
143 | scope.items.length = 0;
144 |
145 | //transform labels
146 | for (var i = 0; i < matches.length; i++) {
147 | locals[parserResult.itemName] = matches[i];
148 | scope.items.push({
149 | id: getMatchId(i),
150 | label: parserResult.viewMapper(scope, locals),
151 | model: matches[i]
152 | });
153 | }
154 |
155 | scope.query = inputValue;
156 | //position pop-up with matches - we need to re-calculate its position each time we are opening a window
157 | //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
158 | //due to other elements being rendered
159 | recalculatePosition();
160 |
161 | element.attr('aria-expanded', true);
162 |
163 | //Select the single remaining option if user input matches
164 | if (selectOnExact && scope.items.length === 1 && inputIsExactMatch(inputValue, 0)) {
165 | scope.select(scope.items[0]);
166 | }
167 | } else {
168 | resetMatches();
169 | isNoResultsSetter(originalScope, true);
170 | scope.isNoResults = true;
171 | }
172 | }
173 | if (onCurrentRequest) {
174 | isLoadingSetter(originalScope, false);
175 | scope.loading = false;
176 |
177 | }
178 | }, function () {
179 | resetMatches();
180 | isLoadingSetter(originalScope, false);
181 | isNoResultsSetter(originalScope, true);
182 | scope.loading = false;
183 | scope.isNoResults = true;
184 | });
185 | },
186 | fireRecalculating = function () {
187 | if (!scope.moveInProgress) {
188 | scope.moveInProgress = true;
189 | scope.$digest();
190 | }
191 |
192 | // Cancel previous timeout
193 | if (timeoutEventPromise) {
194 | $timeout.cancel(timeoutEventPromise);
195 | }
196 |
197 | // Debounced executing recalculate after events fired
198 | timeoutEventPromise = $timeout(function () {
199 | // if popup is visible
200 | if (scope.isOpen) {
201 | recalculatePosition();
202 | }
203 | scope.moveInProgress = false;
204 | scope.$digest();
205 | }, eventDebounceTime);
206 | },
207 |
208 | // recalculate actual position and set new values to scope
209 | // after digest loop is popup in right position
210 | recalculatePosition = function () {
211 | scope.position = appendToBody ? $position.offset($popup) : $position.position(element);
212 | scope.position.top += $popup.prop('offsetHeight');
213 | },
214 |
215 | getFirstSelectedLabel = function () {
216 | for (var i = 0; i < scope.items.length; i++) {
217 | if (scope.items[i].checked) {
218 | return scope.items[i].label;
219 | }
220 | }
221 | return header;
222 | },
223 |
224 | canCheck = function () {
225 | var belowLimit = false;
226 | var atLimit = false;
227 | if (selectLimit === 0 || !isMultiple) {
228 | belowLimit = true;
229 | atLimit = false;
230 | } else {
231 | var checkedItems = scope.items.filter(isChecked);
232 | atLimit = checkedItems.length === selectLimit;
233 | belowLimit = checkedItems.length < selectLimit;
234 | }
235 | scope.maxSelected = atLimit;
236 | return atLimit || belowLimit;
237 | },
238 |
239 | //getHeaderText = function () {
240 | // var localHeader = header;
241 | // if (isEmpty(modelCtrl.$modelValue)) return scope.header = localHeader;
242 | // if (isMultiple) {
243 | // var isArray = modelCtrl.$modelValue instanceof Array;
244 | // if (isArray && modelCtrl.$modelValue.length > 1) {
245 | // localHeader = modelCtrl.$modelValue.length + ' ' + selectedHeader;
246 | // } else {
247 | // localHeader = getFirstSelectedLabel();
248 | // }
249 | // } else {
250 | // //var local = {};
251 | // //local[parserResult.itemName] = parseInt(modelCtrl.$modelValue);
252 | // //localHeader = parserResult.viewMapper(local);
253 | // localHeader = getFirstSelectedLabel();
254 | // }
255 | // scope.header = localHeader;
256 | //},
257 |
258 | isEmpty = function (obj) {
259 | if (!obj) return true;
260 | if (!isComplex && obj) return false;
261 | if (obj.length && obj.length > 0) return false;
262 | for (var prop in obj) if (obj[prop]) return false;
263 | return true;
264 | },
265 |
266 | selectSingle = function (item) {
267 | if (item.checked) {
268 | scope.uncheckAll();
269 | } else {
270 | scope.uncheckAll();
271 | item.checked = true;
272 | }
273 | //setModelValue(false);
274 | },
275 |
276 | selectMultiple = function (item) {
277 | item.checked = !item.checked;
278 | if (!canCheck()) {
279 | item.checked = false;
280 | }
281 | //setModelValue(true);
282 | },
283 |
284 | setModelValue = function (isMultiple) {
285 | var value;
286 | if (isMultiple) {
287 | value = [];
288 | angular.forEach(scope.items, function (item) {
289 | // If map simple values
290 | if (item.checked) {
291 | if (isComplex) {
292 | value.push(item.model);
293 | } else {
294 | var local = {};
295 | local[parserResult.itemName] = item.model;
296 | value.push(parserResult.modelMapper(local));
297 | }
298 | }
299 | })
300 | } else {
301 | angular.forEach(scope.items, function (item) {
302 | if (item.checked) {
303 | if (isComplex) {
304 | value = item.model;
305 | return false;
306 | }
307 | else {
308 | var local = {};
309 | local[parserResult.itemName] = item.model;
310 | value = parserResult.modelMapper(local);
311 | return false;
312 | }
313 | }
314 | })
315 | }
316 | modelCtrl.$setViewValue(value);
317 | },
318 | markChecked = function (newVal) {
319 | if (!angular.isArray(newVal)) {
320 | angular.forEach(scope.items, function (item) {
321 | if (angular.equals(item.model, newVal)) {
322 | item.checked = true;
323 | return false;
324 | }
325 | });
326 | } else {
327 | angular.forEach(newVal, function (i) {
328 | angular.forEach(scope.items, function (item) {
329 | if (angular.equals(item.model, i)) {
330 | item.checked = true;
331 | }
332 | });
333 | });
334 | }
335 | };
336 |
337 | scope.items = [];
338 | scope.header = header;
339 | scope.multiple = isMultiple;
340 | scope.disabled = false;
341 | scope.filterPlaceholder = filterPlaceholder;
342 | scope.selectLimit = selectLimit;
343 | scope.enableFilter = enableFilter;
344 | scope.searchText = { label: '' };
345 | scope.isAutoFocus = isAutoFocus;
346 | scope.scheduleSearchWithTimeout = scheduleSearchWithTimeout;
347 | scope.appendToBody = appendToBody;
348 | scope.moveInProgress = false;
349 | scope.popupId = popupId;
350 | scope.recalculatePosition = recalculatePosition;
351 | scope.customButtonShow = customButtonShow();
352 | scope.customButtonText = customButtonText;
353 |
354 | originalScope.$on('$destroy', function () {
355 | scope.$destroy();
356 | $document.unbind('click', scope.clickHandler);
357 | if (appendToBody) {
358 | $('#' + popupId).remove();
359 | }
360 | });
361 |
362 | // bind events only if appendToBody params exist - performance feature
363 | if (appendToBody) {
364 | angular.element($window).bind('resize', fireRecalculating);
365 | $document.find('body').bind('scroll', fireRecalculating);
366 | }
367 |
368 | // required validator
369 | if (attrs.required || attrs.ngRequired) {
370 | required = true;
371 | }
372 |
373 | attrs.$observe('required', function (newVal) {
374 | required = newVal;
375 | });
376 |
377 | //watch disabled state
378 | scope.$watch(function () {
379 | return $parse(attrs.disabled)(originalScope);
380 | }, function (newVal) {
381 | scope.disabled = newVal;
382 | });
383 |
384 | //watch show state state
385 | if (hasCustomButton) {
386 | scope.$watch(function () {
387 | return $parse(attrs.customButtonShow)(originalScope);
388 | }, function (newVal) {
389 | scope.customButtonShow = newVal;
390 | });
391 | }
392 |
393 | //watch single/multiple state for dynamically change single to multiple
394 | scope.$watch(function () {
395 | return $parse(attrs.multiple)(originalScope);
396 | }, function (newVal) {
397 | isMultiple = newVal || false;
398 | });
399 |
400 | ////watch model change --> This has an issue in that it seems that all models are updated to the same value
401 | scope.$watch(function () {
402 | return modelCtrl.$modelValue;
403 | }, function (newVal, oldVal) {
404 | //when directive initialize, newVal usually undefined. Also, if model value already set in the controller
405 | //for preselected list then we need to mark checked in our scope item. But we don't want to do this every time
406 | //model changes. We need to do this only if it is done outside directive scope, from controller, for example.
407 | if (angular.isDefined(newVal)) {
408 | markChecked(newVal);
409 | // Technically, defining ngChange will already have a watcher triggering its handler
410 | // So, triggering it manually should be redundant
411 | //scope.$eval(changeHandler);
412 | }
413 | //getHeaderText();
414 | modelCtrl.$setValidity('required', scope.valid());
415 | }, true);
416 |
417 | //parseModel();
418 |
419 | var $popup = $compile(popUpEl)(scope);
420 | element.append($popup);
421 | $timeout(function () { recalculatePosition(); }, 100);
422 |
423 | scope.valid = function validModel() {
424 | if (!required) return true;
425 | var value = modelCtrl.$modelValue;
426 | var isValid = itemsSelected || (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value != null);
427 | return isValid;
428 | };
429 |
430 | scope.checkAll = function () {
431 | if (!isMultiple) return;
432 | var items = scope.items;
433 | var totalChecked = 0;
434 | if (useFiltered) {
435 | items = getFilteredItems();
436 | angular.forEach(items, function (item) {
437 | item.checked = false;
438 | });
439 | totalChecked = scope.items.filter(isChecked).length;
440 | }
441 | if (selectLimit <= 0 || (items.length < selectLimit - totalChecked)) {
442 | angular.forEach(items, function (item) {
443 | item.checked = true;
444 | });
445 | } else {
446 | angular.forEach(items, function (item) {
447 | item.checked = false;
448 | });
449 |
450 | for (var i = 0; i < (selectLimit - totalChecked) ; i++) {
451 | items[i].checked = true;
452 | }
453 | scope.maxSelected = true;
454 | }
455 | //setModelValue(true);
456 | };
457 |
458 | scope.uncheckAll = function () {
459 | var items = useFiltered ? getFilteredItems() : scope.items;
460 | angular.forEach(items, function (item) {
461 | item.checked = false;
462 | });
463 | canCheck();
464 | if (isMultiple) {
465 | //setModelValue(true);
466 | }
467 | };
468 |
469 | scope.select = function (item) {
470 | if (isMultiple === false) {
471 | selectSingle(item);
472 | scope.toggleSelect();
473 | } else {
474 | selectMultiple(item);
475 | }
476 | };
477 |
478 | scope.acceptSelection = function () {
479 | setModelValue(isMultiple);
480 | modelCtrl.$setValidity('required', scope.valid());
481 | if (modelCtrl.$modelValue && modelCtrl.$modelValue.length > 0) {
482 | itemsSelected = true;
483 | var items = scope.items.filter(isChecked);
484 | onSelectCallback(originalScope, { $items: items });
485 | scope.clearFilter();
486 | scope.toggleSelect();
487 | }
488 | };
489 |
490 | scope.customClick = function () {
491 | itemsSelected = true;
492 | onCustomCallback(originalScope);
493 | modelCtrl.$setValidity('required', scope.valid());
494 | scope.clearFilter();
495 | scope.toggleSelect();
496 | }
497 |
498 | scope.clearFilter = function () {
499 | resetMatches();
500 | scope.searchText.label = '';
501 | };
502 | }
503 | };
504 | };
505 |
506 | var customselectPopup = function ($document) {
507 | return {
508 | restrict: 'E',
509 | replace: true,
510 | templateUrl: 'template/customselect/customselectPopup.html',
511 | link: function (scope, element, attrs, ctrls) {
512 |
513 | var $dropdown = element.find(".dropdown-menu");
514 | $dropdown.attr("id", scope.popupId);
515 |
516 | if (scope.appendToBody) {
517 | $document.find('body').append($dropdown);
518 | }
519 |
520 | var
521 | clickHandler = function (event) {
522 | if (elementMatchesAnyInArray(event.target, element.find(event.target.tagName)))
523 | return;
524 |
525 | if (scope.appendToBody) {
526 | if (elementMatchesAnyInArray(event.target, $dropdown.find(event.target.tagName)))
527 | return;
528 | }
529 |
530 | element.removeClass('open');
531 | scope.isOpen = false;
532 | $document.unbind('click', clickHandler);
533 | scope.$apply();
534 | },
535 | elementMatchesAnyInArray = function (element, elementArray) {
536 | for (var i = 0; i < elementArray.length; i++)
537 | if (element == elementArray[i])
538 | return true;
539 | return false;
540 | };
541 |
542 | scope.clickHandler = clickHandler;
543 |
544 | scope.toggleSelect = function () {
545 | if (element.hasClass('open') || scope.isOpen) {
546 | element.removeClass('open');
547 | scope.isOpen = false;
548 | $document.unbind('click', clickHandler);
549 | } else {
550 | element.addClass('open');
551 | scope.isOpen = true;
552 | $document.bind('click', clickHandler);
553 | if (scope.isAutoFocus) {
554 | scope.focus();
555 | }
556 | scope.recalculatePosition();
557 | }
558 |
559 | // Figure out if dropup
560 | var parent = element.parent();
561 | var windowScrollTop = $(window).scrollTop();
562 | var windowHeight = $(window).height();
563 | var windowWidth = $(window).width();
564 | var ulElement = element.find("ul:first");
565 | var dropdownHeight = ulElement.height();
566 | var dropdownWidth = ulElement.width();
567 |
568 | // Determine if outside of visible range when dropping down
569 | var elementTop = element.offset().top + element.height() - windowScrollTop;
570 | var elementBottom = windowHeight - element.height() - element.offset().top + windowScrollTop;
571 | if ((elementBottom < dropdownHeight) && (elementTop > dropdownHeight)) {
572 | // Alert should drop up!
573 | scope.dropup = true;
574 | }
575 | else {
576 | scope.dropup = false;
577 | }
578 |
579 | // Figure out if we need left adjust
580 | if (element.offset().left + dropdownWidth >= windowWidth) {
581 | scope.isOffRight = true;
582 | var adjust = ((element.offset().left + dropdownWidth - windowWidth) + 10) * -1.0;
583 | ulElement.css("left", adjust.toString() + "px");
584 | }
585 | else {
586 | scope.isOffRight = false;
587 | ulElement.css("left", "0");
588 | }
589 | };
590 |
591 | scope.focus = function focus() {
592 | if (scope.enableFilter) {
593 | var searchBox = element.find('input')[0];
594 | searchBox.focus();
595 | }
596 | }
597 | }
598 | }
599 | };
600 |
601 | angular.module("long2know").run(["$templateCache", function ($templateCache) {
602 | $templateCache.put("template/customselect/customselectPopup.html",
603 | "
" +
604 | "" +
605 | " " +
606 | " " +
607 | " " +
608 | "" +
638 | "
");
639 | }]);
640 |
641 | customselectParser.$inject = ['$parse'];
642 | customselect.$inject = ['$q', '$timeout', '$parse', '$filter', '$document', '$window', '$uibPosition', '$compile', 'customselectParser'];
643 | customselectPopup.$inject = ['$document'];
644 |
645 | angular
646 | .module("long2know.services")
647 | .factory('customselectParser', customselectParser);
648 |
649 | angular
650 | .module('long2know.directives')
651 | .directive('customselectPopup', customselectPopup);
652 |
653 | angular
654 | .module('long2know.directives')
655 | .directive('customselect', customselect);
656 | })()
--------------------------------------------------------------------------------
/src/dateBeforeAfter.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var long2know;
3 | try {
4 | long2know = angular.module("long2know")
5 | } catch (err) {
6 | long2know = null;
7 | }
8 |
9 | if (!long2know) {
10 | angular.module('long2know.services', ['ngResource', 'ngAnimate']);
11 | angular.module('long2know.controllers', []);
12 | angular.module('long2know.directives', []);
13 | angular.module('long2know.constants', []);
14 | angular.module('long2know',
15 | [
16 | 'long2know.services',
17 | 'long2know.controllers',
18 | 'long2know.directives',
19 | 'long2know.constants'
20 | ]);
21 | }
22 |
23 | var dateBefore = function () {
24 | var directive = {
25 | require: 'ngModel',
26 | link: function (scope, el, attrs, ctrl) {
27 | var isInclusive = attrs.dateOrEquals ? scope.$eval(attrs.dateOrEquals) : false,
28 | validate = function (val1, val2) {
29 | if ((val1 === undefined || val2 === undefined) || (!val1 || !val2)) {
30 | ctrl.$setValidity('dateBefore', true);
31 | return;
32 | };
33 | var isArray = val2 instanceof Array;
34 | var isValid = true;
35 | var date1 = new Date(val1);
36 | if (isArray && val2.length > 0) {
37 | for (var i = 0; i < val2.length; i++) {
38 | if (val2[i]) {
39 | var date2 = new Date(val2[i]);
40 | isValid = isValid && (isInclusive ? date1 <= date2 : date1 < date2);
41 | }
42 | if (!isValid)
43 | break;
44 | }
45 | }
46 | else {
47 | if (val2) {
48 | var date2 = new Date(val2);
49 | isValid = isInclusive ? date1 <= date2 : date1 < date2;
50 | }
51 | }
52 | ctrl.$setValidity('dateBefore', isValid);
53 | };
54 | // Watch the value to compare - trigger validate()
55 | scope.$watch(attrs.dateBefore, function () {
56 | validate(ctrl.$viewValue, scope.$eval(attrs.dateBefore));
57 | });
58 |
59 | ctrl.$parsers.unshift(function (value) {
60 | validate(value, scope.$eval(attrs.dateBefore));
61 | return value;
62 | })
63 | }
64 | }
65 | return directive
66 | };
67 |
68 | var dateAfter = function () {
69 | var directive = {
70 | require: 'ngModel',
71 | link: function (scope, el, attrs, ctrl) {
72 | var isInclusive = attrs.dateOrEquals ? scope.$eval(attrs.dateOrEquals) : false,
73 | validate = function (val1, val2) {
74 | if ((val1 === undefined || val2 === undefined) || (!val1 || !val2)) {
75 | ctrl.$setValidity('dateAfter', true);
76 | return;
77 | };
78 | var isArray = val2 instanceof Array;
79 | var isValid = true;
80 | var date1 = new Date(val1);
81 | if (isArray && val2.length > 0) {
82 | for (var i = 0; i < val2.length; i++) {
83 | if (val2[i]) {
84 | var date2 = new Date(val2[i]);
85 | isValid = isValid && (isInclusive ? date1 >= date2 : date1 > date2);
86 | }
87 | if (!isValid)
88 | break;
89 | }
90 | }
91 | else {
92 | if (val2) {
93 | var date2 = new Date(val2);
94 | isValid = isInclusive ? date1 >= date2 : date1 > date2;
95 | }
96 | }
97 | ctrl.$setValidity('dateAfter', isValid);
98 | };
99 | // Watch the value to compare - trigger validate()
100 | scope.$watch(attrs.dateAfter, function () {
101 | validate(ctrl.$viewValue, scope.$eval(attrs.dateAfter));
102 | });
103 |
104 | ctrl.$parsers.unshift(function (value) {
105 | validate(value, scope.$eval(attrs.dateAfter));
106 | return value;
107 | })
108 | }
109 | }
110 | return directive
111 | };
112 |
113 | angular.module('long2know.directives')
114 | .directive('dateBefore', dateBefore);
115 |
116 | angular.module('long2know.directives')
117 | .directive('dateAfter', dateAfter);
118 | })()
--------------------------------------------------------------------------------
/src/isState.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var long2know;
3 | try {
4 | long2know = angular.module("long2know")
5 | } catch (err) {
6 | long2know = null;
7 | }
8 |
9 | if (!long2know) {
10 | angular.module('long2know.services', ['ngResource', 'ngAnimate']);
11 | angular.module('long2know.controllers', []);
12 | angular.module('long2know.directives', []);
13 | angular.module('long2know.constants', []);
14 | angular.module('long2know',
15 | [
16 | 'long2know.services',
17 | 'long2know.controllers',
18 | 'long2know.directives',
19 | 'long2know.constants'
20 | ]);
21 | }
22 |
23 | var isStateDirective = function ($animate, $timeout) {
24 | var directive = {
25 | restrict: 'A',
26 | scope: {
27 | isState: '=',
28 | isStateClass: '=',
29 | isStateTimeout: '='
30 | },
31 | link: function (scope, element, attrs) {
32 | var
33 | className = angular.isDefined(attrs.isStateClass) ? scope.isStateClass : 'is-state',
34 | timeout = angular.isDefined(attrs.isStateTimeout) ? scope.isStateTimeout : 2000,
35 | startAnimation = function () {
36 | if (scope.isState) {
37 | $animate.addClass(element, className);
38 | $timeout(function () {
39 | scope.isState = false;
40 | $animate.removeClass(element, className);
41 | }, timeout);
42 | } else if (scope.isState === false) {
43 | $animate.removeClass(element, className);
44 | }
45 | };
46 |
47 | // Watch the attribute to toggle
48 | scope.$watch('isState', function () {
49 | startAnimation();
50 | });
51 | }
52 | };
53 | return directive;
54 | };
55 |
56 | isStateDirective.$inject = ['$animate', '$timeout'];
57 | angular.module("long2know.directives")
58 | .directive('isState', isStateDirective);
59 | })()
60 |
--------------------------------------------------------------------------------
/src/multiselect.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var long2know;
3 | try {
4 | long2know = angular.module("long2know")
5 | } catch (err) {
6 | long2know = null;
7 | }
8 |
9 | if (!long2know) {
10 | angular.module('long2know.services', ['ngResource', 'ngAnimate']);
11 | angular.module('long2know.controllers', []);
12 | angular.module('long2know.directives', []);
13 | angular.module('long2know.constants', []);
14 | angular.module('long2know',
15 | [
16 | 'long2know.services',
17 | 'long2know.controllers',
18 | 'long2know.directives',
19 | 'long2know.constants'
20 | ]);
21 | }
22 |
23 | var multiselectParser = function ($parse) {
24 | // 00000111000000000000022200000000000000003333333333333330000000000044000
25 | var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
26 |
27 | return {
28 | parse: function (input) {
29 |
30 | var match = input.match(TYPEAHEAD_REGEXP);
31 | if (!match) {
32 | throw new Error(
33 | 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
34 | ' but got "' + input + '".');
35 | }
36 |
37 | return {
38 | itemName: match[3],
39 | source: $parse(match[4]),
40 | viewMapper: $parse(match[2] || match[1]),
41 | modelMapper: $parse(match[1])
42 | };
43 | }
44 | };
45 | };
46 |
47 | var multiselect = function ($parse, $timeout, $filter, $document, $compile, $window, $position, optionParser) {
48 | return {
49 | restrict: 'EA',
50 | require: ['ngModel', '?^form'],
51 | link: function (originalScope, element, attrs, ctrls) {
52 | var modelCtrl = ctrls[0];
53 | var formCtrl = (ctrls.length > 1 && typeof (ctrls[1]) !== 'undefined') ? ctrls[1] : null;
54 |
55 | //model setter executed upon match selection
56 | var $setModelValue = $parse(attrs.ngModel).assign;
57 |
58 | var
59 | parserResult = optionParser.parse(attrs.options),
60 | isMultiple = attrs.multiple ? originalScope.$eval(attrs.multiple) : false,
61 | isAutoFocus = attrs.autoFocus ? originalScope.$eval(attrs.autoFocus) : false,
62 | isComplex = attrs.complexModels ? originalScope.$eval(attrs.complexModels) : false,
63 | enableFilter = attrs.enableFilter ? originalScope.$eval(attrs.enableFilter) : true,
64 | enableCheckAll = attrs.enableCheckAll ? originalScope.$eval(attrs.enableCheckAll) : true,
65 | enableUncheckAll = attrs.enableUncheckAll ? originalScope.$eval(attrs.enableUncheckAll) : true,
66 | header = attrs.header ? attrs.header : "Select",
67 | selectedHeader = attrs.selectedHeader ? attrs.selectedHeader : 'selected',
68 | selectLimit = attrs.selectLimit ? originalScope.$eval(attrs.selectLimit) : 0,
69 | useFiltered = attrs.selectLimitUseFiltered ? originalScope.$eval(attrs.selectLimitUseFiltered) : true,
70 | filterPlaceholder = attrs.filterPlaceholder ? attrs.filterPlaceholder : "Filter ..",
71 | checkAllLabel = attrs.checkAllLabel ? attrs.checkAllLabel : "Check all",
72 | uncheckAllLabel = attrs.uncheckAllLabel ? attrs.uncheckAllLabel : "Uncheck all",
73 | appendToBody = attrs.appendToBody ? originalScope.$eval(attrs.appendToBody) : false,
74 | orderLabel = attrs.orderLabel ? attrs.orderLabel : "$index",
75 | required = false,
76 | lastSelectedLabel = '',
77 | scope = originalScope.$new(true),
78 | changeHandler = attrs.change || angular.noop,
79 | popUpEl = angular.element('
'),
80 | popupId = 'multiselect-' + scope.$id + '-' + Math.floor(Math.random() * 10000),
81 | timeoutEventPromise,
82 | eventDebounceTime = 200,
83 |
84 | isChecked = function (i) {
85 | return i.checked === true;
86 | },
87 |
88 | getFilteredItems = function () {
89 | var filteredItems = $filter("filter")(scope.items, scope.searchText);
90 | return filteredItems;
91 | },
92 |
93 | getFirstSelectedLabel = function () {
94 | for (var i = 0; i < scope.items.length; i++) {
95 | if (scope.items[i].checked) {
96 | return scope.items[i].label;
97 | }
98 | }
99 | return header;
100 | },
101 | canCheck = function () {
102 | var belowLimit = false;
103 | var atLimit = false;
104 | var aboveLimit = false;
105 | if (selectLimit === 0 || !isMultiple) {
106 | belowLimit = true;
107 | atLimit = false;
108 | } else {
109 | var checkedItems = scope.items.filter(isChecked);
110 | atLimit = checkedItems.length === selectLimit;
111 | aboveLimit = checkedItems.length > selectLimit;
112 | belowLimit = checkedItems.length < selectLimit;
113 | }
114 | scope.maxSelected = atLimit || aboveLimit;
115 | return atLimit || belowLimit;
116 | },
117 | getHeaderText = function () {
118 | var localHeader = header;
119 | if (isEmpty(modelCtrl.$modelValue)) return scope.header = localHeader;
120 | if (isMultiple) {
121 | var isArray = modelCtrl.$modelValue instanceof Array;
122 | if (isArray && modelCtrl.$modelValue.length > 1) {
123 | localHeader = modelCtrl.$modelValue.length + ' ' + selectedHeader;
124 | } else {
125 | localHeader = getFirstSelectedLabel();
126 | }
127 | } else {
128 | //var local = {};
129 | //local[parserResult.itemName] = parseInt(modelCtrl.$modelValue);
130 | //localHeader = parserResult.viewMapper(local);
131 | localHeader = getFirstSelectedLabel();
132 | }
133 | scope.header = localHeader;
134 | },
135 | isEmpty = function (obj) {
136 | if (!obj) return true;
137 | if (!isComplex && obj) return false;
138 | if (obj.length && obj.length > 0) return false;
139 | for (var prop in obj) if (obj[prop]) return false;
140 | return true;
141 | },
142 | parseModel = function () {
143 | scope.items.length = 0;
144 | var model = parserResult.source(originalScope);
145 | if (!angular.isDefined(model)) return;
146 | isArray = modelCtrl.$modelValue instanceof Array;
147 | for (var i = 0; i < model.length; i++) {
148 | var local = {};
149 | local[parserResult.itemName] = model[i];
150 | var value = parserResult.modelMapper(local)
151 | var isChecked = isArray ?
152 | (modelCtrl.$modelValue.indexOf(value.toString()) != -1 || modelCtrl.$modelValue.indexOf(value) != -1) :
153 | (!isEmpty(modelCtrl.$modelValue) && modelCtrl.$modelValue == value);
154 | var item = {
155 | label: parserResult.viewMapper(local),
156 | model: model[i],
157 | checked: isChecked
158 | };
159 | scope.items.push(item);
160 | }
161 | getHeaderText();
162 | },
163 | selectSingle = function (item) {
164 | if (item.checked) {
165 | scope.uncheckAll();
166 | } else {
167 | scope.uncheckAll();
168 | item.checked = true;
169 | }
170 | setModelValue(false);
171 | },
172 | selectMultiple = function (item) {
173 | if (item.checked) {
174 | item.checked = false;
175 | canCheck();
176 | } else if (!scope.maxSelected) {
177 | item.checked = canCheck();
178 | }
179 | setModelValue(true);
180 | },
181 | getModelValue = function (item) {
182 | if (isComplex) {
183 | value = item.model;
184 | }
185 | else {
186 | var local = {};
187 | local[parserResult.itemName] = item.model;
188 | value = parserResult.modelMapper(local);
189 | }
190 | return value;
191 | },
192 | setModelValue = function (isMultiple) {
193 | var value;
194 | if (isMultiple) {
195 | value = [];
196 | angular.forEach(scope.items, function (item) {
197 | // If map simple values
198 | if (item.checked) {
199 | if (isComplex) {
200 | value.push(item.model);
201 | } else {
202 | var local = {};
203 | local[parserResult.itemName] = item.model;
204 | value.push(parserResult.modelMapper(local));
205 | }
206 | }
207 | })
208 | } else {
209 | angular.forEach(scope.items, function (item) {
210 | if (item.checked) {
211 | if (isComplex) {
212 | value = item.model;
213 | return false;
214 | }
215 | else {
216 | var local = {};
217 | local[parserResult.itemName] = item.model;
218 | value = parserResult.modelMapper(local);
219 | return false;
220 | }
221 | }
222 | })
223 | }
224 | scope.triggered = true;
225 | modelCtrl.$setViewValue(value);
226 | },
227 |
228 | markChecked = function (newVal) {
229 | if (!angular.isArray(newVal)) {
230 | angular.forEach(scope.items, function (item) {
231 | var value = getModelValue(item);
232 | if (angular.equals(value, newVal)) {
233 | item.checked = true;
234 | return false;
235 | }
236 | });
237 | } else {
238 | var itemsToCheck = [];
239 | var itemsToUncheck = [];
240 | var itemValues = [];
241 | for (var j = 0; j < scope.items.length; j++) {
242 | itemValues.push(getModelValue(scope.items[j]));
243 | itemsToUncheck.push(j);
244 | };
245 |
246 | for (var i = 0; i < newVal.length; i++) {
247 | for (var j = 0; j < itemValues.length; j++) {
248 | if (angular.equals(itemValues[j], newVal[i])) {
249 | itemsToCheck.push(scope.items[j]);
250 | var index = itemsToUncheck.indexOf(j);
251 | itemsToUncheck.splice(index, 1);
252 | break;
253 | }
254 | }
255 | }
256 |
257 | for (var i = 0; i < itemsToCheck.length; i++) {
258 | itemsToCheck[i].checked = true;
259 | }
260 |
261 | for (var i = 0; i < itemsToUncheck.length; i++) {
262 | scope.items[itemsToUncheck[i]].checked = false;
263 | }
264 |
265 | }
266 | },
267 |
268 | // recalculate actual position and set new values to scope
269 | // after digest loop is popup in right position
270 | recalculatePosition = function () {
271 | scope.position = appendToBody ? $position.offset($popup) : $position.position(element);
272 | scope.position.top += $popup.prop('offsetHeight');
273 | },
274 |
275 | fireRecalculating = function () {
276 | if (!scope.moveInProgress) {
277 | scope.moveInProgress = true;
278 | scope.$digest();
279 | }
280 |
281 | // Cancel previous timeout
282 | if (timeoutEventPromise) {
283 | $timeout.cancel(timeoutEventPromise);
284 | }
285 |
286 | // Debounced executing recalculate after events fired
287 | timeoutEventPromise = $timeout(function () {
288 | // if popup is visible
289 | if (scope.isOpen) {
290 | recalculatePosition();
291 | }
292 | scope.moveInProgress = false;
293 | scope.$digest();
294 | }, eventDebounceTime);
295 | };
296 |
297 | scope.items = [];
298 | scope.header = header;
299 | scope.multiple = isMultiple;
300 | scope.disabled = false;
301 | scope.filterPlaceholder = filterPlaceholder;
302 | scope.checkAllLabel = checkAllLabel;
303 | scope.uncheckAllLabel = uncheckAllLabel;
304 | scope.selectLimit = selectLimit;
305 | scope.enableFilter = enableFilter;
306 | scope.enableCheckAll = enableCheckAll;
307 | scope.enableUncheckAll = enableUncheckAll;
308 | scope.searchText = { label: '' };
309 | scope.isAutoFocus = isAutoFocus;
310 | scope.appendToBody = appendToBody;
311 | scope.moveInProgress = false;
312 | scope.popupId = popupId;
313 | scope.recalculatePosition = recalculatePosition;
314 | scope.isModelValueSet = false;
315 | scope.orderLabel = orderLabel;
316 | originalScope.$on('$destroy', function () {
317 | scope.$destroy();
318 | $document.unbind('click', scope.clickHandler);
319 | if (appendToBody) {
320 | $('#' + popupId).remove();
321 | }
322 | });
323 |
324 | // bind events only if appendToBody params exist - performance feature
325 | if (appendToBody) {
326 | angular.element($window).bind('resize', fireRecalculating);
327 | $document.find('body').bind('scroll', fireRecalculating);
328 | }
329 |
330 | // required validator
331 | if (attrs.required || attrs.ngRequired) {
332 | required = true;
333 | }
334 |
335 | attrs.$observe('required', function (newVal) {
336 | required = newVal;
337 | });
338 |
339 | //watch disabled state
340 | scope.$watch(function () {
341 | return $parse(attrs.ngDisabled)(originalScope);
342 | }, function (newVal) {
343 | scope.disabled = newVal;
344 | });
345 |
346 | //watch single/multiple state for dynamically change single to multiple
347 | scope.$watch(function () {
348 | return $parse(attrs.multiple)(originalScope);
349 | }, function (newVal) {
350 | isMultiple = newVal || false;
351 | });
352 |
353 | //watch option changes for options that are populated dynamically
354 | scope.$watch(function () {
355 | return parserResult.source(originalScope);
356 | }, function (newVal) {
357 | if (angular.isDefined(newVal)) {
358 | parseModel();
359 | setModelValue(isMultiple);
360 | }
361 | }, true);
362 |
363 | ////watch model change --> This has an issue in that it seems that all models are updated to the same value
364 | scope.$watch(function () {
365 | return modelCtrl.$modelValue;
366 | }, function (newVal, oldVal) {
367 | //when directive initializes, newVal is usually undefined. Also, if model value is already set in the controller
368 | //for preselected list then we need to mark checked in our scope item. But we don't want to do this every time the
369 | //model changes. We need to do this only if it is done outside directive scope, from controller, for example.
370 | if (!scope.triggered) {
371 | if (angular.isDefined(newVal)) {
372 | var isArray = newVal instanceof Array;
373 | if ((isArray && newVal.length == 0) || !isArray) {
374 | scope.uncheckAll();
375 | }
376 | markChecked(newVal);
377 | scope.isModelValueSet = true;
378 | // Technically, defining ngChange will already have a watcher triggering its handler
379 | // So, triggering it manually should be redundant
380 | //scope.$eval(changeHandler);
381 | } else if (scope.isModelValueSet) {
382 | // If the model value is cleared externally, and we previously had some things checked,
383 | // we need to uncheck them.
384 | scope.uncheckAll();
385 | scope.isModelValueSet = false;
386 | }
387 | }
388 | getHeaderText();
389 | canCheck();
390 | modelCtrl.$setValidity('required', scope.valid());
391 | scope.triggered = false;
392 | }, true);
393 |
394 | parseModel();
395 | var $popup = $compile(popUpEl)(scope);
396 | element.append($popup);
397 | $timeout(function () { recalculatePosition(); }, 100);
398 |
399 | scope.valid = function validModel() {
400 | if (!required) return true;
401 | var value = modelCtrl.$modelValue;
402 | return (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value != null);
403 | };
404 |
405 | scope.checkAll = function () {
406 | if (!isMultiple) return;
407 | var items = scope.items;
408 | var totalChecked = 0;
409 | if (useFiltered) {
410 | items = getFilteredItems();
411 | angular.forEach(items, function (item) {
412 | item.checked = false;
413 | });
414 | totalChecked = scope.items.filter(isChecked).length;
415 | }
416 | if (selectLimit <= 0 || (items.length < selectLimit - totalChecked)) {
417 | angular.forEach(items, function (item) {
418 | item.checked = true;
419 | });
420 | } else {
421 | angular.forEach(items, function (item) {
422 | item.checked = false;
423 | });
424 |
425 | for (var i = 0; i < (selectLimit - totalChecked) ; i++) {
426 | items[i].checked = true;
427 | }
428 | scope.maxSelected = true;
429 | }
430 | setModelValue(true);
431 | };
432 |
433 | scope.uncheckAll = function () {
434 | var items = useFiltered ? getFilteredItems() : scope.items;
435 | angular.forEach(items, function (item) {
436 | item.checked = false;
437 | });
438 | canCheck();
439 | if (isMultiple) {
440 | setModelValue(true);
441 | }
442 | };
443 |
444 | scope.select = function (item) {
445 | if (isMultiple === false) {
446 | selectSingle(item);
447 | scope.toggleSelect();
448 | } else {
449 | selectMultiple(item);
450 | }
451 | };
452 |
453 | scope.clearFilter = function () {
454 | scope.searchText.label = '';
455 | };
456 | }
457 | };
458 | };
459 |
460 | var multiselectPopup = function ($document) {
461 | return {
462 | restrict: 'E',
463 | replace: true,
464 | require: ['^ngModel', '?^form'],
465 | templateUrl: 'template/multiselect/multiselectPopup.html',
466 | link: function (scope, element, attrs, ctrls) {
467 | var $dropdown = element.find(".dropdown-menu");
468 | $dropdown.attr("id", scope.popupId);
469 |
470 | if (scope.appendToBody) {
471 | $document.find('body').append($dropdown);
472 | }
473 |
474 | var
475 | clickHandler = function (event) {
476 | if (elementMatchesAnyInArray(event.target, element.find(event.target.tagName)))
477 | return;
478 |
479 | if (scope.appendToBody) {
480 | if (elementMatchesAnyInArray(event.target, $dropdown.find(event.target.tagName)))
481 | return;
482 | }
483 |
484 | element.removeClass('open');
485 | scope.isOpen = false;
486 | $document.unbind('click', clickHandler);
487 | scope.$apply();
488 | },
489 | elementMatchesAnyInArray = function (element, elementArray) {
490 | for (var i = 0; i < elementArray.length; i++)
491 | if (element == elementArray[i])
492 | return true;
493 | return false;
494 | };
495 |
496 | scope.clickHandler = clickHandler;
497 | scope.isVisible = false;
498 | scope.isHeightChanged = true;
499 |
500 | var
501 | dropdownHeight,
502 | dropdownWidth;
503 |
504 | scope.toggleSelect = function () {
505 | if (element.hasClass('open') || scope.isOpen) {
506 | element.removeClass('open');
507 | scope.isOpen = false;
508 | $document.unbind('click', clickHandler);
509 | } else {
510 | element.addClass('open');
511 | scope.isOpen = true;
512 | $document.bind('click', clickHandler);
513 | if (scope.isAutoFocus) {
514 | scope.focus();
515 | }
516 | scope.recalculatePosition();
517 | }
518 |
519 | // Figure out if dropup
520 | var parent = element.parent();
521 | var windowScrollTop = $(window).scrollTop();
522 | var windowHeight = $(window).height();
523 | var windowWidth = $(window).width();
524 | var ulElement = element.find("ul:first");
525 |
526 | if (scope.isHeightChanged) {
527 | dropdownHeight = ulElement.height();
528 | dropdownWidth = ulElement.width();
529 | scope.isHeightChanged = false;
530 | }
531 |
532 | // If we have no height/width, the element isn't visisble - we can clone it and show it off screen to get
533 | // its visibile dimensions. Alternatively, we could just make the element visible and then adjust,
534 | // but this might result in some screen flicker... who knows?
535 | if (dropdownHeight <= 0 && dropdownWidth <= 0) {
536 | var clonedElement = $(ulElement)
537 | .clone()
538 | .css('position', 'fixed')
539 | .css('top', '0')
540 | .css('left', '-10000px')
541 | .appendTo(parent)
542 | .removeClass('ng-hide')
543 | .show();
544 |
545 | dropdownHeight = clonedElement.height();
546 | dropdownWidth = clonedElement.width();
547 |
548 | // Memory clean up - also, if you don't remove the clone from the DOM, IE11 increases the height of the HTML DOM element (buggy piece of junk!)
549 | clonedElement.remove();
550 | clonedElement = null;
551 | }
552 |
553 | // Determine if outside of visible range when dropping down
554 | var elementTop = element.offset().top + element.height() - windowScrollTop;
555 | var elementBottom = windowHeight - element.height() - element.offset().top + windowScrollTop;
556 | if ((elementBottom < dropdownHeight) && (elementTop > dropdownHeight)) {
557 | // Alert should drop up!
558 | scope.dropup = true;
559 | }
560 | else {
561 | scope.dropup = false;
562 | }
563 |
564 | // Figure out if we need left adjust
565 | if (element.offset().left + dropdownWidth >= windowWidth) {
566 | scope.isOffRight = true;
567 | var adjust = ((element.offset().left + dropdownWidth - windowWidth) + 10) * -1.0;
568 | ulElement.css("left", adjust.toString() + "px");
569 | }
570 | else {
571 | scope.isOffRight = false;
572 | ulElement.css("left", "0");
573 | }
574 | };
575 |
576 | scope.focus = function focus() {
577 | if (scope.enableFilter) {
578 | var searchBox = element.find('input')[0];
579 | searchBox.focus();
580 | }
581 | }
582 | }
583 | }
584 | };
585 |
586 | // IE11 doesn't enable the filter box when parent changes is using disabled attribute - so, use ng-disabled in your own HTML!
587 | angular.module("long2know").run(["$templateCache", function ($templateCache) {
588 | $templateCache.put("template/multiselect/multiselectPopup.html",
589 | "
" +
590 | "" +
591 | " " +
592 | " " +
593 | " " +
594 | "" +
616 | "
");
617 | }]);
618 |
619 | multiselectParser.$inject = ['$parse'];
620 | multiselect.$inject = ['$parse', '$timeout', '$filter', '$document', '$compile', '$window', '$uibPosition', 'multiselectParser'];
621 | multiselectPopup.$inject = ['$document'];
622 |
623 | angular
624 | .module("long2know.services")
625 | .factory('multiselectParser', multiselectParser);
626 |
627 | angular
628 | .module('long2know.directives')
629 | .directive('multiselectPopup', multiselectPopup);
630 |
631 | angular
632 | .module('long2know.directives')
633 | .directive('multiselect', multiselect);
634 | })()
635 |
--------------------------------------------------------------------------------
/src/onRepeatFinish.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var long2know;
3 | try {
4 | long2know = angular.module("long2know")
5 | } catch (err) {
6 | long2know = null;
7 | }
8 |
9 | if (!long2know) {
10 | angular.module('long2know.services', ['ngResource', 'ngAnimate']);
11 | angular.module('long2know.controllers', []);
12 | angular.module('long2know.directives', []);
13 | angular.module('long2know.constants', []);
14 | angular.module('long2know',
15 | [
16 | 'long2know.services',
17 | 'long2know.controllers',
18 | 'long2know.directives',
19 | 'long2know.constants'
20 | ]);
21 | }
22 |
23 | var onRepeatFinish = function ($timeout) {
24 | var directive = {
25 | restrict: 'A',
26 | link: function (scope, element, attr) {
27 | if (scope.$last === true) {
28 | if (attr.onRepeatFinish) {
29 | scope.$eval(attr.onRepeatFinish);
30 | } else {
31 | $timeout(function () {
32 | scope.$emit('ngRepeatFinished');
33 | });
34 | }
35 | }
36 | }
37 | };
38 | return directive;
39 | };
40 |
41 | onRepeatFinish.$inject = ['$timeout', '$log'];
42 | angular.module("long2know.directives")
43 | .directive('onRepeatFinish', onRepeatFinish);
44 | })()
--------------------------------------------------------------------------------
/src/triStateCheckbox.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var long2know;
3 | try {
4 | long2know = angular.module("long2know")
5 | } catch (err) {
6 | long2know = null;
7 | }
8 |
9 | if (!long2know) {
10 | angular.module('long2know.services', ['ngResource', 'ngAnimate']);
11 | angular.module('long2know.controllers', []);
12 | angular.module('long2know.directives', []);
13 | angular.module('long2know.constants', []);
14 | angular.module('long2know',
15 | [
16 | 'long2know.services',
17 | 'long2know.controllers',
18 | 'long2know.directives',
19 | 'long2know.constants'
20 | ]);
21 | }
22 |
23 | var triStateCheckbox = function () {
24 | var directive = {
25 | replace: true,
26 | restrict: 'E',
27 | scope: {
28 | checkboxes: '=',
29 | masterSet: '=',
30 | setMaster: '=',
31 | masterClicked: '=',
32 | masterChange: '=',
33 | masterSetOff: '@masterSetOff',
34 | childClick: '@childClick'
35 | },
36 | template: '
',
37 | controller: ['$scope', '$timeout', '$element', function ($scope, $timeout, $element) {
38 | $scope.setState = function () {
39 | var set = 0;
40 | for (i = 0; i < $scope.checkboxes.length; i++)
41 | set += $scope.checkboxes[i].isSelected ? 1 : 0;
42 | $element.prop('indeterminate', false);
43 | $scope.master = (set === 0) ? false : true;
44 | if (set > 0 && set < i) {
45 | $scope.master = false;
46 | $element.prop('indeterminate', true);
47 | }
48 |
49 | if ($scope.master === true) {
50 | $element.prop('checked', true);
51 | } else {
52 | $element.prop('checked', false);
53 | }
54 | };
55 |
56 | $scope.$on($scope.masterSetOff, function () {
57 | $element.prop('indeterminate', false);
58 | $element.prop('checked', false);
59 | });
60 |
61 | $scope.clicked = function () {
62 | $scope.masterChanged();
63 | if ($scope.masterClicked) {
64 | $scope.masterClicked();
65 | }
66 | }
67 |
68 | $scope.masterChanged = function () {
69 | for (i = 0; i < $scope.checkboxes.length; i++) {
70 | $scope.checkboxes[i].isSelected = $scope.master;
71 | }
72 | if ($scope.masterChange) {
73 | $scope.masterChange();
74 | }
75 | };
76 |
77 | if (!$scope.childClick) {
78 | $scope.$watch('checkboxes', function () {
79 | $scope.setState();
80 | }, true);
81 | } else {
82 | $scope.$on($scope.childClick, function () {
83 | $scope.setState();
84 | });
85 | $scope.setState();
86 | }
87 | }]
88 | };
89 |
90 | return directive;
91 | };
92 |
93 | triStateCheckbox.$inject = [];
94 | angular.module("long2know.directives")
95 | .directive('triStateCheckbox', triStateCheckbox);
96 | })()
--------------------------------------------------------------------------------