Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/**
2
 * JavaScript for the user selectors.
3
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4
 * @package userselector
5
 */
6
 
7
// Define the core_user namespace if it has not already been defined
8
M.core_user = M.core_user || {};
9
// Define a user selectors array for against the cure_user namespace
10
M.core_user.user_selectors = [];
11
/**
12
 * Retrieves an instantiated user selector or null if there isn't one by the requested name
13
 * @param {string} name The name of the selector to retrieve
14
 * @return bool
15
 */
16
M.core_user.get_user_selector = function (name) {
17
    return this.user_selectors[name] || null;
18
};
19
 
20
/**
21
 * Initialise a new user selector.
22
 *
23
 * @param {YUI} Y The YUI3 instance
24
 * @param {string} name the control name/id.
25
 * @param {string} hash the hash that identifies this selector in the user's session.
26
 * @param {array} extrafields extra fields we are displaying for each user in addition to fullname.
27
 * @param {string} lastsearch The last search that took place
28
 * @param {int} searchtype the last search option that took place
29
 */
30
M.core_user.init_user_selector = function(Y, name, hash, extrafields, lastsearch, searchtype) {
31
    // Creates a new user_selector object
32
    var user_selector = {
33
        /** This id/name used for this control in the HTML. */
34
        name : name,
35
        /** Array of fields to display for each user, in addition to fullname. */
36
        extrafields: extrafields,
37
        /** Number of seconds to delay before submitting a query request */
38
        querydelay : 0.5,
39
        /** The input element that contains the search term. */
40
        searchfield : Y.one('#' + name + '_searchtext'),
41
        /** The clear button. */
42
        clearbutton : null,
43
        /** The select element that contains the list of users. */
44
        listbox : Y.one('#' + name),
45
        /** Used to hold the timeout id of the timeout that waits before doing a search. */
46
        timeoutid : null,
47
        /** Stores any in-progress remote requests. */
48
        iotransactions : {},
49
        /** The last string that we searched for, so we can avoid unnecessary repeat searches. */
50
        lastsearch : lastsearch,
51
        /** Whether any options where selected last time we checked. Used by
52
         *  handle_selection_change to track when this status changes. */
53
        selectionempty : true,
54
        /** The last search option that we use for*/
55
        searchtype: searchtype,
56
        /**
57
         * Initialises the user selector object
58
         * @constructor
59
         */
60
        init : function() {
61
            // Hide the search button and replace it with a label.
62
            var searchbutton = Y.one('#' + this.name + '_searchbutton');
63
            this.searchfield.insert(Y.Node.create('<label for="' + this.name + '_searchtext">' + searchbutton.get('value') + '</label>'), this.searchfield);
64
            searchbutton.remove();
65
 
66
            // Hook up the event handler for when the search text changes.
67
            this.searchfield.on('keyup', this.handle_keyup, this);
68
 
69
            // Hook up the event handler for when the selection changes.
70
            this.listbox.on('keyup', this.handle_selection_change, this);
71
            this.listbox.on('click', this.handle_selection_change, this);
72
            this.listbox.on('change', this.handle_selection_change, this);
73
 
74
            // And when the search any substring preference changes. Do an immediate re-search.
75
            Y.one('#userselector_searchfromstartid').on('click', this.handle_searchtype_change, this);
76
            Y.one('#userselector_searchanywhereid').on('click', this.handle_searchtype_change, this);
77
            Y.one('#userselector_searchexactmatchesonlyid').on('click', this.handle_searchtype_change, this);
78
 
79
            // Define our custom event.
80
            //this.createEvent('selectionchanged');
81
            this.selectionempty = this.is_selection_empty();
82
 
83
            // Replace the Clear submit button with a clone that is not a submit button.
84
            var clearbtn = Y.one('#' + this.name + '_clearbutton');
85
            this.clearbutton = Y.Node.create('<input type="button" value="' + clearbtn.get('value') + '" class="btn btn-secondary mx-1"/>');
86
            clearbtn.replace(Y.Node.getDOMNode(this.clearbutton));
87
            this.clearbutton.set('id', this.name + "_clearbutton");
88
            this.clearbutton.on('click', this.handle_clear, this);
89
            this.clearbutton.set('disabled', (this.get_search_text() == ''));
90
 
91
            this.send_query(false);
92
        },
93
        /**
94
         * Key up hander for the search text box.
95
         * @param {Y.Event} e the keyup event.
96
         */
97
        handle_keyup : function(e) {
98
            //  Trigger an ajax search after a delay.
99
            this.cancel_timeout();
100
            this.timeoutid = Y.later(this.querydelay * 1000, e, function(obj){obj.send_query(false)}, this);
101
 
102
            // Enable or diable the clear button.
103
            this.clearbutton.set('disabled', (this.get_search_text() == ''));
104
 
105
            // If enter was pressed, prevent a form submission from happening.
106
            if (e.keyCode == 13) {
107
                e.halt();
108
            }
109
        },
110
        /**
111
         * Handles when the selection has changed. If the selection has changed from
112
         * empty to not-empty, or vice versa, then fire the event handlers.
113
         */
114
        handle_selection_change : function() {
115
            var isselectionempty = this.is_selection_empty();
116
            if (isselectionempty !== this.selectionempty) {
117
                this.fire('user_selector:selectionchanged', isselectionempty);
118
            }
119
            this.selectionempty = isselectionempty;
120
        },
121
        /**
122
         * Trigger a re-search and set the user prefs when the search radio option is changed.
123
         *
124
         *  @param {Y.Event} e the change event.
125
         */
126
        handle_searchtype_change: function(e) {
127
            this.clear_search_radio_state();
128
            e.currentTarget.set('checked', 1);
129
            this.searchtype = e.currentTarget.get('value');
130
            require(['core_user/repository'], function(UserRepository) {
131
                UserRepository.setUserPreference('userselector_searchtype', e.currentTarget.get('value'));
132
            });
133
            if (this.lastsearch != '' && this.get_search_text() != '') {
134
                this.send_query(true);
135
            }
136
        },
137
 
138
        /**
139
         * Click handler for the clear button..
140
         */
141
        handle_clear : function() {
142
            this.searchfield.set('value', '');
143
            this.clearbutton.set('disabled',true);
144
            this.send_query(false);
145
        },
146
 
147
        /**
148
         * Clear all checked state in the radio search option.
149
         */
150
        clear_search_radio_state: function() {
151
            Y.one('#userselector_searchfromstartid').set('checked', 0);
152
            Y.one('#userselector_searchanywhereid').set('checked', 0);
153
            Y.one('#userselector_searchexactmatchesonlyid').set('checked', 0);
154
        },
155
 
156
        /**
157
         * Fires off the ajax search request.
158
         */
159
        send_query : function(forceresearch) {
160
            // Cancel any pending timeout.
161
            this.cancel_timeout();
162
 
163
            var value = this.get_search_text();
164
            this.searchfield.set('class', '');
165
            if (this.lastsearch == value && !forceresearch) {
166
                return;
167
            }
168
 
169
            // Try to cancel existing transactions.
170
            Y.Object.each(this.iotransactions, function(trans) {
171
                trans.abort();
172
            });
173
            var iotrans = Y.io(M.cfg.wwwroot + '/user/selector/search.php', {
174
                method: 'POST',
175
                data: 'selectorid=' + hash + '&sesskey=' + M.cfg.sesskey +
176
                    '&search=' + value + '&userselector_searchtype=' + this.searchtype,
177
                on: {
178
                    complete: this.handle_response
179
                },
180
                context:this
181
            });
182
            this.iotransactions[iotrans.id] = iotrans;
183
 
184
            this.lastsearch = value;
185
            this.listbox.setStyle('background','url(' + M.util.image_url('i/loading', 'moodle') + ') no-repeat center center');
186
        },
187
        /**
188
         * Handle what happens when we get some data back from the search.
189
         * @param {int} requestid not used.
190
         * @param {object} response the list of users that was returned.
191
         */
192
        handle_response : function(requestid, response) {
193
            try {
194
                delete this.iotransactions[requestid];
195
                if (!Y.Object.isEmpty(this.iotransactions)) {
196
                    // More searches pending. Wait until they are all done.
197
                    return;
198
                }
199
                this.listbox.setStyle('background','');
200
                var data = Y.JSON.parse(response.responseText);
201
                if (data.error) {
202
                    this.searchfield.addClass('error');
203
                    return new M.core.ajaxException(data);
204
                }
205
                this.output_options(data);
206
 
207
                // If updated userSummaries are present, overwrite the global variable
208
                // that's output by group_non_members_selector::print_user_summaries() in user/selector/lib.php
209
                if (typeof data.userSummaries !== "undefined") {
210
                    /* global userSummaries:true */
211
                    /* exported userSummaries */
212
                    userSummaries = data.userSummaries;
213
                }
214
            } catch (e) {
215
                this.listbox.setStyle('background','');
216
                this.searchfield.addClass('error');
217
                return new M.core.exception(e);
218
            }
219
        },
220
        /**
221
         * This method should do the same sort of thing as the PHP method
222
         * user_selector_base::output_options.
223
         * @param {object} data the list of users to populate the list box with.
224
         */
225
        output_options : function(data) {
226
            // Clear out the existing options, keeping any ones that are already selected.
227
            var selectedusers = {};
228
            this.listbox.all('optgroup').each(function(optgroup){
229
                optgroup.all('option').each(function(option){
230
                    if (option.get('selected')) {
231
                        selectedusers[option.get('value')] = {
232
                            id : option.get('value'),
233
                            name : option.get('innerText') || option.get('textContent'),
234
                            disabled: option.get('disabled')
235
                        }
236
                    }
237
                    option.remove();
238
                }, this);
239
                optgroup.remove();
240
            }, this);
241
 
242
            // Output each optgroup.
243
            var count = 0;
244
            for (var key in data.results) {
245
                var groupdata = data.results[key];
246
                this.output_group(groupdata.name, groupdata.users, selectedusers, true);
247
                count ++;
248
            }
249
            if (!count) {
250
                var searchstr = (this.lastsearch != '') ? this.insert_search_into_str(M.util.get_string('nomatchingusers', 'moodle'), this.lastsearch) : M.util.get_string('none', 'moodle');
251
                this.output_group(searchstr, {}, selectedusers, true)
252
            }
253
 
254
            // If there were previously selected users who do not match the search, show them too.
255
            if (this.get_option('preserveselected') && selectedusers) {
256
                this.output_group(this.insert_search_into_str(M.util.get_string('previouslyselectedusers', 'moodle'), this.lastsearch), selectedusers, true, false);
257
            }
258
            this.handle_selection_change();
259
        },
260
        /**
261
         * This method should do the same sort of thing as the PHP method
262
         * user_selector_base::output_optgroup.
263
         *
264
         * @param {string} groupname the label for this optgroup.v
265
         * @param {object} users the users to put in this optgroup.
266
         * @param {boolean|object} selectedusers if true, select the users in this group.
267
         * @param {boolean} processsingle
268
         */
269
        output_group : function(groupname, users, selectedusers, processsingle) {
270
            var optgroup = Y.Node.create('<optgroup></optgroup>');
271
            this.listbox.append(optgroup);
272
 
273
            var count = 0;
274
            for (var key in users) {
275
                var user = users[key];
276
                var option = Y.Node.create('<option value="' + user.id + '">' + user.name + '</option>');
277
                if (user.disabled) {
278
                    option.setAttribute('disabled', 'disabled');
279
                } else if (selectedusers === true || selectedusers[user.id]) {
280
                    option.setAttribute('selected', 'selected');
281
                    delete selectedusers[user.id];
282
                }
283
                optgroup.append(option);
284
                if (user.infobelow) {
285
                    extraoption = Y.Node.create('<option disabled="disabled" class="userselector-infobelow"/>');
286
                    extraoption.appendChild(document.createTextNode(user.infobelow));
287
                    optgroup.append(extraoption);
288
                }
289
                count ++;
290
            }
291
 
292
            if (count > 0) {
293
                optgroup.set('label', groupname + ' (' + count + ')');
294
                if (processsingle && count === 1 && this.get_option('autoselectunique') && option.get('disabled') == false) {
295
                    option.setAttribute('selected', 'selected');
296
                }
297
            } else {
298
                optgroup.set('label', groupname);
299
                optgroup.append(Y.Node.create('<option disabled="disabled">\u00A0</option>'));
300
            }
301
        },
302
        /**
303
         * Replace
304
         * @param {string} str
305
         * @param {string} search The search term
306
         * @return string
307
         */
308
        insert_search_into_str : function(str, search) {
309
            return str.replace("%%SEARCHTERM%%", search);
310
        },
311
        /**
312
         * Gets the search text
313
         * @return String the value to search for, with leading and trailing whitespace trimmed.
314
         */
315
        get_search_text : function() {
316
            return this.searchfield.get('value').toString().replace(/^ +| +$/, '');
317
        },
318
        /**
319
         * Returns true if the selection is empty (nothing is selected)
320
         * @return Boolean check all the options and return whether any are selected.
321
         */
322
        is_selection_empty : function() {
323
            var selection = false;
324
            this.listbox.all('option').each(function(){
325
                if (this.get('selected')) {
326
                    selection = true;
327
                }
328
            });
329
            return !(selection);
330
        },
331
        /**
332
         * Cancel the search delay timeout, if there is one.
333
         */
334
        cancel_timeout : function() {
335
            if (this.timeoutid) {
336
                clearTimeout(this.timeoutid);
337
                this.timeoutid = null;
338
            }
339
        },
340
        /**
341
         * @param {string} name The name of the option to retrieve
342
         * @return the value of one of the option checkboxes.
343
         */
344
        get_option : function(name) {
345
            var checkbox = Y.one('#userselector_' + name + 'id');
346
            if (checkbox) {
347
                return (checkbox.get('checked'));
348
            } else {
349
                return false;
350
            }
351
        }
352
    };
353
    // Augment the user selector with the EventTarget class so that we can use
354
    // custom events
355
    Y.augment(user_selector, Y.EventTarget, null, null, {});
356
    // Initialise the user selector
357
    user_selector.init();
358
    // Store the user selector so that it can be retrieved
359
    this.user_selectors[name] = user_selector;
360
    // Return the user selector
361
    return user_selector;
362
};
363
 
364
/**
365
 * Initialise a class that updates the user's preferences when they change one of
366
 * the options checkboxes.
367
 * @constructor
368
 * @param {YUI} Y
369
 * @return Tracker object
370
 */
371
M.core_user.init_user_selector_options_tracker = function(Y) {
372
    // Create a user selector options tracker
373
    var user_selector_options_tracker = {
374
        /**
375
         * Initlises the option tracker and gets everything going.
376
         * @constructor
377
         */
378
        init : function() {
379
            var settings = [
380
                'userselector_preserveselected',
381
                'userselector_autoselectunique',
382
            ];
383
            for (var s in settings) {
384
                var setting = settings[s];
385
                Y.one('#' + setting + 'id').on('click', this.set_user_preference, this, setting);
386
            }
387
        },
388
        /**
389
         * Sets a user preference for the options tracker
390
         * @param {Y.Event|null} e
391
         * @param {string} name The name of the preference to set
392
         */
393
        set_user_preference : function(e, name) {
394
            require(['core_user/repository'], function(UserRepository) {
395
                UserRepository.setUserPreference(name, Y.one('#' + name + 'id').get('checked'));
396
            });
397
        }
398
    };
399
    // Initialise the options tracker
400
    user_selector_options_tracker.init();
401
    // Return it just incase it is ever wanted
402
    return user_selector_options_tracker;
403
};