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