· 6 years ago · Jan 31, 2020, 02:30 AM
1var calendars = CALENDARS;
2var API_KEY = CALENDARS[0].api_key;
3
4var templatePrograms=`
5<hr>
6
7<div class="row">
8 <div class="small-12 large-4 columns">
9
10 <div class="active-search-group">
11 <h3 data-total-records="{{$ctrl.events.length}}">Filter Results:</h3>
12
13 <div>Branches: [<a href="javascript:;" ng-click="checkAllBranches()">Select all</a>] [<a href="javascript:;" ng-click="uncheckAllBranches()">Clear</a>]<br>
14 <label ng-repeat="c in $ctrl.branches | unique: 'branch' | orderBy: 'branch' ">
15 <input type="checkbox" checklist-model="getBranchesModel()" checklist-value="c.branch" ng-change="checkBranch(c.branch, checked)"> {{c.branch}}
16 </label>
17 </div>
18 <div>Calendars: [<a href="javascript:;" ng-click="checkAllCalendars()">Select all</a>] [<a href="javascript:;" ng-click="uncheckAllCalendars()">Clear</a>]<br>
19 <label ng-repeat="c in $ctrl.branches | unique: 'name' | orderBy: 'name' ">
20 <input type="checkbox" checklist-model="getCalendarsModel()" checklist-value="c.name" ng-change="checkCalendar(c.name, checked)"> {{c.name}}
21 </label>
22 </div>
23
24 <div>Time of day: [<a href="javascript:;" ng-click="checkAllPeriods()">Select all</a>] [<a href="javascript:;" ng-click="uncheckAllPeriods()">Clear</a>]<br>
25 <label ng-repeat="pe in $ctrl.periods">
26 <input type="checkbox" checklist-model="getPeriodsModel()" checklist-value="pe" ng-change="checkPeriod(pe, checked)"> {{pe}}
27 </label>
28 </div>
29
30 <div>Day of the week: [<a href="javascript:;" ng-click="checkAllDays()">Select all</a>] [<a href="javascript:;" ng-click="uncheckAllDays()">Clear</a>]<br>
31 <label ng-repeat="d in $ctrl.days">
32 <input type="checkbox" checklist-model="getDaysModel()" checklist-value="d" ng-change="checkDay(d, checked)"> {{d}}
33 </label>
34 </div>
35
36 <div>Search:<br>
37 <input ng-model="$ctrl.keyword" type="text" ng-change="gotoPage1()" placeholder="Type Keyword">
38 </div>
39
40 <div>
41 Week-view beginning on {{$ctrl.dateStart |date:"MM/dd/yyyy"}}:<br>
42 <datepicker minDate="$ctrl.dateStart" show-weeks="false" class="well well-lg" ng-model="$ctrl.dateStart"></datepicker>
43
44 <!--
45 End date: {{$ctrl.dateEnd|date:"MM/dd/yyyy"}}<br>
46 <datepicker minDate="moment($ctrl.dateStart).add(1,'d')" show-weeks="false" class="well well-lg" ng-model="$ctrl.dateEnd"></datepicker>
47 -->
48 </div>
49
50 <button ng-click="reset()" style="font-size:14px;">Show everything</button>
51 <button ng-click="clear()" style="font-size:14px;" ng-disabled="!($ctrl.branch.length || $ctrl.calendar.length || $ctrl.period.length || $ctrl.day.length || $ctrl.keyword)">Clear all filters</button>
52 </div>
53
54 </div>
55
56 <div class="small-12 large-8 columns">
57
58 <div class="active-program-lister">
59 <span class="curPage" ng-show="currentPage">Page {{currentPage}}</span>
60 <span>Week on display goes from {{$ctrl.dateStart|date:"MM/dd/yyyy"}} to {{$ctrl.dateEnd|date:"MM/dd/yyyy"}}.</span>
61
62 <a href="javascript:void(0)" class="globalToggle" ng-show="$ctrl.events.length" ng-click="detailsGlobalToggle()">Expand/collapse all details</a>
63
64{{$ctrl.eventsWeekday}}
65 <div ng-repeat="d in $ctrl.eventsWeekday">
66 {{p}}<br>
67 </div>
68
69
70 <div class="active-page">
71 <dir-pagination-controls on-page-change="onPageChange()"></dir-pagination-controls>
72 </div>
73
74
75 <div dir-paginate="p in ($ctrl.events | weekdayFilter:$ctrl.day | periodFilter:$ctrl.period | branchFilter2:{branch: $ctrl.branch} | calendarFilter2:{name: $ctrl.calendar} | keywordFilter: $ctrl.keyword | orderBy: 'start.dateTime' | itemsPerPage: 10) track by $index" current-page="currentPage">
76 <div class="item">
77 <h4 class="secondary-bg dark-bg" ng-show="p.start.weekdayPrev != p.start.weekday">{{p.start.weekday}}<hr></h4>
78
79 <h4 class="secondary-bg dark-bg" data-id="{{p.id}}">{{p.start.period}}: {{p.summary}}</h4>
80
81 <div class="info">
82
83 <h5 class="primary light">{{p.organizer.displayName}}</h5>
84 <div class="row">
85 <div class="columns small-12 medium-4 large-4">
86 <p><span class="label">Start:</span><br>{{p.start.dateTime | date : 'short'}}</p>
87 </div>
88 <div class="columns small-12 medium-4 large-4">
89 <p><span class="label">End:</span><br>
90 <span ng-show="p.end.dateTime">{{p.end.dateTime | date : 'short'}}</span>
91 </p>
92 </div>
93 <div class="columns small-12 medium-4 large-4">
94 <p><span class="label"></span><br>
95 <a href="{{p.htmlLink}}" class="button" target="_blank">add to your calendar</a>
96 </p>
97 </div>
98 </div>
99
100 <div class="align-bottom-group">
101 <div class="row">
102 <div class="columns small-12 medium-6 large-6 medium-push-6 reg-button">
103 </div>
104 <div class="columns small-12 medium-6 large-6 medium-pull-6" ng-show="p.description.length">
105 <a class="more" id="more{{p.id}}" ng-click="toggle(p.id)">More Details </a>
106 </div>
107 <div class="columns small-12 medium-6 large-6 medium-pull-6" ng-show="!p.description.length">
108 No description.
109 </div>
110 </div>
111 </div>
112 </div>
113 <div class="details" id="desc{{p.id}}" style="display:none;">
114 {{p.description || stripHTML}}
115 </div>
116 </div>
117 </div>
118 </div>
119
120 </div>
121
122 <div class="active-page">
123 <dir-pagination-controls on-page-change="onPageChange()"></dir-pagination-controls>
124 </div>
125
126 <p ng-show="($ctrl.events | weekdayFilter:$ctrl.day | periodFilter:$ctrl.period | branchFilter2:{branch: $ctrl.branch} | calendarFilter2:{name: $ctrl.calendar} | keywordFilter: $ctrl.keyword).length == 0" style="margin-top:20px">
127 There are currently no items matching your filters.
128 </p>
129 </div>
130</div>
131`
132
133function handleClientLoad() {
134 gapi.load('client:auth2', initClient);
135}
136
137function updateSigninStatus(isSignedIn) {
138 //do nothing because we don't need this
139 //without authorization, we can only look at public data
140}
141
142angular
143 .module("theApp", [
144 "ngRoute",
145 "angular-loading-bar",
146 "angularUtils.directives.dirPagination",
147 "angular.filter",
148 "checklist-model",
149 "ui.bootstrap"
150 ]);
151
152angular
153 .module("theApp")
154 .config(['$locationProvider', function($locationProvider){
155 $locationProvider.html5Mode({enabled: true, requireBase: false});
156}]);
157
158angular
159 .module("theApp")
160 .component("programsList", {
161 template: templatePrograms,
162 //templateUrl: "//"+ document.domain +"/graphics/active_api/template-program-list.html",
163 controller: function ProgramsList($window, $location, $scope, $http, $filter, $sce) {
164 var self = this;
165 var tz = "-05:00";
166
167 /* these initial dates will be overwritten by user values later if they're present on the URL */
168 $scope.$ctrl.dateStart = moment().format("YYYY-MM-DD");
169 $scope.$ctrl.dateEnd = moment().add(6,"d").format("YYYY-MM-DD");
170
171 self.weekdayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
172
173 var pullDatabase = function () {
174 var ps = [];
175 calendars.forEach(function (o, key) {
176 var cID = o.id;
177 var dateStart = `${moment($scope.$ctrl.dateStart,"YYYY-MM-DD").format("YYYY-MM-DD")}T00:00:01${tz}`;
178 var dateEnd = `${moment($scope.$ctrl.dateStart,"YYYY-MM-DD").add(6,"d").format("YYYY-MM-DD")}T23:59:59${tz}`;
179 var url = `https://www.googleapis.com/calendar/v3/calendars/${cID}/events?key=${API_KEY}&showDeleted=false&timeMin=${dateStart}&timeMax=${dateEnd}&singleEvents=true&orderBy=startTime`;
180 var p = $http.get(url);
181 ps.push(p);
182 });
183
184 var all = [];
185 Promise.all(ps).then( function(a) {
186 a.forEach( function (r) {
187 // merge all calendars together
188 all = all.concat(r.data.items);
189 });
190 var merged = []
191 all.forEach( function (e) {
192 // remove events that are not confirmed
193 if (e.status == "confirmed")
194 merged.push(e);
195 });
196
197 merged.sort( (o1, o2) => (o1.start.dateTime < o2.start.dateTime) ? -1 : 1);
198 self.allEvents = angular.copy(merged);
199
200 angular.forEach(self.allEvents, function (v, k, o) {
201 // Let's call this database 1. There's another way to build this database which is
202 // also required if we're to support all kinds of descriptions of events. It's only
203 // with database 2 that we can be sure to display the correct weekday of an event.
204 // To understand you this, you must understand all forms of expressions that the API
205 // uses to express events. I would really describe this more clearly if I had more time.
206 v.start.period = $scope.getPeriod(moment(v.start.dateTime));
207 v.start.weekday = self.weekdayNames[moment(v.start.dateTime).weekday()];
208 var prevItem = o[k - 1];
209 if (prevItem) {
210 // if there's a next item, get its weekDay
211 // we need this to make a special thing (display a weekday header) in the view
212 v.start.weekdayPrev = self.weekdayNames[moment(prevItem.start.dateTime).weekday()];
213 v.start.periodPrev = $scope.getPeriod(moment(prevItem.start.dateTime));
214 }
215 });
216
217 self.eventsWeekday = [];
218 console.log($scope.$ctrl.dateStart);
219 [0,1,2,3,4,5,6].every(function (i) {
220 // This strategy is not general enough for an arbitrary date interval. It assumes
221 // just a week long interval. (It only runs seven times.)
222 var m = moment($scope.$ctrl.dateStart).add(i, "d");
223 var wd = self.weekdayNames[m.weekday()];
224 console.log(wd);
225 if (!self.eventsWeekday[wd]) {
226 self.eventsWeekday[wd] = [];
227 }
228 angular.forEach(self.allEvents, function (v, k, o) {
229 if (v.start.weekday == wd) {
230 /*
231 We should probably memorize the real date here somehow.
232 If we just instantiate a current date's object, we'll get
233 the local timezone. That is a recipe for disaster due to
234 timezone differences. Let's just keep the string of the date.
235 We can figure out the time by looking at the event itself.
236 */
237 v.start.realEventDate = m.format("YYYY-MM-DD");
238 self.eventsWeekday[wd].push(v);
239 }
240 });
241 return true;
242 });
243 console.log(self.eventsWeekday);
244
245 self.events = angular.copy(self.allEvents);
246 // end-of promises-then-code
247 });
248 } // end of database
249
250 $scope.getPeriod = function (x) {
251 var d = moment(moment(x).format("HH:mm"),"HH:mm");
252 midnight = moment("00:00","HH:mm");
253 almost_midnight = moment("23:59","HH:mm");
254 midday = moment("12:00","HH:mm");
255 afternoon = moment("18:00","HH:mm");
256 if (d > midnight && d <= midday)
257 return "Morning";
258 if (d > midday && d <= afternoon)
259 return "Afternoon";
260 if (d > afternoon && d <= almost_midnight)
261 return "Evening";
262 console.log(d);
263 return "unknown-period";
264 }
265
266 $scope.updateUrl = function() {
267 var oldUrl = $location.absUrl();
268 $location.search({
269 branch: $scope.$ctrl.branch.join(),
270 calendar: $scope.$ctrl.calendar.join(),
271 period: $scope.$ctrl.period.join(),
272 day: $scope.$ctrl.day.join(),
273 dateStart: moment($scope.$ctrl.dateStart,"YYYY-MM-DD").format("YYYY-MM-DD"),
274 /*dateEnd: moment($scope.$ctrl.dateEnd,"YYYY-MM-DD").format("YYYY-MM-DD"),*/
275 keyword: $scope.$ctrl.keyword,
276 currentPage: $scope.currentPage
277 });
278 newPath = $location.absUrl();
279 $window.history.replaceState({}, "any", newPath);
280 }
281
282 $scope.$watch(
283 function () { return $scope.$ctrl.dateStart; },
284 function (newVal) {
285 $scope.updateUrl();
286 pullDatabase();
287 }
288 )
289 /*
290 $scope.$watch(
291 function () { return $scope.$ctrl.dateEnd; },
292 function (newVal) {
293 $scope.updateUrl();
294 pullDatabase();
295 }
296 )
297 */
298 $scope.$watch(
299 function () { return $scope.$ctrl.day.join(); },
300 function (newVal) { $scope.updateUrl(); }
301 )
302 $scope.$watch(
303 function () { return $scope.$ctrl.period.join(); },
304 function (newVal) { $scope.updateUrl(); }
305 )
306 $scope.$watch(
307 function () { return $scope.$ctrl.branch.join(); },
308 function (newVal) { $scope.updateUrl(); }
309 )
310 $scope.$watch(
311 function () { return $scope.$ctrl.calendar.join(); },
312 function (newVal) { $scope.updateUrl(); }
313 )
314 $scope.$watch(
315 function () { return $scope.currentPage; },
316 function (newVal) { $scope.updateUrl(); }
317 )
318 $scope.$watch(
319 function () { return $scope.$ctrl.keyword; },
320 function (newVal) { $scope.updateUrl(); }
321 )
322
323 $scope.gotoPage1 = function () {
324 /* This is directly related to
325 <ANY dir-paginate="..." current-page="currentPage">.
326 Make sure you have it properly set. */
327 $scope.currentPage = 1;
328 }
329
330 $scope.onPageChange = function (page) {
331 window.scrollTo(0,0);
332 }
333
334 $scope.detailsGlobalToggle = function () {
335 if (!$(".globalToggle").hasClass('open')){
336 $(".details").show();
337 $(".more").addClass('active');
338 $(".globalToggle").addClass('open');
339 } else {
340 $(".details").hide();
341 $(".more").removeClass('active');
342 $(".globalToggle").removeClass('open');
343 }
344 }
345
346 $scope.toggle = function (id) {
347 $("#desc" + id).toggle();
348 $("#more" + id).toggleClass('active');
349 };
350
351 self.branches = CALENDARS;
352
353 var cals = [];
354 var bs = [];
355 var periods = ["Morning", "Afternoon", "Evening"];
356 self.periods = angular.copy(periods);
357 var days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
358 self.days = angular.copy(days);
359
360 angular.forEach(self.branches, function (v, k, o) {
361 if (! cals.includes(v.name) )
362 cals.push(v.name);
363 if (! bs.includes(v.branch) )
364 bs.push(v.branch);
365 });
366
367 $scope.reset = function () {
368 $scope.$ctrl.calendar = angular.copy(cals);
369 $scope.$ctrl.branch = angular.copy(bs);
370 $scope.$ctrl.period = angular.copy(periods);
371 $scope.$ctrl.day = angular.copy(days);
372 $scope.$ctrl.keyword = "";
373 }
374
375 $scope.clear = function () {
376 $scope.$ctrl.calendar = [];
377 $scope.$ctrl.branch = [];
378 $scope.$ctrl.period = [];
379 $scope.$ctrl.day = [];
380 $scope.$ctrl.keyword = "";
381 }
382
383 $scope.$ctrl.branch = $location.search().branch ? $location.search().branch.split(",") : angular.copy(bs);
384 $scope.$ctrl.calendar = $location.search().calendar ? $location.search().calendar.split(",") : angular.copy(cals);
385 $scope.$ctrl.period = $location.search().period ? $location.search().period.split(",") : angular.copy(periods);
386 $scope.$ctrl.day = $location.search().day ? $location.search().day.split(",") : angular.copy(days);
387 $scope.$ctrl.dateStart = $location.search().dateStart || $scope.$ctrl.dateStart;
388 /*$scope.$ctrl.dateEnd = $location.search().dateEnd || $scope.$ctrl.dateEnd;*/
389 $scope.$ctrl.keyword = $location.search().keyword || "";
390 $scope.currentPage = $location.search().currentPage || 1;
391
392 // branches checklist implementation
393 $scope.getBranchesModel = function() {
394 return $scope.$ctrl.branch;
395 };
396
397 $scope.checkBranch = function(value, checked) {
398 var idx = $scope.$ctrl.branch.indexOf(value);
399 if (idx >= 0 && !checked) {
400 $scope.$ctrl.branch.splice(idx, 1);
401 }
402 if (idx < 0 && checked) {
403 $scope.$ctrl.branch.push(value);
404 }
405 };
406
407 $scope.checkAllBranches = function() {
408 $scope.$ctrl.branch = angular.copy(bs);
409 };
410 $scope.uncheckAllBranches = function() {
411 $scope.$ctrl.branch = [];
412 };
413 // end-of
414
415 // calendar checklist implementation
416 $scope.getCalendarsModel = function() {
417 return $scope.$ctrl.calendar;
418 };
419
420 $scope.checkCalendar = function(value, checked) {
421 var idx = $scope.$ctrl.calendar.indexOf(value);
422 if (idx >= 0 && !checked) {
423 $scope.$ctrl.calendar.splice(idx, 1);
424 }
425 if (idx < 0 && checked) {
426 $scope.$ctrl.calendar.push(value);
427 }
428 };
429
430 $scope.checkAllCalendars = function() {
431 $scope.$ctrl.calendar = angular.copy(cals);
432 };
433 $scope.uncheckAllCalendars = function() {
434 $scope.$ctrl.calendar = [];
435 };
436 // end-of
437
438 // periods checklist implementation
439 $scope.getPeriodsModel = function() {
440 return $scope.$ctrl.period;
441 };
442 $scope.checkPeriod = function(value, checked) {
443 var idx = $scope.$ctrl.period.indexOf(value);
444 if (idx >= 0 && !checked) {
445 $scope.$ctrl.period.splice(idx, 1);
446 }
447 if (idx < 0 && checked) {
448 $scope.$ctrl.period.push(value);
449 }
450 };
451 $scope.checkAllPeriods = function() {
452 $scope.$ctrl.period = angular.copy(periods);
453 };
454 $scope.uncheckAllPeriods = function() {
455 $scope.$ctrl.period = [];
456 };
457 // end-of
458
459 // weekdays checklist implementation
460 $scope.getDaysModel = function() {
461 return $scope.$ctrl.day;
462 };
463 $scope.checkDay = function(value, checked) {
464 var idx = $scope.$ctrl.day.indexOf(value);
465 if (idx >= 0 && !checked) {
466 $scope.$ctrl.day.splice(idx, 1);
467 }
468 if (idx < 0 && checked) {
469 $scope.$ctrl.day.push(value);
470 }
471 };
472 $scope.checkAllDays = function() {
473 $scope.$ctrl.day = angular.copy(days);
474 };
475 $scope.uncheckAllDays = function() {
476 $scope.$ctrl.day = [];
477 };
478 // end-of
479
480 self.trustSrc = function(src) {
481 return $sce.trustAsResourceUrl(src);
482 }
483
484 }
485 });
486
487angular
488 .module("theApp")
489 .config(['$locationProvider', //'$routeProvider',
490 function config($locationProvider//, $routeProvider
491 ) {
492 $locationProvider.hashPrefix('!');
493 /*
494 $routeProvider
495 .when('/activities', {
496 template: '<activities-list></activities-list>'
497 })
498 .when('/main', {
499 template: '<programs-list></programs-list>'
500 })
501 .when('/program/:pid', {
502 template: '<program-detail></program-detail>'
503 })
504 .otherwise('/main');
505 */
506 }
507 ]);
508
509angular
510 .module("theApp")
511 .filter('weekdayFilter', function() {
512 return function(ls, whichDs) {
513 var filtered = [];
514 angular.forEach(ls, function(item) {
515 if (!whichDs)
516 filtered.push(item);
517 else {
518 angular.forEach(whichDs, function (v, k, o) {
519 if (item.start.weekday.includes(v))
520 filtered.push(item);
521 });
522 }
523 });
524 return filtered;
525 };
526 });
527
528angular
529 .module("theApp")
530 .filter('periodFilter', function() {
531 return function(ls, whichPs) {
532 var filtered = [];
533 angular.forEach(ls, function(item) {
534 if (!whichPs)
535 filtered.push(item);
536 else {
537 angular.forEach(whichPs, function (v, k, o) {
538 if (item.start.period.includes(v))
539 filtered.push(item);
540 });
541 }
542 });
543 return filtered;
544 };
545 });
546
547angular
548 .module("theApp")
549 .filter('calendarFilter2', function() {
550 return function(ls, whichCals, scope) {
551 var filtered = [];
552 angular.forEach(ls, function(item) {
553 if (!whichCals)
554 filtered.push(item);
555 else {
556 angular.forEach(whichCals.name, function (v, k, o) {
557 if (item.organizer.displayName && item.organizer.displayName.includes(v))
558 filtered.push(item);
559 });
560 }
561 });
562 return filtered;
563 };
564 });
565
566angular
567 .module("theApp")
568 .filter('calendarFilter', function() {
569 return function(ls, whichCals) {
570 var filtered = [];
571 angular.forEach(ls, function(item) {
572 if (!whichCals)
573 filtered.push(item);
574 else {
575 angular.forEach(whichCals.name, function (v, k, o) {
576 if (item.name.includes(v))
577 filtered.push(item);
578 });
579 }
580 });
581 return filtered;
582 };
583 });
584
585angular
586 .module("theApp")
587 .filter('branchFilter2', function() {
588 return function(ls, whichBs, scope) {
589 var filtered = [];
590 angular.forEach(ls, function(item) {
591 if (!whichBs)
592 filtered.push(item);
593 else {
594 angular.forEach(whichBs.branch, function (v, k, o) {
595 if (item.organizer.displayName && item.organizer.displayName.includes(v))
596 filtered.push(item);
597 });
598 }
599 });
600 return filtered;
601 };
602 });
603
604angular
605 .module("theApp")
606 .filter('branchFilter', function() {
607 return function(ls, whichBs) {
608 var filtered = [];
609 angular.forEach(ls, function(item) {
610 if (!whichBs)
611 filtered.push(item);
612 else {
613 angular.forEach(whichBs.branch, function (v, k, o) {
614 if (item.branch.includes(v))
615 filtered.push(item);
616 });
617 }
618 });
619 return filtered;
620 };
621 });
622
623angular
624 .module("theApp")
625 .filter('nonempty', function() {
626 return function(ls, property) {
627 var filtered = [];
628 angular.forEach(ls, function(item) {
629 if (item[property]) filtered.push(item);
630 });
631 return filtered;
632 };
633 });
634
635angular
636 .module('theApp')
637 .filter('stripHTML', function() {
638 return function(text) {
639 return text ? String(text).replace(/<[^>]+>/gm, '') : '';
640 };
641 }
642);
643
644angular
645 .module("theApp")
646 .filter('keywordFilter', function() {
647 /* This filter is built just so we can search for /keyword/ in
648 * various different fields. It's an or-like filter. */
649 return function(ls, keyword) {
650 var s = keyword && keyword.trim().toLowerCase();
651 var filtered = [];
652 angular.forEach(ls, function(item) {
653 if (!item.description)
654 item.description = "";
655 if (!item.summary)
656 item.summary = "";
657 if (!s)
658 filtered.push(item);
659 else
660 if (item.description.toLowerCase().includes(s) || item.summary.toLowerCase().includes(s))
661 filtered.push(item);
662 });
663 return filtered;
664 };
665 });
666
667/**
668 * Checklist-model
669 * AngularJS directive for list of checkboxes
670 * https://github.com/vitalets/checklist-model
671 * License: MIT http://opensource.org/licenses/MIT
672 */
673
674angular.module('checklist-model', [])
675.directive('checklistModel', ['$parse', '$compile', function($parse, $compile) {
676 // contains
677 function contains(arr, item, comparator) {
678 if (angular.isArray(arr)) {
679 for (var i = arr.length; i--;) {
680 if (comparator(arr[i], item)) {
681 return true;
682 }
683 }
684 }
685 return false;
686 }
687
688 // add
689 function add(arr, item, comparator) {
690 arr = angular.isArray(arr) ? arr : [];
691 if(!contains(arr, item, comparator)) {
692 arr.push(item);
693 }
694 return arr;
695 }
696
697 // remove
698 function remove(arr, item, comparator) {
699 if (angular.isArray(arr)) {
700 for (var i = arr.length; i--;) {
701 if (comparator(arr[i], item)) {
702 arr.splice(i, 1);
703 break;
704 }
705 }
706 }
707 return arr;
708 }
709
710 // http://stackoverflow.com/a/19228302/1458162
711 function postLinkFn(scope, elem, attrs) {
712 // exclude recursion, but still keep the model
713 var checklistModel = attrs.checklistModel;
714 attrs.$set("checklistModel", null);
715 // compile with `ng-model` pointing to `checked`
716 $compile(elem)(scope);
717 attrs.$set("checklistModel", checklistModel);
718
719 // getter / setter for original model
720 var getter = $parse(checklistModel);
721 var setter = getter.assign;
722 var checklistChange = $parse(attrs.checklistChange);
723 var checklistBeforeChange = $parse(attrs.checklistBeforeChange);
724
725 // value added to list
726 var value = attrs.checklistValue ? $parse(attrs.checklistValue)(scope.$parent) : attrs.value;
727
728
729 var comparator = angular.equals;
730
731 if (attrs.hasOwnProperty('checklistComparator')){
732 if (attrs.checklistComparator[0] == '.') {
733 var comparatorExpression = attrs.checklistComparator.substring(1);
734 comparator = function (a, b) {
735 return a[comparatorExpression] === b[comparatorExpression];
736 };
737
738 } else {
739 comparator = $parse(attrs.checklistComparator)(scope.$parent);
740 }
741 }
742
743 // watch UI checked change
744 scope.$watch(attrs.ngModel, function(newValue, oldValue) {
745 if (newValue === oldValue) {
746 return;
747 }
748
749 if (checklistBeforeChange && (checklistBeforeChange(scope) === false)) {
750 scope[attrs.ngModel] = contains(getter(scope.$parent), value, comparator);
751 return;
752 }
753
754 setValueInChecklistModel(value, newValue);
755
756 if (checklistChange) {
757 checklistChange(scope);
758 }
759 });
760
761 function setValueInChecklistModel(value, checked) {
762 var current = getter(scope.$parent);
763 if (angular.isFunction(setter)) {
764 if (checked === true) {
765 setter(scope.$parent, add(current, value, comparator));
766 } else {
767 setter(scope.$parent, remove(current, value, comparator));
768 }
769 }
770
771 }
772
773 // declare one function to be used for both $watch functions
774 function setChecked(newArr, oldArr) {
775 if (checklistBeforeChange && (checklistBeforeChange(scope) === false)) {
776 setValueInChecklistModel(value, scope[attrs.ngModel]);
777 return;
778 }
779 scope[attrs.ngModel] = contains(newArr, value, comparator);
780 }
781
782 // watch original model change
783 // use the faster $watchCollection method if it's available
784 if (angular.isFunction(scope.$parent.$watchCollection)) {
785 scope.$parent.$watchCollection(checklistModel, setChecked);
786 } else {
787 scope.$parent.$watch(checklistModel, setChecked, true);
788 }
789 }
790
791 return {
792 restrict: 'A',
793 priority: 1000,
794 terminal: true,
795 scope: true,
796 compile: function(tElement, tAttrs) {
797 if ((tElement[0].tagName !== 'INPUT' || tAttrs.type !== 'checkbox') && (tElement[0].tagName !== 'MD-CHECKBOX') && (!tAttrs.btnCheckbox)) {
798 throw 'checklist-model should be applied to `input[type="checkbox"]` or `md-checkbox`.';
799 }
800
801 if (!tAttrs.checklistValue && !tAttrs.value) {
802 throw 'You should provide `value` or `checklist-value`.';
803 }
804
805 // by default ngModel is 'checked', so we set it if not specified
806 if (!tAttrs.ngModel) {
807 // local scope var storing individual checkbox model
808 tAttrs.$set("ngModel", "checked");
809 }
810
811 return postLinkFn;
812 }
813 };
814}]);