checklist-model.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. /**
  2. * Checklist-model
  3. * AngularJS directive for list of checkboxes
  4. */
  5. angular.module('checklist-model', [])
  6. .directive('checklistModel', ['$parse', '$compile', function($parse, $compile) {
  7. // contains
  8. function contains(arr, item, comparator) {
  9. if (angular.isArray(arr)) {
  10. for (var i = arr.length; i--;) {
  11. if (comparator(arr[i], item)) {
  12. return true;
  13. }
  14. }
  15. }
  16. return false;
  17. }
  18. // add
  19. function add(arr, item, comparator) {
  20. arr = angular.isArray(arr) ? arr : [];
  21. if(!contains(arr, item, comparator)) {
  22. arr.push(item);
  23. }
  24. return arr;
  25. }
  26. // remove
  27. function remove(arr, item, comparator) {
  28. if (angular.isArray(arr)) {
  29. for (var i = arr.length; i--;) {
  30. if (comparator(arr[i], item)) {
  31. arr.splice(i, 1);
  32. break;
  33. }
  34. }
  35. }
  36. return arr;
  37. }
  38. // http://stackoverflow.com/a/19228302/1458162
  39. function postLinkFn(scope, elem, attrs) {
  40. // compile with `ng-model` pointing to `checked`
  41. $compile(elem)(scope);
  42. // getter / setter for original model
  43. var getter = $parse(attrs.checklistModel);
  44. var setter = getter.assign;
  45. var checklistChange = $parse(attrs.checklistChange);
  46. // value added to list
  47. var value = $parse(attrs.checklistValue)(scope.$parent);
  48. var comparator = angular.equals;
  49. if (attrs.hasOwnProperty('checklistComparator')){
  50. comparator = $parse(attrs.checklistComparator)(scope.$parent);
  51. }
  52. // watch UI checked change
  53. scope.$watch('checked', function(newValue, oldValue) {
  54. if (newValue === oldValue) {
  55. return;
  56. }
  57. var current = getter(scope.$parent);
  58. if (newValue === true) {
  59. setter(scope.$parent, add(current, value, comparator));
  60. } else {
  61. setter(scope.$parent, remove(current, value, comparator));
  62. }
  63. if (checklistChange) {
  64. checklistChange(scope);
  65. }
  66. });
  67. // declare one function to be used for both $watch functions
  68. function setChecked(newArr, oldArr) {
  69. scope.checked = contains(newArr, value, comparator);
  70. }
  71. // watch original model change
  72. // use the faster $watchCollection method if it's available
  73. if (angular.isFunction(scope.$parent.$watchCollection)) {
  74. scope.$parent.$watchCollection(attrs.checklistModel, setChecked);
  75. } else {
  76. scope.$parent.$watch(attrs.checklistModel, setChecked, true);
  77. }
  78. }
  79. return {
  80. restrict: 'A',
  81. priority: 1000,
  82. terminal: true,
  83. scope: true,
  84. compile: function(tElement, tAttrs) {
  85. if (tElement[0].tagName !== 'INPUT' || tAttrs.type !== 'checkbox') {
  86. throw 'checklist-model should be applied to `input[type="checkbox"]`.';
  87. }
  88. if (!tAttrs.checklistValue) {
  89. throw 'You should provide `checklist-value`.';
  90. }
  91. // exclude recursion
  92. tElement.removeAttr('checklist-model');
  93. // local scope var storing individual checkbox model
  94. tElement.attr('ng-model', 'checked');
  95. return postLinkFn;
  96. }
  97. };
  98. }]);