Fixed several formatting issues
[cds-indico.git] / indico / htdocs / js / indico / RegistrationForm / section.js
blobd34604f2e63ee3efd24e7a0fb62130a6e06a8f93
1 /* This file is part of Indico.
2  * Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
3  *
4  * Indico is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 3 of the
7  * License, or (at your option) any later version.
8  *
9  * Indico is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with Indico; if not, see <http://www.gnu.org/licenses/>.
16  */
18 ndRegForm.controller('SectionCtrl', function($scope, $rootScope, regFormFactory) {
19     $scope.sectionApi = {};
20     $scope.actions = {};
22     var getRequestParams = function(section) {
23         return {
24             confId: $rootScope.confId,
25             sectionId: section.id
26         };
27     };
29     $scope.sectionApi.disableSection = function(section) {
30         $scope.$parent.animations.recoverSectionButton = 'button-highlight';
31         regFormFactory.Sections.disable(getRequestParams(section), function(updatedSection) {
32             regFormFactory.processResponse(updatedSection, {
33                 success: function(updatedSection)  {
34                     section.enabled = updatedSection.enabled;
35                 }
36             });
37         });
38     };
40     $scope.sectionApi.saveConfig = function(section, data) {
41         var requestParams = angular.extend(getRequestParams(section), data);
42         regFormFactory.Sections.save(requestParams, function(updatedSection) {
43             regFormFactory.processResponse(updatedSection, {
44                 success: function(updatedSection)  {
45                     $scope.section = angular.extend($scope.section, updatedSection);
46                     if (updatedSection.id == 'sessions') {
47                         $scope.fetchSessions();
48                     }
49                 }
50             });
51         });
52     };
54     $scope.sectionApi.updateTitle = function(section, data) {
55         var requestParams = angular.extend(getRequestParams(section), data);
57         regFormFactory.Sections.title(requestParams, function(updatedSection) {
58             regFormFactory.processResponse(updatedSection, {
59                 success: function(updatedSection) {
60                     $scope.section.title = updatedSection.title;
61                 }
62             });
63         });
64     };
66     $scope.sectionApi.updateDescription = function(section, data) {
67         var requestParams = angular.extend(getRequestParams(section), data);
69         regFormFactory.Sections.description(requestParams, function(updatedSection) {
70             regFormFactory.processResponse(updatedSection, {
71                 success: function(updatedSection)  {
72                     $scope.section.description = updatedSection.description;
73                 }
74             });
75         });
76     };
78     $scope.sectionApi.moveField = function(section, field, position) {
79         var requestParams = angular.extend(getRequestParams(section), {
80             fieldId: field.id,
81             endPos: position
82         });
84         regFormFactory.Fields.move(requestParams, function(updatedSection) {
85             regFormFactory.processResponse(updatedSection, {
86                 success: function(updatedSection) {}
87             });
88             // TODO in case backend rejects request we should update scope with something like:
89             // if (response.error) {
90             //     $scope.section.items = response.updatedSection.items;
91             // }
92         });
93     };
95     $scope.sectionApi.removeField = function(section, field) {
96         $scope.dialogs.removefield.field = field;
98         $scope.dialogs.removefield.callback = function(success) {
99             if (success) {
100                 var requestParams = angular.extend(getRequestParams(section), {
101                     fieldId: field.id
102                 });
104                 $scope.$apply(regFormFactory.Fields.remove(requestParams, {}, function(updatedSection) {
105                     regFormFactory.processResponse(updatedSection, {
106                         success: function(updatedSection) {
107                             $scope.section.items = updatedSection.items;
108                         }
109                     });
110                 }));
111             }
112         };
114         $scope.dialogs.removefield.open = true;
115     };
117     $scope.actions.openAddField = function(section, field, type) {
118         $scope.dialogs.newfield = true;
119         section.items.push({
120             id: -1,
121             disabled: false,
122             input: field,
123             lock: [],
124             values: {
125                 inputType: type
126             }
127         });
128     };
131 ndRegForm.directive('ndSection', function($rootScope, url) {
132     return {
133         replace: true,
134         restrict: 'E',
135         templateUrl: url.tpl('section.tpl.html'),
136         controller: 'SectionCtrl',
138         link: function(scope, element) {
140             scope.buttons = {
141                 newfield: false,
142                 config: false,
143                 disable: false
144             };
146             scope.dialogs = {
147                 newfield: false,
148                 config: {
149                     open: false,
150                     actions: {},
151                     formData: []
152                 },
153                 removefield: {
154                     open: false
155                 }
156             };
158             scope.state = {
159                 collapsed: false
160             };
162             scope.$on('collapse', function(event, collapsed) {
163                 scope.state.collapsed = collapsed;
164             });
166             scope.$watch('state.collapsed', function(val) {
167                 var content = angular.element(element.children()[2]);
168                 if (val) {
169                     content.slideUp();
170                 } else {
171                     content.slideDown();
172                 }
173             });
175             scope.$watch('section.title', function(newVal, oldVal) {
176                 if (newVal !== oldVal) {
177                     scope.sectionApi.updateTitle(scope.section, {title: newVal});
178                 }
179             });
181             scope.$watch('section.description', function(newVal, oldVal) {
182                 if (newVal !== oldVal) {
183                     scope.sectionApi.updateDescription(scope.section, {description: newVal});
184                 }
185             });
187             scope.dialogs.config.actions.onOk = function(dialogScope) {
188                 if (dialogScope.sectionForm.$invalid === true) {
189                     // TODO Uncomment when AngularJS >= 1.2
190                     //      Current version doesn't generate ngForm names dynamicly
191                     // var forms = _.filter($.map(dialogScope.sectionForm, function(value, index) {
192                     //     return index;
193                     // }), function(index) {
194                     //     return index[0] != '$';
195                     // });
197                     // var firstInvalid = _.find(dialogScope.sectionForm, function(form) {
198                     //     return form.$invalid;
199                     // });
201                     // var invalid = _.find(forms, function(f) {
202                     //     return dialogScope.sectionForm[f].$invalid;
203                     // });
205                     // dialogScope.$apply(dialogScope.setSelectedTab(firstInvalid));
206                     return false;
207                 }
209                 scope.sectionApi.saveConfig(dialogScope.section, dialogScope.formData);
210                 return true;
211             };
213             scope.dialogs.config.actions.onCancel = function(dialogScope) {
214             };
215         }
216     };
219 ndRegForm.directive("ndGeneralSection", function($timeout, url, sortableoptions) {
220     return {
221         require: 'ndSection',
222         controller: 'SectionCtrl',
224         link: function(scope) {
225             scope.buttons.newfield = true;
226             scope.buttons.disable = true;
227             scope.tplGeneralField = url.tpl('sections/generalfield.tpl.html');
229             scope.sectionApi.removeNewField = function() {
230                 if (scope.section.items[scope.section.items.length-1].id == -1) {
231                     $timeout(function() {
232                         scope.section.items.pop();
233                     }, 0);
234                 }
235             };
237             scope.fieldSortableOptions = {
238                 update: function(e, ui) {
239                     scope.sectionApi.moveField(scope.section, ui.item.scope().field, ui.item.index());
240                 },
241                 // TODO Re-enable when solved: http://bugs.jqueryui.com/ticket/5772
242                 // containment: '.field-list',
243                 handle: ".regform-field .field-sortable-handle",
244                 placeholder: "regform-field-sortable-placeholder"
245             };
247             angular.extend(scope.fieldSortableOptions, sortableoptions);
248         }
249     };
252 ndRegForm.directive("ndPersonalDataSection", function() {
253     return {
254         require: 'ndGeneralSection',
255         priority: -1,
256         link: function(scope) {
257             scope.buttons.disable = false;
258         }
259     };
262 ndRegForm.directive("ndAccommodationSection", function($rootScope) {
263     return {
264         require: 'ndSection',
265         link: function(scope) {
266             scope.buttons.config = true;
267             scope.buttons.disable = true;
269             scope.accommodation = {};
270             scope.$watch('userdata.accommodation', function() {
271                 if (scope.userdata.accommodation === undefined ||
272                     scope.userdata.accommodation.accommodationType === null) {
273                     return;
274                 }
275                 scope.accommodation.typeId = scope.userdata.accommodation.accommodationType.id;
276                 scope.accommodation.arrivalDate = scope.userdata.accommodation.arrivalDate;
277                 scope.accommodation.departureDate = scope.userdata.accommodation.departureDate;
278             });
280             scope.billableOptionPayed = function(userdata) {
281                 if (userdata.accommodation !== undefined) {
282                     var accommodation = userdata.accommodation.accommodationType || {};
283                     return accommodation.billable === true && userdata.paid === true;
284                 }
286                 return false;
287             };
289             scope.updateArrival = function(arrival) {
290                 scope.arrival = arrival;
291                 scope.arrivalUpdated = true;
292             };
294             scope.possibleDeparture = function(departure) {
295                 if (scope.arrival !== undefined) {
296                     var arrival = moment(scope.arrival, 'DD/MM/YYY');
297                     departure = moment(departure[0], 'DD/MM/YYY');
298                     return arrival.isBefore(departure);
299                 }
301                 return true;
302             };
304             scope.dialogs.config.arrivalDates = {
305                 sDate: moment($rootScope.confSdate).format('DD/MM/YYYY'),
306                 eDate: moment($rootScope.confEdate).format('DD/MM/YYYY')
307             };
309             scope.dialogs.config.departureDates = {
310                 sDate: moment($rootScope.confSdate).format('DD/MM/YYYY'),
311                 eDate: moment($rootScope.confEdate).format('DD/MM/YYYY')
312             };
314             scope.dialogs.config.updateArrivalDates = function(offset) {
315                 offset = offset || [0, 0];
316                 scope.dialogs.config.arrivalDates.sDate =
317                     moment($rootScope.confSdate)
318                         .subtract('d', parseInt(offset[0], 10))
319                         .format('DD/MM/YYYY');
320                 scope.dialogs.config.arrivalDates.eDate =
321                     moment($rootScope.confEdate)
322                         .subtract('d', parseInt(offset[1], 10))
323                         .format('DD/MM/YYYY');
324             };
325             scope.dialogs.config.updateArrivalDates(scope.section.arrivalOffsetDates);
327             scope.dialogs.config.updateDepartureDates = function(offset) {
328                 offset = offset || [0, 0];
329                 scope.dialogs.config.departureDates.sDate =
330                     moment($rootScope.confSdate)
331                         .add('d', parseInt(offset[0], 10))
332                         .format('DD/MM/YYYY');
333                 scope.dialogs.config.departureDates.eDate =
334                     moment($rootScope.confEdate)
335                         .add('d', parseInt(offset[1], 10))
336                         .format('DD/MM/YYYY');
337             };
338             scope.dialogs.config.updateDepartureDates(scope.section.departureOffsetDates);
340             scope.dialogs.config.formData.push('arrivalOffsetDates');
341             scope.dialogs.config.formData.push('departureOffsetDates');
342             scope.dialogs.config.tabs = [
343                 {id: 'config', name: $T("Configuration"), type: 'config' },
344                 {id: 'editAccomodation', name: $T("Edit accommodations"), type: 'editionTable' }
345             ];
347             scope.dialogs.config.editionTable = {
348                 sortable: false,
349                 colNames: [
350                     $T("Accommodation option"),
351                     $T("Billable"),
352                     $T("Price"),
353                     $T("Places limit"),
354                     $T("Cancelled")
355                 ],
356                 actions: ['remove'],
357                 colModel: [
358                        {
359                            name:'caption',
360                            index:'caption',
361                            align: 'center',
362                            width:100,
363                            editoptions: {size:"30",maxlength:"50"},
364                            editable: true,
365                            edittype: "text"
366                        },
367                        {
368                            name:'billable',
369                            index:'billable',
370                            width:60,
371                            editable: true,
372                            align: 'center',
373                            edittype:'bool_select',
374                            defaultVal: true
376                        },
377                        {
378                            name:'price',
379                            index:'price',
380                            align: 'center',
381                            width:50,
382                            editable: true,
383                            edittype: "text",
384                            editoptions:{size:"7",maxlength:"20"}
386                        },
387                        {
388                            name:'placesLimit',
389                            index:'placesLimit',
390                            align: 'center',
391                            width:80,
392                            editable: true,
393                            edittype: "text",
394                            editoptions:{size:"7",maxlength:"20"}
395                        },
396                        {
397                            name:'cancelled',
398                            index:'cancelled',
399                            width:60,
400                            editable: true,
401                            align: 'center',
402                            defaultVal: false,
403                            edittype:'bool_select'
404                        }
406                   ]
407             };
408         }
409     };
412 ndRegForm.directive("ndFurtherInformationSection", function() {
413     return {
414         require: 'ndSection',
415         link: function(scope) {
416             scope.buttons.disable = true;
418             scope.$watch('section.content', function(newVal, oldVal) {
419                 if (newVal !== oldVal) {
420                     scope.sectionApi.updateDescription(scope.section, {description: newVal});
421                 }
422             });
423         }
424     };
427 ndRegForm.directive("ndReasonSection", function() {
428     return {
429         require: 'ndSection',
430         link: function(scope) {
431             scope.buttons.disable = true;
432         }
433     };
436 ndRegForm.directive("ndSessionsSection", function($rootScope, regFormFactory) {
437     return {
438         require: 'ndSection',
440         controller: function($scope) {
441             var hasSession = function(id) {
442                 return _.find($scope.section.items, function(session) {
443                     return session.id == id;
444                 }) !== undefined;
445             };
447             $scope.anyBillableSessionPayed = function(userdata) {
448                 if (userdata.paid) {
449                     return _.any(userdata.sessionList, function(item) {
450                         var session = _.find($scope.section.items, function(session) {
451                             return session.id == item.id;
452                         }) || {};
454                         return session.billable && session.price !== 0;
455                     });
456                 }
458                 return false;
459             };
461             $scope.anySessionEnabled = function() {
462                 return _.any($scope.section.items, function(session) {
463                     return session.enabled === true;
464                 });
465             };
467             $scope.fetchSessions = function() {
468                 var sessions = regFormFactory.Sessions.query({confId: $rootScope.confId}, function() {
469                     _.each(sessions, function (item, ind) {
470                         if(!hasSession(item.id)) {
471                             $scope.section.items.push({
472                                 id: item.id,
473                                 caption: item.title,
474                                 billable: false,
475                                 price: 0,
476                                 enabled: false
477                             });
478                         }
479                     });
480                 });
481             };
483             $scope.fetchSessions();
484         },
486         link: function(scope) {
487             scope.buttons.config = true;
488             scope.buttons.disable = true;
490             scope.isSelected = function(sessionId) {
491                 return _.any(scope.userdata.sessionList, function(e) {
492                     return sessionId == e.id;
493                 });
494             };
496             scope.dialogs.config.formData.push('type');
497             scope.dialogs.config.tabs = [
498                 {id: 'config', name: $T("Configuration"), type: 'config'},
499                 {id: 'editSessions', name: $T("Manage sessions"), type: 'editionTable'}
500             ];
502             scope.dialogs.config.editionTable = {
503                 sortable: false,
505                 colNames:[
506                     $T('Session'),
507                     $T('Billable'),
508                     $T('Price'),
509                     $T('Enabled')
510                 ],
512                 actions: [''],
513                 colModel: [
514                        {
515                            name:'caption',
516                            index:'caption',
517                            align: 'left',
518                            width:200,
519                            editoptions:{size:"30",maxlength:"80"},
520                            editable: false,
521                            edittype: "text"
522                        },
523                        {
524                            name:'billable',
525                            index:'billable',
526                            width:60,
527                            editable: true,
528                            align: 'center',
529                            edittype:'bool_select',
530                            defaultVal: true
531                        },
532                        {
533                            name:'price',
534                            index:'price',
535                            align: 'center',
536                            width:80,
537                            editable: true,
538                            edittype: "text",
539                            editoptions:{size:"7",maxlength:"20"}
540                        },
541                        {
542                            name:'enabled',
543                            index:'enabled',
544                            width:60,
545                            editable: true,
546                            align: 'center',
547                            edittype:'bool_select',
548                            defaultVal: true
549                        }
550                     ]
551             };
552         }
553     };
556 ndRegForm.directive("ndSocialEventSection", function() {
557     return {
558         require: 'ndSection',
559         link: function(scope) {
560             scope.buttons.config = true;
561             scope.buttons.disable = true;
562             // keep track of the selected radio item
563             scope.selectedRadioInput = {};
564             // Keep track of the selected checkbox items (multiple)
565             scope.selectedInputs = _.object(_.map(_.filter(scope.section.items, function(item) {
566                     return item.cancelled != 'false';
567                 }), function(item){
568                     return [item.id, false];
569                 }));
570             // Keep track of the number of accompanying people
571             scope.selectedPlaces = _.object(_.map(_.filter(scope.section.items, function(item) {
572                     return item.cancelled != 'false';
573                 }), function(item){
574                     return [item.id, 1];
575                 }));
577             scope.$watch('userdata.socialEvents', function() {
578                 angular.forEach(scope.userdata.socialEvents, function(item){
579                     scope.selectedRadioInput.id = item.id;  // Used when radio buttons
580                     scope.selectedInputs[item.id] = true;  // Used when checkboxes
581                     scope.selectedPlaces[item.id] = scope.getNoPlacesFromUserData(item);
582                 });
583             });
585             scope.anySelected = function() {
586                 return _.any(scope.selectedInputs, function(e) {
587                     return e;
588                 });
589             };
591             scope.getMaxRegistrations = function(item) {
592                 if (item.placesLimit !== 0) {
593                     return Math.min(item.maxPlace + 1, item.noPlacesLeft + scope.getNoPlacesFromUserData(item));
594                 } else {
595                     return item.maxPlace + 1;
596                 }
597             };
599             scope.noAvailableEvent =function() {
600                 if (scope.section.items.length === 0) {
601                     return true;
602                 } else {
603                     return _.every(scope.section.items, function(item) {
604                         return item.cancelled;
605                     });
606                 }
607             };
609             scope.anyCancelledEvent = function() {
610                 return _.any(scope.section.items, function(item) {
611                     return item.cancelled;
612                 });
613             };
615             scope.anyBillableEventPayed = function(userdata) {
616                 if (userdata.paid) {
617                     return _.any(userdata.socialEvents, function(item) {
618                         return item.price !== 0;
619                     });
620                 }
622                 return false;
623             };
625             scope.getNoPlacesFromUserData = function(item) {
626                 var e = _.find(scope.userdata.socialEvents, function(e) {
627                     return item.id == e.id;
628                 });
629                 if (e !== undefined) {
630                     return e.noPlaces;
631                 } else {
632                     return 0;
633                 }
634             };
636             scope.getNoPlaces = function(item) {
637                 if (scope.section.selectionType == 'multiple' && !scope.selectedInputs[item.id]) {
638                     return 0;
639                 }
640                 if (scope.section.selectionType == 'unique' && item.id !== scope.selectedRadioInput.id) {
641                     return 0;
642                 }
643                 return scope.selectedPlaces[item.id];
644             };
646             scope.dialogs.config.formData.push('introSentence');
647             scope.dialogs.config.formData.push('mandatory');
648             scope.dialogs.config.formData.push('selectionType');
649             scope.dialogs.config.tabs = [
650                 {id: 'config', name: $T("Configuration"), type: 'config'},
651                 {id: 'editEvents', name: $T("Edit events"), type: 'editionTable'},
652                 {id: 'canceledEvent', name: $T("Canceled events"), type: 'cancelEvent'}
653             ];
655             scope.dialogs.config.editionTable = {
656                 sortable: false,
658                 colNames: [
659                     $T("Event name"),
660                     $T("Billable"),
661                     $T("Price"),
662                     $T("Capacity"),
663                     '',
664                     $T("Accompanying"),
665                     $T("Must pay")
666                 ],
668                 colHelp: [
669                     '',
670                     $T('Uncheck to make the attendance free of charge without changing the price'),
671                     $T('Price for attending the event'),
672                     $T('Maximum amount of participants on the event'),
673                     '',
674                     $T('Limit of accompanying persons a participant can bring'),
675                     $T('Whether accompanying persons have to pay attendance or not')
676                 ],
678                 actions: [
679                     'remove',
680                     ['cancel', $T('Cancel this event'),'#tab-canceledEvent','icon-disable']
681                 ],
683                 colModel: [
684                         {
685                            name:'caption',
686                            index:'caption',
687                            align: 'center',
688                            width:140,
689                            editoptions: {size: "25", maxlength: "50"},
690                            editable: true,
691                            edittype: "text"
692                         },
693                         {
694                            name:'billable',
695                            index:'billable',
696                            width:60,
697                            editable: true,
698                            align: 'center',
699                            defaultVal : false,
700                            edittype:'bool_select'
701                         },
702                         {
703                            name: 'price',
704                            index: 'price',
705                            align: 'center',
706                            width: 50,
707                            editable: true,
708                            edittype: "text",
709                            editoptions: {size: "7", maxlength: "20"},
710                         },
711                         {
712                            name:'placesLimit',
713                            index:'placesLimit',
714                            align: 'center',
715                            sortable:false,
716                            width:80,
717                            editable: true,
718                            edittype: "text",
719                            editoptions:{size:"5",maxlength:"20"}
720                         },
721                         {width: 20},
722                         {
723                            name: 'maxPlace',
724                            index: 'maxPlace',
725                            align: 'center',
726                            className: 'accompanying-col',
727                            width: 60,
728                            editable: true,
729                            edittype: "int",
730                            editoptions: {size:"6", maxlength:"20"}
731                         },
732                         {
733                            name:'pricePerPlace',
734                            index:'pricePerPlace',
735                            width:80,
736                            editable: true,
737                            align: 'center',
738                            className: 'accompanying-col',
739                            defaultVal : false,
740                            edittype:'bool_select'
741                         }
742                   ]
743             };
745             scope.dialogs.config.canceledTable = {
746                 sortable: false,
747                 colNames:[$T("Event name"), $T("Reason for cancellation")],
748                 actions: ['remove', ['uncancel', $T('Uncancel this event'),'#tab-editEvents','icon-checkmark']],
749                 colModel: [
750                         {
751                             index:'caption',
752                             align: 'left',
753                             width:160,
754                             editoptions:{size:"30",maxlength:"50"},
755                             editable: false
756                         },
757                         {
758                             name:'reason',
759                             index:'cancelledReason',
760                             width:250,
761                             editoptions:{size:"30",maxlength:"50"},
762                             editable: true,
763                             edittype: 'text'
764                         }
766                      ]
767             };
768         }
769     };
772 ndRegForm.directive('ndSectionDialog', function(url) {
773     return {
774         require: 'ndDialog',
776         controller: function($scope) {
777             $scope.templateUrl = url.tpl('sections/dialogs/base.tpl.html');
778             $scope.actions.init = function() {
779                 $scope.section = $scope.data;
781                 $scope.formData = {};
782                 $scope.formData.items = [];
784                 _.each($scope.config.formData, function(item) {
785                     if (Array.isArray(item) && $scope.section[item[0]] !== undefined) {
786                         $scope.formData[item[1]] = angular.copy($scope.section[item[0]][item[1]]);
787                     } else {
788                         $scope.formData[item] = angular.copy($scope.section[item]);
789                     }
790                 });
792                 _.each($scope.section.items, function (item, ind) {
793                     $scope.formData.items[ind] = angular.copy(item);
794                 });
796                 $scope.tabSelected = $scope.config.tabs[0].id;
797             };
799             $scope.addItem = function () {
800                  $scope.formData.items.push({id:'isNew', cancelled: false});
801             };
802         },
804         link: function(scope) {
805             scope.getTabTpl = function(section_id, tab_type) {
806                 return url.tpl('sections/dialogs/{0}-{1}.tpl.html'.format(section_id, tab_type));
807             };
808         }
809     };
812 ndRegForm.filter('possibleDeparture', function () {
813     return function (departure, scope) {
814         if (scope.accommodation.arrival !== undefined) {
815             var arrival = moment(scope.accommodation.arrival, 'DD/MM/YYY');
816             var possibleDepartures = {};
817             _.each(scope.section.departureDates, function(value, key) {
818                 var departure = moment(key, 'DD/MM/YYY');
819                 if(arrival.isBefore(departure) || arrival.isSame(departure)) {
820                     possibleDepartures[key] = value;
821                 }
822             });
823             return possibleDepartures;
824         } else {
825             return scope.section.departureDates;
826         }
828     };