Merge branch 'MDL-30899_23' of git://github.com/dmonllao/moodle into MOODLE_23_STABLE
[moodle.git] / enrol / manual / yui / quickenrolment / quickenrolment.js
1 YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
3     var UEP = {
4         NAME : 'Enrolment Manager',
5         /** Properties **/
6         BASE : 'base',
7         SEARCH : 'search',
8         PARAMS : 'params',
9         URL : 'url',
10         AJAXURL : 'ajaxurl',
11         MULTIPLE : 'multiple',
12         PAGE : 'page',
13         COURSEID : 'courseid',
14         USERS : 'users',
15         USERCOUNT : 'userCount',
16         REQUIREREFRESH : 'requiresRefresh',
17         LASTSEARCH : 'lastPreSearchValue',
18         INSTANCES : 'instances',
19         OPTIONSTARTDATE : 'optionsStartDate',
20         DEFAULTROLE : 'defaultRole',
21         DEFAULTSTARTDATE : 'defaultStartDate',
22         DEFAULTDURATION : 'defaultDuration',
23         ASSIGNABLEROLES : 'assignableRoles',
24         DISABLEGRADEHISTORY : 'disableGradeHistory',
25         RECOVERGRADESDEFAULT : 'recoverGradesDefault'
26     };
27     /** CSS classes for nodes in structure **/
28     var CSS = {
29         PANEL : 'user-enroller-panel',
30         WRAP : 'uep-wrap',
31         HEADER : 'uep-header',
32         CONTENT : 'uep-content',
33         AJAXCONTENT : 'uep-ajax-content',
34         SEARCHRESULTS : 'uep-search-results',
35         TOTALUSERS : 'totalusers',
36         USERS : 'users',
37         USER : 'user',
38         MORERESULTS : 'uep-more-results',
39         LIGHTBOX : 'uep-loading-lightbox',
40         LOADINGICON : 'loading-icon',
41         FOOTER : 'uep-footer',
42         ENROL : 'enrol',
43         ENROLLED : 'enrolled',
44         COUNT : 'count',
45         PICTURE : 'picture',
46         DETAILS : 'details',
47         FULLNAME : 'fullname',
48         EXTRAFIELDS : 'extrafields',
49         OPTIONS : 'options',
50         ODD  : 'odd',
51         EVEN : 'even',
52         HIDDEN : 'hidden',
53         RECOVERGRADES : 'recovergrades',
54         RECOVERGRADESTITLE : 'recovergradestitle',
55         SEARCHOPTIONS : 'uep-searchoptions',
56         COLLAPSIBLEHEADING : 'collapsibleheading',
57         COLLAPSIBLEAREA : 'collapsiblearea',
58         ENROLMENTOPTION : 'uep-enrolment-option',
59         SEARCHCONTROLS : 'uep-controls',
60         ROLE : 'role',
61         STARTDATE : 'startdate',
62         DURATION : 'duration',
63         ACTIVE : 'active',
64         SEARCH : 'uep-search',
65         CLOSE : 'close',
66         CLOSEBTN : 'close-button'
67     };
68     var create = Y.Node.create;
70     var USERENROLLER = function(config) {
71         USERENROLLER.superclass.constructor.apply(this, arguments);
72     };
73     Y.extend(USERENROLLER, Y.Base, {
74         _searchTimeout : null,
75         _loadingNode : null,
76         _escCloseEvent : null,
77         initializer : function(config) {
78             var recovergrades = null;
79             if (this.get(UEP.DISABLEGRADEHISTORY) != true) {
80                 recovergrades = create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.RECOVERGRADES+'"></div>')
81                     .append(create('<label class="'+CSS.RECOVERGRADESTITLE+'" for="'+CSS.RECOVERGRADES+'">'+M.str.enrol.recovergrades+'</label>'))
82                     .append(create('<input type="checkbox" id="'+CSS.RECOVERGRADES+'" name="'+CSS.RECOVERGRADES+'"'+ this.get(UEP.RECOVERGRADESDEFAULT) +' />'))
83             }
85             this.set(UEP.BASE, create('<div class="'+CSS.PANEL+' '+CSS.HIDDEN+'"></div>')
86                 .append(create('<div class="'+CSS.WRAP+'"></div>')
87                     .append(create('<div class="'+CSS.HEADER+' header"></div>')
88                         .append(create('<div class="'+CSS.CLOSE+'"></div>'))
89                         .append(create('<h2>'+M.str.enrol.enrolusers+'</h2>')))
90                     .append(create('<div class="'+CSS.CONTENT+'"></div>')
91                         .append(create('<div class="'+CSS.SEARCHCONTROLS+'"></div>')
92                             .append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.ROLE+'"><label for="id_enrol_manual_assignable_roles">'+M.str.role.assignroles+'</label></div>')
93                                     .append(create('<select id="id_enrol_manual_assignable_roles"><option value="">'+M.str.enrol.none+'</option></select>'))
94                             )
95                             .append(create('<div class="'+CSS.SEARCHOPTIONS+'"></div>')
96                                 .append(create('<div class="'+CSS.COLLAPSIBLEHEADING+'"><img alt="" />'+M.str.enrol.enrolmentoptions+'</div>'))
97                                 .append(create('<div class="'+CSS.COLLAPSIBLEAREA+' '+CSS.HIDDEN+'"></div>')
98                                     .append(recovergrades)
99                                     .append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.STARTDATE+'">'+M.str.moodle.startingfrom+'</div>')
100                                         .append(create('<select></select>')))
101                                     .append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.DURATION+'">'+M.str.enrol.enrolperiod+'</div>')
102                                         .append(create('<select><option value="0" selected="selected">'+M.str.enrol.unlimitedduration+'</option></select>')))
103                                 )
104                             )
105                         )
106                         .append(create('<div class="'+CSS.AJAXCONTENT+'"></div>'))
107                         .append(create('<div class="'+CSS.LIGHTBOX+' '+CSS.HIDDEN+'"></div>')
108                             .append(create('<img alt="loading" class="'+CSS.LOADINGICON+'" />')
109                                 .setAttribute('src', M.util.image_url('i/loading', 'moodle')))
110                             .setStyle('opacity', 0.5)))
111                     .append(create('<div class="'+CSS.FOOTER+'"></div>')
112                         .append(create('<div class="'+CSS.SEARCH+'"><label>'+M.str.enrol.usersearch+'</label></div>')
113                             .append(create('<input type="text" id="enrolusersearch" value="" />'))
114                         )
115                         .append(create('<div class="'+CSS.CLOSEBTN+'"></div>')
116                             .append(create('<input type="button" value="'+M.str.enrol.finishenrollingusers+'" />'))
117                         )
118                     )
119                 )
120             );
122             this.set(UEP.SEARCH, this.get(UEP.BASE).one('#enrolusersearch'));
123             Y.all('.enrol_manual_plugin input').each(function(node){
124                 if (node.getAttribute('type', 'submit')) {
125                     node.on('click', this.show, this);
126                 }
127             }, this);
128             this.get(UEP.BASE).one('.'+CSS.HEADER+' .'+CSS.CLOSE).on('click', this.hide, this);
129             this.get(UEP.BASE).one('.'+CSS.FOOTER+' .'+CSS.CLOSEBTN+' input').on('click', this.hide, this);
130             this._loadingNode = this.get(UEP.BASE).one('.'+CSS.CONTENT+' .'+CSS.LIGHTBOX);
131             var params = this.get(UEP.PARAMS);
132             params['id'] = this.get(UEP.COURSEID);
133             this.set(UEP.PARAMS, params);
135             Y.on('key', this.preSearch, this.get(UEP.SEARCH), 'down:13', this);
137             Y.one(document.body).append(this.get(UEP.BASE));
139             var base = this.get(UEP.BASE);
140             base.plug(Y.Plugin.Drag);
141             base.dd.addHandle('.'+CSS.HEADER+' h2');
142             base.one('.'+CSS.HEADER+' h2').setStyle('cursor', 'move');
144             var collapsedimage = 't/collapsed'; // ltr mode
145             if ( Y.one(document.body).hasClass('dir-rtl') ) {
146                 collapsedimage = 't/collapsed_rtl';
147             } else {
148                 collapsedimage = 't/collapsed';
149             }
151             this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).one('img').setAttribute('src', M.util.image_url(collapsedimage, 'moodle'));
152             this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).once('click', function() {
153                 // We want to do this just once, the first time the controls are shown.
154                 this.populateStartDates();
155                 this.populateDuration();
156             }, this);
157             this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).on('click', function(){
158                 this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).toggleClass(CSS.ACTIVE);
159                 this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEAREA).toggleClass(CSS.HIDDEN);
160                 if (this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEAREA).hasClass(CSS.HIDDEN)) {
161                     this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).one('img').setAttribute('src', M.util.image_url(collapsedimage, 'moodle'));
162                 } else {
163                     this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).one('img').setAttribute('src', M.util.image_url('t/expanded', 'moodle'));
164                 }
165             }, this);
166             this.populateAssignableRoles();
167         },
168         populateAssignableRoles : function() {
169             this.on('assignablerolesloaded', function(){
170                 var roles = this.get(UEP.ASSIGNABLEROLES);
171                 var s = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.ROLE+' select');
172                 var v = this.get(UEP.DEFAULTROLE);
173                 var index = 0, count = 0;
174                 for (var i in roles) {
175                     count++;
176                     var option = create('<option value="'+i+'">'+roles[i]+'</option>');
177                     if (i == v) {
178                         index = count;
179                     }
180                     s.append(option);
181                 }
182                 s.set('selectedIndex', index);
183                 Y.one('#id_enrol_manual_assignable_roles').focus();
184             }, this);
185             this.getAssignableRoles();
186         },
187         populateStartDates : function() {
188             var select = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.STARTDATE+' select');
189             var defaultvalue = this.get(UEP.DEFAULTSTARTDATE);
190             var options = this.get(UEP.OPTIONSTARTDATE);
191             var index = 0, count = 0;
192             for (var i in options) {
193                 count++;
194                 var option = create('<option value="'+i+'">'+options[i]+'</option>');
195                 if (i == defaultvalue) {
196                     index = count;
197                 }
198                 select.append(option);
199             }
200             select.set('selectedIndex', index);
201         },
202         populateDuration : function() {
203             var select = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.DURATION+' select');
204             var defaultvalue = this.get(UEP.DEFAULTDURATION);
205             var index = 0, count = 0;
206             var durationdays = M.util.get_string('durationdays', 'enrol', '{a}');
207             for (var i = 1; i <= 365; i++) {
208                 count++;
209                 var option = create('<option value="'+i+'">'+durationdays.replace('{a}', i)+'</option>');
210                 if (i == defaultvalue) {
211                     index = count;
212                 }
213                 select.append(option);
214             }
215             select.set('selectedIndex', index);
216         },
217         getAssignableRoles : function(){
218             Y.io(M.cfg.wwwroot+'/enrol/ajax.php', {
219                 method:'POST',
220                 data:'id='+this.get(UEP.COURSEID)+'&action=getassignable&sesskey='+M.cfg.sesskey,
221                 on: {
222                     complete: function(tid, outcome, args) {
223                         try {
224                             var roles = Y.JSON.parse(outcome.responseText);
225                             this.set(UEP.ASSIGNABLEROLES, roles.response);
226                         } catch (e) {
227                             new M.core.exception(e);
228                         }
229                         this.getAssignableRoles = function() {
230                             this.fire('assignablerolesloaded');
231                         };
232                         this.getAssignableRoles();
233                     }
234                 },
235                 context:this
236             });
237         },
238         preSearch : function(e) {
239             this.search(null, false);
240             /*
241             var value = this.get(UEP.SEARCH).get('value');
242             if (value.length < 3 || value == this.get(UEP.LASTSEARCH)) {
243                 return;
244             }
245             this.set(UEP.LASTSEARCH, value);
246             if (this._searchTimeout) {
247                 clearTimeout(this._searchTimeout);
248                 this._searchTimeout = null;
249             }
250             var self = this;
251             this._searchTimeout = setTimeout(function(){
252                 self._searchTimeout = null;
253                 self.search(null, false);
254             }, 300);
255             */
256         },
257         show : function(e) {
258             e.preventDefault();
259             e.halt();
261             var base = this.get(UEP.BASE);
262             base.removeClass(CSS.HIDDEN);
263             var x = (base.get('winWidth') - 400)/2;
264             var y = (parseInt(base.get('winHeight'))-base.get('offsetHeight'))/2 + parseInt(base.get('docScrollY'));
265             if (y < parseInt(base.get('winHeight'))*0.1) {
266                 y = parseInt(base.get('winHeight'))*0.1;
267             }
268             base.setXY([x,y]);
270             if (this.get(UEP.USERS)===null) {
271                 this.search(e, false);
272             }
274             this._escCloseEvent = Y.on('key', this.hide, document.body, 'down:27', this);
275             var rolesselect = Y.one('#id_enrol_manual_assignable_roles');
276             if (rolesselect) {
277                 rolesselect.focus();
278             }
279         },
280         hide : function(e) {
281             if (this._escCloseEvent) {
282                 this._escCloseEvent.detach();
283                 this._escCloseEvent = null;
284             }
285             this.get(UEP.BASE).addClass(CSS.HIDDEN);
286             if (this.get(UEP.REQUIREREFRESH)) {
287                 window.location = this.get(UEP.URL);
288             }
289         },
290         search : function(e, append) {
291             if (e) {
292                 e.halt();
293                 e.preventDefault();
294             }
295             var on, params;
296             if (append) {
297                 this.set(UEP.PAGE, this.get(UEP.PAGE)+1);
298             } else {
299                 this.set(UEP.USERCOUNT, 0);
300                 this.set(UEP.PAGE, 0);
301             }
302             params = this.get(UEP.PARAMS);
303             params['sesskey'] = M.cfg.sesskey;
304             params['action'] = 'searchusers';
305             params['search'] = this.get(UEP.SEARCH).get('value');
306             params['page'] = this.get(UEP.PAGE);
307             if (this.get(UEP.MULTIPLE)) {
308                 alert('oh no there are multiple');
309             } else {
310                 var instance = this.get(UEP.INSTANCES)[0];
311                 params['enrolid'] = instance.id;
312             }
313             Y.io(M.cfg.wwwroot+this.get(UEP.AJAXURL), {
314                 method:'POST',
315                 data:build_querystring(params),
316                 on : {
317                     start : this.displayLoading,
318                     complete: this.processSearchResults,
319                     end : this.removeLoading
320                 },
321                 context:this,
322                 arguments:{
323                     append:append,
324                     enrolid:params['enrolid']
325                 }
326             });
327         },
328         displayLoading : function() {
329             this._loadingNode.removeClass(CSS.HIDDEN);
330         },
331         removeLoading : function() {
332             this._loadingNode.addClass(CSS.HIDDEN);
333         },
334         processSearchResults : function(tid, outcome, args) {
335             try {
336                 var result = Y.JSON.parse(outcome.responseText);
337                 if (result.error) {
338                     return new M.core.ajaxException(result);
339                 }
340             } catch (e) {
341                 new M.core.exception(e);
342             }
343             if (!result.success) {
344                 this.setContent = M.str.enrol.errajaxsearch;
345             }
346             var users;
347             if (!args.append) {
348                 users = create('<div class="'+CSS.USERS+'"></div>');
349             } else {
350                 users = this.get(UEP.BASE).one('.'+CSS.SEARCHRESULTS+' .'+CSS.USERS);
351             }
352             var count = this.get(UEP.USERCOUNT);
353             for (var i in result.response.users) {
354                 count++;
355                 var user = result.response.users[i];
356                 users.append(create('<div class="'+CSS.USER+' clearfix" rel="'+user.id+'"></div>')
357                     .addClass((count%2)?CSS.ODD:CSS.EVEN)
358                     .append(create('<div class="'+CSS.COUNT+'">'+count+'</div>'))
359                     .append(create('<div class="'+CSS.PICTURE+'"></div>')
360                         .append(create(user.picture)))
361                     .append(create('<div class="'+CSS.DETAILS+'"></div>')
362                         .append(create('<div class="'+CSS.FULLNAME+'">'+user.fullname+'</div>'))
363                         .append(create('<div class="'+CSS.EXTRAFIELDS+'">'+user.extrafields+'</div>')))
364                     .append(create('<div class="'+CSS.OPTIONS+'"></div>')
365                         .append(create('<input type="button" class="'+CSS.ENROL+'" value="'+M.str.enrol.enrol+'" />')))
366                 );
367             }
368             this.set(UEP.USERCOUNT, count);
369             if (!args.append) {
370                 var usersstr = (result.response.totalusers == '1')?M.str.enrol.ajaxoneuserfound:M.util.get_string('ajaxxusersfound','enrol', result.response.totalusers);
371                 var content = create('<div class="'+CSS.SEARCHRESULTS+'"></div>')
372                     .append(create('<div class="'+CSS.TOTALUSERS+'">'+usersstr+'</div>'))
373                     .append(users);
374                 if (result.response.totalusers > (this.get(UEP.PAGE)+1)*25) {
375                     var fetchmore = create('<div class="'+CSS.MORERESULTS+'"><a href="#">'+M.str.enrol.ajaxnext25+'</a></div>');
376                     fetchmore.on('click', this.search, this, true);
377                     content.append(fetchmore)
378                 }
379                 this.setContent(content);
380                 Y.delegate("click", this.enrolUser, users, '.'+CSS.USER+' .'+CSS.ENROL, this, args);
381             } else {
382                 if (result.response.totalusers <= (this.get(UEP.PAGE)+1)*25) {
383                     this.get(UEP.BASE).one('.'+CSS.MORERESULTS).remove();
384                 }
385             }
386         },
387         enrolUser : function(e, args) {
388             var user = e.currentTarget.ancestor('.'+CSS.USER);
389             var params = [];
390             params['id'] = this.get(UEP.COURSEID);
391             params['userid'] = user.getAttribute("rel");
392             params['enrolid'] = args.enrolid;
393             params['sesskey'] = M.cfg.sesskey;
394             params['action'] = 'enrol';
395             params['role'] = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.ROLE+' select').get('value');
396             params['startdate'] = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.STARTDATE+' select').get('value');
397             params['duration'] = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.DURATION+' select').get('value');
398             if (this.get(UEP.DISABLEGRADEHISTORY) != true) {
399                 params['recovergrades'] = this.get(UEP.BASE).one('#'+CSS.RECOVERGRADES).get('checked')?1:0;
400             } else {
401                 params['recovergrades'] = 0;
402             }
404             Y.io(M.cfg.wwwroot+this.get(UEP.AJAXURL), {
405                 method:'POST',
406                 data:build_querystring(params),
407                 on: {
408                     start : this.displayLoading,
409                     complete : function(tid, outcome, args) {
410                         try {
411                             var result = Y.JSON.parse(outcome.responseText);
412                             if (result.error) {
413                                 return new M.core.ajaxException(result);
414                             } else {
415                                 args.userNode.addClass(CSS.ENROLLED);
416                                 args.userNode.one('.'+CSS.ENROL).remove();
417                                 this.set(UEP.REQUIREREFRESH, true);
418                             }
419                         } catch (e) {
420                             new M.core.exception(e);
421                         }
422                     },
423                     end : this.removeLoading
424                 },
425                 context:this,
426                 arguments:{
427                     params : params,
428                     userNode : user
429                 }
430             });
432         },
433         setContent: function(content) {
434             this.get(UEP.BASE).one('.'+CSS.CONTENT+' .'+CSS.AJAXCONTENT).setContent(content);
435         }
436     }, {
437         NAME : UEP.NAME,
438         ATTRS : {
439             url : {
440                 validator : Y.Lang.isString
441             },
442             ajaxurl : {
443                 validator : Y.Lang.isString
444             },
445             base : {
446                 setter : function(node) {
447                     var n = Y.one(node);
448                     if (!n) {
449                         Y.fail(UEP.NAME+': invalid base node set');
450                     }
451                     return n;
452                 }
453             },
454             users : {
455                 validator : Y.Lang.isArray,
456                 value : null
457             },
458             courseid : {
459                 value : null
460             },
461             params : {
462                 validator : Y.Lang.isArray,
463                 value : []
464             },
465             instances : {
466                 validator : Y.Lang.isArray,
467                 setter : function(instances) {
468                     var i,ia = [], count=0;
469                     for (i in instances) {
470                         ia.push(instances[i]);
471                         count++;
472                     }
473                     this.set(UEP.MULTIPLE, (count>1));
474                 }
475             },
476             multiple : {
477                 validator : Y.Lang.isBool,
478                 value : false
479             },
480             page : {
481                 validator : Y.Lang.isNumber,
482                 value : 0
483             },
484             userCount : {
485                 value : 0,
486                 validator : Y.Lang.isNumber
487             },
488             requiresRefresh : {
489                 value : false,
490                 validator : Y.Lang.isBool
491             },
492             search : {
493                 setter : function(node) {
494                     var n = Y.one(node);
495                     if (!n) {
496                         Y.fail(UEP.NAME+': invalid search node set');
497                     }
498                     return n;
499                 }
500             },
501             lastPreSearchValue : {
502                 value : '',
503                 validator : Y.Lang.isString
504             },
505             strings  : {
506                 value : {},
507                 validator : Y.Lang.isObject
508             },
509             defaultRole : {
510                 value : 0
511             },
512             defaultStartDate : {
513                 value : 2,
514                 validator : Y.Lang.isNumber
515             },
516             defaultDuration : {
517                 value : ''
518             },
519             assignableRoles : {
520                 value : []
521             },
522             optionsStartDate : {
523                 value : []
524             },
525             disableGradeHistory : {
526                 value : 0
527             },
528             recoverGradesDefault : {
529                 value : ''
530             }
531         }
532     });
533     Y.augment(USERENROLLER, Y.EventTarget);
535     M.enrol_manual = M.enrol_manual || {};
536     M.enrol_manual.quickenrolment = {
537         init : function(cfg) {
538             new USERENROLLER(cfg);
539         }
540     }
542 }, '@VERSION@', {requires:['base','node', 'overlay', 'io-base', 'test', 'json-parse', 'event-delegate', 'dd-plugin', 'event-key', 'moodle-enrol-notification']});