Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
/**
17
 * The User Selector for the grade history report.
18
 *
19
 * @module     moodle-gradereport_history-userselector
20
 * @package    gradereport_history
21
 * @copyright  2013 NetSpot Pty Ltd (https://www.netspot.com.au)
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 * @main       moodle-gradereport_history-userselector
24
 */
25
 
26
/**
27
 * @module moodle-gradereport_history-userselector
28
 */
29
 
30
var COMPONENT = 'gradereport_history';
31
var USP = {
32
    AJAXURL: 'ajaxurl',
33
    BASE: 'base',
34
    CHECKBOX_NAME_PREFIX: 'usp-u',
35
    COURSEID: 'courseid',
36
    DIALOGUE_PREFIX: 'moodle-dialogue',
37
    NAME: 'gradereport_history_usp',
38
    PAGE: 'page',
39
    PARAMS: 'params',
40
    PERPAGE: 'perPage',
41
    SEARCH: 'search',
42
    SEARCHBTN: 'searchbtn',
43
    SELECTEDUSERS: 'selectedUsers',
44
    URL: 'url',
45
    USERCOUNT: 'userCount'
46
};
47
var CSS = {
48
    ACCESSHIDE: 'accesshide',
49
    AJAXCONTENT: 'usp-ajax-content',
50
    CHECKBOX: 'usp-checkbox',
51
    CLOSE: 'close',
52
    CLOSEBTN: 'usp-finish',
53
    CONTENT: 'usp-content',
54
    DETAILS: 'details',
55
    EXTRAFIELDS: 'extrafields',
56
    FIRSTADDED: 'usp-first-added',
57
    FULLNAME: 'fullname',
58
    HEADER: 'usp-header',
59
    HIDDEN: 'hidden',
60
    LIGHTBOX: 'usp-loading-lightbox',
61
    LOADINGICON: 'loading-icon',
62
    MORERESULTS: 'usp-more-results',
63
    OPTIONS: 'options',
64
    PICTURE: 'usp-picture',
65
    RESULTSCOUNT: 'usp-results-count',
66
    SEARCH: 'usp-search',
67
    SEARCHBTN: 'usp-search-btn',
68
    SEARCHFIELD: 'usp-search-field',
69
    SEARCHRESULTS: 'usp-search-results',
70
    SELECTED: 'selected',
71
    USER: 'usp-user',
72
    USERS: 'usp-users',
73
    WRAP: 'usp-wrap'
74
};
75
var SELECTORS = {
76
    AJAXCONTENT: '.' + CSS.AJAXCONTENT,
77
    FINISHBTN: '.' + CSS.CLOSEBTN + ' input',
78
    FIRSTADDED: '.' + CSS.FIRSTADDED,
79
    FULLNAME: '.' + CSS.FULLNAME + ' label',
80
    LIGHTBOX: '.' + CSS.LIGHTBOX,
81
    MORERESULTS: '.' + CSS.MORERESULTS,
82
    OPTIONS: '.' + CSS.OPTIONS,
83
    PICTURE: '.' + CSS.USER + ' .userpicture',
84
    RESULTSCOUNT: '.' + CSS.RESULTSCOUNT,
85
    RESULTSUSERS: '.' + CSS.SEARCHRESULTS + ' .' + CSS.USERS,
86
    SEARCHBTN: '.' + CSS.SEARCHBTN,
87
    SEARCHFIELD: '.' + CSS.SEARCHFIELD,
88
    SELECTEDNAMES: '.felement .selectednames',
89
    TRIGGER: '.gradereport_history_plugin input.selectortrigger',
90
    USER: '.' + CSS.USER,
91
    USERFULLNAMES: 'input[name="userfullnames"]',
92
    USERIDS: 'input[name="userids"]',
93
    USERSELECT: '.' + CSS.CHECKBOX + ' input[type=checkbox]'
94
};
95
 
96
/**
97
 * User Selector.
98
 *
99
 * @namespace M.gradereport_history
100
 * @class UserSelector
101
 * @constructor
102
 */
103
 
104
var USERSELECTOR = function() {
105
    USERSELECTOR.superclass.constructor.apply(this, arguments);
106
};
107
Y.namespace('M.gradereport_history').UserSelector = Y.extend(USERSELECTOR, M.core.dialogue, {
108
 
109
    /**
110
     * Whether or not this is the first time the user displays the dialogue within that request.
111
     *
112
     * @property _firstDisplay
113
     * @type Boolean
114
     * @private
115
     */
116
    _firstDisplay: true,
117
 
118
    /**
119
     * The list of all the users selected while the dialogue is open.
120
     *
121
     * @type Object
122
     * @property _usersBufferList
123
     * @private
124
     */
125
    _usersBufferList: null,
126
 
127
    /**
128
     * The Node on which the focus is set.
129
     *
130
     * @property _userTabFocus
131
     * @type Node
132
     * @private
133
     */
134
    _userTabFocus: null,
135
 
136
    /**
137
     * Compiled template function for a user node.
138
     *
139
     * @property _userTemplate
140
     * @type Function
141
     * @private
142
     */
143
    _userTemplate: null,
144
 
145
    initializer: function() {
146
        var bb = this.get('boundingBox'),
147
            content,
148
            params,
149
            tpl;
150
 
151
        tpl = Y.Handlebars.compile(
152
            '<div class="{{CSS.WRAP}}">' +
153
                '<div class="{{CSS.HEADER}}">' +
154
                    '<div class="{{CSS.SEARCH}}" role="search">' +
155
                        '<form>' +
156
                            '<input type="text" class="{{CSS.SEARCHFIELD}}" ' +
157
                                'aria-label="{{get_string "search" "moodle"}}" value="" />' +
158
                            '<input type="submit" class="{{CSS.SEARCHBTN}}"' +
159
                                'value="{{get_string "search" "moodle"}}">' +
160
                        '</form>' +
161
                        '<div aria-live="polite" class="{{CSS.RESULTSCOUNT}}">{{get_string "loading" "admin"}}</div>' +
162
                    '</div>' +
163
                '</div>' +
164
                '<div class="{{CSS.CONTENT}}">' +
165
                    '<form>' +
166
                        '<div class="{{CSS.AJAXCONTENT}}" aria-live="polite"></div>' +
167
                        '<div class="{{CSS.LIGHTBOX}} {{CSS.HIDDEN}}">' +
168
                            '<img class="{{CSS.LOADINGICON}}" alt="{{get_string "loading" "admin"}}"' +
169
                                'src="{{{loadingIcon}}}">' +
170
                        '</div>' +
171
                        '<div class="{{CSS.CLOSEBTN}}">' +
172
                            '<input type="submit" value="{{get_string "finishselectingusers" COMPONENT}}">' +
173
                        '</div>' +
174
                    '</form>' +
175
                '</div>' +
176
            '</div>');
177
 
178
        content = Y.Node.create(
179
            tpl({
180
                COMPONENT: COMPONENT,
181
                CSS: CSS,
182
                loadingIcon: M.util.image_url('i/loading', 'moodle')
183
            })
184
        );
185
 
186
        // Set the title and content.
187
        this.getStdModNode(Y.WidgetStdMod.HEADER).prepend(Y.Node.create('<h1>' + this.get('title') + '</h1>'));
188
        this.setStdModContent(Y.WidgetStdMod.BODY, content, Y.WidgetStdMod.REPLACE);
189
 
190
        // Use standard dialogue class name. This removes the default styling of the footer.
191
        this.get('boundingBox').one('.moodle-dialogue-wrap').addClass('moodle-dialogue-content');
192
 
193
        // Add the event on the button that opens the dialogue.
194
        Y.one(SELECTORS.TRIGGER).on('click', this.show, this);
195
 
196
        // The button to finalize the selection.
197
        bb.one(SELECTORS.FINISHBTN).on('click', this.finishSelectingUsers, this);
198
 
199
        // Delegate the keyboard navigation in the users list.
200
        bb.delegate('key', this.userKeyboardNavigation, 'down:38,40', SELECTORS.AJAXCONTENT, this);
201
 
202
        // Delegate the action to select a user.
203
        Y.delegate('click', this.selectUser, SELECTORS.AJAXCONTENT, SELECTORS.USERSELECT, this);
204
        Y.delegate('click', this.selectUser, SELECTORS.AJAXCONTENT, SELECTORS.PICTURE, this);
205
 
206
        params = this.get(USP.PARAMS);
207
        params.id = this.get(USP.COURSEID);
208
        this.set(USP.PARAMS, params);
209
 
210
        bb.one(SELECTORS.SEARCHBTN).on('click', this.search, this, false);
211
    },
212
 
213
    /**
214
     * Display the dialogue.
215
     *
216
     * @method show
217
     */
218
    show: function(e) {
219
        var bb;
220
        this._usersBufferList = Y.clone(this.get(USP.SELECTEDUSERS));
221
        if (this._firstDisplay) {
222
            // Load the default list of users when the dialogue is loaded for the first time.
223
            this._firstDisplay = false;
224
            this.search(e, false);
225
        } else {
226
            // Leave the content as is, but reset the selection.
227
            bb = this.get('boundingBox');
228
 
229
            // Remove all the selected users.
230
            bb.all(SELECTORS.USER).each(function(node) {
231
                this.markUserNode(node, false);
232
            }, this);
233
 
234
            // Select the users.
235
            Y.Object.each(this._usersBufferList, function(v, k) {
236
                var user = bb.one(SELECTORS.USER + '[data-userid="' + k + '"]');
237
                if (user) {
238
                    this.markUserNode(user, true);
239
                }
240
            }, this);
241
 
242
            // Reset the tab focus.
243
            this.setUserTabFocus(bb.one(SELECTORS.USER));
244
        }
245
        return Y.namespace('M.gradereport_history.UserSelector').superclass.show.call(this);
246
    },
247
 
248
    /**
249
     * Search for users.
250
     *
251
     * @method search
252
     * @param {EventFacade} e The event.
253
     * @param {Boolean} append Whether we want to append the results to the current results or not.
254
     */
255
    search: function(e, append) {
256
        if (e) {
257
            e.preventDefault();
258
        }
259
        var params;
260
        if (append) {
261
            this.set(USP.PAGE, this.get(USP.PAGE) + 1);
262
        } else {
263
            this.set(USP.USERCOUNT, 0);
264
            this.set(USP.PAGE, 0);
265
        }
266
        params = this.get(USP.PARAMS);
267
        params.sesskey = M.cfg.sesskey;
268
        params.action = 'searchusers';
269
        params.search = this.get('boundingBox').one(SELECTORS.SEARCHFIELD).get('value');
270
        params.page = this.get(USP.PAGE);
271
        params.perpage = this.get(USP.PERPAGE);
272
 
273
        Y.io(M.cfg.wwwroot + this.get(USP.AJAXURL), {
274
            method: 'POST',
275
            data: window.build_querystring(params),
276
            on: {
277
                start: this.preSearch,
278
                complete: this.processSearchResults,
279
                end: this.postSearch
280
            },
281
            context: this,
282
            "arguments": {      // Quoted because this is a reserved keyword.
283
                append: append
284
            }
285
        });
286
    },
287
 
288
    /**
289
     * Pre search callback.
290
     *
291
     * @method preSearch
292
     * @param {String} transactionId The transaction ID.
293
     * @param {Object} args The arguments passed from YUI.io()
294
     */
295
    preSearch: function(unused, args) {
296
        var bb = this.get('boundingBox');
297
 
298
        // Display the lightbox.
299
        bb.one(SELECTORS.LIGHTBOX).removeClass(CSS.HIDDEN);
300
 
301
        // Set the number of results to 'loading...'.
302
        if (!args.append) {
303
            bb.one(SELECTORS.RESULTSCOUNT).setHTML(M.util.get_string('loading', 'admin'));
304
        }
305
    },
306
 
307
    /**
308
     * Post search callback.
309
     *
310
     * @method postSearch
311
     * @param {String} transactionId The transaction ID.
312
     * @param {Object} args The arguments passed from YUI.io()
313
     */
314
    postSearch: function(transactionId, args) {
315
        var bb = this.get('boundingBox'),
316
            firstAdded = bb.one(SELECTORS.FIRSTADDED),
317
            firstUser;
318
 
319
        // Hide the lightbox.
320
        bb.one(SELECTORS.LIGHTBOX).addClass(CSS.HIDDEN);
321
 
322
        if (args.append && firstAdded) {
323
            // Sets the focus on the newly added user if we are appending results.
324
            this.setUserTabFocus(firstAdded);
325
            firstAdded.one(SELECTORS.USERSELECT).focus();
326
        } else {
327
            // New search result, set the tab focus on the first user returned.
328
            firstUser = bb.one(SELECTORS.USER);
329
            if (firstUser) {
330
                this.setUserTabFocus(firstUser);
331
            }
332
        }
333
    },
334
 
335
    /**
336
     * Process and display the search results.
337
     *
338
     * @method processSearchResults
339
     * @param {String} tid The transaction ID.
340
     * @param {Object} outcome The response object.
341
     * @param {Object} args The arguments passed from YUI.io().
342
     */
343
    processSearchResults: function(tid, outcome, args) {
344
        var result = false,
345
            error = false,
346
            bb = this.get('boundingBox'),
347
            users,
348
            userTemplate,
349
            count,
350
            selected,
351
            i,
352
            firstAdded = true,
353
            node,
354
            content,
355
            fetchmore,
356
            totalUsers;
357
 
358
        // Decodes the result.
359
        try {
360
            result = Y.JSON.parse(outcome.responseText);
361
            if (!result.success || result.error) {
362
                error = true;
363
            }
364
        } catch (e) {
365
            error = true;
366
        }
367
 
368
        // There was an error.
369
        if (error) {
370
            this.setContent('');
371
            bb.one(SELECTORS.RESULTSCOUNT).setHTML(M.util.get_string('errajaxsearch', COMPONENT));
372
            return;
373
        }
374
 
375
        // Create the div containing the users when it is a fresh search.
376
        if (!args.append) {
377
            users = Y.Node.create('<div role="listbox" aria-activedescendant="" aria-multiselectable="true" class="' +
378
                                   CSS.USERS +
379
                                   '"></div>');
380
        } else {
381
            users = bb.one(SELECTORS.RESULTSUSERS);
382
        }
383
 
384
        // Compile the template for each user node.
385
        if (!this._userTemplate) {
386
            this._userTemplate = Y.Handlebars.compile(
387
                '<div role="option" aria-selected="false" class="{{CSS.USER}} clearfix" ' +
388
                        'data-userid="{{userId}}">' +
389
                    '<div class="{{CSS.CHECKBOX}}">' +
390
                        '<input name="{{USP.CHECKBOX_NAME_PREFIX}}{{userId}}" type="checkbox" tabindex="-1"' +
391
                            'id="{{checkboxId}}" aria-describedby="{{checkboxId}} {{extraFieldsId}}"/>' +
392
                    '</div>' +
393
                    '<div class="{{CSS.PICTURE}}">{{{picture}}}</div>' +
394
                    '<div class="{{CSS.DETAILS}}">' +
395
                        '<div class="{{CSS.FULLNAME}}">' +
396
                            '<label for="{{checkboxId}}">{{fullname}}</label>' +
397
                        '</div>' +
398
                        '<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{{extrafields}}}</div>' +
399
                    '</div>' +
400
                '</div>'
401
            );
402
        }
403
        userTemplate = this._userTemplate;
404
 
405
        // Append the users one by one.
406
        count = this.get(USP.USERCOUNT);
407
        selected = '';
408
        var user;
409
        for (i in result.response.users) {
410
            count++;
411
            user = result.response.users[i];
412
 
413
            // If already selected.
414
            if (Y.Object.hasKey(this._usersBufferList, user.userid)) {
415
                selected = true;
416
            } else {
417
                selected = false;
418
            }
419
 
420
            node = Y.Node.create(userTemplate({
421
                checkboxId: Y.guid(),
422
                COMPONENT: COMPONENT,
423
                count: count,
424
                CSS: CSS,
425
                extrafields: user.extrafields,
426
                extraFieldsId: Y.guid(),
427
                fullname: user.fullname,
428
                picture: user.picture,
429
                userId: user.userid,
430
                USP: USP
431
            }));
432
 
433
            this.markUserNode(node, selected);
434
 
435
            // Noting the first user that was when adding more results.
436
            if (args.append && firstAdded) {
437
                users.all(SELECTORS.FIRSTADDED).removeClass(CSS.FIRSTADDED);
438
                node.addClass(CSS.FIRSTADDED);
439
                firstAdded = false;
440
            }
441
            users.append(node);
442
        }
443
        this.set(USP.USERCOUNT, count);
444
 
445
        // Update the count of users, and add a button to load more if need be.
446
        totalUsers = parseInt(result.response.totalusers, 10);
447
        if (!args.append) {
448
            if (totalUsers === 0) {
449
                bb.one(SELECTORS.RESULTSCOUNT).setHTML(M.util.get_string('noresults', 'moodle'));
450
                content = '';
451
            } else {
452
                if (totalUsers === 1) {
453
                    bb.one(SELECTORS.RESULTSCOUNT).setHTML(M.util.get_string('foundoneuser', COMPONENT));
454
                } else {
455
                    bb.one(SELECTORS.RESULTSCOUNT).setHTML(M.util.get_string('foundnusers', COMPONENT, totalUsers));
456
                }
457
 
458
                content = Y.Node.create('<div class="' + CSS.SEARCHRESULTS + '"></div>')
459
                    .append(users);
460
                if (result.response.totalusers > (this.get(USP.PAGE) + 1) * this.get(USP.PERPAGE)) {
461
                    fetchmore = Y.Node.create('<div class="' + CSS.MORERESULTS + '">' +
462
                        '<a href="#" role="button">' + M.util.get_string('loadmoreusers', COMPONENT) + '</a></div>');
463
                    fetchmore.one('a').on('click', this.search, this, true);
464
                    fetchmore.one('a').on('key', this.search, 'space', this, true);
465
                    content.append(fetchmore);
466
                }
467
            }
468
            this.setContent(content);
469
        } else {
470
            if (totalUsers <= (this.get(USP.PAGE) + 1) * this.get(USP.PERPAGE)) {
471
                bb.one(SELECTORS.MORERESULTS).remove();
472
            }
473
        }
474
    },
475
 
476
    /**
477
     * When the user has finished selecting users.
478
     *
479
     * @method finishSelectingUsers
480
     * @param {EventFacade} e The event.
481
     */
482
    finishSelectingUsers: function(e) {
483
        e.preventDefault();
484
        this.applySelection();
485
        this.hide();
486
    },
487
 
488
    /**
489
     * Apply the selection made.
490
     *
491
     * @method applySelection
492
     * @param {EventFacade} e The event.
493
     */
494
    applySelection: function() {
495
        var userIds = Y.Object.keys(this._usersBufferList);
496
        this.set(USP.SELECTEDUSERS, Y.clone(this._usersBufferList))
497
            .setNameDisplay();
498
        Y.one(SELECTORS.USERIDS).set('value', userIds.join());
499
    },
500
 
501
    /**
502
     * Select a user.
503
     *
504
     * @method SelectUser
505
     * @param {EventFacade} e The event.
506
     */
507
    selectUser: function(e) {
508
        var user = e.currentTarget.ancestor(SELECTORS.USER),
509
            checkbox = user.one(SELECTORS.USERSELECT),
510
            fullname = user.one(SELECTORS.FULLNAME).get('innerHTML'),
511
            checked = checkbox.get('checked'),
512
            userId = user.getData('userid');
513
 
514
        if (e.currentTarget !== checkbox) {
515
            // We triggered the selection from another node, so we need to change the checkbox value.
516
            checked = !checked;
517
        }
518
 
519
        if (checked) {
520
            // Selecting the user.
521
            this._usersBufferList[userId] = fullname;
522
        } else {
523
            // De-selecting the user.
524
            delete this._usersBufferList[userId];
525
            delete this._usersBufferList[parseInt(userId, 10)]; // Also remove numbered keys.
526
        }
527
 
528
        this.markUserNode(user, checked);
529
    },
530
 
531
    /**
532
     * Mark a user node as selected or not.
533
     *
534
     * This only takes care of the DOM side of things, not the internal mechanism
535
     * storing what users have been selected or not.
536
     *
537
     * @param {Node} node The user node.
538
     * @param {Boolean} selected True to mark as selected.
539
     * @chainable
540
     */
541
    markUserNode: function(node, selected) {
542
        if (selected) {
543
            node.addClass(CSS.SELECTED)
544
                .set('aria-selected', true)
545
                .one(SELECTORS.USERSELECT)
546
                    .set('checked', true);
547
        } else {
548
            node.removeClass(CSS.SELECTED)
549
                .set('aria-selected', false)
550
                .one(SELECTORS.USERSELECT)
551
                    .set('checked', false);
552
        }
553
        return this;
554
    },
555
 
556
    /**
557
     * Set the content of the dialogue.
558
     *
559
     * @method setContent
560
     * @param {String} content The content.
561
     * @chainable
562
     */
563
    setContent: function(content) {
564
        this.get('boundingBox').one(SELECTORS.AJAXCONTENT).setHTML(content);
565
        return this;
566
    },
567
 
568
    /**
569
     * Display the names of the selected users in the form.
570
     *
571
     * @method setNameDisplay
572
     */
573
    setNameDisplay: function() {
574
        var namelist = Y.Object.values(this.get(USP.SELECTEDUSERS));
575
        Y.one(SELECTORS.SELECTEDNAMES).set('innerHTML', namelist.join(', '));
576
        Y.one(SELECTORS.USERFULLNAMES).set('value', namelist.join());
577
    },
578
 
579
    /**
580
     * User keyboard navigation.
581
     *
582
     * @method userKeyboardNavigation
583
     */
584
    userKeyboardNavigation: function(e) {
585
        var bb = this.get('boundingBox'),
586
            users = bb.all(SELECTORS.USER),
587
            direction = 1,
588
            user,
589
            current = e.target.ancestor(SELECTORS.USER, true);
590
 
591
        if (e.keyCode === 38) {
592
            direction = -1;
593
        }
594
 
595
        user = this.findFocusableUser(users, current, direction);
596
        if (user) {
597
            e.preventDefault();
598
            user.one(SELECTORS.USERSELECT).focus();
599
            this.setUserTabFocus(user);
600
        }
601
    },
602
 
603
    /**
604
     * Find the next or previous focusable node.
605
     *
606
     * @param {NodeList} users The list of users.
607
     * @param {Node} user The user to start with.
608
     * @param {Number} direction The direction in which to go.
609
     * @return {Node|null} A user node, or null if not found.
610
     * @method findFocusableUser
611
     */
612
    findFocusableUser: function(users, user, direction) {
613
        var index = users.indexOf(user);
614
 
615
        if (users.size() < 1) {
616
            Y.log('The users list is empty', 'debug', COMPONENT);
617
            return null;
618
        }
619
 
620
        if (index < 0) {
621
            Y.log('Unable to find the user in the list of users', 'debug', COMPONENT);
622
            return users.item(0);
623
        }
624
 
625
        index += direction;
626
 
627
        // Wrap the navigation when reaching the top of the bottom.
628
        if (index < 0) {
629
            index = users.size() - 1;
630
        } else if (index >= users.size()) {
631
            index = 0;
632
        }
633
 
634
        return users.item(index);
635
    },
636
 
637
    /**
638
     * Set the user tab focus.
639
     *
640
     * @param {Node} user The user node.
641
     * @method setUserTabFocus
642
     */
643
    setUserTabFocus: function(user) {
644
        if (this._userTabFocus) {
645
            this._userTabFocus.setAttribute('tabindex', '-1');
646
        }
647
        if (!user) {
648
            // We were not passed a user, there is apparently none in the dialogue. Nothing to do here \\\o/.
649
            return;
650
        }
651
 
652
        this._userTabFocus = user.one(SELECTORS.USERSELECT);
653
        this._userTabFocus.setAttribute('tabindex', '0');
654
 
655
        this.get('boundingBox').one(SELECTORS.RESULTSUSERS).setAttribute('aria-activedescendant', this._userTabFocus.generateID());
656
    }
657
 
658
}, {
659
    NAME: USP.NAME,
660
    CSS_PREFIX: USP.CSS_PREFIX,
661
    ATTRS: {
662
 
663
        /**
664
         * The header.
665
         *
666
         * @attribute title
667
         * @default selectusers language string.
668
         * @type String
669
         */
670
        title: {
671
            validator: Y.Lang.isString,
672
            valueFn: function() {
673
                return M.util.get_string('selectusers', COMPONENT);
674
            }
675
        },
676
 
677
        /**
678
         * The current page URL.
679
         *
680
         * @attribute url
681
         * @default null
682
         * @type String
683
         */
684
        url: {
685
            validator: Y.Lang.isString,
686
            value: null
687
        },
688
 
689
        /**
690
         * The URL to the Ajax file.
691
         *
692
         * @attribute ajaxurl
693
         * @default null
694
         * @type String
695
         */
696
        ajaxurl: {
697
            validator: Y.Lang.isString,
698
            value: null
699
        },
700
 
701
        /**
702
         * The names of the selected users.
703
         *
704
         * The keys are the user IDs, the values are their fullname.
705
         *
706
         * @attribute selectedUsers
707
         * @default null
708
         * @type Object
709
         */
710
        selectedUsers: {
711
            validator: Y.Lang.isObject,
712
            value: null,
713
            getter: function(v) {
714
                if (v === null) {
715
                    return {};
716
                }
717
                return v;
718
            }
719
        },
720
 
721
        /**
722
         * The course ID.
723
         *
724
         * @attribute courseid
725
         * @default null
726
         * @type Number
727
         */
728
        courseid: {
729
            value: null
730
        },
731
 
732
        /**
733
         * Array of parameters.
734
         *
735
         * @attribute params
736
         * @default []
737
         * @type Array
738
         */
739
        params: {
740
            validator: Y.Lang.isArray,
741
            value: []
742
        },
743
 
744
        /**
745
         * The page we are on.
746
         *
747
         * @attribute page
748
         * @default 0
749
         * @type Number
750
         */
751
        page: {
752
            validator: Y.Lang.isNumber,
753
            value: 0
754
        },
755
 
756
        /**
757
         * The number of users displayed.
758
         *
759
         * @attribute userCount
760
         * @default 0
761
         * @type Number
762
         */
763
        userCount: {
764
            value: 0,
765
            validator: Y.Lang.isNumber
766
        },
767
 
768
        /**
769
         * The number of results per page.
770
         *
771
         * @attribute perPage
772
         * @default 25
773
         * @type Number
774
         */
775
        perPage: {
776
            value: 25,
777
            Validator: Y.Lang.isNumber
778
        }
779
 
780
    }
781
});
782
 
783
Y.Base.modifyAttrs(Y.namespace('M.gradereport_history.UserSelector'), {
784
 
785
    /**
786
     * List of extra classes.
787
     *
788
     * @attribute extraClasses
789
     * @default ['gradereport_history_usp']
790
     * @type Array
791
     */
792
    extraClasses: {
793
        value: [
794
            'gradereport_history_usp'
795
        ]
796
    },
797
 
798
    /**
799
     * Whether to focus on the target that caused the Widget to be shown.
800
     *
801
     * @attribute focusOnPreviousTargetAfterHide
802
     * @default true
803
     * @type Node
804
     */
805
    focusOnPreviousTargetAfterHide: {
806
        value: true
807
    },
808
 
809
    /**
810
     *
811
     * Width.
812
     *
813
     * @attribute width
814
     * @default '500px'
815
     * @type String|Number
816
     */
817
    width: {
818
        value: '500px'
819
    },
820
 
821
    /**
822
     * Boolean indicating whether or not the Widget is visible.
823
     *
824
     * @attribute visible
825
     * @default false
826
     * @type Boolean
827
     */
828
    visible: {
829
        value: false
830
    },
831
 
832
   /**
833
    * Whether the widget should be modal or not.
834
    *
835
    * @attribute modal
836
    * @type Boolean
837
    * @default true
838
    */
839
    modal: {
840
        value: true
841
    },
842
 
843
   /**
844
    * Whether the widget should be draggable or not.
845
    *
846
    * @attribute draggable
847
    * @type Boolean
848
    * @default true
849
    */
850
    draggable: {
851
        value: true
852
    }
853
 
854
});
855
 
856
Y.namespace('M.gradereport_history.UserSelector').init = function(cfg) {
857
    return new USERSELECTOR(cfg);
858
};