Project

General

Profile

1
/**
2
 * Checklist-model
3
 * AngularJS directive for list of checkboxes
4
 * https://github.com/vitalets/checklist-model
5
 * License: MIT http://opensource.org/licenses/MIT
6
 */
7

    
8
 /* commonjs package manager support (eg componentjs) */
9
 if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
10
   module.exports = 'checklist-model';
11
 }
12

    
13
angular.module('checklist-model', [])
14
.directive('checklistModel', ['$parse', '$compile', function($parse, $compile) {
15
  // contains
16
  function contains(arr, item, comparator) {
17
    if (angular.isArray(arr)) {
18
      for (var i = arr.length; i--;) {
19
        if (comparator(arr[i], item)) {
20
          return true;
21
        }
22
      }
23
    }
24
    return false;
25
  }
26

    
27
  // add
28
  function add(arr, item, comparator) {
29
    arr = angular.isArray(arr) ? arr : [];
30
      if(!contains(arr, item, comparator)) {
31
          arr.push(item);
32
      }
33
    return arr;
34
  }
35

    
36
  // remove
37
  function remove(arr, item, comparator) {
38
    if (angular.isArray(arr)) {
39
      for (var i = arr.length; i--;) {
40
        if (comparator(arr[i], item)) {
41
          arr.splice(i, 1);
42
          break;
43
        }
44
      }
45
    }
46
    return arr;
47
  }
48

    
49
  // http://stackoverflow.com/a/19228302/1458162
50
  function postLinkFn(scope, elem, attrs) {
51
     // exclude recursion, but still keep the model
52
    var checklistModel = attrs.checklistModel;
53
    attrs.$set("checklistModel", null);
54
    // compile with `ng-model` pointing to `checked`
55
    $compile(elem)(scope);
56
    attrs.$set("checklistModel", checklistModel);
57

    
58
    // getter for original model
59
    var checklistModelGetter = $parse(checklistModel);
60
    var checklistChange = $parse(attrs.checklistChange);
61
    var checklistBeforeChange = $parse(attrs.checklistBeforeChange);
62
    var ngModelGetter = $parse(attrs.ngModel);
63

    
64

    
65

    
66
    var comparator = function (a, b) {
67
      if(!isNaN(a) && !isNaN(b)) {
68
        return String(a) === String(b);
69
      } else {
70
        return angular.equals(a,b);
71
      }
72
    };
73

    
74
    if (attrs.hasOwnProperty('checklistComparator')){
75
      if (attrs.checklistComparator[0] == '.') {
76
        var comparatorExpression = attrs.checklistComparator.substring(1);
77
        comparator = function (a, b) {
78
          return a[comparatorExpression] === b[comparatorExpression];
79
        };
80

    
81
      } else {
82
        comparator = $parse(attrs.checklistComparator)(scope.$parent);
83
      }
84
    }
85

    
86
    // watch UI checked change
87
    var unbindModel = scope.$watch(attrs.ngModel, function(newValue, oldValue) {
88
      if (newValue === oldValue) {
89
        return;
90
      }
91

    
92
      if (checklistBeforeChange && (checklistBeforeChange(scope) === false)) {
93
        ngModelGetter.assign(scope, contains(checklistModelGetter(scope.$parent), getChecklistValue(), comparator));
94
        return;
95
      }
96

    
97
      setValueInChecklistModel(getChecklistValue(), newValue);
98

    
99
      if (checklistChange) {
100
        checklistChange(scope);
101
      }
102
    });
103

    
104
    // watches for value change of checklistValue
105
    var unbindCheckListValue = scope.$watch(getChecklistValue, function(newValue, oldValue) {
106
      if( newValue != oldValue && angular.isDefined(oldValue) && scope[attrs.ngModel] === true ) {
107
        var current = checklistModelGetter(scope.$parent);
108
        checklistModelGetter.assign(scope.$parent, remove(current, oldValue, comparator));
109
        checklistModelGetter.assign(scope.$parent, add(current, newValue, comparator));
110
      }
111
    }, true);
112

    
113
    var unbindDestroy = scope.$on('$destroy', destroy);
114

    
115
    function destroy() {
116
      unbindModel();
117
      unbindCheckListValue();
118
      unbindDestroy();
119
    }
120

    
121
    function getChecklistValue() {
122
      return attrs.checklistValue ? $parse(attrs.checklistValue)(scope.$parent) : attrs.value;
123
    }
124

    
125
    function setValueInChecklistModel(value, checked) {
126
      var current = checklistModelGetter(scope.$parent);
127
      if (angular.isFunction(checklistModelGetter.assign)) {
128
        if (checked === true) {
129
          checklistModelGetter.assign(scope.$parent, add(current, value, comparator));
130
        } else {
131
          checklistModelGetter.assign(scope.$parent, remove(current, value, comparator));
132
        }
133
      }
134

    
135
    }
136

    
137
    // declare one function to be used for both $watch functions
138
    function setChecked(newArr, oldArr) {
139
      if (checklistBeforeChange && (checklistBeforeChange(scope) === false)) {
140
        setValueInChecklistModel(getChecklistValue(), ngModelGetter(scope));
141
        return;
142
      }
143
      ngModelGetter.assign(scope, contains(newArr, getChecklistValue(), comparator));
144
    }
145

    
146
    // watch original model change
147
    // use the faster $watchCollection method if it's available
148
    if (angular.isFunction(scope.$parent.$watchCollection)) {
149
        scope.$parent.$watchCollection(checklistModel, setChecked);
150
    } else {
151
        scope.$parent.$watch(checklistModel, setChecked, true);
152
    }
153
  }
154

    
155
  return {
156
    restrict: 'A',
157
    priority: 1000,
158
    terminal: true,
159
    scope: true,
160
    compile: function(tElement, tAttrs) {
161

    
162
      if (!tAttrs.checklistValue && !tAttrs.value) {
163
        throw 'You should provide `value` or `checklist-value`.';
164
      }
165

    
166
      // by default ngModel is 'checked', so we set it if not specified
167
      if (!tAttrs.ngModel) {
168
        // local scope var storing individual checkbox model
169
        tAttrs.$set("ngModel", "checked");
170
      }
171

    
172
      return postLinkFn;
173
    }
174
  };
175
}]);
(7-7/12)